/*
* 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 : OUNIT.CPP
//Description : Object Unit
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if(GAME_FRAMES_PER_DAY!=FRAMES_PER_DAY)
#error
#endif
#ifdef NO_DEBUG_UNIT
#undef err_when
#undef err_here
#undef err_if
#undef err_else
#undef err_now
#define err_when(cond)
#define err_here()
#define err_if(cond)
#define err_else
#define err_now(msg)
#undef DEBUG
#endif
//--------- Begin of function Unit::Unit ---------//
//
Unit::Unit()
{
// ##### patch begin Gilbert 21/1 ######//
// unit_id = 0;
memset( sizeof(Sprite) + (char *)this, 0, sizeof(Unit) - sizeof(Sprite));
// ##### patch end Gilbert 21/1 ######//
}
//----------- End of function Unit::Unit -----------//
//--------- Begin of function Unit::~Unit ---------//
//
Unit::~Unit()
{
deinit();
}
//----------- End of function Unit::~Unit -----------//
//--------- Begin of function Unit::init ---------//
//
// unitId - the id. of the unit
// nationRecno - the recno of nation
// [int] rankId - rank id. of the unit (none for non-human unit)
// [int] unitLoyalty - loyalty of the unit (none for non-human unit)
// [int] startXLoc, startYLoc - the starting location of the unit
// (if startXLoc < 0, this is a unit for hire, and is not a unit of the game yet. init_sprite() won't be called for this unit)
// (default: -1, -1)
//
// Note: sprite_recno must be initialized first before calling Unit::init()
//
void Unit::init(int unitId, int nationRecno, int rankId, int unitLoyalty, int startXLoc, int startYLoc)
{
//------------ set basic vars -------------//
nation_recno = (char) nationRecno;
rank_id = rankId; // rank_id must be initialized before init_unit_id() as init_unit_id() may overwrite it
nation_contribution = 0; // nation_contribution must be initialized before init_unit_id() as init_unit_id() may overwrite it
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
{
team_info = (TeamInfo*) mem_add( sizeof(TeamInfo) );
team_info->member_count = 0;
}
else
team_info = NULL;
init_unit_id(unitId);
group_select_id = 0;
unit_group_id = unit_array.cur_group_id++;
race_id = (char) unit_res[unit_id]->race_id;
//------- init unit name ---------//
if( race_id )
{
name_id = race_res[race_id]->get_new_name_id();
}
else //---- init non-human unit series no. ----//
{
if( nation_recno )
name_id = ++nation_array[nation_recno]->last_unit_name_id_array[unit_id-1];
else
name_id = 0;
}
//------- init ai_unit ----------//
if( nation_recno )
ai_unit = nation_array[nation_recno]->nation_type == NATION_AI;
else
ai_unit = 0;
err_when( unitLoyalty < 0 || unitLoyalty > 100 );
//----------------------------------------------//
ai_action_id = 0;
action_misc = ACTION_MISC_STOP;
action_misc_para = 0;
action_mode2 = action_mode = ACTION_STOP;
action_para2 = action_para = 0;
action_x_loc2 = action_y_loc2 = action_x_loc = action_y_loc = -1;
memset(blocked_edge, 0, sizeof(char)*4);
attack_range = 0; //store the attack range of the current attack mode if the unit is ordered to attack
leader_unit_recno= 0;
team_id = 0;
selected_flag = 0;
waiting_term = 0;
swapping = 0; // indicate whether swapping is processed.
spy_recno = 0;
range_attack_x_loc = -1;
range_attack_y_loc = -1;
//------- initialize path seek vars -------//
result_node_array = NULL;
result_node_count = result_node_recno = result_path_dist = 0;
//------- initialize way point vars -------//
way_point_array = NULL;
way_point_array_size = 0;
way_point_count = 0;
//---------- initialize game vars ----------//
unit_mode = 0;
unit_mode_para = 0;
max_hit_points = unit_res[unit_id]->hit_points;
hit_points = max_hit_points;
loyalty = unitLoyalty;
can_guard_flag = 0;
can_attack_flag = 1;
force_move_flag = 0;
ai_no_suitable_action = 0;
cur_power = 0;
max_power = 0;
total_reward = 0;
home_camp_firm_recno = 0;
seek_path_fail_count = 0;
ignore_power_nation = 0;
aggressive_mode = 1; // the default mode is 1
err_when( loyalty<0 || loyalty>100 );
//--------- init skill potential ---------//
if( m.random(10)==0 ) // 1 out of 10 has a higher than normal potential in this skill
{
skill.skill_potential = 50+m.random(51); // 50 to 100 potential
}
//------ initialize the base Sprite class -------//
if( startXLoc >= 0 )
init_sprite( startXLoc, startYLoc );
else
{
cur_x = -1;
}
//------------- set attack_dir ------------//
attack_dir = final_dir;
//-------------- update loyalty -------------//
update_loyalty();
//--------------- init AI info -------------//
if( ai_unit )
{
Nation* nationPtr = nation_array[nation_recno];
if( rank_id==RANK_GENERAL || rank_id==RANK_KING )
nationPtr->add_general_info(sprite_recno);
switch( unit_res[unit_id]->unit_class )
{
case UNIT_CLASS_CARAVAN:
nationPtr->add_caravan_info(sprite_recno);
break;
case UNIT_CLASS_SHIP:
nationPtr->add_ship_info(sprite_recno);
break;
}
}
//----------- init derived class ----------//
init_derived();
}
//----------- End of function Unit::init -----------//
//--------- Begin of function Unit::init_unit_id ---------//
void Unit::init_unit_id(int unitId)
{
unit_id = unitId;
UnitInfo* unitInfo = unit_res[unit_id];
sprite_id = unitInfo->sprite_id;
sprite_info = sprite_res[sprite_id];
mobile_type = unitInfo->mobile_type;
//--- if this unit is a weapon unit with multiple versions ---//
set_combat_level(100); // set combat level default to 100, for human units, it will be adjusted later by individual functions
int techLevel;
if( nation_recno &&
unitInfo->unit_class == UNIT_CLASS_WEAPON &&
(techLevel=unitInfo->nation_tech_level_array[nation_recno-1]) > 0 )
{
set_weapon_version(techLevel);
}
fix_attack_info();
//-------- set unit count ----------//
if( nation_recno )
{
if( rank_id != RANK_KING )
unitInfo->inc_nation_unit_count(nation_recno);
if( rank_id == RANK_GENERAL )
unitInfo->inc_nation_general_count(nation_recno);
}
//--------- increase monster count ----------//
if( unit_res[unit_id]->unit_class == UNIT_CLASS_MONSTER )
unit_res.mobile_monster_count++;
}
//----------- End of function Unit::init_unit_id -----------//
//--------- Begin of function Unit::deinit_unit_id ---------//
void Unit::deinit_unit_id()
{
if( sys.signal_exit_flag )
return;
//-----------------------------------------//
UnitInfo *unitInfo = unit_res[unit_id];
if( nation_recno )
{
if( rank_id != RANK_KING )
unitInfo->dec_nation_unit_count(nation_recno);
if( rank_id == RANK_GENERAL )
unitInfo->dec_nation_general_count(nation_recno);
}
//--------- if the unit is a spy ----------//
//
// A spy has double identity and is counted
// by both the true controlling nation and
// the deceiving nation.
//
//-----------------------------------------//
if( spy_recno && true_nation_recno() != nation_recno )
{
err_when( !race_id );
int trueNationRecno = true_nation_recno();
if( rank_id != RANK_KING )
unitInfo->dec_nation_unit_count(trueNationRecno);
if( rank_id == RANK_GENERAL )
unitInfo->dec_nation_general_count(trueNationRecno);
}
//--------- decrease monster count ----------//
if( unit_res[unit_id]->unit_class == UNIT_CLASS_MONSTER )
{
unit_res.mobile_monster_count--;
err_when( unit_res.mobile_monster_count < 0 );
}
}
//----------- End of function Unit::deinit_unit_id -----------//
//--------- Begin of function Unit::set_spy ---------//
void Unit::set_spy(int spyRecno)
{
spy_recno = spyRecno;
}
//---------- End of function Unit::set_spy ---------//
//--------- Begin of function Unit::init_sprite ---------//
//
// startXLoc, startYLoc - the starting location of the unit
//
void Unit::init_sprite(int startXLoc, int startYLoc)
{
err_when( !world.get_loc(startXLoc, startYLoc)->can_move(unit_res[unit_id]->mobile_type) );
err_when(unit_res[unit_id]->unit_class==UNIT_CLASS_SHIP && (startXLoc%2 || startYLoc%2));
Sprite::init(unit_res[unit_id]->sprite_id, startXLoc, startYLoc);
//--------------------------------------------------------------------//
// move_to_?_loc is always the current location of the unit as
// cur_action == SPRITE_IDLE
//--------------------------------------------------------------------//
original_action_mode = 0;
ai_original_target_x_loc = -1;
attack_range = 0;
move_to_x_loc = next_x_loc();
move_to_y_loc = next_y_loc();
go_x = next_x;
go_y = next_y;
//-------- set the cargo_recno -------------//
char w, h;
short x, y;
err_if(!sprite_recno) // sprite_recno must be initialized first before calling Unit::init()
err_here();
for(h=0, y=startYLoc; hloc_height; h++, y++)
{
for(w=0, x=startXLoc; wloc_width; w++, x++)
{
err_if( world.get_unit_recno(x, y, mobile_type) ) // it must be 0 to put the sprite in this location
err_here();
world.set_unit_recno(x, y, mobile_type, sprite_recno);
}
}
if( is_own() ||
(nation_recno && nation_array[nation_recno]->is_allied_with_player) )
{
world.unveil(startXLoc, startYLoc, startXLoc+sprite_info->loc_width-1,
startYLoc+sprite_info->loc_height-1 );
world.visit(startXLoc, startYLoc, startXLoc+sprite_info->loc_width-1,
startYLoc+sprite_info->loc_height-1, unit_res[unit_id]->visual_range,
unit_res[unit_id]->visual_extend);
}
err_when(result_node_array || result_path_dist);
}
//----------- End of function Unit::init_sprite -----------//
//--------- Begin of function Unit::deinit ---------//
void Unit::deinit()
{
err_when( unit_array.is_truly_deleted(sprite_recno) );
if( !unit_id )
return;
//-------- if this is a king --------//
if( !sys.signal_exit_flag && nation_recno )
{
if( rank_id == RANK_KING ) // check nation_recno because monster kings will die also.
{
king_die();
err_when( unit_array.is_truly_deleted(sprite_recno) );
}
else if( rank_id == RANK_GENERAL )
{
general_die();
err_when( unit_array.is_truly_deleted(sprite_recno) );
}
}
//------------ free up team_info -----------//
if( team_info )
{
mem_del(team_info);
team_info = NULL;
}
//---- if this is a general, deinit its link with its soldiers ----//
//
// We do not use team_info because monsters and rebels also use
// leader_unit_recno and they do not use keep the member info
// in team_info.
//
//-----------------------------------------------------------------//
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
{
for( int i=unit_array.size() ; i>0 ; i-- )
{
if( unit_array.is_deleted(i) )
continue;
if( unit_array[i]->leader_unit_recno == sprite_recno )
unit_array[i]->leader_unit_recno = 0;
}
}
//----- if this is a unit on a ship ------//
if( unit_mode == UNIT_MODE_ON_SHIP )
{
if( !unit_array.is_deleted(unit_mode_para) ) // the ship may have been destroyed at the same time. Actually when the ship is destroyed, all units onboard are killed and this function is called.
{
Unit* unitPtr = unit_array[unit_mode_para];
err_when( unit_res[unitPtr->unit_id]->unit_class != UNIT_CLASS_SHIP );
((UnitMarine*)unitPtr)->del_unit(sprite_recno);
}
}
//----- if this is a ship in the harbor -----//
else if( unit_mode == UNIT_MODE_IN_HARBOR )
{
if( !firm_array.is_deleted(unit_mode_para) ) // the ship may have been destroyed at the same time. Actually when the ship is destroyed, all firms onboard are killed and this function is called.
{
Firm* firmPtr = firm_array[unit_mode_para];
err_when( firmPtr->firm_id != FIRM_HARBOR );
((FirmHarbor*)firmPtr)->del_hosted_ship(sprite_recno);
}
}
//----- if this unit is a constructor in a firm -------//
else if( unit_mode == UNIT_MODE_CONSTRUCT )
{
err_when( firm_array[unit_mode_para]->builder_recno != sprite_recno );
firm_array[unit_mode_para]->builder_recno = 0;
}
//-------- if this is a spy ---------//
if( spy_recno )
{
spy_array.del_spy( spy_recno );
spy_recno = 0;
}
//---------- reset command ----------//
if( power.command_unit_recno == sprite_recno )
power.reset_command();
//-----------------------------------//
deinit_unit_id();
//-------- reset seek path ----------//
reset_path();
//----- if cur_x == -1, the unit has not yet been hired -----//
if( cur_x >= 0 )
deinit_sprite();
//------------------------------------------------//
//
// Prime rule:
//
// world.get_loc(next_x_loc() and next_y_loc())->cargo_recno
// is always = sprite_recno
//
// no matter what cur_action is.
//
//------------------------------------------------//
//
// Relationship between (next_x, next_y) and (cur_x, cur_y)
//
// when SPRITE_WAIT, SPRITE_IDLE, SPRITE_READY_TO_MOVE,
// SPRITE_ATTACK, SPRITE_DIE:
//
// (next_x, next_y) == (cur_x, cur_y), it's the location of the sprite.
//
// when SPRITE_MOVE:
//
// (next_x, next_y) != (cur_x, cur_y)
// (next_x, next_y) is where the sprite is moving towards.
// (cur_x , cur_y ) is the location of the sprite.
//
//------------------------------------------------//
//--------------- deinit AI info -------------//
if( ai_unit )
{
if( !nation_array.is_deleted(nation_recno) )
{
Nation* nationPtr = nation_array[nation_recno];
if( rank_id==RANK_GENERAL || rank_id==RANK_KING )
nationPtr->del_general_info(sprite_recno);
switch( unit_res[unit_id]->unit_class )
{
case UNIT_CLASS_CARAVAN:
nationPtr->del_caravan_info(sprite_recno);
break;
case UNIT_CLASS_SHIP:
nationPtr->del_ship_info(sprite_recno);
break;
}
}
}
//-------------- reset unit_id ---------------//
unit_id = 0;
}
//----------- End of function Unit::deinit -----------//
//--------- Begin of function Unit::deinit_sprite ---------//
//
// [int] keepSelected - keep it selected if it is current selected.
// (default: 0)
//
void Unit::deinit_sprite(int keepSelected)
{
err_when(result_node_array!=NULL);
if( cur_x == -1 )
return;
//---- if this unit is led by a leader, only mobile units has leader_unit_recno assigned to a leader -----//
if( leader_unit_recno )
{
if( !unit_array.is_deleted(leader_unit_recno) ) // the leader unit may have been killed at the same time
unit_array[leader_unit_recno]->del_team_member(sprite_recno);
leader_unit_recno = 0;
}
//-------- clear the cargo_recno ----------//
short w, h;
short x, y;
for(h=0, y=next_y_loc(); hloc_height; h++, y++)
{
for(w=0, x=next_x_loc(); wloc_width; w++, x++)
{
err_if( world.get_unit_recno(x, y, mobile_type) != sprite_recno ) // it must be 0 to put the sprite in this location
err_here();
world.set_unit_recno(x, y, mobile_type, 0);
}
}
cur_x = -1;
//---- reset other parameters related to this unit ----//
if( !keepSelected )
{
if( unit_array.selected_recno == sprite_recno )
{
unit_array.selected_recno = 0;
info.disp();
}
if( power.command_unit_recno == sprite_recno )
power.command_id = 0;
}
//------- deinit unit mode -------//
deinit_unit_mode();
}
//----------- End of function Unit::deinit_sprite -----------//
//--------- Begin of function Unit::deinit_unit_mode ---------//
//
void Unit::deinit_unit_mode()
{
if( sys.signal_exit_flag )
return;
//----- this unit was defending the town before it gets killed ----//
if(unit_mode==UNIT_MODE_DEFEND_TOWN)
{
if(!town_array.is_deleted(unit_mode_para))
{
Town *townPtr = town_array[unit_mode_para];
if(nation_recno == townPtr->nation_recno)
townPtr->reduce_defender_count();
}
set_mode(0); // reset mode
}
//----- this is a monster unit defending its town ------//
else if( unit_mode==UNIT_MODE_MONSTER && unit_mode_para )
{
if(((UnitMonster*)this)->monster_action_mode!=MONSTER_ACTION_DEFENSE)
return;
FirmMonster* firmMonster = (FirmMonster*) firm_array[unit_mode_para];
err_when( firmMonster->firm_id != FIRM_MONSTER );
firmMonster->reduce_defender_count(rank_id);
}
}
//----------- End of function Unit::deinit_unit_mode -----------//
//--------- Begin of function Unit::king_die ---------//
//
void Unit::king_die()
{
//--------- add news ---------//
news_array.king_die(nation_recno);
//--- see if the units, firms and towns of the nation are all destroyed ---//
Nation* nationPtr = nation_array[nation_recno];
nationPtr->king_unit_recno = 0;
}
//----------- End of function Unit::king_die -----------//
//--------- Begin of function Unit::general_die ---------//
//
void Unit::general_die()
{
//--------- add news ---------//
if( nation_recno == nation_array.player_recno )
news_array.general_die(sprite_recno);
}
//----------- End of function Unit::general_die -----------//
//--------- Begin of function Unit::unit_name ---------//
//
// [int] withTitle - whether return a string with the title of the unit
// or not. (default: 1)
//
char* Unit::unit_name(int withTitle)
{
static String str;
UnitInfo* unitInfo = unit_res[unit_id];
//------------------------------------//
if( race_id )
{
str = "";
if( withTitle )
{
if( unit_mode == UNIT_MODE_REBEL )
{
if( rank_id == RANK_GENERAL )
str = "Rebel Leader ";
}
else
{
if( rank_id == RANK_KING )
str = "King ";
else if( rank_id == RANK_GENERAL )
str = "General ";
}
}
str = translate.process(str);
if( rank_id == RANK_KING ) // use the player name
str += nation_array[nation_recno]->king_name();
else
str += race_res[race_id]->get_name(name_id);
}
else
{
str = unitInfo->name;
//--- for weapons, the rank_id is used to store the version of the weapon ---//
if( unitInfo->unit_class == UNIT_CLASS_WEAPON && get_weapon_version() > 1 )
{
str += " ";
str += m.roman_number(get_weapon_version());
}
if( unitInfo->unit_class != UNIT_CLASS_GOD ) // God doesn't have any series no.
{
str += " ";
str += name_id; // name id is the series no. of the unit
}
}
return str;
}
//----------- End of function Unit::unit_name ---------//
//--------- Begin of function Unit::set_name ---------//
//
// Set the name id. of this unit.
//
void Unit::set_name(WORD newNameId)
{
//------- free up the existing name id. ------//
race_res[race_id]->free_name_id(name_id);
//------- set the new name id. ---------//
name_id = newNameId;
//-------- register usage of the new name id. ------//
race_res[race_id]->use_name_id(name_id);
}
//----------- End of function Unit::set_name ---------//
//--------- Begin of function Unit::is_own ---------//
//
int Unit::is_own()
{
return is_nation(nation_array.player_recno);
}
//----------- End of function Unit::is_own ---------//
//--------- Begin of function Unit::is_own_spy ---------//
//
int Unit::is_own_spy()
{
return spy_recno && spy_array[spy_recno]->true_nation_recno == nation_array.player_recno;
}
//----------- End of function Unit::is_own_spy ---------//
//--------- Begin of function Unit::is_nation ---------//
//
// Whether the unit belongs to the specific nation.
//
int Unit::is_nation(int nationRecno)
{
if( nation_recno == nationRecno )
return 1;
if( spy_recno && spy_array[spy_recno]->true_nation_recno == nationRecno )
return 1;
return 0;
}
//----------- End of function Unit::is_nation ---------//
//--------- Begin of function Unit::is_civilian ---------//
//
int Unit::is_civilian()
{
return race_id>0 && skill.combat_level<20 &&
skill.skill_id != SKILL_LEADING &&
unit_mode != UNIT_MODE_DEFEND_TOWN &&
unit_mode != UNIT_MODE_REBEL;
}
//----------- End of function Unit::is_civilian ---------//
//--------- Begin of function Unit::true_nation_recno ---------//
//
// The true nation recno of the unit, taking care of the
// situation where the unit is a spy.
//
int Unit::true_nation_recno()
{
if( spy_recno )
return spy_array[spy_recno]->true_nation_recno;
else
return nation_recno;
}
//----------- End of function Unit::true_nation_recno ---------//
//--------- Begin of function Unit::next_day ---------//
//
void Unit::next_day()
{
int unitRecno = sprite_recno;
err_when( unit_array.is_deleted(unitRecno) );
err_when( race_id && !is_visible() && unit_mode==0 );
#ifdef DEBUG
if( unit_mode == UNIT_MODE_UNDER_TRAINING )
{
Town* townPtr =town_array[unit_mode_para];
err_when( townPtr->train_unit_recno != sprite_recno );
err_when( townPtr->nation_recno != nation_recno );
}
#endif
//------- functions for non-independent nations only ------//
if( nation_recno )
{
pay_expense();
if( unit_array.is_deleted(unitRecno) ) // if its hit points go down to 0, is_deleted() will return 1.
return;
//------- update loyalty -------------//
if( info.game_date%30 == sprite_recno%30 )
{
update_loyalty();
err_when( unit_array.is_deleted(unitRecno) );
}
//------- think about rebeling -------------//
if( info.game_date%15 == sprite_recno%15 )
{
if( think_betray() )
return;
}
}
//------- recover from damage -------//
if( info.game_date%15 == sprite_recno%15 ) // recover one point per two weeks
{
process_recover();
err_when( unit_array.is_deleted(unitRecno) );
}
//------- restore cur_power --------//
cur_power += 5;
if( cur_power > max_power)
cur_power = max_power;
//------- king undie flag (for testing games only) --------//
if( config.king_undie_flag && rank_id == RANK_KING &&
nation_recno && !nation_array[nation_recno]->is_ai() )
{
hit_points = max_hit_points;
}
//-------- if aggresive_mode is 1 --------//
if( nation_recno && is_visible() )
think_aggressive_action();
//---------- debug ------------//
#ifdef DEBUG
err_when( unit_res[unit_id]->unit_class != UNIT_CLASS_HUMAN && race_id );
if( spy_recno )
{
err_when( spy_array.is_deleted(spy_recno) );
Spy* spyPtr = spy_array[spy_recno];
err_when( nation_recno != spyPtr->cloaked_nation_recno );
if( unit_mode == UNIT_MODE_OVERSEE )
{
err_when( spyPtr->spy_place != SPY_FIRM );
err_when( spyPtr->spy_place_para != unit_mode_para );
}
else
{
err_when( spyPtr->spy_place != SPY_MOBILE );
err_when( spyPtr->spy_place_para != sprite_recno );
}
}
if( leader_unit_recno )
{
Unit* unitPtr = unit_array[leader_unit_recno];
err_when( unitPtr->rank_id != RANK_GENERAL && unitPtr->rank_id != RANK_KING );
}
err_when( (rank_id == RANK_GENERAL || rank_id == RANK_KING) &&
!team_info );
if( leader_unit_recno )
{
err_when( unit_array.is_truly_deleted(leader_unit_recno) );
err_when( unit_array[leader_unit_recno]->nation_recno != nation_recno );
// err_when( unit_array[leader_unit_recno]->team_id != team_id );
}
err_when( hit_points > max_hit_points );
err_when( max_hit_points == 0 );
err_when( skill.combat_level<=0 );
err_when( skill.combat_level>100 );
err_when( unit_mode==UNIT_MODE_REBEL && spy_recno ); // no rebel spies
err_when( unit_mode==UNIT_MODE_REBEL && nation_recno ); // all rebels must be independent units
err_when( unit_mode==UNIT_MODE_DEFEND_TOWN && spy_recno ); // no rebel spies
err_when( loyalty < 0 || loyalty > 100 );
err_when( skill.skill_id == SKILL_SPYING ); // skill.skill_id should never be SKILL_SPYING, it will be shown in spy_recno if it's a spy
err_when( nation_contribution < 0 );
err_when( nation_contribution > MAX_NATION_CONTRIBUTION );
err_when( ai_unit && ( !nation_recno || !nation_array[nation_recno]->is_ai() ) );
#else // fix bug on fly in the release version
if( skill.combat_level > 100 )
skill.combat_level = 100;
#endif
}
//----------- End of function Unit::next_day -----------//
//--------- Begin of function Unit::process_recover ---------//
//
void Unit::process_recover()
{
if( hit_points==0 || hit_points == max_hit_points ) // this unit is dead already
return;
err_when( hit_points > max_hit_points );
//---- overseers in firms and ships in harbors recover faster ----//
int hitPointsInc;
if( unit_mode == UNIT_MODE_OVERSEE ||
unit_mode == UNIT_MODE_IN_HARBOR )
{
hitPointsInc = 2;
}
//------ for units on ships --------//
else if( unit_mode == UNIT_MODE_ON_SHIP )
{
//--- if the ship where the unit is on is in the harbor, the unit recovers faster ---//
if( unit_array[unit_mode_para]->unit_mode == UNIT_MODE_IN_HARBOR )
hitPointsInc = 2;
else
hitPointsInc = 1;
}
//----- only recover when the unit is not moving -----//
else if( cur_action == SPRITE_IDLE )
{
hitPointsInc = 1;
}
else
return;
//---------- recover now -----------//
hit_points += hitPointsInc;
if( hit_points > max_hit_points )
hit_points = max_hit_points;
}
//----------- End of function Unit::process_recover -----------//
//--------- Begin of function Unit::update_loyalty ---------//
//
// How loyalty of units are updated:
//
// General: in a military camp - updated in FirmCamp::update_loyalty()
// mobile - no update
//
// Soldiers led by a general: in a military camp - updated in FirmCamp::update_loyalty()
// mobile - updated here
//
// Other units: no update.
//
void Unit::update_loyalty()
{
if( !nation_recno || rank_id==RANK_KING || !unit_res[unit_id]->race_id )
return;
if( unit_mode == UNIT_MODE_CONSTRUCT ) // constructor worker will not change their loyalty when they are in a building
return;
//----- if this unit is a spy, set its fake loyalty ------//
if( spy_recno ) // a spy's loyalty is always >= 70
{
if( loyalty < 70 )
loyalty = 70+m.random(20); // initialize it to be a number between 70 and 90
target_loyalty = loyalty;
return;
}
//-------- if this is a general ---------//
Nation* ownNation = nation_array[nation_recno];
int rc=0;
if( rank_id==RANK_GENERAL )
{
//----- the general's power affect his loyalty ----//
int targetLoyalty = commander_power();
//----- the king's race affects the general's loyalty ----//
if( ownNation->race_id == race_id )
targetLoyalty += 20;
//----- the kingdom's reputation affects the general's loyalty ----//
targetLoyalty += (int)ownNation->reputation/4;
//--- the king's leadership also affect the general's loyalty -----//
if( ownNation->king_unit_recno )
targetLoyalty += unit_array[ownNation->king_unit_recno]->skill.skill_level / 4;
//-- if the unit is rewarded less than the amount of contribution he made, he will become unhappy --//
if( nation_contribution > total_reward*2 )
{
int decLoyalty = (nation_contribution - total_reward*2)/2;
targetLoyalty -= min(50, decLoyalty); // this affect 50 points at maximum
}
targetLoyalty = min( targetLoyalty, 100 );
target_loyalty = max( targetLoyalty, 0 );
}
//-------- if this is a soldier ---------//
else if( rank_id==RANK_SOLDIER )
{
if( leader_unit_recno )
{
//----------------------------------------//
//
// If this soldier is led by a general,
// the targeted loyalty
//
// = race friendliness between the unit and the general / 2
// + the leader unit's leadership / 2
//
//----------------------------------------//
if( unit_array.is_deleted(leader_unit_recno) )
{
leader_unit_recno = 0;
return;
}
Unit* leaderUnit = unit_array[leader_unit_recno];
int targetLoyalty = 30 + leaderUnit->skill.get_skill(SKILL_LEADING);
//---------------------------------------------------//
//
// Soldiers with higher combat and leadership skill
// will get discontented if they are led by a general
// with low leadership.
//
//---------------------------------------------------//
targetLoyalty -= skill.combat_level/2;
targetLoyalty -= skill.skill_level;
if( leaderUnit->rank_id == RANK_KING )
targetLoyalty += 20;
if( race_res.is_same_race(race_id, leaderUnit->race_id) )
targetLoyalty += 20;
if( targetLoyalty < 0 )
targetLoyalty = 0;
targetLoyalty = min( targetLoyalty, 100 );
target_loyalty = max( targetLoyalty, 0 );
}
else
{
target_loyalty = 0;
}
}
//--------- update loyalty ---------//
err_when( target_loyalty < 0 || target_loyalty > 100 );
if( target_loyalty > loyalty ) // only increase, no decrease. Decrease are caused by events. Increases are made gradually
{
int incValue = (target_loyalty - loyalty)/10;
int newLoyalty = (int) loyalty + max(1, incValue);
if( newLoyalty > target_loyalty )
newLoyalty = target_loyalty;
loyalty = newLoyalty;
}
else if( target_loyalty < loyalty ) // only increase, no decrease. Decrease are caused by events. Increases are made gradually
{
loyalty--;
}
err_when( loyalty < 0 || loyalty > 100 );
}
//-------- End of function Unit::update_loyalty -----------//
//--------- Begin of function Unit::commander_power ---------//
//
// A commander's power is determined:
//
// -Population of the towns he controls
// -The employment rate of the towns he controls, the higher the
// employment rate, the higher his power is
// -If there are any other commanders controls the towns at the same time.
// -the no. of soldiers led by the commander and their combat levels.
//
int Unit::commander_power()
{
//---- if the commander is in a military camp -----//
int commanderPower=0;
if( unit_mode == UNIT_MODE_OVERSEE )
{
Firm* firmPtr = firm_array[unit_mode_para];
if( firmPtr->firm_id == FIRM_CAMP )
{
Town* townPtr;
for( int i=firmPtr->linked_town_count-1 ; i>=0 ; i-- )
{
if( firmPtr->linked_town_enable_array[i] == LINK_EE )
{
townPtr = town_array[firmPtr->linked_town_array[i]];
commanderPower += townPtr->population / townPtr->linked_active_camp_count();
}
}
commanderPower += firmPtr->worker_count*3; // 0 to 24
}
else if( firmPtr->firm_id == FIRM_BASE )
{
commanderPower = 60;
}
}
else
{
commanderPower = team_info->member_count*3; // 0 to 24
}
return commanderPower;
}
//----------- End of function Unit::commander_power -----------//
//--------- Begin of function Unit::think_betray ---------//
//
int Unit::think_betray()
{
int unitRecno = sprite_recno;
err_when( unit_array.is_deleted(unitRecno) );
if( spy_recno ) // spies do not betray here, spy has its own functions for betrayal
return 0;
//----- if the unit is in training or is constructing a building, do not rebel -------//
if( !is_visible() && unit_mode != UNIT_MODE_OVERSEE )
return 0;
if( loyalty >= UNIT_BETRAY_LOYALTY ) // you when unit is
return 0;
if( !unit_res[unit_id]->race_id || !nation_recno ||
rank_id==RANK_KING || spy_recno )
{
return 0;
}
err_when(unit_res[unit_id]->unit_class == UNIT_CLASS_GOD);
err_when(unit_id==UNIT_CARAVAN);
//------ turn towards other nation --------//
int i, bestNationRecno=0, nationScore, bestScore=loyalty; // the score must be larger than the current loyalty
Nation *curNation, *nationPtr;
int unitRegionId = region_id();
if( loyalty==0 ) // if the loyalty is 0, it will definitely betray
bestScore = -100;
curNation = nation_array[nation_recno];
for( i=nation_array.size() ; i>0 ; i-- )
{
if( nation_array.is_deleted(i) )
continue;
if( !curNation->get_relation(i)->has_contact || i==nation_recno )
continue;
nationPtr = nation_array[i];
//--- only if the nation has a base town in the region where the unit stands ---//
if( !region_array.nation_has_base_town(unitRegionId, i) )
continue;
//------------------------------------------------//
nationScore = (int) nationPtr->reputation
+ (nationPtr->overall_rating - curNation->overall_rating);
if( race_res.is_same_race(nationPtr->race_id, race_id) )
nationScore += 30;
if( nationScore > bestScore )
{
bestScore = nationScore;
bestNationRecno = i;
}
}
err_when( unit_array.is_deleted(unitRecno) );
if( bestNationRecno )
{
return betray(bestNationRecno);
}
else if( loyalty==0 )
{
//----------------------------------------------//
// If there is no good nation to turn towards to and
// the loyalty has dropped to 0, resign itself and
// leave the nation.
//
// However, if the unit is spy, it will stay with the
// nation as it has never been really loyal to the nation.
//---------------------------------------------//
if( rank_id != RANK_KING && is_visible() &&
!spy_recno )
{
resign(COMMAND_AUTO);
return 1;
}
}
return 0;
}
//-------- End of function Unit::think_betray -----------//
//--------- Begin of function Unit::betray ---------//
//
// If this unit is a spy, this function betray() will be
// called by Unit::spy_change_nation() or Firm::capture_firm().
//
// If this is not a spy, this function will only be called
// by think_betray() and other nation deinit functions.
//
int Unit::betray(int newNationRecno)
{
int unitRecno = sprite_recno;
//### begin alex 18/3 ###//
//err_when( unit_array.is_deleted(unitRecno) );
err_when( unit_array.is_truly_deleted(unitRecno) );
//#### end alex 18/3 ####//
err_when( rank_id == RANK_KING );
if( nation_recno == newNationRecno )
return 0;
if( unit_mode == UNIT_MODE_CONSTRUCT || // don't change nation when the unit is constructing a firm
unit_mode == UNIT_MODE_ON_SHIP ) // don't change nation when the unit is constructing a firm
{
return 0;
}
//---------- add news -----------//
if( nation_recno == nation_array.player_recno ||
newNationRecno == nation_array.player_recno )
{
//--- if this is a spy, don't display news message for betrayal as it is already displayed in Unit::spy_change_nation() ---//
if( !spy_recno )
news_array.unit_betray(sprite_recno, newNationRecno);
}
//------ change nation now ------//
//### begin alex 18/3 ###//
//err_when( unit_array.is_deleted(unitRecno) );
err_when( unit_array.is_truly_deleted(unitRecno) );
//#### end alex 18/3 ####//
change_nation(newNationRecno);
//### begin alex 18/3 ###//
//err_when( unit_array.is_deleted(unitRecno) );
err_when( unit_array.is_truly_deleted(unitRecno) );
//#### end alex 18/3 ####//
//-------- set the loyalty of the unit -------//
if( nation_recno )
{
Nation* nationPtr = nation_array[nation_recno];
loyalty = UNIT_BETRAY_LOYALTY + m.random(5);
if( nationPtr->reputation > 0 )
change_loyalty( (int) nationPtr->reputation );
if( race_res.is_same_race( nationPtr->race_id, race_id ) )
change_loyalty( 30 );
err_when( loyalty < 0 || loyalty > 100 );
update_loyalty(); // update target loyalty
}
else //------ if change to independent rebel -------//
{
loyalty = 0; // no loyalty needed
}
//--- if this unit is a general, change nation for the units he commands ---//
DWORD newTeamId = unit_array.cur_team_id++;
if( rank_id==RANK_GENERAL )
{
Unit* unitPtr;
int i, nationReputation = (int) nation_array[nation_recno]->reputation;
for( i=unit_array.size() ; i>0 ; i-- )
{
if( unit_array.is_deleted(i) )
continue;
unitPtr = unit_array[i];
//---- capture the troop this general commands -----//
if( unitPtr->leader_unit_recno == sprite_recno &&
unitPtr->rank_id == RANK_SOLDIER && unitPtr->is_visible() )
{
if( unitPtr->spy_recno ) // if the unit is a spy
unitPtr->spy_change_nation(newNationRecno, COMMAND_AUTO);
else
unitPtr->change_nation(newNationRecno);
unitPtr->team_id = newTeamId; // assign new team_id or checking for nation_recno
}
}
}
team_id = newTeamId;
//### begin alex 18/3 ###//
//err_when( unit_array.is_deleted(unitRecno) );
err_when( unit_array.is_truly_deleted(unitRecno) );
//#### end alex 18/3 ####//
//------ go to meet the new master -------//
if( is_visible() && nation_recno )
{
if( !spy_recno || spy_array[spy_recno]->notify_cloaked_nation_flag )
{
if( rank_id == RANK_GENERAL ) // generals shouldn't automatically be assigned to camps, they should just move near your villages
ai_move_to_nearby_town();
else
think_normal_human_action(); // this is an AI function in OUNITAI.CPP
}
}
//### begin alex 18/3 ###//
//err_when( unit_array.is_deleted(unitRecno) );
err_when( unit_array.is_truly_deleted(unitRecno) );
//#### end alex 18/3 ####//
return 1;
}
//-------- End of function Unit::betray -----------//
//--------- Begin of function Unit::change_nation ---------//
//
// This function is called when a unit change nation.
// It is not necessarily a result of betray, when a spy
// hands over his new nation to his parent nation, this
// function will also be called.
//
// newNationRecno - change the nation of the unit.
//
void Unit::change_nation(int newNationRecno)
{
err_when( newNationRecno && nation_array.is_deleted(newNationRecno) );
err_when( unit_mode == UNIT_MODE_REBEL ); // rebels do not change nation
//---------------------------------//
int oldAiUnit = ai_unit;
int oldNationRecno = nation_recno;
group_select_id = 0; // clear group select id
if(way_point_count)
reset_way_point_array();
//-- if the player is giving a command to this unit, cancel the command --//
if( nation_recno == nation_array.player_recno &&
sprite_recno == unit_array.selected_recno &&
power.command_id )
{
power.command_id = 0;
}
//---------- stop all action to attack this unit ------------//
unit_array.stop_attack_unit(sprite_recno);
//---- update nation_unit_count_array[] ----//
unit_res[unit_id]->unit_change_nation(newNationRecno, nation_recno, rank_id);
//------- if the nation has an AI action -------//
stop2(); // clear the existing order
//---------------- update vars ----------------//
unit_group_id = unit_array.cur_group_id++; // separate from the current group
nation_recno = newNationRecno;
home_camp_firm_recno = 0; // reset it
original_action_mode = 0;
if( race_id )
{
nation_contribution = 0; // contribution to the nation
total_reward = 0;
}
//-------- if change to one of the existing nations ------//
ai_unit = nation_recno && nation_array[nation_recno]->is_ai();
//------------ update AI info --------------//
if( oldAiUnit )
{
Nation* nationPtr = nation_array[oldNationRecno];
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
nationPtr->del_general_info(sprite_recno);
else if( unit_res[unit_id]->unit_class == UNIT_CLASS_CARAVAN )
nationPtr->del_caravan_info(sprite_recno);
else if( unit_res[unit_id]->unit_class == UNIT_CLASS_SHIP )
nationPtr->del_ship_info(sprite_recno);
}
if( ai_unit && nation_recno != 0 )
{
Nation* nationPtr = nation_array[nation_recno];
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
nationPtr->add_general_info(sprite_recno);
else if( unit_res[unit_id]->unit_class == UNIT_CLASS_CARAVAN )
nationPtr->add_caravan_info(sprite_recno);
else if( unit_res[unit_id]->unit_class == UNIT_CLASS_SHIP )
nationPtr->add_ship_info(sprite_recno);
}
//------ if this unit oversees a firm -----//
if( unit_mode==UNIT_MODE_OVERSEE )
firm_array[unit_mode_para]->change_nation(newNationRecno);
//----- this unit was defending the town before it gets killed ----//
else if( unit_mode==UNIT_MODE_DEFEND_TOWN )
{
if( !town_array.is_deleted(unit_mode_para) )
town_array[unit_mode_para]->reduce_defender_count();
set_mode(0); // reset unit mode
}
//---- if the unit is no longer the same nation as the leader ----//
if( leader_unit_recno )
{
Unit* leaderUnit = unit_array[leader_unit_recno];
if( leaderUnit->nation_recno != nation_recno )
{
leaderUnit->del_team_member(sprite_recno);
leader_unit_recno = 0;
team_id = 0;
}
}
//------ if it is currently selected -------//
if( selected_flag )
info.disp();
}
//----------- End of function Unit::change_nation -----------//
//--------- Begin of function Unit::pay_expense ---------//
//
void Unit::pay_expense()
{
if( game.game_mode == GAME_TEST ) // no deduction in testing game
return;
if( !nation_recno )
return;
//--- if it's a mobile spy or the spy is in its own firm, no need to pay salary here as Spy::pay_expense() will do that ---//
//
// -If your spies are mobile:
// >your nation pays them 1 food and $5 dollars per month
//
// -If your spies are in an enemy's town or firm:
// >the enemy pays them 1 food and the normal salary of their jobs.
//
// >your nation pays them $5 dollars per month. (your nation pays them no food)
//
// -If your spies are in your own town or firm:
// >your nation pays them 1 food and $5 dollars per month
//
//------------------------------------------------------//
if( spy_recno )
{
if( is_visible() ) // the cost will be deducted in spy_array
return;
if( unit_mode == UNIT_MODE_OVERSEE &&
firm_array[unit_mode_para]->nation_recno == true_nation_recno() )
{
return;
}
}
//---------- if it's a human unit -----------//
//
// The unit is paid even during its training period in a town
//
//-------------------------------------------//
Nation* nationPtr = nation_array[nation_recno];
if( unit_res[unit_id]->race_id )
{
//---------- reduce cash -----------//
if( nationPtr->cash > 0 )
{
if( rank_id == RANK_SOLDIER )
nationPtr->add_expense( EXPENSE_MOBILE_UNIT, (float) SOLDIER_YEAR_SALARY / 365, 1 );
if( rank_id == RANK_GENERAL )
nationPtr->add_expense( EXPENSE_GENERAL, (float) GENERAL_YEAR_SALARY / 365, 1 );
}
else // decrease loyalty if the nation cannot pay the unit
{
change_loyalty(-1);
}
//---------- reduce food -----------//
if( unit_res[unit_id]->race_id ) // if it's a human unit
{
if( nationPtr->food > 0 )
nationPtr->consume_food((float) PERSON_FOOD_YEAR_CONSUMPTION / 365);
else
{
if( info.game_date%NO_FOOD_LOYALTY_DECREASE_INTERVAL == 0 ) // decrease 1 loyalty point every 2 days
change_loyalty(-1);
}
}
}
else //----- it's a non-human unit ------//
{
if( nationPtr->cash > 0 )
{
int expenseType;
switch(unit_res[unit_id]->unit_class)
{
case UNIT_CLASS_WEAPON:
expenseType = EXPENSE_WEAPON;
break;
case UNIT_CLASS_SHIP:
expenseType = EXPENSE_SHIP;
break;
case UNIT_CLASS_CARAVAN:
expenseType = EXPENSE_CARAVAN;
break;
default:
expenseType = EXPENSE_MOBILE_UNIT;
}
nationPtr->add_expense( expenseType, (float) unit_res[unit_id]->year_cost / 365, 1 );
}
else // decrease hit points if the nation cannot pay the unit
{
if( unit_res[unit_id]->unit_class != UNIT_CLASS_CARAVAN ) // Even when caravans are not paid, they still stay in your service.
{
if( hit_points > 0 )
{
hit_points--;
if( hit_points < 0 )
hit_points = (float) 0;
//--- when the hit points drop to zero and the unit is destroyed ---//
if( hit_points==0 )
{
if( nation_recno == nation_array.player_recno )
{
int unitClass = unit_res[unit_id]->unit_class;
if( unitClass==UNIT_CLASS_WEAPON )
news_array.weapon_ship_worn_out(unit_id, get_weapon_version());
else if( unitClass==UNIT_CLASS_SHIP )
news_array.weapon_ship_worn_out(unit_id, 0);
}
}
}
}
}
}
}
//----------- End of function Unit::pay_expense -----------//
//--------- Begin of function Unit::change_hit_points ---------//
//
void Unit::change_hit_points(float changePoints)
{
hit_points += changePoints;
if( hit_points < 0 )
hit_points = (float) 0;
if( hit_points > max_hit_points )
hit_points = max_hit_points;
}
//-------- End of function Unit::change_hit_points -----------//
//--------- Begin of function Unit::change_loyalty ---------//
//
// changeAmt - amount of loyalty to be changed.
//
void Unit::change_loyalty(int changeAmt)
{
int newLoyalty = loyalty + changeAmt;
newLoyalty = max(0, newLoyalty);
loyalty = min(100, newLoyalty);
}
//----------- End of function Unit::change_loyalty -----------//
//------- Begin of function Unit::inc_minor_combat_level --------//
//
void Unit::inc_minor_combat_level(int incLevel)
{
err_when( incLevel<0 || incLevel>100 ); // it cannot be larger than 100, because the current code can't handle it
skill.combat_level_minor += incLevel;
if( skill.combat_level_minor > 100 )
{
if( skill.combat_level < 100 )
set_combat_level(skill.combat_level+1);
skill.combat_level_minor -= 100;
}
}
//-------- End of function Unit::inc_minor_combat_level ---------//
//------- Begin of function Unit::inc_minor_skill_level --------//
//
void Unit::inc_minor_skill_level(int incLevel)
{
err_when( incLevel<0 || incLevel>100 );
skill.skill_level_minor += incLevel;
if( skill.skill_level_minor > 100 )
{
if( skill.skill_level < 100 )
skill.skill_level++;
skill.skill_level_minor -= 100;
}
}
//-------- End of function Unit::inc_minor_skill_level ---------//
//--------- Begin of function Unit::set_combat_level ---------//
//
void Unit::set_combat_level(int combatLevel)
{
err_when( combatLevel<=0 || combatLevel>100 );
skill.combat_level = combatLevel;
UnitInfo* unitInfo = unit_res[unit_id];
int oldMaxHitPoints = max_hit_points;
max_hit_points = unitInfo->hit_points * combatLevel / 100;
hit_points = hit_points * max_hit_points / oldMaxHitPoints;
hit_points = min(hit_points, max_hit_points);
// --------- update can_guard_flag -------//
if( combatLevel >= unitInfo->guard_combat_level)
{
can_guard_flag = sprite_info->can_guard_flag;
#ifdef AMPLUS
if( unit_id == UNIT_ZULU )
can_guard_flag |= 4; // shield during attack delay
#endif
}
else
{
can_guard_flag = 0;
}
max_power = skill.combat_level + 50;
cur_power = min(cur_power, max_power);
}
//-------- End of function Unit::set_combat_level -----------//
//--------- Begin of function Unit::set_rank ---------//
//
// Only if the unit has leadership skill, it can be a general or king.
//
void Unit::set_rank(int rankId)
{
err_when( !race_id );
#ifdef DEBUG
if( !is_visible() )
{
err_when( rank_id==RANK_GENERAL && rankId==RANK_SOLDIER );
err_when( rank_id==RANK_SOLDIER && rankId==RANK_GENERAL );
}
#endif
if( rank_id == rankId )
return;
//------- promote --------//
if( rankId > rank_id )
change_loyalty(PROMOTE_LOYALTY_INCREASE);
//------- demote -----------//
else if( rankId < rank_id && rank_id != RANK_KING ) // no decrease in loyalty if a spy king hands his nation to his parent nation and become a general again
change_loyalty(-DEMOTE_LOYALTY_DECREASE);
//---- update nation_general_count_array[] ----//
if( nation_recno )
{
UnitInfo* unitInfo = unit_res[unit_id];
if( rank_id == RANK_GENERAL ) // if it was a general originally
unitInfo->dec_nation_general_count(nation_recno);
if( rankId == RANK_GENERAL ) // if the new rank is general
unitInfo->inc_nation_general_count(nation_recno);
//------ if demote a king to a unit ------//
if( rank_id == RANK_KING && rankId != RANK_KING )
unitInfo->inc_nation_unit_count(nation_recno); // since kings are not included in nation_unit_count, when it is no longer a king, we need to re-increase it.
//------ if promote a unit to a king ------//
if( rank_id != RANK_KING && rankId == RANK_KING )
unitInfo->dec_nation_unit_count(nation_recno); // since kings are not included in nation_unit_count, we need to decrease it
}
//----- reset leader_unit_recno if demote a general to soldier ----//
if( rank_id == RANK_GENERAL && rankId == RANK_SOLDIER )
{
//----- reset leader_unit_recno of the units he commands ----//
for( int i=unit_array.size() ; i>0 ; i-- )
{
Unit* unitPtr = (Unit*) unit_array.get_ptr(i); // don't use is_deleted() as it filters out units that are currently dying
if( unitPtr && unitPtr->leader_unit_recno == sprite_recno )
{
unitPtr->leader_unit_recno = 0;
unitPtr->team_id = 0;
}
}
//--------- deinit team_info ---------//
err_when( !team_info );
mem_del(team_info);
team_info = NULL;
team_id = 0;
}
//----- if this is a soldier being promoted to a general -----//
else if( rank_id == RANK_SOLDIER && rankId == RANK_GENERAL )
{
//-- if this soldier is formerly commanded by a general, detech it ---//
if( leader_unit_recno )
{
if( !unit_array.is_deleted(leader_unit_recno) ) // the leader unit may have been killed at the same time
unit_array[leader_unit_recno]->del_team_member(sprite_recno);
leader_unit_recno = 0;
}
}
//-------------- update AI info --------------//
if( ai_unit )
{
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
nation_array[nation_recno]->del_general_info(sprite_recno);
rank_id = rankId;
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
nation_array[nation_recno]->add_general_info(sprite_recno);
}
else
{
rank_id = rankId;
}
//----- if this is a general/king ------//
if( rank_id == RANK_GENERAL || rank_id == RANK_KING )
{
//--------- init team_info -------//
if( !team_info )
{
team_info = (TeamInfo*) mem_add( sizeof(TeamInfo) );
team_info->member_count = 0;
}
//--- set leadership if this unit does not have any now ----//
if( skill.skill_id != SKILL_LEADING )
{
skill.skill_id = SKILL_LEADING;
skill.skill_level = 10 + m.random(40);
}
}
//------ refresh if the current unit is selected -----//
if( unit_array.selected_recno == sprite_recno )
info.disp();
}
//-------- End of function Unit::set_rank -----------//
//--------- Begin of function Unit::embark ---------//
//
// Order this unit to embark an vehicle
//
// vehicleRecno - recno of the vehicle.
//
void Unit::embark(int vehicleRecno)
{
err_here(); // this function is no longer functional
Unit* vehiclePtr = unit_array[vehicleRecno];
if( unit_res[unit_id]->vehicle_id == vehiclePtr->unit_id ) // not the right vehicle
return;
int vehicleUnitId = unit_res[unit_id]->vehicle_unit_id;
float vehicleHitPoints = vehiclePtr->hit_points;
int xLoc = vehiclePtr->cur_x_loc();
int yLoc = vehiclePtr->cur_y_loc();
//------- delete the vehicle unit --------//
unit_array.del(vehicleRecno); // delete the vehicle (e.g. horse)
//--------- add the combined unit -------//
int newUnitRecno = unit_array.add_unit(vehicleUnitId, nation_recno, rank_id, loyalty, xLoc, yLoc); // add the combined unit (e.g. cavalry)
UnitVehicle* unitVehicle = (UnitVehicle*) unit_array[newUnitRecno];
unitVehicle->skill = skill;
unitVehicle->set_combat_level(skill.combat_level);
unitVehicle->solider_hit_points = (int) hit_points;
unitVehicle->vehicle_hit_points = (int) vehicleHitPoints;
unitVehicle->hit_points = (float) unitVehicle->solider_hit_points +
unitVehicle->vehicle_hit_points;
//-------- delete the solider unit ---------//
unit_array.del(sprite_recno); // delete the embarker (e.g. knight)
}
//-------- End of function Unit::embark -----------//
//--------- Begin of function Unit::reward ---------//
//
// rewardNationRecno - the nation which does this reward.
//
void Unit::reward(int rewardNationRecno)
{
// ###### patch begin Gilbert 24/9 ########//
if( nation_array[rewardNationRecno]->cash < REWARD_COST )
return;
// ###### patch end Gilbert 24/9 ########//
//--------- if this is a spy ---------//
if( spy_recno && true_nation_recno() == rewardNationRecno ) // if the spy's owning nation rewards the spy
{
spy_array[spy_recno]->change_loyalty(REWARD_LOYALTY_INCREASE);
}
//--- if this spy's nation_recno & true_nation_recno() are both == rewardNationRecno, it's true loyalty and cloaked loyalty will both be increased ---//
if( nation_recno == rewardNationRecno )
{
total_reward += REWARD_COST;
change_loyalty(REWARD_LOYALTY_INCREASE);
}
nation_array[rewardNationRecno]->add_expense(EXPENSE_REWARD_UNIT, (float)REWARD_COST);
}
//----------- End of function Unit::reward -----------//
//------- Begin of function Unit::overseer_migrate ---------//
//
// Order the overseer migrate to a new town but still keeps
// working for the same firm.
//
// destTownRecno - the recno of the town the worker should
// migrate to.
//
void Unit::overseer_migrate(int destTownRecno)
{
err_when( unit_mode!=UNIT_MODE_OVERSEE );
int curTownRecno = firm_array[unit_mode_para]->overseer_town_recno;
//------- decrease the population of the unit's home town ------//
town_array[curTownRecno]->dec_pop(race_id, 1);
//--------- increase the population of the target town ------//
town_array[destTownRecno]->inc_pop(race_id, 1, loyalty );
}
//-------- End of function Unit::overseer_migrate ---------//
//--------- Begin of function Unit::group_transform ---------//
void Unit::group_transform(char remoteAction, short *selectedArray, short selectedCount)
{
}
//----------- End of function Unit::group_transform -----------//
//--------- Begin of function Unit::transform ---------//
//
// Transform the unit into another unit type.
//
void Unit::transform()
{
/*
UnitInfo* unitInfo = unit_res[unit_id];
if( unitInfo->transform_unit_id==0 )
return;
UnitInfo* newUnitInfo = unit_res[unitInfo->transform_unit_id];
//--- check if the unit has the required combat level ---//
if( skill.combat_level < unitInfo->transform_combat_level )
return;
//------ free up space on the map for the new unit -----//
int xLoc=next_x_loc(), yLoc=next_y_loc();
stop();
deinit_sprite(1); // 1-keep the unit selected if it is currently selected
//---- locate the space for the new unit as their size, mobile type may be different ----//
SpriteInfo* spriteInfo = sprite_res[newUnitInfo->sprite_id];
int xLoc2 = xLoc + spriteInfo->loc_width-1;
int yLoc2 = yLoc + spriteInfo->loc_height-1;
if( !world.check_unit_space(xLoc, yLoc, xLoc2, yLoc2, newUnitInfo->mobile_type) ) // first check if if is free to create unit on the same location
{
if( !world.locate_space( xLoc, yLoc, xLoc+spriteInfo->loc_width-1,
yLoc+spriteInfo->loc_height-1, spriteInfo->loc_width,
spriteInfo->loc_height, newUnitInfo->mobile_type) )
{
init_sprite( xLoc, yLoc ); // not able to find space for the new unit, transformation cancelled.
return;
}
}
//--------- transform now ------------//
deinit_unit_id();
init_unit_id(unitInfo->transform_unit_id);
init_sprite( xLoc, yLoc );
*/
}
//----------- End of function Unit::transform -----------//
//--------- Begin of function Unit::spy_change_nation ---------//
//
// Change the deceiving nation recno of a spy unit which you control.
//
// newNationRecno - the new nation the spy changes its cloack to
// remoteAction - remote action type
//
void Unit::spy_change_nation(int newNationRecno, char remoteAction)
{
if( newNationRecno == nation_recno )
return;
if( newNationRecno && nation_array.is_deleted(newNationRecno) ) // this can happen in a multiplayer message
return;
//------- if this is a remote action -------//
if( !remoteAction && remote.is_enable() )
{
// packet structure
short *shortPtr = (short *)remote.new_send_queue_msg(MSG_UNIT_SPY_NATION, 2*sizeof(short) );
*shortPtr = sprite_recno;
shortPtr[1] = newNationRecno;
return;
}
//----- update the var in Spy ------//
Spy* spyPtr = spy_array[spy_recno];
//--- when a spy change cloak to another nation, he can't cloak as a general, he must become a soldier first ---//
if( is_visible() && // if the spy is a commander in a camp, don't set its rank to soldier
rank_id == RANK_GENERAL &&
newNationRecno != spyPtr->true_nation_recno )
{
set_rank(RANK_SOLDIER);
}
//---------------------------------------------------//
//
// If this spy unit is a general or an overseer of the
// cloaked nation, when he changes nation, that will
// inevitably be noticed by the cloaked nation.
//
//---------------------------------------------------//
if( spyPtr->true_nation_recno != nation_array.player_recno ) // only send news message if he is not the player's own spy
{
if( rank_id == RANK_GENERAL || unit_mode == UNIT_MODE_OVERSEE ||
spyPtr->notify_cloaked_nation_flag )
{
//-- if this spy's cloaked nation is the player's nation, the player will be notified --//
if( nation_recno == nation_array.player_recno )
news_array.unit_betray(sprite_recno, newNationRecno);
}
//---- send news to the cloaked nation if notify flag is on ---//
if( spyPtr->notify_cloaked_nation_flag )
{
if( newNationRecno == nation_array.player_recno ) // cloaked as the player's nation
news_array.unit_betray(sprite_recno, newNationRecno);
}
}
//--------- change nation recno now --------//
spyPtr->cloaked_nation_recno = newNationRecno;
betray(newNationRecno); // call the betray function to change natino. There is no difference between a spy changing nation and a unit truly betrays
}
//----------- End of function Unit::spy_change_nation -----------//
//--------- Begin of function Unit::can_spy_change_nation ---------//
//
// Whether the spy unit can change its spy cloak now or not.
//
// If there are enemy nearby, the unit cannot change its cloak.
//
int Unit::can_spy_change_nation()
{
if( !spy_recno )
return 0;
//--------------------------------------------//
int xLoc1=cur_x_loc()-SPY_ENEMY_RANGE, yLoc1=cur_y_loc()-SPY_ENEMY_RANGE;
int xLoc2=cur_x_loc()+SPY_ENEMY_RANGE, yLoc2=cur_y_loc()+SPY_ENEMY_RANGE;
xLoc1 = max(0, xLoc1);
yLoc1 = max(0, yLoc1);
xLoc2 = min(MAX_WORLD_X_LOC-1, xLoc2);
yLoc2 = min(MAX_WORLD_Y_LOC-1, yLoc2);
int xLoc, yLoc;
int unitRecno, trueNationRecno = true_nation_recno();
Location* locPtr;
for( yLoc=yLoc1 ; yLoc<=yLoc2 ; yLoc++ )
{
locPtr = world.get_loc(xLoc1, yLoc);
for( xLoc=xLoc1 ; xLoc<=xLoc2 ; xLoc++, locPtr++ )
{
if( locPtr->has_unit(UNIT_LAND) )
unitRecno = locPtr->unit_recno(UNIT_LAND);
else if( locPtr->has_unit(UNIT_SEA) )
unitRecno = locPtr->unit_recno(UNIT_SEA);
else if( locPtr->has_unit(UNIT_AIR) )
unitRecno = locPtr->unit_recno(UNIT_AIR);
else
continue;
if( unit_array.is_deleted(unitRecno) ) // the unit is dying, its recno is still in the location
continue;
if( unit_array[unitRecno]->true_nation_recno() != trueNationRecno )
return 0;
}
}
return 1;
}
//----------- End of function Unit::can_spy_change_nation -----------//
//--------- Begin of function Unit::resign ---------//
//
// Resign the unit.
//
void Unit::resign(int remoteAction)
{
if( !remoteAction && remote.is_enable() )
{
// packet structure :
short *shortPtr = (short *)remote.new_send_queue_msg(MSG_UNIT_RESIGN, 2*sizeof(short));
*shortPtr = sprite_recno;
shortPtr[1] = nation_array.player_recno;
return;
}
//---- increase the wandering count when a unit is disbanded ----//
if( race_id )
town_array.race_wander_pop_array[race_id-1] += 2; // disbanding one resulted in two wandering units to make the effect more significant
//--- if the unit is visible, call stop2() so if it has an AI action queue, that will be reset ---//
if( is_visible() )
stop2();
//--- if the spy is resigned by an enemy, display a message ---//
if( spy_recno && true_nation_recno() != nation_recno ) // the spy is cloaked in an enemy nation when it is resigned
{
//------ decrease reputation ------//
nation_array[true_nation_recno()]->change_reputation((float)-SPY_KILLED_REPUTATION_DECREASE);
//------- add news message -------//
if( true_nation_recno() == nation_array.player_recno || // display when the player's spy is revealed or the player has revealed an enemy spy
nation_recno == nation_array.player_recno )
{
//--- If a spy is caught, the spy's nation's reputation wil decrease ---//
news_array.spy_killed(spy_recno);
}
}
//----------------------------------------------//
if( rank_id == RANK_GENERAL ) // if this is a general, news_array.general_die() will be called, set news_add_flag to 0 to suppress the display of thew news
news_array.news_add_flag=0;
unit_array.del( sprite_recno );
news_array.news_add_flag=1;
}
//----------- End of function Unit::resign -----------//
//--------- Begin of function Unit::region_id ---------//
//
// Return the region id. of this unit.
//
BYTE Unit::region_id()
{
if( is_visible() )
{
return world.get_region_id( next_x_loc(), next_y_loc() );
}
else
{
if( unit_mode == UNIT_MODE_OVERSEE )
return firm_array[unit_mode_para]->region_id;
}
return 0;
}
//----------- End of function Unit::region_id -----------//
//--------- Begin of function Unit::del_team_member ---------//
//
// Delete a specific member of the team led by this leader.
//
void Unit::del_team_member(int unitRecno)
{
err_when( !team_info );
for( int i=0 ; imember_count ; i++ )
{
if( team_info->member_unit_array[i] == unitRecno )
{
err_when( team_info->member_count > MAX_TEAM_MEMBER );
m.del_array_rec( team_info->member_unit_array, team_info->member_count,
sizeof( team_info->member_unit_array[0] ), i+1 );
team_info->member_count--;
return;
}
}
//-------------------------------------------------------//
//
// Note: for rebels and monsters, although they have
// leader_unit_recno, their team_info is not used.
// So del_team_member() won't be able to match the
// unit in its member_unit_array[].
//
//-------------------------------------------------------//
}
//----------- End of function Unit::del_team_member -----------//
//--------- Begin of function Unit::validate_team ---------//
//
// Validate the member in this commander's team. If there
// are any units with hit_points <= 0, delete them.
//
// Those unit may just be killed, so soon that the Unit's set_die()
// function hsa been called yet. validate_team() function must
// be called before all issunig any new team actions.
//
void Unit::validate_team()
{
err_when( !team_info );
int unitRecno;
for( int i=team_info->member_count-1 ; i>=0 ; i-- )
{
unitRecno = team_info->member_unit_array[i];
if( unit_array.is_deleted(unitRecno) )
{
err_when( team_info->member_count > MAX_TEAM_MEMBER );
m.del_array_rec( team_info->member_unit_array, team_info->member_count,
sizeof( team_info->member_unit_array[0] ), i+1 );
team_info->member_count--;
}
}
}
//----------- End of function Unit::validate_team -----------//
//--------- Begin of function Unit::commanded_soldier_count ---------//
//
// Return the no. of soldiers commanded by this unit.
//
int Unit::commanded_soldier_count()
{
if( rank_id != RANK_GENERAL && rank_id != RANK_KING )
return 0;
//--------------------------------------//
err_when( !team_info );
int soldierCount=0;
if( is_visible() )
{
soldierCount = team_info->member_count-1;
if( soldierCount < 0 ) // member_count can be 0
soldierCount = 0;
}
else
{
if( unit_mode == UNIT_MODE_OVERSEE )
{
Firm* firmPtr = firm_array[unit_mode_para];
if( firmPtr->firm_id == FIRM_CAMP ) // it can be an overseer of a seat of powre
soldierCount = firmPtr->worker_count;
}
}
return soldierCount;
}
//----------- End of function Unit::commanded_soldier_count -----------//
//----------- Begin of function Unit::fix_attack_info -----------//
void Unit::fix_attack_info()
{
int techLevel;
UnitInfo *unitInfo = unit_res[unit_id];
attack_count = unitInfo->attack_count;
if( attack_count > 0 && unitInfo->first_attack > 0)
attack_info_array = unit_res.attack_info_array+unitInfo->first_attack-1;
else
attack_info_array = NULL;
if( unitInfo->unit_class == UNIT_CLASS_WEAPON &&
(techLevel=get_weapon_version()) > 0 )
{
switch( unit_id )
{
case UNIT_BALLISTA:
#ifdef AMPLUS
case UNIT_F_BALLISTA:
#endif
attack_count = 2;
break;
case UNIT_EXPLOSIVE_CART:
attack_count = 0;
break;
default:
attack_count = 1;
}
if( attack_count > 0)
{
attack_info_array += (techLevel-1) * attack_count;
}
else
{
// no attack like explosive cart
attack_info_array = NULL;
}
}
}
//----------- End of function Unit::fix_attack_info -----------//
//----------- Begin of function Unit::return_camp -----------//
//
// Order this unit to return to the camp. For ordering many
// units to return to a camp, UnitArray::return_camp() should
// be called instead.
//
int Unit::return_camp()
{
if( !home_camp_firm_recno )
return 0;
err_when( firm_array.is_deleted(home_camp_firm_recno) );
Firm* firmPtr = firm_array[home_camp_firm_recno];
if( firmPtr->region_id != region_id() )
return 0;
err_when( firmPtr->firm_id != FIRM_CAMP );
err_when( firmPtr->nation_recno != nation_recno );
//--------- assign now ---------//
assign(firmPtr->loc_x1, firmPtr->loc_y1);
force_move_flag = 1;
return cur_action != SPRITE_IDLE;
}
//----------- End of function Unit::return_camp -----------//
//----------- Begin of function Unit::unit_power -----------//
//
// Return a power index of the weapon, this is for calculating
// the total combat level of a target.
//
int Unit::unit_power()
{
UnitInfo* unitInfo = unit_res[unit_id];
if( unitInfo->unit_class == UNIT_CLASS_WEAPON )
{
return (int) hit_points + (unitInfo->weapon_power + get_weapon_version() - 1) * 15;
}
else
{
return (int) hit_points;
}
}
//----------- End of function Unit::unit_power -----------//
//----------- Begin of function Unit::get_cur_loc -----------//
//
// xLoc, yLoc - reference vars for returning the
// location of this unit
//
// return : 0 - if this unit is invisible
// 1 - if a location has been returned.
//
int Unit::get_cur_loc(short& xLoc, short& yLoc)
{
if( is_visible() )
{
xLoc = next_x_loc(); // update location
yLoc = next_y_loc();
}
else if( unit_mode == UNIT_MODE_OVERSEE ||
unit_mode==UNIT_MODE_CONSTRUCT ||
unit_mode == UNIT_MODE_IN_HARBOR )
{
Firm* firmPtr = firm_array[unit_mode_para];
xLoc = firmPtr->center_x;
yLoc = firmPtr->center_y;
}
else if( unit_mode == UNIT_MODE_ON_SHIP )
{
Unit* unitPtr = unit_array[unit_mode_para];
//### begin alex 22/10 ###//
//xLoc = unitPtr->next_x_loc();
//yLoc = unitPtr->next_y_loc();
if(unitPtr->is_visible())
{
xLoc = unitPtr->next_x_loc();
yLoc = unitPtr->next_y_loc();
}
else
{
err_when(unitPtr->unit_mode!=UNIT_MODE_IN_HARBOR);
Firm *firmPtr = firm_array[unitPtr->unit_mode_para];
xLoc = firmPtr->center_x;
yLoc = firmPtr->center_y;
}
//#### end alex 22/10 ####//
}
else
return 0;
return 1;
}
//----------- End of function Unit::get_cur_loc -----------//
//----------- Begin of function Unit::add_way_point -----------//
// Add the point to the way_point_array if it is not in the array.
// Otherwise, remove the point from the way_point_array.
//
// x - x coordinate of the point
// y - y coordinate of the point
//
void Unit::add_way_point(short x, short y)
{
if(way_point_count>=100)
return; // too many way point
if(way_point_count>1) // don't allow to remove the 1st node, since the unit is moving there
{
ResultNode *nodePtr = way_point_array + 1;
for(int i=1; inode_x == x && nodePtr->node_y == y) // remove this node
{
m.del_array_rec(way_point_array, way_point_count, sizeof(ResultNode), i+1); // remove 1st node
way_point_count--;
return; // there should be one and only one node with the same value
}
}
}
//-------------- add new node -----------------//
if(way_point_count>=way_point_array_size) // buffer full
{
way_point_array_size += WAY_POINT_ADJUST_SIZE;
if(way_point_count)
way_point_array = (ResultNode*) mem_resize(way_point_array, way_point_array_size*sizeof(ResultNode));
else
way_point_array = (ResultNode*) mem_add(sizeof(ResultNode)*WAY_POINT_ADJUST_SIZE);
}
ResultNode *nodePtr = way_point_array + way_point_count;
nodePtr->node_x = x;
nodePtr->node_y = y;
way_point_count++;
if(way_point_count==1)
move_to(x, y);
}
//----------- End of function Unit::add_way_point -----------//
//----------- Begin of function Unit::reset_way_point_array -----------//
void Unit::reset_way_point_array()
{
//------------------------------------------------------------------------------------//
// There are nly two conditions to reset the way_point_array
// 1) action_mode2!=ACTION_MOVE in Unit::stop()
// 2) dest? != node_? in the first node of way_point_array in calling Unit::move_to()
//------------------------------------------------------------------------------------//
if(way_point_array_size)
{
mem_del(way_point_array);
way_point_array = NULL;
way_point_array_size = 0;
way_point_count = 0;
}
}
//----------- End of function Unit::reset_way_point_array -----------//
//----------- Begin of function Unit::process_way_point -----------//
// move to the next way point and remove this point from the
// way_point_array
//
void Unit::process_way_point()
{
err_when(action_mode!=ACTION_STOP || action_mode2!=ACTION_STOP || cur_action!=SPRITE_IDLE);
int destX, destY;
if(way_point_count>1)
{
ResultNode *nodePtr = way_point_array+1;
destX = nodePtr->node_x;
destY = nodePtr->node_y;
m.del_array_rec(way_point_array, way_point_count, sizeof(ResultNode), 1); // remove 1st node
way_point_count--;
}
else // only one unprocessed node
{
ResultNode *nodePtr = way_point_array;
destX = nodePtr->node_x;
destY = nodePtr->node_y;
//m.del_array_rec(way_point_array, way_point_count, sizeof(ResultNode), 1); // remove 1st node
//way_point_count--;
}
move_to(destX, destY);
}
//----------- End of function Unit::process_way_point -----------//
//----------- Begin of function TeamInfo::TeamInfo ---------//
TeamInfo::TeamInfo()
{
memset( this, 0, sizeof(TeamInfo) );
}
//----------- End of function TeamInfo::TeamInfo ---------//