/*
* 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 : OBULLET.CPP
//Description : Object Bullet
//Owner : Alex
#include
#include
#include
#include
#include
#include
#include
#include
// -------- Define constant ---------//
const int SCAN_RADIUS = 2;
const int SCAN_RANGE = SCAN_RADIUS * 2 + 1;
// from the closet to the far
static char spiral_x[SCAN_RANGE*SCAN_RANGE] =
{ 0, 0,-1, 0, 1,-1,-1, 1, 1, 0,-2, 0, 2, -1,-2,-2,-1, 1, 2, 2, 1,-2,-2, 2, 2};
static char spiral_y[SCAN_RANGE*SCAN_RANGE] =
{ 0,-1, 0, 1, 0,-1, 1, 1,-1,-2, 0, 2, 0, -2,-1, 1, 2, 2, 1,-1,-2,-2, 2, 2,-2};
//--------- Begin of function Bullet::Bullet -------//
Bullet::Bullet()
{
sprite_id = 0;
}
//--------- End of function Bullet::Bullet -------//
//--------- Begin of function Bullet::init ---------//
//
// parentType - the type of object emits the bullet
// parentRecno - the recno of the object
// targetXLoc - the x loc of the target
// targetYLoc - the y loc of the target
// targetMobileType - target mobile type
//
void Bullet::init(char parentType, short parentRecno, short targetXLoc, short targetYLoc, char targetMobileType)
{
parent_type = parentType;
parent_recno = parentRecno;
target_mobile_type = targetMobileType;
//**** BUGHERE, using parentType and parentRecno to allow bullet by firm, town, etc.
//**** BUGHERE, only allow bullet by unit for this version
err_when(parent_type!=BULLET_BY_UNIT);
Unit *parentUnit = unit_array[parentRecno];
//---------- copy attack info from the parent unit --------//
AttackInfo* attackInfo = parentUnit->attack_info_array+parentUnit->cur_attack;
attack_damage = parentUnit->actual_damage();
damage_radius = attackInfo->bullet_radius;
nation_recno = parentUnit->nation_recno;
// ###### begin Gilbert 26/6 ########## //
fire_radius = attackInfo->fire_radius;
// ###### end Gilbert 26/6 ########## //
//----- clone vars from sprite_res for fast access -----//
sprite_id = attackInfo->bullet_sprite_id;
sprite_info = sprite_res[sprite_id];
sprite_info->load_bitmap_res(); // the sprite bitmap will be freed by ~Sprite(), so we don't have to add ~Bullet() to free it.
//--------- set the starting position of the bullet -------//
cur_action = SPRITE_MOVE;
cur_frame = 1;
set_dir(parentUnit->attack_dir);
SpriteFrame* spriteFrame = cur_sprite_frame();
origin_x = cur_x = parentUnit->cur_x;
origin_y = cur_y = parentUnit->cur_y;
//------ set the target position and bullet mobile_type -------//
target_x_loc = targetXLoc;
target_y_loc = targetYLoc;
go_x = target_x_loc * ZOOM_LOC_WIDTH + ZOOM_LOC_WIDTH/2 - spriteFrame->offset_x - spriteFrame->width/2; // -spriteFrame->offset_x to make abs_x1 & abs_y1 = original x1 & y1. So the bullet will be centered on the target
go_y = target_y_loc * ZOOM_LOC_HEIGHT + ZOOM_LOC_HEIGHT/2 - spriteFrame->offset_y - spriteFrame->height/2;
mobile_type = parentUnit->mobile_type;
//---------- set bullet movement steps -----------//
int xStep = (go_x - cur_x)/attackInfo->bullet_speed;
int yStep = (go_y - cur_y)/attackInfo->bullet_speed;
total_step = max(1, max(abs(xStep), abs(yStep)));
cur_step = 0;
err_when( total_step < 0 ); // number overflow
}
//----------- End of function Bullet::init -----------//
//--------- Begin of function Bullet::process_move --------//
void Bullet::process_move()
{
//-------------- update position -----------------//
//
// If it gets very close to the destination, fit it
// to the destination ingoring the normal vector.
//
//------------------------------------------------//
cur_x = origin_x + (int)(go_x-origin_x) * cur_step / total_step;
cur_y = origin_y + (int)(go_y-origin_y) * cur_step / total_step;
//cur_step++;
//------- update frame id. --------//
if( ++cur_frame > cur_sprite_move()->frame_count )
cur_frame = 1;
//----- if the sprite has reach the destintion ----//
//if( cur_step > total_step )
if( ++cur_step > total_step )
{
check_hit();
cur_action = SPRITE_DIE; // Explosion
// ###### begin Gilbert 17/5 #########//
// if it has die frame, adjust cur_x, cur_y to be align with the target_x_loc, target_y_loc
if( sprite_info->die.first_frame_recno )
{
next_x = cur_x = target_x_loc * ZOOM_LOC_WIDTH;
next_y =cur_y = target_y_loc * ZOOM_LOC_HEIGHT;
}
// ###### end Gilbert 17/5 #########//
cur_frame = 1;
}
else if( total_step - cur_step == 1 )
{
warn_target();
}
}
//---------- End of function Bullet::process_move ----------//
//--------- Begin of function Bullet::process_die --------//
//
// return : 1 - dying animation completes.
// 0 - still dying
//
int Bullet::process_die()
{
// ------- sound effect --------//
se_res.sound(cur_x_loc(), cur_y_loc(), cur_frame, 'S',sprite_id,"DIE");
//--------- next frame ---------//
if( ++cur_frame > sprite_info->die.frame_count )
// ####### begin Gilbert 28/6 ########//
if( ++cur_frame > sprite_info->die.frame_count )
{
// ------- set fire on the target area --------//
if( fire_radius > 0)
{
Location *locPtr;
if( fire_radius == 1)
{
locPtr = world.get_loc(target_x_loc, target_y_loc);
if( locPtr->can_set_fire() && locPtr->fire_str() < 30 )
locPtr->set_fire_str(30);
if( locPtr->fire_src() > 0 )
locPtr->set_fire_src(1); // such that the fire will be put out quickly
}
else
{
short x, y, x1, y1, x2, y2;
// ##### begin Gilbert 2/10 ######//
x1 = target_x_loc - fire_radius + 1;
if( x1 < 0 )
x1 = 0;
y1 = target_y_loc - fire_radius + 1;
if( y1 < 0 )
y1 = 0;
x2 = target_x_loc + fire_radius - 1;
if( x2 >= world.max_x_loc )
x2 = world.max_x_loc-1;
y2 = target_y_loc + fire_radius - 1;
if( y2 >= world.max_y_loc )
y2 = world.max_y_loc-1;
// ##### end Gilbert 2/10 ######//
for( y = y1; y <= y2; ++y)
{
locPtr = world.get_loc(x1, y);
for( x = x1; x <= x2; ++x, ++locPtr)
{
// ##### begin Gilbert 30/10 ######//
int dist = abs(x-target_x_loc) + abs(y-target_y_loc);
if( dist > fire_radius)
continue;
int fl = 30 - dist * 7;
if( fl < 10 )
fl = 10;
if( locPtr->can_set_fire() && locPtr->fire_str() < fl )
locPtr->set_fire_str(fl);
if( locPtr->fire_src() > 0 )
locPtr->set_fire_src(1); // such that the fire will be put out quickly
// ##### begin Gilbert 30/10 ######//
}
}
}
}
return 1;
}
// ####### end Gilbert 28/6 ########//
return 0;
}
//--------- End of function Bullet::process_die --------//
//--------- Begin of function Bullet::hit_target --------//
// ####### begin Gilbert 14/5 #########//
void Bullet::hit_target(short x, short y)
{
//---- check if there is any unit in the target location ----//
Location* locPtr = world.get_loc(x, y);
// ####### end Gilbert 14/5 #########//
short targetUnitRecno = locPtr->unit_recno(target_mobile_type);
if(unit_array.is_deleted(targetUnitRecno))
return; // the target unit is deleted
Unit* targetUnit = unit_array[targetUnitRecno];
Unit* parentUnit;
if(unit_array.is_deleted(parent_recno))
//### begin alex 26/9 ###//
// parentUnit = NULL; // parent is dead
{
parentUnit = NULL; // parent is dead
if(nation_array.is_deleted(nation_recno))
return;
}
//#### end alex 26/9 ####//
else
{
parentUnit = unit_array[parent_recno];
nation_recno = parentUnit->nation_recno;
}
float attackDamage = attenuated_damage(targetUnit->cur_x, targetUnit->cur_y);
// -------- if the unit is guarding reduce damage ----------//
err_when(unit_array.is_deleted(locPtr->unit_recno(target_mobile_type)));
// ##### begin Gilbert 14/5 #########//
if( attackDamage == 0 )
return;
if( targetUnit->is_nation(nation_recno) )
{
if( targetUnit->unit_id == UNIT_EXPLOSIVE_CART )
((UnitExpCart *)targetUnit)->trigger_explode();
return;
}
// ##### end Gilbert 14/5 #########//
// ##### begin Gilbert 3/9 #########//
if( !nation_array.should_attack(nation_recno, targetUnit->nation_recno) )
return;
// ##### end Gilbert 3/9 #########//
if(targetUnit->is_guarding())
{
switch(targetUnit->cur_action)
{
case SPRITE_IDLE:
case SPRITE_READY_TO_MOVE:
case SPRITE_TURN:
case SPRITE_MOVE:
// #ifdef AMPLUS
case SPRITE_ATTACK:
// #endif
// ####### begin Gilbert 9/9 #######//
// check if on the opposite direction
if( (targetUnit->cur_dir & 7)== ((cur_dir + 4 ) & 7)
|| (targetUnit->cur_dir & 7)== ((cur_dir + 3 ) & 7)
|| (targetUnit->cur_dir & 7)== ((cur_dir + 5 ) & 7) )
// ####### end Gilbert 9/9 #######//
{
attackDamage = attackDamage > (float)10/ATTACK_SLOW_DOWN ? attackDamage - (float)10/ATTACK_SLOW_DOWN : 0;
se_res.sound( targetUnit->cur_x_loc(), targetUnit->cur_y_loc(), 1,
'S', targetUnit->sprite_id, "DEF", 'S', sprite_id );
}
break;
}
}
targetUnit->hit_target(parentUnit, targetUnit, attackDamage);
}
//---------- End of function Bullet::hit_target ----------//
//------- Begin of function Bullet::hit_building -----//
// building means firm or town
//
// ###### begin Gilbert 14/5 #########//
void Bullet::hit_building(short x, short y)
{
Location* locPtr = world.get_loc(x, y);
if(locPtr->is_firm())
{
Firm *firmPtr = firm_array[locPtr->firm_recno()];
// ##### begin Gilbert 3/9 #########//
if( !firmPtr || !nation_array.should_attack(nation_recno, firmPtr->nation_recno) )
// ##### end Gilbert 3/9 #########//
return;
}
else if(locPtr->is_town())
{
Town *townPtr = town_array[locPtr->town_recno()];
// ##### begin Gilbert 3/9 #########//
if( !townPtr || !nation_array.should_attack(nation_recno, townPtr->nation_recno) )
// ##### end Gilbert 3/9 #########//
return;
}
else
return;
float attackDamage = attenuated_damage(x * ZOOM_LOC_WIDTH, y * ZOOM_LOC_HEIGHT );
// BUGHERE : hit building of same nation?
if( attackDamage == 0)
return;
Unit *virtualUnit, *parentUnit;
if(unit_array.is_deleted(parent_recno))
{
parentUnit = NULL;
//### begin alex 26/9 ###//
if(nation_array.is_deleted(nation_recno))
return;
//#### end alex 26/9 ####//
for(int i=unit_array.size(); i>0; i--)
{
if(unit_array.is_deleted(i))
continue;
virtualUnit = unit_array[i];
break;
}
if(!virtualUnit)
return; //**** BUGHERE
}
else
virtualUnit = parentUnit = unit_array[parent_recno];
virtualUnit->hit_building(parentUnit, target_x_loc, target_y_loc, attackDamage);
// ####### end Gilbert 14/5 ########//
}
//---------- End of function Bullet::hit_building ----------//
//------- Begin of function Bullet::hit_wall -----//
// ###### begin Gilbert 14/5 #########//
void Bullet::hit_wall(short x, short y)
{
Location* locPtr = world.get_loc(x, y);
if(!locPtr->is_wall())
return;
float attackDamage = attenuated_damage(x * ZOOM_LOC_WIDTH, y * ZOOM_LOC_HEIGHT );
if( attackDamage == 0)
return;
// ###### end Gilbert 14/5 #########//
Unit *virtualUnit, *parentUnit;
if(unit_array.is_deleted(parent_recno))
{
parentUnit = NULL;
//### begin alex 26/9 ###//
if(nation_array.is_deleted(nation_recno))
return;
//#### end alex 26/9 ####//
for(int i=unit_array.size(); i>0; i--)
{
if(unit_array.is_deleted(i))
continue;
virtualUnit = unit_array[i];
break;
}
if(!virtualUnit)
return; //**** BUGHERE
}
else
virtualUnit = parentUnit = unit_array[parent_recno];
// ###### begin Gilbert 14/5 #########//
virtualUnit->hit_wall(parentUnit, target_x_loc, target_y_loc, attackDamage);
// ###### end Gilbert 14/5 ########//
}
//---------- End of function Bullet::hit_wall ----------//
//--------- Begin of function Bullet::check_hit -------//
// check if the bullet hit a target
// return true if hit
int Bullet::check_hit()
{
err_when(SCAN_RANGE != 5);
short x,y;
short townHit[SCAN_RANGE*SCAN_RANGE];
short firmHit[SCAN_RANGE*SCAN_RANGE];
int hitCount = 0;
int townHitCount = 0;
int firmHitCount = 0;
for( int c = 0; c < SCAN_RANGE*SCAN_RANGE; ++c )
{
x = target_x_loc + spiral_x[c];
y = target_y_loc + spiral_y[c];
if( x >= 0 && x < world.max_x_loc && y >= 0 && y < world.max_y_loc )
{
Location *locPtr = world.get_loc(x, y);
if(target_mobile_type==UNIT_AIR)
{
if(locPtr->has_unit(UNIT_AIR))
{
hit_target(x,y);
hitCount++;
}
}
else
{
if(locPtr->is_firm())
{
short firmRecno = locPtr->firm_recno();
// check this firm has not been attacked
short *firmHitPtr;
for( firmHitPtr = firmHit+firmHitCount-1; firmHitPtr >= firmHit; --firmHitPtr )
{
if( *firmHitPtr == firmRecno )
break;
}
if( firmHitPtr < firmHit ) // not found
{
firmHit[firmHitCount++] = firmRecno;
hit_building(x,y);
hitCount++;
}
}
else if( locPtr->is_town() )
{
short townRecno = locPtr->town_recno();
// check this town has not been attacked
short *townHitPtr;
for( townHitPtr = townHit+townHitCount-1; townHitPtr >= townHit; --townHitPtr )
{
if( *townHitPtr == townRecno )
break;
}
if( townHitPtr < townHit ) // not found
{
townHit[townHitCount++] = townRecno;
hit_building(x,y);
hitCount++;
}
}
else if(locPtr->is_wall())
{
hit_wall(x,y);
hitCount++;
}
else
{
hit_target(x,y); // note: no error checking here because mobile_type should be taken into account
hitCount++;
}
}
}
}
return hitCount;
}
//--------- End of function Bullet::check_hit -------//
//--------- Begin of function Bullet::warn_target -------//
//
// warn a unit before hit
// return true if a unit is warned
int Bullet::warn_target()
{
err_when(SCAN_RANGE != 5);
short x,y;
int warnCount = 0;
for( int c = 0; c < SCAN_RANGE*SCAN_RANGE; ++c )
{
x = target_x_loc + spiral_x[c];
y = target_y_loc + spiral_y[c];
if( x >= 0 && x < world.max_x_loc && y >= 0 && y < world.max_y_loc )
{
Location *locPtr = world.get_loc(x, y);
//char targetMobileType;
//if( (targetMobileType = locPtr->has_any_unit()) != 0)
//{
// short unitRecno = locPtr->unit_recno(UNIT_LAND);
short unitRecno = locPtr->unit_recno(target_mobile_type);
if( !unit_array.is_deleted(unitRecno) )
{
Unit *unitPtr = unit_array[unitRecno];
// ####### begin Gilbert 9/9 ########//
if( attenuated_damage( unitPtr->cur_x, unitPtr->cur_y) > 0 )
// ####### end Gilbert 9/9 ########//
{
warnCount++;
switch(unitPtr->cur_action)
{
case SPRITE_IDLE:
case SPRITE_READY_TO_MOVE:
//case SPRITE_TURN:
if( unitPtr->can_stand_guard() && !unitPtr->is_guarding() )
{
unitPtr->set_dir( (cur_dir + 4 ) & 7); // opposite direction of arrow
unitPtr->set_guard_on();
}
break;
case SPRITE_MOVE:
if( unitPtr->can_move_guard() && !unitPtr->is_guarding()
// ###### begin Gilbert 9/9 #######//
&& ( (unitPtr->cur_dir & 7)== ((cur_dir + 4 ) & 7)
|| (unitPtr->cur_dir & 7)== ((cur_dir + 5 ) & 7)
|| (unitPtr->cur_dir & 7)== ((cur_dir + 3 ) & 7)
)
)
// ###### end Gilbert 9/9 #######//
{
unitPtr->set_guard_on();
}
break;
#ifdef AMPLUS
case SPRITE_ATTACK:
if( unitPtr->can_attack_guard() && !unitPtr->is_guarding()
&& unitPtr->remain_attack_delay >= GUARD_COUNT_MAX
&& ( (unitPtr->cur_dir & 7)== ((cur_dir + 4 ) & 7)
|| (unitPtr->cur_dir & 7)== ((cur_dir + 5 ) & 7)
|| (unitPtr->cur_dir & 7)== ((cur_dir + 3 ) & 7)
)
)
{
unitPtr->set_guard_on();
}
break;
#endif
}
}
}
//}
}
}
return warnCount;
}
//--------- End of function Bullet::warn_target -------//
//--------- Begin of function Bullet::display_layer -------//
char Bullet::display_layer()
{
if( mobile_type == UNIT_AIR || target_mobile_type == UNIT_AIR )
return 8;
else
return 1;
}
//--------- End of function Bullet::display_layer -------//
//------- Begin of function Bullet::attenuated_damage -----//
float Bullet::attenuated_damage(short curX, short curY)
{
short d = m.points_distance(curX, curY, target_x_loc * ZOOM_LOC_WIDTH, target_y_loc * ZOOM_LOC_HEIGHT);
// damage drops from attack_damage to attack_damage/2, as range drops from 0 to damage_radius
err_when(damage_radius == 0);
if( d > damage_radius)
return (float) 0;
else
//return ((attack_damage * (2*damage_radius-d) + 2*damage_radius-1)/ (2*damage_radius) ); // ceiling
return attack_damage - attack_damage*d/(2*damage_radius);
}
//------- End of function Bullet::attenuated_damage -----//
#ifdef DEBUG
//------- Begin of function BulletArray::operator[] -----//
Bullet* BulletArray::operator[](int recNo)
{
Bullet* bulletPtr = (Bullet*) get_ptr(recNo);
if( !bulletPtr )
err.run( "BulletArray[] is deleted" );
return bulletPtr;
}
//--------- End of function BulletArray::operator[] ----//
#endif