1 /*
2 * Seven Kingdoms: Ancient Adversaries
3 *
4 * Copyright 1997,1998 Enlight Software Ltd.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 //Filename : OBULLET.CPP
22 //Description : Object Bullet
23 //Owner : Alex
24
25 #include <OVGA.h>
26 #include <OUNIT.h>
27 #include <OBULLET.h>
28 #include <OWORLD.h>
29 #include <OSERES.h>
30 #include <OU_CART.h>
31 #include <OTOWN.h>
32 #include <ONATIONA.h>
33
34 // -------- Define constant ---------//
35
36 const int SCAN_RADIUS = 2;
37 const int SCAN_RANGE = SCAN_RADIUS * 2 + 1;
38
39 // from the closet to the far
40 static char spiral_x[SCAN_RANGE*SCAN_RANGE] =
41 { 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};
42 static char spiral_y[SCAN_RANGE*SCAN_RANGE] =
43 { 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};
44
45 //--------- Begin of function Bullet::Bullet -------//
46
Bullet()47 Bullet::Bullet()
48 {
49 sprite_id = 0;
50 }
51 //--------- End of function Bullet::Bullet -------//
52
53
54 //--------- Begin of function Bullet::init ---------//
55 //
56 // <char> parentType - the type of object emits the bullet
57 // <short> parentRecno - the recno of the object
58 // <short> targetXLoc - the x loc of the target
59 // <short> targetYLoc - the y loc of the target
60 // <char> targetMobileType - target mobile type
61 //
init(char parentType,short parentRecno,short targetXLoc,short targetYLoc,char targetMobileType)62 void Bullet::init(char parentType, short parentRecno, short targetXLoc, short targetYLoc, char targetMobileType)
63 {
64 parent_type = parentType;
65 parent_recno = parentRecno;
66 target_mobile_type = targetMobileType;
67
68 //**** BUGHERE, using parentType and parentRecno to allow bullet by firm, town, etc.
69 //**** BUGHERE, only allow bullet by unit for this version
70 err_when(parent_type!=BULLET_BY_UNIT);
71 Unit *parentUnit = unit_array[parentRecno];
72
73 //---------- copy attack info from the parent unit --------//
74
75 AttackInfo* attackInfo = parentUnit->attack_info_array+parentUnit->cur_attack;
76
77 attack_damage = parentUnit->actual_damage();
78 damage_radius = attackInfo->bullet_radius;
79 nation_recno = parentUnit->nation_recno;
80 // ###### begin Gilbert 26/6 ########## //
81 fire_radius = attackInfo->fire_radius;
82 // ###### end Gilbert 26/6 ########## //
83
84 //----- clone vars from sprite_res for fast access -----//
85
86 sprite_id = attackInfo->bullet_sprite_id;
87 sprite_info = sprite_res[sprite_id];
88
89 sprite_info->load_bitmap_res(); // the sprite bitmap will be freed by ~Sprite(), so we don't have to add ~Bullet() to free it.
90
91 //--------- set the starting position of the bullet -------//
92
93 cur_action = SPRITE_MOVE;
94 cur_frame = 1;
95 set_dir(parentUnit->attack_dir);
96
97 SpriteFrame* spriteFrame = cur_sprite_frame();
98
99 origin_x = cur_x = parentUnit->cur_x;
100 origin_y = cur_y = parentUnit->cur_y;
101
102 //------ set the target position and bullet mobile_type -------//
103
104 target_x_loc = targetXLoc;
105 target_y_loc = targetYLoc;
106
107 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
108 go_y = target_y_loc * ZOOM_LOC_HEIGHT + ZOOM_LOC_HEIGHT/2 - spriteFrame->offset_y - spriteFrame->height/2;
109
110 mobile_type = parentUnit->mobile_type;
111
112 //---------- set bullet movement steps -----------//
113
114 int xStep = (go_x - cur_x)/attackInfo->bullet_speed;
115 int yStep = (go_y - cur_y)/attackInfo->bullet_speed;
116
117 total_step = MAX(1, MAX(abs(xStep), abs(yStep)));
118 cur_step = 0;
119
120 err_when( total_step < 0 ); // number overflow
121 }
122 //----------- End of function Bullet::init -----------//
123
124
125 //--------- Begin of function Bullet::process_move --------//
126
process_move()127 void Bullet::process_move()
128 {
129 //-------------- update position -----------------//
130 //
131 // If it gets very close to the destination, fit it
132 // to the destination ingoring the normal vector.
133 //
134 //------------------------------------------------//
135
136 cur_x = origin_x + (int)(go_x-origin_x) * cur_step / total_step;
137 cur_y = origin_y + (int)(go_y-origin_y) * cur_step / total_step;
138
139 //cur_step++;
140
141 //------- update frame id. --------//
142
143 if( ++cur_frame > cur_sprite_move()->frame_count )
144 cur_frame = 1;
145
146 //----- if the sprite has reach the destintion ----//
147
148 //if( cur_step > total_step )
149 if( ++cur_step > total_step )
150 {
151 check_hit();
152
153 cur_action = SPRITE_DIE; // Explosion
154
155 // ###### begin Gilbert 17/5 #########//
156 // if it has die frame, adjust cur_x, cur_y to be align with the target_x_loc, target_y_loc
157 if( sprite_info->die.first_frame_recno )
158 {
159 next_x = cur_x = target_x_loc * ZOOM_LOC_WIDTH;
160 next_y =cur_y = target_y_loc * ZOOM_LOC_HEIGHT;
161 }
162 // ###### end Gilbert 17/5 #########//
163
164 cur_frame = 1;
165 }
166 else if( total_step - cur_step == 1 )
167 {
168 warn_target();
169 }
170 }
171 //---------- End of function Bullet::process_move ----------//
172
173
174 //--------- Begin of function Bullet::process_die --------//
175 //
176 // return : <int> 1 - dying animation completes.
177 // 0 - still dying
178 //
process_die()179 int Bullet::process_die()
180 {
181
182 // ------- sound effect --------//
183 se_res.sound(cur_x_loc(), cur_y_loc(), cur_frame, 'S',sprite_id,"DIE");
184
185 //--------- next frame ---------//
186 if( ++cur_frame > sprite_info->die.frame_count )
187 // ####### begin Gilbert 28/6 ########//
188 if( ++cur_frame > sprite_info->die.frame_count )
189 {
190 // ------- set fire on the target area --------//
191 if( fire_radius > 0)
192 {
193 Location *locPtr;
194 if( fire_radius == 1)
195 {
196 locPtr = world.get_loc(target_x_loc, target_y_loc);
197 if( locPtr->can_set_fire() && locPtr->fire_str() < 30 )
198 locPtr->set_fire_str(30);
199 if( locPtr->fire_src() > 0 )
200 locPtr->set_fire_src(1); // such that the fire will be put out quickly
201 }
202 else
203 {
204 short x, y, x1, y1, x2, y2;
205 // ##### begin Gilbert 2/10 ######//
206 x1 = target_x_loc - fire_radius + 1;
207 if( x1 < 0 )
208 x1 = 0;
209 y1 = target_y_loc - fire_radius + 1;
210 if( y1 < 0 )
211 y1 = 0;
212 x2 = target_x_loc + fire_radius - 1;
213 if( x2 >= world.max_x_loc )
214 x2 = world.max_x_loc-1;
215 y2 = target_y_loc + fire_radius - 1;
216 if( y2 >= world.max_y_loc )
217 y2 = world.max_y_loc-1;
218 // ##### end Gilbert 2/10 ######//
219 for( y = y1; y <= y2; ++y)
220 {
221 locPtr = world.get_loc(x1, y);
222 for( x = x1; x <= x2; ++x, ++locPtr)
223 {
224 // ##### begin Gilbert 30/10 ######//
225 int dist = abs(x-target_x_loc) + abs(y-target_y_loc);
226 if( dist > fire_radius)
227 continue;
228 int fl = 30 - dist * 7;
229 if( fl < 10 )
230 fl = 10;
231 if( locPtr->can_set_fire() && locPtr->fire_str() < fl )
232 locPtr->set_fire_str(fl);
233 if( locPtr->fire_src() > 0 )
234 locPtr->set_fire_src(1); // such that the fire will be put out quickly
235 // ##### begin Gilbert 30/10 ######//
236 }
237 }
238 }
239 }
240 return 1;
241 }
242 // ####### end Gilbert 28/6 ########//
243 return 0;
244 }
245 //--------- End of function Bullet::process_die --------//
246
247
248 //--------- Begin of function Bullet::hit_target --------//
249
250 // ####### begin Gilbert 14/5 #########//
hit_target(short x,short y)251 void Bullet::hit_target(short x, short y)
252 {
253 //---- check if there is any unit in the target location ----//
254
255 Location* locPtr = world.get_loc(x, y);
256 // ####### end Gilbert 14/5 #########//
257
258 short targetUnitRecno = locPtr->unit_recno(target_mobile_type);
259 if(unit_array.is_deleted(targetUnitRecno))
260 return; // the target unit is deleted
261
262 Unit* targetUnit = unit_array[targetUnitRecno];
263
264 Unit* parentUnit;
265 if(unit_array.is_deleted(parent_recno))
266 //### begin alex 26/9 ###//
267 // parentUnit = NULL; // parent is dead
268 {
269 parentUnit = NULL; // parent is dead
270 if(nation_array.is_deleted(nation_recno))
271 return;
272 }
273 //#### end alex 26/9 ####//
274 else
275 {
276 parentUnit = unit_array[parent_recno];
277 nation_recno = parentUnit->nation_recno;
278 }
279
280 float attackDamage = attenuated_damage(targetUnit->cur_x, targetUnit->cur_y);
281
282 // -------- if the unit is guarding reduce damage ----------//
283 err_when(unit_array.is_deleted(locPtr->unit_recno(target_mobile_type)));
284 // ##### begin Gilbert 14/5 #########//
285 if( attackDamage == 0 )
286 return;
287
288 if( targetUnit->nation_recno == nation_recno )
289 {
290 if( targetUnit->unit_id == UNIT_EXPLOSIVE_CART )
291 ((UnitExpCart *)targetUnit)->trigger_explode();
292 return;
293 }
294 // ##### end Gilbert 14/5 #########//
295
296 // ##### begin Gilbert 3/9 #########//
297 if( !nation_array.should_attack(nation_recno, targetUnit->nation_recno) )
298 return;
299 // ##### end Gilbert 3/9 #########//
300 if(targetUnit->is_guarding())
301 {
302 switch(targetUnit->cur_action)
303 {
304 case SPRITE_IDLE:
305 case SPRITE_READY_TO_MOVE:
306 case SPRITE_TURN:
307 case SPRITE_MOVE:
308 case SPRITE_ATTACK:
309 // ####### begin Gilbert 9/9 #######//
310 // check if on the opposite direction
311 if( (targetUnit->cur_dir & 7)== ((cur_dir + 4 ) & 7)
312 || (targetUnit->cur_dir & 7)== ((cur_dir + 3 ) & 7)
313 || (targetUnit->cur_dir & 7)== ((cur_dir + 5 ) & 7) )
314 // ####### end Gilbert 9/9 #######//
315 {
316 attackDamage = attackDamage > (float)10/ATTACK_SLOW_DOWN ? attackDamage - (float)10/ATTACK_SLOW_DOWN : 0;
317 se_res.sound( targetUnit->cur_x_loc(), targetUnit->cur_y_loc(), 1,
318 'S', targetUnit->sprite_id, "DEF", 'S', sprite_id );
319 }
320 break;
321 }
322 }
323
324 targetUnit->hit_target(parentUnit, targetUnit, attackDamage, nation_recno);
325 }
326 //---------- End of function Bullet::hit_target ----------//
327
328
329 //------- Begin of function Bullet::hit_building -----//
330 // building means firm or town
331 //
332 // ###### begin Gilbert 14/5 #########//
hit_building(short x,short y)333 void Bullet::hit_building(short x, short y)
334 {
335 Location* locPtr = world.get_loc(x, y);
336
337 if(locPtr->is_firm())
338 {
339 Firm *firmPtr = firm_array[locPtr->firm_recno()];
340 // ##### begin Gilbert 3/9 #########//
341 if( !firmPtr || !nation_array.should_attack(nation_recno, firmPtr->nation_recno) )
342 // ##### end Gilbert 3/9 #########//
343 return;
344 }
345 else if(locPtr->is_town())
346 {
347 Town *townPtr = town_array[locPtr->town_recno()];
348 // ##### begin Gilbert 3/9 #########//
349 if( !townPtr || !nation_array.should_attack(nation_recno, townPtr->nation_recno) )
350 // ##### end Gilbert 3/9 #########//
351 return;
352 }
353 else
354 return;
355
356 float attackDamage = attenuated_damage(x * ZOOM_LOC_WIDTH, y * ZOOM_LOC_HEIGHT );
357 // BUGHERE : hit building of same nation?
358 if( attackDamage == 0)
359 return;
360
361 Unit *virtualUnit = NULL, *parentUnit;
362 if(unit_array.is_deleted(parent_recno))
363 {
364 parentUnit = NULL;
365 //### begin alex 26/9 ###//
366 if(nation_array.is_deleted(nation_recno))
367 return;
368 //#### end alex 26/9 ####//
369
370 for(int i=unit_array.size(); i>0; i--)
371 {
372 if(unit_array.is_deleted(i))
373 continue;
374
375 virtualUnit = unit_array[i];
376 break;
377 }
378
379 if(!virtualUnit)
380 return; //**** BUGHERE
381 }
382 else
383 virtualUnit = parentUnit = unit_array[parent_recno];
384
385 virtualUnit->hit_building(parentUnit, target_x_loc, target_y_loc, attackDamage, nation_recno);
386 // ####### end Gilbert 14/5 ########//
387 }
388 //---------- End of function Bullet::hit_building ----------//
389
390
391 //------- Begin of function Bullet::hit_wall -----//
392 // ###### begin Gilbert 14/5 #########//
hit_wall(short x,short y)393 void Bullet::hit_wall(short x, short y)
394 {
395 Location* locPtr = world.get_loc(x, y);
396
397 if(!locPtr->is_wall())
398 return;
399
400 float attackDamage = attenuated_damage(x * ZOOM_LOC_WIDTH, y * ZOOM_LOC_HEIGHT );
401 if( attackDamage == 0)
402 return;
403 // ###### end Gilbert 14/5 #########//
404
405 Unit *virtualUnit, *parentUnit;
406 if(unit_array.is_deleted(parent_recno))
407 {
408 parentUnit = NULL;
409 //### begin alex 26/9 ###//
410 if(nation_array.is_deleted(nation_recno))
411 return;
412 //#### end alex 26/9 ####//
413
414 for(int i=unit_array.size(); i>0; i--)
415 {
416 if(unit_array.is_deleted(i))
417 continue;
418
419 virtualUnit = unit_array[i];
420 break;
421 }
422
423 if(!virtualUnit)
424 return; //**** BUGHERE
425 }
426 else
427 virtualUnit = parentUnit = unit_array[parent_recno];
428
429 // ###### begin Gilbert 14/5 #########//
430 virtualUnit->hit_wall(parentUnit, target_x_loc, target_y_loc, attackDamage, nation_recno);
431 // ###### end Gilbert 14/5 ########//
432 }
433 //---------- End of function Bullet::hit_wall ----------//
434
435
436 //--------- Begin of function Bullet::check_hit -------//
437 // check if the bullet hit a target
438 // return true if hit
check_hit()439 int Bullet::check_hit()
440 {
441 err_when(SCAN_RANGE != 5);
442
443 short x,y;
444 short townHit[SCAN_RANGE*SCAN_RANGE];
445 short firmHit[SCAN_RANGE*SCAN_RANGE];
446 int hitCount = 0;
447 int townHitCount = 0;
448 int firmHitCount = 0;
449
450 for( int c = 0; c < SCAN_RANGE*SCAN_RANGE; ++c )
451 {
452 x = target_x_loc + spiral_x[c];
453 y = target_y_loc + spiral_y[c];
454 if( x >= 0 && x < world.max_x_loc && y >= 0 && y < world.max_y_loc )
455 {
456 Location *locPtr = world.get_loc(x, y);
457 if(target_mobile_type==UNIT_AIR)
458 {
459 if(locPtr->has_unit(UNIT_AIR))
460 {
461 hit_target(x,y);
462 hitCount++;
463 }
464 }
465 else
466 {
467 if(locPtr->is_firm())
468 {
469 short firmRecno = locPtr->firm_recno();
470 // check this firm has not been attacked
471 short *firmHitPtr;
472 for( firmHitPtr = firmHit+firmHitCount-1; firmHitPtr >= firmHit; --firmHitPtr )
473 {
474 if( *firmHitPtr == firmRecno )
475 break;
476 }
477 if( firmHitPtr < firmHit ) // not found
478 {
479 firmHit[firmHitCount++] = firmRecno;
480 hit_building(x,y);
481 hitCount++;
482 }
483 }
484 else if( locPtr->is_town() )
485 {
486 short townRecno = locPtr->town_recno();
487 // check this town has not been attacked
488 short *townHitPtr;
489 for( townHitPtr = townHit+townHitCount-1; townHitPtr >= townHit; --townHitPtr )
490 {
491 if( *townHitPtr == townRecno )
492 break;
493 }
494 if( townHitPtr < townHit ) // not found
495 {
496 townHit[townHitCount++] = townRecno;
497 hit_building(x,y);
498 hitCount++;
499 }
500 }
501 else if(locPtr->is_wall())
502 {
503 hit_wall(x,y);
504 hitCount++;
505 }
506 else
507 {
508 hit_target(x,y); // note: no error checking here because mobile_type should be taken into account
509 hitCount++;
510 }
511 }
512 }
513 }
514
515 return hitCount;
516 }
517 //--------- End of function Bullet::check_hit -------//
518
519
520 //--------- Begin of function Bullet::warn_target -------//
521 //
522 // warn a unit before hit
523 // return true if a unit is warned
warn_target()524 int Bullet::warn_target()
525 {
526 err_when(SCAN_RANGE != 5);
527
528 short x,y;
529 int warnCount = 0;
530
531 for( int c = 0; c < SCAN_RANGE*SCAN_RANGE; ++c )
532 {
533 x = target_x_loc + spiral_x[c];
534 y = target_y_loc + spiral_y[c];
535 if( x >= 0 && x < world.max_x_loc && y >= 0 && y < world.max_y_loc )
536 {
537 Location *locPtr = world.get_loc(x, y);
538 //char targetMobileType;
539 //if( (targetMobileType = locPtr->has_any_unit()) != 0)
540 //{
541 // short unitRecno = locPtr->unit_recno(UNIT_LAND);
542 short unitRecno = locPtr->unit_recno(target_mobile_type);
543 if( !unit_array.is_deleted(unitRecno) )
544 {
545 Unit *unitPtr = unit_array[unitRecno];
546 // ####### begin Gilbert 9/9 ########//
547 if( attenuated_damage( unitPtr->cur_x, unitPtr->cur_y) > 0 )
548 // ####### end Gilbert 9/9 ########//
549 {
550 warnCount++;
551 switch(unitPtr->cur_action)
552 {
553 case SPRITE_IDLE:
554 case SPRITE_READY_TO_MOVE:
555 //case SPRITE_TURN:
556 if( unitPtr->can_stand_guard() && !unitPtr->is_guarding() )
557 {
558 unitPtr->set_dir( (cur_dir + 4 ) & 7); // opposite direction of arrow
559 unitPtr->set_guard_on();
560 }
561 break;
562 case SPRITE_MOVE:
563 if( unitPtr->can_move_guard() && !unitPtr->is_guarding()
564 // ###### begin Gilbert 9/9 #######//
565 && ( (unitPtr->cur_dir & 7)== ((cur_dir + 4 ) & 7)
566 || (unitPtr->cur_dir & 7)== ((cur_dir + 5 ) & 7)
567 || (unitPtr->cur_dir & 7)== ((cur_dir + 3 ) & 7)
568 )
569 )
570 // ###### end Gilbert 9/9 #######//
571 {
572 unitPtr->set_guard_on();
573 }
574 break;
575 case SPRITE_ATTACK:
576 if( unitPtr->can_attack_guard() && !unitPtr->is_guarding()
577 && unitPtr->remain_attack_delay >= GUARD_COUNT_MAX
578 && ( (unitPtr->cur_dir & 7)== ((cur_dir + 4 ) & 7)
579 || (unitPtr->cur_dir & 7)== ((cur_dir + 5 ) & 7)
580 || (unitPtr->cur_dir & 7)== ((cur_dir + 3 ) & 7)
581 )
582 )
583 {
584 unitPtr->set_guard_on();
585 }
586 break;
587 }
588 }
589 }
590 //}
591 }
592 }
593
594 return warnCount;
595 }
596 //--------- End of function Bullet::warn_target -------//
597
598
599 //--------- Begin of function Bullet::display_layer -------//
display_layer()600 char Bullet::display_layer()
601 {
602 if( mobile_type == UNIT_AIR || target_mobile_type == UNIT_AIR )
603 return 8;
604 else
605 return 1;
606 }
607 //--------- End of function Bullet::display_layer -------//
608
609
610 //------- Begin of function Bullet::attenuated_damage -----//
attenuated_damage(short curX,short curY)611 float Bullet::attenuated_damage(short curX, short curY)
612 {
613 short d = misc.points_distance(curX, curY, target_x_loc * ZOOM_LOC_WIDTH, target_y_loc * ZOOM_LOC_HEIGHT);
614 // damage drops from attack_damage to attack_damage/2, as range drops from 0 to damage_radius
615 err_when(damage_radius == 0);
616 if( d > damage_radius)
617 return (float) 0;
618 else
619 //return ((attack_damage * (2*damage_radius-d) + 2*damage_radius-1)/ (2*damage_radius) ); // ceiling
620 return attack_damage - attack_damage*d/(2*damage_radius);
621 }
622 //------- End of function Bullet::attenuated_damage -----//
623
624 #ifdef DYNARRAY_DEBUG_ELEMENT_ACCESS
625
626 //------- Begin of function BulletArray::operator[] -----//
627
operator [](int recNo)628 Bullet* BulletArray::operator[](int recNo)
629 {
630 Bullet* bulletPtr = (Bullet*) get_ptr(recNo);
631
632 if( !bulletPtr )
633 err.run( "BulletArray[] is deleted" );
634
635 return bulletPtr;
636 }
637
638 //--------- End of function BulletArray::operator[] ----//
639
640 #endif
641
642
643