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