1 /*
2
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 */
19 /*
20 * $Source: r:/prj/cit/src/RCS/damage.c $
21 * $Revision: 1.206 $
22 * $Author: xemu $
23 * $Date: 1994/10/27 04:56:12 $
24 */
25
26 #include <stdbool.h>
27 #include <stdlib.h>
28
29 #include "Shock.h"
30
31 #include "effect.h"
32 #include "newmfd.h"
33 #include "damage.h"
34 #include "objwpn.h"
35 #include "objects.h"
36 #include "objsim.h"
37 #include "objprop.h"
38 #include "weapons.h"
39 #include "player.h"
40 #include "combat.h"
41 #include "cybrnd.h"
42 #include "mainloop.h"
43 #include "trigger.h"
44 #include "musicai.h"
45 #include "sfxlist.h"
46 #include "ai.h"
47 #include "frprotox.h"
48 #include "frflags.h"
49 #include "gamerend.h"
50 #include "hkeyfunc.h"
51 #include "gameloop.h"
52 #include "target.h"
53 #include "objbit.h"
54 #include "physunit.h"
55 #include "hud.h"
56 #include "faketime.h"
57 #include "otrip.h"
58 #include "grenades.h"
59 #include "softdef.h"
60 #include "aiflags.h"
61 #include "input.h"
62 #include "mapflags.h"
63 #include "wares.h"
64 #include "drugs.h"
65 #include "diffq.h"
66 #include "hudobj.h"
67 #include "amap.h"
68 #include "target.h"
69 #include "status.h"
70 #include "cutsloop.h"
71
72 #define SQUARE(x) ((x) * (x))
73 #define PLAYER_DEFENSE_VALUE 4
74 #define MAX_DAMAGE 2500
75
76 extern void set_dmg_percentage(int which, ubyte percent);
77
78 //----------------
79 // Internal Prototypes
80 //----------------
81 int random_bell_modifier(uchar attack_on_player);
82 int randomize_damage(int damage, uchar attack_on_player);
83 int armor_absorption(int raw_damage, int obj_triple, ubyte penetrate);
84 int shield_absorb_damage(int damage, ubyte dtype, byte shield_absorb, ubyte shield_threshold);
85 uchar kill_player(void);
86 void regenerate_player(void);
87 void player_dies();
88 ubyte damage_player(int damage, ubyte dtype, ubyte flags);
89 void slow_proj_hit(ObjID id, ObjID victim);
90 void critter_hit_effect(ObjID target, ubyte effect, Combat_Pt location, int damage, int max_damage);
91
92 // -------------------------------------------
93 // destroy_destroyed_objects()
94 //
95
destroy_destroyed_objects(void)96 void destroy_destroyed_objects(void) {
97 int i, j;
98 uchar change_target = FALSE;
99 uchar dupe;
100 ObjID id;
101
102 if (destroyed_obj_count != 0) {
103 for (i = 0; i < destroyed_obj_count; i++) {
104 // Look through the rest of the list for duplicates
105 // if so, don't delete us now since we'll get what's coming
106 // to us later...
107 // Yeah, yeah, so this is an N-squared algorithm, but hopefully the size of
108 // the list is pretty damn short...
109 dupe = FALSE;
110 id = destroyed_ids[i];
111
112 for (j = i + 1; j < destroyed_obj_count; j++) {
113 if (id == destroyed_ids[j]) {
114 dupe = TRUE;
115 break;
116 }
117 }
118 if (!dupe) {
119 {
120 // check if the physics object contributed to the lighting of the map
121 if (objs[id].obclass == CLASS_PHYSICS) {
122 ObjSpecID osid = objs[id].specID;
123 if (objPhysicss[osid].p2.x) {
124 MapElem *mmp;
125 mmp = MAP_GET_XY(OBJ_LOC_TO_LIGHT_LOC(objPhysicss[osid].p1.x),
126 OBJ_LOC_TO_LIGHT_LOC(objPhysicss[osid].p1.y));
127 me_rend3_set(mmp, me_bits_rend3(mmp) - 1);
128 objPhysicss[osid].p2.x = 0;
129 }
130 } else if (objs[id].obclass == CLASS_GRENADE) {
131 do_grenade_explosion(id, TRUE);
132 }
133
134 // If we destroyed a creature which was being targeted, we
135 // need to switch the current target
136 if (destroyed_ids[i] == player_struct.curr_target)
137 change_target = TRUE;
138
139 obj_destroy(destroyed_ids[i]);
140
141 if (change_target) {
142 toggle_current_target();
143 change_target = FALSE;
144 }
145 }
146 }
147
148 // object_dead();
149 }
150
151 chg_set_flg(DEMOVIEW_UPDATE);
152
153 // "clear" the list
154 destroyed_obj_count = 0;
155 }
156 }
157
158 // -------------------------------------------
159 // is_obj_destroyed()
160 //
161
is_obj_destroyed(ObjID id)162 uchar is_obj_destroyed(ObjID id) {
163 int i;
164 uchar found = FALSE;
165
166 for (i = 0; i < destroyed_obj_count; i++) {
167 if (destroyed_ids[i] == id) {
168 found = TRUE;
169 break;
170 }
171 }
172 return (found);
173 }
174
175 // ---------------------------------------------
176 // random_bell_modifier()
177 //
178
random_bell_modifier(uchar attack_on_player)179 int random_bell_modifier(uchar attack_on_player) {
180 int rtotal;
181 int i;
182 int rval;
183 int retval;
184 ubyte dies, die_value, handicap;
185 ubyte difficulty = (global_fullmap->cyber) ? player_struct.difficulty[CYBER_DIFF_INDEX]
186 : player_struct.difficulty[COMBAT_DIFF_INDEX];
187
188 if (attack_on_player)
189 difficulty = (difficulty == 0) ? 3 : 4 - difficulty;
190
191 switch (difficulty) {
192 case (0):
193 dies = 2;
194 die_value = 50;
195 handicap = 15;
196 break;
197 case (1):
198 dies = 3;
199 die_value = 33;
200 handicap = 6;
201 break;
202 case (2):
203 dies = 5;
204 die_value = 20;
205 handicap = 0;
206 break;
207 case (3):
208 dies = 8;
209 die_value = 12;
210 handicap = 0;
211 break;
212 }
213
214 rtotal = handicap;
215 for (i = 0; i < dies; i++) {
216 rval = RndRange(&damage_rnd, 0, die_value);
217 rtotal += rval;
218 }
219
220 if (rtotal <= 2)
221 retval = -12; // 00-02
222 else if (rtotal <= 8)
223 retval = -8; // 03-08
224 else if (rtotal <= 16)
225 retval = -6; // 09-16
226 else if (rtotal <= 28)
227 retval = -3; // 17-28
228 else if (rtotal <= 40)
229 retval = -1; // 29-40
230 else if (rtotal <= 60)
231 retval = 0; // 41-60
232 else if (rtotal <= 72)
233 retval = 1; // 61-72
234 else if (rtotal <= 84)
235 retval = 3; // 73-84
236 else if (rtotal <= 92)
237 retval = 6; // 85-92
238 else if (rtotal <= 98)
239 retval = 8; // 93-98
240 else
241 retval = 12; // 99-100
242
243 return (retval);
244 }
245
246 // -------------------------------------------
247 // randomize_damage()
248 //
249
randomize_damage(int damage,uchar attack_on_player)250 int randomize_damage(int damage, uchar attack_on_player) {
251 int dtotal;
252 ubyte iterations;
253 int i;
254 ubyte difficulty = (global_fullmap->cyber) ? player_struct.difficulty[CYBER_DIFF_INDEX]
255 : player_struct.difficulty[COMBAT_DIFF_INDEX];
256
257 dtotal = damage / 2;
258 iterations = damage / 8;
259
260 // if damage div 8 is non-zero add an extra iteration
261 // yes, we might have a value that's a little bigger than normal
262 if (damage % 8)
263 iterations++;
264
265 for (i = 0; i < iterations; i++)
266 dtotal += RndRange(&damage_rnd, 1, 7);
267
268 // if we're playing on difficulty 3 - reduce damage by a third
269 if (!attack_on_player && (difficulty == 3))
270 dtotal = (dtotal << 1) / 3;
271
272 return (dtotal);
273 }
274
275 // ---------------------------------------------
276 // object_affect()
277 //
278 // there has been a hit, now we check how much an object is affected
279 //
280
object_affect(ObjID target_id,short dtype)281 ubyte object_affect(ObjID target_id, short dtype) {
282 ubyte affected = 0;
283 int resis;
284
285 if (!target_id) {
286 return 0;
287 }
288
289 resis = ObjProps[OPNUM(target_id)].resistances;
290 affected = (resis & dtype & DAMAGE_TYPE_FIELD) ? 1 : 0;
291 if (affected) {
292 // are the super damage the same, and we have a non-zero primary damage
293 if ((SUPER_DAMAGE(resis) == SUPER_DAMAGE(dtype)) && (SUPER_DAMAGE(resis))) {
294 ubyte difficulty = (global_fullmap->cyber) ? player_struct.difficulty[CYBER_DIFF_INDEX]
295 : player_struct.difficulty[COMBAT_DIFF_INDEX];
296
297 // don't do as much super damage if we're on combat diff 3
298 affected = (difficulty == 3) ? 3 : 4;
299 }
300 // are the primary damage the same, and we have a non-zero primary damage
301 else if ((PRIMARY_DAMAGE(resis) == PRIMARY_DAMAGE(dtype)) && (PRIMARY_DAMAGE(resis))) {
302 affected = 2;
303 }
304 }
305 // mprintf("attack on %d, affect : %d\n", target_id, affected);
306 return (affected);
307 }
308
309 // -----------------------------------------------------------------------------------------
310 // armor_absorption()
311 //
312
armor_absorption(int raw_damage,int obj_triple,ubyte penetrate)313 int armor_absorption(int raw_damage, int obj_triple, ubyte penetrate) {
314 short real_penetration = (((short)penetrate * (90 + RndRange(&damage_rnd, 0, 20))) / 100);
315 int damage;
316
317 damage = (ObjProps[OPTRIP(obj_triple)].armor - real_penetration);
318 damage = (damage > 0) ? raw_damage - damage : raw_damage;
319
320 if (damage < 0)
321 damage = 0;
322
323 return (damage);
324 }
325
326 // some globals
327 uchar sound_hurt_threshold = 10;
328 uchar static_pain_time = 64;
329 uchar static_pain_base = 30;
330 uchar static_pain_delta = 30;
331 uchar shield_blowout_threshold = 15;
332
333 short fr_solidfr_time;
334 short fr_sfx_time;
335
336 // this is the voodoo threshold for shields, so that we have a reference
337 // to do effects with. (static)
338 #define VOODOO_SHIELD_THRESHOLD 100
339
340 // -------------------------------------------------------------------------------------------
341 // shield_absorb_damage()
342 //
343 // returns damage to the player after shields
344
345 short shield_absorb_perc = 0;
346 uchar shield_used;
347
shield_absorb_damage(int damage,ubyte ub,byte shield_absorb,ubyte shield_threshold)348 int shield_absorb_damage(int damage, ubyte ub, byte shield_absorb, ubyte shield_threshold) {
349 ubyte shield_drain = 0;
350
351 if ((damage | player_struct.hit_points) == 0)
352 return 0; // 0 hp is already dead, eh?, 0 damage no matter
353
354 if (damage > shield_threshold) {
355 // absorption rate will be given a +/- 8 percentage for variation - unless we're at zero already
356 shield_absorb = (shield_absorb) ? shield_absorb + RndRange(&damage_rnd, 0, 16) - 8 : 0;
357
358 if (shield_absorb > 0) {
359 // figure out how much damage is absorbed by shields
360 // hey - it's even a percentage
361 // WHY - make it 0-255, dont divide needlessly, arghgqghgghdsghgfdgfjfghdfgj
362 shield_drain = (damage * shield_absorb) / 100;
363 // let's absorb the damage... now!
364 damage -= shield_drain;
365 } else
366 shield_absorb = 0;
367 } else {
368 if (shield_absorb) {
369 // if we're below threshold, we've absorbed all damage...
370 shield_absorb = 100;
371 shield_drain = damage;
372 } else
373 shield_drain = 0;
374 }
375
376 // Hud me baby
377 // oh,and sound too.
378
379 if (shield_absorb > 0) {
380 shield_absorb_perc = shield_absorb;
381 hud_set_time(HUD_SHIELD, CIT_CYCLE);
382
383 // do the sound if we're not in cyberspace
384 if (!global_fullmap->cyber)
385 (shield_absorb > 80) ? play_digi_fx(SFX_SHIELD_2, 1) : play_digi_fx(SFX_SHIELD_1, 1);
386 } else if (((rand() & 0x1A) < damage) && !global_fullmap->cyber)
387 play_digi_fx(SFX_PLAYER_HURT, 1);
388
389 // let's make sure that we don't go over the 100% shield flash
390 if (shield_drain > VOODOO_SHIELD_THRESHOLD)
391 shield_drain = VOODOO_SHIELD_THRESHOLD;
392
393 // let's reuse the shield_drain variable
394 // let's get the percentage absorbed (w.r.t. the VOODOO_SHIELD_THRESHOLD)
395 shield_drain = (ubyte)(((int)shield_drain << 8) / VOODOO_SHIELD_THRESHOLD);
396
397 if ((shield_used = (shield_drain > 0)) == TRUE)
398 set_dmg_percentage(DMG_SHIELD, shield_drain);
399
400 return (damage);
401 }
402
403 uchar alternate_death = FALSE;
404 extern bool gPlayingGame;
405 extern bool gDeadPlayerQuit;
406
407 // kill_player()
408 // kills the player, checks for traps and stuff, so on
409 // returns true if player is really dead dead dead
kill_player(void)410 uchar kill_player(void) {
411 ObjSpecID osid;
412 uchar quick_death = TRUE;
413 uchar dummy;
414 extern uchar clear_player_data;
415
416 INFO("Player died!\n");
417
418 // Look for a player death trigger. If so, do it.
419 // If not, play appropriate dying cutscene.
420 osid = objTraps[0].id;
421 while (osid != OBJ_SPEC_NULL) {
422 if (ID2TRIP(objTraps[osid].id) == PLRDETH_TRIG_TRIPLE)
423 if (trap_activate(objTraps[osid].id, &dummy))
424 quick_death = FALSE;
425 osid = objTraps[osid].next;
426 }
427
428 // if we died not from a trap - then we should clear the player data
429 #ifdef TEST_REBIRTH
430 clear_player_data = FALSE;
431 return FALSE;
432 #else
433 clear_player_data = (quick_death | alternate_death);
434
435 if (quick_death) {
436 amap_reset();
437 secret_render_fx = 0;
438 play_cutscene(DEATH_CUTSCENE, FALSE);
439 }
440
441 return (quick_death | alternate_death);
442 #endif
443 }
444
regenerate_player(void)445 void regenerate_player(void) {
446 extern void wear_off_drug(int i);
447 extern void regenetron_door_hack(void);
448 int i;
449 for (i = 0; i < NUM_DAMAGE_TYPES; i++)
450 player_struct.hit_points_lost[i] = 0;
451 hud_unset(HUD_RADPOISON | HUD_BIOPOISON);
452 player_struct.curr_target = OBJ_NULL;
453 player_struct.fatigue = 0;
454
455 // clear physics state
456 player_struct.posture = 0;
457 player_struct.foot_planted = 0;
458 player_struct.leanx = 0;
459 player_struct.leany = 0;
460 player_struct.eye_pos = 0;
461
462 for (i = 0; i < NUM_DRUGZ; i++)
463 wear_off_drug(i);
464 regenetron_door_hack();
465 }
466
467 // -----------------------------------------------------------------------------------------
468 // damage_player()
469 //
470 // returns 0 if player is still alive
471 // returns 1 if player is dead
472
473 #define CSHIELD_THRESHOLDS(lev) 0
474
475 #define MAX_CSHIELD_ABSORB 120
476 #define NUM_CSHIELD_LEVELS 10
477 #define CSHIELD_ABSORB_RATES(lev) ((lev) == 0) ? 0 : (MAX_CSHIELD_ABSORB / (NUM_CSHIELD_LEVELS - (lev)))
478
479 #define MAX_FATIGUE 10000
480 #define DEATH_TICKS CIT_CYCLE
481
482 ulong player_death_time = 0;
483
484 // Something has caused the player to become a fatality
485 // typically this is damage, but can be delayed-death due to craze
player_dies()486 void player_dies() {
487 extern void physics_zero_all_controls();
488 extern void clear_digi_fx();
489 extern short inventory_page;
490 #ifdef AUDIOLOGS
491 extern char secret_pending_hack;
492 secret_pending_hack = 0;
493 #endif
494
495 // we should play funky death music
496 mai_player_death();
497 reset_input_system();
498 chg_set_sta(GL_CHG_2); // disable the input system
499
500 extern uchar weapon_button_up;
501 weapon_button_up = TRUE;
502
503 // clear off hud & prep for funky regen FX
504 // hud_unset(HUD_ALL);
505 physics_zero_all_controls();
506
507 // clear edms_state
508 LG_memset(player_struct.edms_state, 0, sizeof(fix) * 12);
509
510 // reset the inventory page
511 inventory_page = 0;
512
513 // hey no more things to do to player while he's dead
514 // it's really a secret - don't do things to player
515 // while there are cool secret_render_fx going on
516 player_struct.dead = TRUE;
517 set_dmg_percentage(DMG_BLOOD, 100); // 100 is an arbitrary number - we're trying to get it to do red static
518 secret_render_fx = DYING_REND_SFX;
519
520 clear_digi_fx();
521 player_struct.num_deaths++;
522 }
523
524 // -------------------------------------------------------
525 // damage_player()
526 //
527
528 #define DAMAGE_DIFFICULTY ((global_fullmap->cyber) ? 3 : 0)
529
damage_player(int damage,ubyte dtype,ubyte flags)530 ubyte damage_player(int damage, ubyte dtype, ubyte flags) {
531 ubyte *cur_hp;
532 short rawval;
533 uchar dead = 0, damage_dealt = FALSE;
534 char dlev, dmg_type = DMG_BLOOD;
535
536 if (secret_render_fx > 0)
537 return 0;
538
539 if ((damage <= 0) || (player_struct.hit_points == 0)) // 0 hp is already dead, eh?
540 return 0;
541
542 if (global_fullmap->cyber && !player_struct.difficulty[CYBER_DIFF_INDEX])
543 return 0;
544
545 cur_hp = (global_fullmap->cyber) ? &player_struct.cspace_hp : &player_struct.hit_points;
546
547 shield_used = FALSE;
548
549 // check if shields should play a role
550 if ((dtype == RADIATION_TYPE) || (dtype == BIO_TYPE))
551 dmg_type = DMG_RAD;
552 else if (!(flags & NO_SHIELD_ABSORBTION)) {
553 byte absorb_rate, thresh_val;
554
555 // compensate for difficulty level
556 dlev = player_struct.difficulty[DAMAGE_DIFFICULTY];
557 if (dlev != 3)
558 damage >>= (2 - dlev);
559 if (global_fullmap->cyber) {
560 if (player_struct.softs.defense[SOFTWARE_CSHIELD]) {
561 absorb_rate = CSHIELD_ABSORB_RATES(player_struct.softs.defense[SOFTWARE_CSHIELD]);
562 thresh_val = CSHIELD_THRESHOLDS(player_struct.softs.defense[SOFTWARE_CSHIELD]);
563 } else
564 absorb_rate = thresh_val = 0;
565 } else {
566 absorb_rate = (byte)player_struct.shield_absorb_rate;
567 thresh_val = player_struct.shield_threshold;
568 }
569 damage = shield_absorb_damage(damage, dtype, absorb_rate, thresh_val);
570 }
571
572 if (damage <= 0)
573 return 0;
574
575 #ifdef WACKY_STATIC_USAGE
576 // Play digi FX should go in here when we have appropriate SFX
577 if ((!global_fullmap->cyber) && (damage > static_pain_base + rand() % static_pain_delta)) {
578 extern char static_density, static_color, static_grouping;
579 // Turn on fullscreen static & turn off any SFX that might be otherwise going on.
580 fr_global_mod_flag(FR_SOLIDFR_STATIC, FR_SOLIDFR_MASK | FR_SFX_MASK);
581 fr_solidfr_time = (static_pain_time);
582 play_digi_fx(SFX_STATIC, -1);
583 }
584 #endif
585
586 // did we take more damage than hit points?? - eeeegggads! we're dead
587 if ((*cur_hp) <= damage) {
588 damage_dealt = TRUE;
589 {
590 if (global_fullmap
591 ->cyber) { // No matter what, the player can't be killed in one shot.....it takes at least two shots
592 player_struct.cspace_hp = (player_struct.cspace_hp == 1) ? 0 : 1;
593 } else // normal (non-cyberspace) damage - player's dead dead dead
594 {
595 if (*cur_hp > 0) {
596 #ifdef CRAZE_NODEATH
597 if ((player_struct.drug_status[DRUG_LSD] > 0) && (QUESTVAR_GET(COMBAT_DIFF_QVAR) < 3))
598 *cur_hp = 1;
599 else
600 #endif
601 {
602 *cur_hp = 0;
603 dead = TRUE;
604
605 // if we're carrying an object - let's drop it here!
606 if (object_on_cursor != OBJ_NULL) {
607 obj_move_to(object_on_cursor, &objs[PLAYER_OBJ].loc, TRUE);
608 pop_cursor_object();
609 }
610
611 // signal for the effects that happen with death
612 player_dies();
613 }
614 }
615 }
616 }
617 }
618 if (!damage_dealt) {
619 extern int mai_damage_sum;
620
621 *cur_hp -= damage;
622 mai_damage_sum += damage;
623 }
624
625 if (*cur_hp == 0)
626 rawval = damage << 8;
627 else
628 rawval = ((damage << 8) / (*cur_hp)); // what is this minmax3/20xff thing?
629 // it's my voodoo - minman
630 if (!shield_used)
631 set_dmg_percentage(dmg_type, (ubyte)lg_min(lg_max(rawval, ((damage * 3) / 2)), 0x00FF));
632
633 // makes sure gamescreen knows that it should be updated
634 chg_set_flg(VITALS_UPDATE);
635 return (dead);
636 }
637
638 // --------------------------------------------------
639 // damage_object()
640 //
641 // return 0 - if object is still alive after damage
642 // return 1 - if object has been destroyed
643 //
644 // also does the appropriate texture map change if
645 // object is destroyed???????
646
damage_object(ObjID target_id,int damage,int dtype,ubyte flags)647 ubyte damage_object(ObjID target_id, int damage, int dtype, ubyte flags) {
648 int obclass = objs[target_id].obclass;
649 int dead = 0;
650 uchar tranq = FALSE;
651 uchar stun = FALSE;
652 short target_hp = ObjProps[OPNUM(target_id)].hit_points;
653
654 // If we've already been destroyed, or don't care, thendon't bother us.
655 if ((objs[target_id].info.inst_flags & INDESTRUCT_FLAG) ||
656 ((target_id != player_struct.rep) && (objs[target_id].info.current_hp == 0)))
657 return (0);
658
659 // let the player get his/her own special treatment
660 if (target_id == PLAYER_OBJ)
661 dead = damage_player(damage, (ubyte)PRIMARY_DAMAGE(dtype), flags);
662 else {
663 // are we still alive - then do special stuff that we only care about if
664 // we're still alive, makes too much sense
665 if (objs[target_id].info.current_hp > damage) {
666 // damage object - but it's not dead yet.
667 dead = 0;
668 if (obclass == CLASS_CRITTER) {
669 int pct = (450L * damage) / objs[target_id].info.current_hp;
670 tranq = dtype & TRANQ_FLAG;
671 stun = (flags & STUN_ATTACK);
672
673 if (tranq) {
674 tranq = FALSE;
675 // okay tranq - only if we've done damage, and we're lucky and the damage we're doing
676 // is a decent amount of the remaining life
677 if (damage)
678 tranq = (RndRange(&damage_rnd, 0, 100) < pct);
679 } else if (stun) {
680 if (objs[target_id].subclass == CRITTER_SUBCLASS_ROBOT)
681 stun = FALSE;
682 else
683 stun = (RndRange(&damage_rnd, 0, 100) < pct);
684 }
685 ai_critter_hit(objs[target_id].specID, damage, tranq, stun);
686 }
687 // get rid of the hit points
688 objs[target_id].info.current_hp -= damage;
689 } else {
690 objs[target_id].info.current_hp = 0;
691 dead = 1;
692
693 // Check to see whether or not there is cool special stuff to do when this thing
694 // gets destroyed. obj_combat_destroy returns whether or not to go ahead and
695 // continue the destruction process
696 if (obj_combat_destroy(target_id))
697 ADD_DESTROYED_OBJECT(target_id);
698
699 if (DESTROY_SOUND_EFFECT(ObjProps[OPNUM(target_id)].destroy_effect)) {
700 extern ObjID damage_sound_id;
701 extern char damage_sound_fx;
702
703 damage_sound_fx = SFX_CPU_EXPLODE;
704 damage_sound_id = target_id;
705 }
706 }
707
708 if ((obclass == CLASS_CRITTER) && !global_fullmap->cyber) {
709 ubyte seriousness = 0;
710 extern void hud_report_damage(ObjID target, byte seriousness);
711
712 // marc's desired code
713 if (stun)
714 seriousness = 7;
715 else if (tranq)
716 seriousness = 6;
717 else if (!object_affect(target_id, dtype))
718 seriousness = 5;
719 else if (damage > target_hp)
720 seriousness = 4;
721 else if (damage > (target_hp * 4) / 5)
722 seriousness = 3;
723 else if (damage > target_hp / 5)
724 seriousness = 2;
725 else if (damage > 0)
726 seriousness = 1;
727
728 hud_report_damage(target_id, seriousness);
729 }
730
731 // If we damaged the currently targeted creature, let mfds know...
732 if ((target_id == player_struct.curr_target) && !global_fullmap->cyber)
733 mfd_notify_func(MFD_TARGET_FUNC, MFD_TARGET_SLOT, FALSE, MFD_ACTIVE, TRUE);
734 }
735 return (dead);
736 }
737
738 // -----------------------------------------------
739 // simple_damage_object() takes damage of a particular type with particular flags,
740 // and applies it to the object if it is vulnerable to the type.
741 // returns whether the object was destroyed.
742
simple_damage_object(ObjID target,int damage,ubyte dtype,ubyte flags)743 uchar simple_damage_object(ObjID target, int damage, ubyte dtype, ubyte flags) {
744 if (object_affect(target, 1 << (dtype - 1)))
745 return damage_object(target, damage, dtype, flags);
746 return FALSE;
747 }
748
749 #define FIRST_ENRG_PROJ_TYPE 7
750 #define DAMAGE_PROJ_DISTANCE (fix_make(0, 0x6000))
751
752 // OBJ_NULL victim means terrain
slow_proj_hit(ObjID id,ObjID victim)753 void slow_proj_hit(ObjID id, ObjID victim) {
754 Combat_Pt origin;
755 ObjLoc loc = objs[id].loc;
756 ObjRefID current_ref;
757 ObjID current_id;
758 ubyte affect;
759 ubyte dtype;
760 ubyte proj_power;
761 ubyte special_effect = EFFECT_VAL(ObjProps[OPNUM(id)].destroy_effect);
762 int a;
763 int weapon_triple;
764
765 current_ref = MAP_GET_XY(OBJ_LOC_BIN_X(objs[id].loc), OBJ_LOC_BIN_Y(objs[id].loc))->objRef;
766
767 if (objPhysicss[objs[id].specID].owner != PLAYER_OBJ) {
768 if (special_effect)
769 do_special_effect_location(id, special_effect, 0xFF, &loc, 0);
770 return;
771 }
772
773 a = objPhysicss[objs[id].specID].bullet_triple;
774 if (objs[id].info.type >= FIRST_ENRG_PROJ_TYPE) {
775 weapon_triple = MAKETRIP(CLASS_GUN, GUN_SUBCLASS_BEAMPROJ, TRIP2TY(a));
776 proj_power = TRIP2CL(a);
777 dtype = BeamprojGunProps[SCTRIP(weapon_triple)].damage_type;
778 } else {
779 weapon_triple = MAKETRIP(CLASS_GUN, GUN_SUBCLASS_SPECIAL, TRIP2TY(a));
780 proj_power = 100;
781 dtype = SpecialGunProps[SCTRIP(weapon_triple)].damage_type;
782 }
783 if (weapon_triple == RAILGUN_TRIPLE) {
784 do_explosion(loc, id, 0, &(game_explosions[1]));
785 play_digi_fx_obj(SFX_EXPLOSION_1, 1, id);
786 } else if (victim == OBJ_NULL) {
787 while (current_ref != OBJ_REF_NULL) {
788 current_id = objRefs[current_ref].obj;
789
790 if (current_id != id) {
791 affect = object_affect(current_id, dtype);
792 if (affect) {
793 fix dist, deltax, deltay, deltaz;
794
795 deltax = fix_from_obj_coord(objs[id].loc.x - objs[current_id].loc.x);
796 deltay = fix_from_obj_coord(objs[id].loc.y - objs[current_id].loc.y);
797 deltaz = fix_from_obj_height_val(objs[id].loc.z - objs[current_id].loc.z);
798 dist = fix_mul(deltax, deltax) + fix_mul(deltay, deltay) + fix_mul(deltaz, deltaz);
799
800 if (dist < DAMAGE_PROJ_DISTANCE) {
801
802 origin.x = fix_make(-1, 0);
803 origin.y = fix_make(-1, 0);
804 origin.z = fix_make(-1, 0);
805
806 player_attack_object(current_id, weapon_triple, proj_power, origin);
807 }
808 }
809 }
810 current_ref = objRefs[current_ref].next;
811 }
812 } else {
813 origin.x = fix_from_obj_coord(loc.x);
814 origin.y = fix_from_obj_coord(loc.y);
815 origin.z = fix_from_obj_height(loc.z);
816
817 player_attack_object(victim, weapon_triple, proj_power, origin);
818 }
819 if (special_effect)
820 do_special_effect_location(id, special_effect, 0xFF, &loc, 0);
821 }
822
823 // returns whether it is being killed...
special_terrain_hit(ObjID cobjid)824 uchar special_terrain_hit(ObjID cobjid) {
825 if (is_obj_destroyed(cobjid))
826 return TRUE;
827
828 if (objs[cobjid].obclass == CLASS_GRENADE) {
829 if (objGrenades[objs[cobjid].specID].flags & GREN_ACTIVE_FLAG)
830 ADD_DESTROYED_OBJECT(cobjid);
831 else {
832 return FALSE; // dead grenades stay alive
833 // in the sense that they are not live, but should remain physics live, see
834 }
835 } else if (objs[cobjid].obclass == CLASS_PHYSICS) {
836 // only one terrain hit per turn!
837 if (objPhysicss[objs[cobjid].specID].p3.x)
838 return FALSE;
839 else
840 objPhysicss[objs[cobjid].specID].p3.x = 3;
841
842 slow_proj_hit(cobjid, OBJ_NULL);
843 EDMS_obey_collisions(objs[cobjid].info.ph);
844
845 if (PhysicsProps[CPNUM(cobjid)].flags & PROJ_PRESERVE_WALL)
846 return FALSE;
847
848 // Signal a miss to our controller
849 ai_misses(objs[objPhysicss[objs[cobjid].specID].owner].specID);
850 }
851 ADD_DESTROYED_OBJECT(cobjid);
852 return TRUE;
853 }
854
855 #define DMG_THRESH 0x60000
856 #define HACK_THRESH 0x1800
857 #define SPCL_THRESH 0x80
858
859 // HEY COMMENTED OUT PROCEDURE
860 #ifdef CALLS_WERENT_SLOW
terrain_damage_object(physics_handle ph,fix raw_damage)861 uchar terrain_damage_object(physics_handle ph, fix raw_damage) {
862 uchar dead = FALSE;
863 ObjID target = physics_handle_to_id(ph);
864
865 if (ObjProps[OPNUM(cobjid)].flags & SPCL_TERR_DMG) {
866 if (raw_damage > SPCL_THRESH) {
867 objs[target].info.current_hp = 0;
868 ADD_DESTROYED_OBJECT(target);
869 dead = TRUE;
870 }
871 } else
872 dead = simple_damage_object(target, (raw_damage - HACK_THRESH) >> 10, EXPLOSION_FLAG, NO_SHIELD_ABSORBTION);
873
874 return (dead);
875 }
876 #endif
877
878 // ------------------------------
879 // compute_damage()
880 //
881 // computes damage to be inflicted on target
882 //
883 // target ObjID of object that will be damaged
884 // damage_type damage type of the attack example : energy, explosion, physical, needle, etc...
885 // damage_mod raw value of damage inflicted
886 // offense offensive value of the attack
887 // penet penetration value of the attack
888 // power_level percentage of damage to be inflicted
889 // *effect returns the effect number to be played
890 // *effect_row a pointer to the effect row
891
compute_damage(ObjID target,int damage_type,int damage_mod,ubyte offense,ubyte penet,int power_level,ubyte * effect,ubyte * effect_row,ubyte attack_effect_type)892 int compute_damage(ObjID target, int damage_type, int damage_mod, ubyte offense, ubyte penet, int power_level,
893 ubyte *effect, ubyte *effect_row, ubyte attack_effect_type) {
894 int damage = 0;
895 int delta;
896 int modifier;
897 ubyte affect;
898
899 // AFFECTIVENESS
900 //
901 affect = object_affect(target, damage_type);
902 if (affect) {
903 damage = (damage_mod * power_level * affect) / 100;
904
905 damage = armor_absorption(damage, ID2TRIP(target), penet);
906
907 if (!global_fullmap->cyber) {
908 // Compute the CRITICAL HIT affector
909 //
910 delta = random_bell_modifier((target == PLAYER_OBJ));
911 modifier = (target == PLAYER_OBJ) ? (offense + delta - PLAYER_DEFENSE_VALUE)
912 : ((offense - ObjProps[OPNUM(target)].defense_value) + delta);
913
914 if (modifier < -3)
915 damage /= SQUARE(modifier + 3); // plus because it's negative
916 else if ((modifier > 3) && (ObjProps[OPNUM(target)].defense_value != 0xFF)) {
917 // we are going to do critical damage, but we must check that the defense value
918 // isn't 0xFF (meaning it's invulnerable to critical hits).
919
920 // just put an upper bound to be safe.
921 if (modifier > 12)
922 modifier = 12;
923 damage = (damage * modifier) / 3;
924 }
925 }
926
927 // TOUGHNESS
928 if (ObjProps[OPNUM(target)].toughness != 3)
929 damage >>= ObjProps[OPNUM(target)].toughness;
930 else
931 damage = 0;
932
933 if (damage < 0) {
934 damage = 0;
935 } else {
936 damage = randomize_damage(damage, target == PLAYER_OBJ);
937
938 // bound to realistic max
939 if (damage > MAX_DAMAGE)
940 damage = MAX_DAMAGE;
941 }
942
943 // take either the normal effect, unless we did lots of damage - then play bigger one
944 if ((effect_row != NULL) && (attack_effect_type != SPECIAL_TYPE) && (effect != NULL) &&
945 !global_fullmap->cyber) {
946 // check to see if we did damage - if so - do appropriate hit effect
947 // otherwise do a wall hit effect - MEANS NO DAMAGE/EFFECT
948 if (damage)
949 *effect = (damage < (damage_mod * 7) / 6) ? *(effect_row) : *(effect_row + 1);
950 else
951 *effect = effect_matrix[NON_CRITTER_EFFECT][attack_effect_type][0];
952 } else if (effect != NULL)
953 *effect = 0;
954 } else if (attack_effect_type != SPECIAL_TYPE) {
955 // we didn't affect - so do the no effect one!
956 if (effect)
957 *effect = (!global_fullmap->cyber) ? effect_matrix[NON_CRITTER_EFFECT][attack_effect_type][0] : 0;
958 } else {
959 // Special damage type with no damage = no effect.
960 if (effect) {
961 *effect = 0;
962 }
963 }
964
965 return (damage);
966 }
967
968 // --------------------------------------------------------------
969 // critter_hit_effect()
970 //
critter_hit_effect(ObjID target,ubyte effect,Combat_Pt location,int damage,int max_damage)971 void critter_hit_effect(ObjID target, ubyte effect, Combat_Pt location, int damage, int max_damage) {
972 fix radius, height;
973 byte ht;
974 ObjLoc loc = objs[target].loc;
975
976 // temporary - to hit effect_center - will take care of later
977 SET_EFFECT_LOC(target, EFFECT_CENTER);
978
979 SET_EFFECT_NUM(target, effect);
980 SET_EFFECT_FRAME(target, 0);
981
982 radius = fix_make(ObjProps[OPNUM(target)].physics_xr, 0) / (PHYSICS_RADIUS_UNIT * 4);
983
984 height = fix_from_obj_height_val(loc.z);
985 ht = ((height - location.z) / radius) + 4;
986 if (ht < 1)
987 ht = 1;
988 else if (ht > 7)
989 ht = 7;
990
991 SET_EFFECT_HEIGHT(target, ht);
992
993 if (damage < (max_damage / 3)) {
994 SET_EFFECT_DUAL(target, 0);
995 SET_EFFECT_SCALE(target, 1);
996 } else if (damage < max_damage) {
997 SET_EFFECT_DUAL(target, 0);
998 SET_EFFECT_SCALE(target, 2);
999 } else {
1000 SET_EFFECT_DUAL(target, 1);
1001 SET_EFFECT_SCALE(target, 3);
1002 }
1003 }
1004
1005 // ---------------------------------------------------------------------------
1006 // get_damage_estimate()
1007 //
1008 // Returns a number from DAMAGE_MIN to DAMAGE_MAX indicating how wounded
1009 // a creature is.
1010
get_damage_estimate(ObjSpecID osid)1011 int get_damage_estimate(ObjSpecID osid) {
1012 ObjID id = objCritters[osid].id;
1013 int triple = ID2TRIP(id);
1014
1015 return (DAMAGE_MAX - ((objs[id].info.current_hp * DAMAGE_MAX) / ObjProps[OPTRIP(triple)].hit_points));
1016 }
1017
1018 // -------------------------------------------------------
1019 // attack_object()
1020 //
1021 // target ObjID of object that will be damaged
1022 // damage_type damage type of the attack example : energy, explosion, physical, needle, etc...
1023 // damage_mod raw value of damage inflicted
1024 // offense offensive value of the attack
1025 // penet penetration value of the attack
1026 // flags flags for the attack (currently just to see if player's shields absorb damage)
1027 // power_level percentage of damage to be inflicted
1028 // *effect returns the effect number to be played
1029 // *effect_row a pointer to the effect row
1030 //
1031 // returns whether target died
1032
attack_object(ObjID target,int damage_type,int damage_mod,ubyte offense,ubyte penet,ubyte flags,int power_level,ubyte * effect_row,ubyte * effect,ubyte attack_effect_type,int * damage_inflicted)1033 ubyte attack_object(ObjID target, int damage_type, int damage_mod, ubyte offense, ubyte penet, ubyte flags,
1034 int power_level, ubyte *effect_row, ubyte *effect, ubyte attack_effect_type,
1035 int *damage_inflicted) {
1036 int damage;
1037 char diff;
1038
1039 if (effect)
1040 *effect = 0;
1041 // check to see if we have a valid target
1042 if (target == OBJ_NULL)
1043 return (0);
1044 else if (is_obj_destroyed(target))
1045 return (0);
1046 // the next is same as dead!
1047 else if ((objs[target].info.current_hp == 0) && DESTROY_OBJ_EFFECT(ObjProps[OPNUM(target)].destroy_effect))
1048 return (0);
1049
1050 // get the difficulty level
1051 diff = (global_fullmap->cyber) ? player_struct.difficulty[CYBER_DIFF_INDEX]
1052 : player_struct.difficulty[COMBAT_DIFF_INDEX];
1053
1054 // look combat difficult 0 setting - KILL OBJECTS IN ONE SHOT!!! - if object's toughness isn't 3
1055 if ((ObjProps[OPNUM(target)].toughness != 3) && !diff && (target != PLAYER_OBJ)) {
1056 damage = objs[target].info.current_hp;
1057 if (effect)
1058 *effect = (effect_row) ? *(effect_row + 1) : 0;
1059 } else {
1060 #ifdef SELFRUN // we do max damage if we're in self run
1061 damage = ((objs[target].obclass == CLASS_CRITTER) && (target != PLAYER_OBJ)) ? 0xFF : 0;
1062 #else
1063 damage = compute_damage(target, damage_type, damage_mod, offense, penet, power_level, effect, effect_row,
1064 attack_effect_type);
1065 #endif
1066 }
1067
1068 if (damage_inflicted)
1069 *damage_inflicted = damage;
1070
1071 // okay let's check if we're destroying an object that has a flagged destroy effect
1072 // if the high bit is flagged then we will destroy the object during the animation
1073 if ((objs[target].info.current_hp <= damage) && DESTROY_OBJ_EFFECT(ObjProps[OPNUM(target)].destroy_effect)) {
1074 objs[target].info.current_hp = 0;
1075 if (effect)
1076 *effect = ObjProps[OPNUM(target)].destroy_effect;
1077 return (TRUE);
1078 }
1079 return (damage_object(target, damage, damage_type, flags));
1080 }
1081
1082 // -------------------------------------------------------------
1083 // player_attack_object()
1084 //
1085
1086 #define CRAZE_DAMAGE_MOD 2
1087
player_attack_object(ObjID target,int wpn_triple,int power_level,Combat_Pt origin)1088 ubyte player_attack_object(ObjID target, int wpn_triple, int power_level, Combat_Pt origin) {
1089 ubyte offense;
1090 int damage_mod;
1091 int wpn_class = TRIP2CL(wpn_triple);
1092 int dtype;
1093 int damage_inflicted;
1094 int prop_val;
1095 ubyte penet;
1096 ubyte effect;
1097 ubyte *effect_row;
1098 ubyte attack_effect_type;
1099 ubyte special_effect = 0;
1100 ubyte flags = 0;
1101 ObjID effect_id = OBJ_NULL;
1102 uchar dead = FALSE;
1103 uchar new_loc = FALSE;
1104 ObjLoc loc;
1105 ubyte effect_class =
1106 (objs[target].obclass == CLASS_CRITTER) ? CritterProps[CPNUM(target)].hit_effect : NON_CRITTER_EFFECT;
1107
1108 // Special targeting ware hack
1109 if ((objs[target].obclass == CLASS_CRITTER) && (get_player_ware_version(WARE_HARD, HARDWARE_TARGET) > 3) &&
1110 (player_struct.curr_target == OBJ_NULL) && (!global_fullmap->cyber)) {
1111 select_current_target(target, FALSE);
1112 }
1113
1114 switch (wpn_class) {
1115 case (CLASS_GUN): // Beam weapon is the only gun with damage type
1116 switch (TRIP2SC(wpn_triple)) {
1117 case (GUN_SUBCLASS_HANDTOHAND):
1118 prop_val = SCTRIP(wpn_triple);
1119 damage_mod = HandtohandGunProps[prop_val].damage_modifier;
1120 offense = HandtohandGunProps[prop_val].offense_value;
1121 if (player_struct.drug_status[DRUG_LSD] > 0) {
1122 damage_mod <<= CRAZE_DAMAGE_MOD;
1123 offense += CRAZE_DAMAGE_MOD;
1124 }
1125 dtype = HandtohandGunProps[prop_val].damage_type;
1126 penet = HandtohandGunProps[prop_val].penetration;
1127 attack_effect_type = HAND_TYPE;
1128 break;
1129 case (GUN_SUBCLASS_BEAM):
1130 prop_val = SCTRIP(wpn_triple);
1131 damage_mod = BeamGunProps[prop_val].damage_modifier;
1132 offense = BeamGunProps[prop_val].offense_value;
1133 dtype = BeamGunProps[prop_val].damage_type;
1134 penet = BeamGunProps[prop_val].penetration;
1135 attack_effect_type = BEAM_TYPE;
1136 break;
1137 case (GUN_SUBCLASS_SPECIAL):
1138 prop_val = SCTRIP(wpn_triple);
1139 damage_mod = SpecialGunProps[prop_val].damage_modifier;
1140 offense = SpecialGunProps[prop_val].offense_value;
1141 dtype = SpecialGunProps[prop_val].damage_type;
1142 penet = SpecialGunProps[prop_val].penetration;
1143 attack_effect_type = SPECIAL_TYPE;
1144 special_effect = EFFECT_VAL(ObjProps[OPTRIP(SpecialGunProps[prop_val].proj_triple)].destroy_effect);
1145 break;
1146 case (GUN_SUBCLASS_BEAMPROJ):
1147 prop_val = SCTRIP(wpn_triple);
1148 damage_mod = BeamprojGunProps[prop_val].damage_modifier;
1149 offense = BeamprojGunProps[prop_val].offense_value;
1150 dtype = BeamprojGunProps[prop_val].damage_type;
1151 penet = BeamprojGunProps[prop_val].penetration;
1152 attack_effect_type = SPECIAL_TYPE;
1153 special_effect = EFFECT_VAL(ObjProps[OPTRIP(BeamprojGunProps[prop_val].proj_triple)].destroy_effect);
1154 if (BeamprojGunProps[prop_val].flags & 0x02)
1155 flags |= STUN_ATTACK;
1156 break;
1157 }
1158 break;
1159 case (CLASS_AMMO):
1160 damage_mod = AmmoProps[CPTRIP(wpn_triple)].damage_modifier;
1161 offense = AmmoProps[CPTRIP(wpn_triple)].offense_value;
1162 dtype = AmmoProps[CPTRIP(wpn_triple)].damage_type;
1163 penet = AmmoProps[CPTRIP(wpn_triple)].penetration;
1164 attack_effect_type = PROJ_TYPE;
1165 break;
1166 case (CLASS_GRENADE):
1167 damage_mod = GrenadeProps[CPTRIP(wpn_triple)].damage_modifier;
1168 offense = GrenadeProps[CPTRIP(wpn_triple)].offense_value;
1169 dtype = GrenadeProps[CPTRIP(wpn_triple)].damage_type;
1170 penet = GrenadeProps[CPTRIP(wpn_triple)].penetration;
1171 attack_effect_type = GREN_TYPE;
1172 break;
1173 default:
1174 return (0);
1175 break;
1176 }
1177 effect_row = effect_matrix[effect_class][attack_effect_type];
1178 dead = attack_object(target, dtype, damage_mod, offense, penet, flags, power_level, effect_row, &effect,
1179 attack_effect_type, &damage_inflicted);
1180
1181 // this is for a slow projectile spang - not for objects exploding
1182 if (attack_effect_type == SPECIAL_TYPE)
1183 effect = 0; // special_effect;
1184
1185 if (dead) {
1186 ubyte old_effect = effect;
1187
1188 // check to see if we're suppose to play an animation if object is destroyed....
1189 if (EFFECT_VAL(ObjProps[OPNUM(target)].destroy_effect))
1190 effect = ObjProps[OPNUM(target)].destroy_effect;
1191
1192 if (old_effect != effect) {
1193 new_loc = TRUE;
1194 loc = objs[target].loc;
1195 }
1196 }
1197
1198 if (effect != 0) {
1199 // if (objs[target].obclass == CLASS_CRITTER)
1200 // critter_hit_effect(target, effect, origin, damage_inflicted, damage_mod);
1201 //#ifdef REMOVE_OLD_EFFECT
1202 // else
1203 //#endif
1204 {
1205 fix deltax, deltay, dist;
1206
1207 // if it's a 3-d model - let's get the right place
1208 if (ObjProps[OPNUM(target)].render_type == 1) {
1209 loc = objs[target].loc;
1210
1211 if (ObjProps[OPNUM(target)].physics_xr > 20) {
1212 // let's randomize the hit by a bit if object is big enough
1213 loc.x += (RndRange(&damage_rnd, 0, 0x40) - 0x20);
1214 loc.y += (RndRange(&damage_rnd, 0, 0x40) - 0x20);
1215 loc.z +=
1216 ((RndRange(&damage_rnd, 0, 0x10) - 0x08) +
1217 obj_height_from_fix(fix_make(ObjProps[OPNUM(target)].physics_xr, 0) / PHYSICS_RADIUS_UNIT));
1218 }
1219
1220 // and in the end - let's bring the effect up to the center of the object
1221 } else if (!new_loc) {
1222 // if we've been given bad info - let's just go on
1223 if ((origin.x < 0) || (fix_int(origin.y) > 64)) {
1224 return (dead);
1225 }
1226
1227 loc.x = obj_coord_from_fix(origin.x);
1228 loc.y = obj_coord_from_fix(origin.y);
1229 loc.z = obj_height_from_fix(origin.z);
1230 }
1231
1232 deltax = OBJ_LOC_VAL_TO_FIX(objs[PLAYER_OBJ].loc.x - loc.x);
1233 deltay = OBJ_LOC_VAL_TO_FIX(objs[PLAYER_OBJ].loc.y - loc.y);
1234 dist = fix_fast_pyth_dist(deltax, deltay) << 2;
1235
1236 // move explosion towards player
1237 loc.x += obj_coord_from_fix(fix_div(deltax, dist));
1238 loc.y += obj_coord_from_fix(fix_div(deltay, dist));
1239
1240 effect_id = do_special_effect_location(target, effect, 0xFF, &loc, 0);
1241 }
1242 }
1243
1244 if (attack_effect_type == BEAM_TYPE) {
1245 extern ObjID beam_effect_id;
1246 if (effect_id != OBJ_NULL) {
1247 beam_effect_id = effect_id;
1248 hudobj_set_id(beam_effect_id, TRUE);
1249 }
1250 }
1251
1252 return (dead);
1253 }
1254