/* Copyright (C) 2015-2018 Night Dive Studios, LLC. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * $Source: r:/prj/cit/src/RCS/damage.c $ * $Revision: 1.206 $ * $Author: xemu $ * $Date: 1994/10/27 04:56:12 $ */ #include #include "Shock.h" #include "MoviePlay.h" #include "effect.h" #include "newmfd.h" #include "damage.h" #include "objwpn.h" #include "objects.h" #include "objsim.h" #include "objprop.h" #include "weapons.h" #include "player.h" #include "combat.h" #include "cybrnd.h" #include "mainloop.h" #include "trigger.h" #include "musicai.h" #include "sfxlist.h" #include "ai.h" #include "frprotox.h" #include "frflags.h" #include "gamerend.h" #include "hkeyfunc.h" #include "gameloop.h" #include "target.h" #include "objbit.h" #include "physunit.h" #include "hud.h" #include "faketime.h" #include "otrip.h" #include "grenades.h" #include "softdef.h" #include "aiflags.h" #include "input.h" #include "mapflags.h" #include "wares.h" #include "drugs.h" #include "diffq.h" #include "hudobj.h" #include "amap.h" #include "target.h" #include "status.h" #define SQUARE(x) ((x) * (x)) #define PLAYER_DEFENSE_VALUE 4 #define MAX_DAMAGE 2500 extern void set_dmg_percentage(int which, ubyte percent); //---------------- // Internal Prototypes //---------------- int random_bell_modifier(bool attack_on_player); int randomize_damage(int damage,bool attack_on_player); int armor_absorption(int raw_damage, int obj_triple, ubyte penetrate); int shield_absorb_damage(int damage, ubyte dtype, byte shield_absorb, ubyte shield_threshold); bool kill_player(void); void regenerate_player(void); void player_dies(); ubyte damage_player(int damage, ubyte dtype, ubyte flags); void slow_proj_hit(ObjID id, ObjID victim); void critter_hit_effect(ObjID target, ubyte effect,Combat_Pt location, int damage, int max_damage); // ------------------------------------------- // destroy_destroyed_objects() // void destroy_destroyed_objects(void) { int i, j; bool change_target = FALSE; bool dupe; ObjID id; if (destroyed_obj_count != 0) { for (i=0; icyber) ? player_struct.difficulty[CYBER_DIFF_INDEX] : player_struct.difficulty[COMBAT_DIFF_INDEX]; if (attack_on_player) difficulty = (difficulty==0) ? 3 : 4-difficulty; switch (difficulty) { case(0): dies = 2; die_value = 50; handicap = 15; break; case(1): dies = 3; die_value = 33; handicap = 6; break; case(2): dies = 5; die_value = 20; handicap = 0; break; case(3): dies = 8; die_value = 12; handicap = 0; break; } rtotal = handicap; for (i=0;icyber) ? player_struct.difficulty[CYBER_DIFF_INDEX] : player_struct.difficulty[COMBAT_DIFF_INDEX]; dtotal = damage/2; iterations = damage/8; // if damage div 8 is non-zero add an extra iteration // yes, we might have a value that's a little bigger than normal if (damage % 8) iterations++; for (i=0; icyber) ? player_struct.difficulty[CYBER_DIFF_INDEX] : player_struct.difficulty[COMBAT_DIFF_INDEX]; // don't do as much super damage if we're on combat diff 3 affected = (difficulty==3) ? 3 : 4; } // are the primary damage the same, and we have a non-zero primary damage else if ((PRIMARY_DAMAGE(resis) == PRIMARY_DAMAGE(dtype)) && (PRIMARY_DAMAGE(resis))) { affected = 2; } } // mprintf("attack on %d, affect : %d\n", target_id, affected); return(affected); } // ----------------------------------------------------------------------------------------- // armor_absorption() // int armor_absorption(int raw_damage, int obj_triple, ubyte penetrate) { short real_penetration = (((short) penetrate * (90 + RndRange(&damage_rnd, 0, 20)))/100); int damage; damage = (ObjProps[OPTRIP(obj_triple)].armor - real_penetration); damage = (damage > 0) ? raw_damage - damage : raw_damage; if (damage < 0) damage = 0; return(damage); } // some globals uchar sound_hurt_threshold = 10; uchar static_pain_time = 64; uchar static_pain_base = 30; uchar static_pain_delta = 30; uchar shield_blowout_threshold = 15; short fr_solidfr_time; short fr_sfx_time; // this is the voodoo threshold for shields, so that we have a reference // to do effects with. (static) #define VOODOO_SHIELD_THRESHOLD 100 // ------------------------------------------------------------------------------------------- // shield_absorb_damage() // // returns damage to the player after shields short shield_absorb_perc = 0; bool shield_used; int shield_absorb_damage(int damage, ubyte, byte shield_absorb, ubyte shield_threshold) { ubyte shield_drain=0; if ((damage|player_struct.hit_points)==0) return 0; // 0 hp is already dead, eh?, 0 damage no matter if (damage > shield_threshold) { // absorption rate will be given a +/- 8 percentage for variation - unless we're at zero already shield_absorb = (shield_absorb) ? shield_absorb + RndRange(&damage_rnd, 0, 16) - 8 : 0; if (shield_absorb>0) { // figure out how much damage is absorbed by shields // hey - it's even a percentage // WHY - make it 0-255, dont divide needlessly, arghgqghgghdsghgfdgfjfghdfgj shield_drain = (damage * shield_absorb)/100; // let's absorb the damage... now! damage -= shield_drain; } else shield_absorb = 0; } else { if (shield_absorb) { // if we're below threshold, we've absorbed all damage... shield_absorb = 100; shield_drain = damage; } else shield_drain = 0; } // Hud me baby // oh,and sound too. if (shield_absorb > 0) { shield_absorb_perc = shield_absorb; hud_set_time(HUD_SHIELD, CIT_CYCLE); // do the sound if we're not in cyberspace if (!global_fullmap->cyber) (shield_absorb > 80) ? play_digi_fx(SFX_SHIELD_2,1) : play_digi_fx(SFX_SHIELD_1,1); } else if (((rand() & 0x1A) < damage) && !global_fullmap->cyber) play_digi_fx(SFX_PLAYER_HURT,1); // let's make sure that we don't go over the 100% shield flash if (shield_drain > VOODOO_SHIELD_THRESHOLD) shield_drain = VOODOO_SHIELD_THRESHOLD; // let's reuse the shield_drain variable // let's get the percentage absorbed (w.r.t. the VOODOO_SHIELD_THRESHOLD) shield_drain = (ubyte) (((int) shield_drain << 8) / VOODOO_SHIELD_THRESHOLD); if ((shield_used = (shield_drain > 0)) == TRUE) set_dmg_percentage(DMG_SHIELD,shield_drain); return(damage); } bool alternate_death = FALSE; extern Boolean gPlayingGame; extern Boolean gDeadPlayerQuit; // kill_player() // kills the player, checks for traps and stuff, so on // returns true if player is really dead dead dead bool kill_player(void) { ObjSpecID osid; bool quick_death = TRUE; bool dummy; extern bool clear_player_data; // Look for a player death trigger. If so, do it. // If not, play appropriate dying cutscene. osid = objTraps[0].id; while (osid != OBJ_SPEC_NULL) { if (ID2TRIP(objTraps[osid].id)==PLRDETH_TRIG_TRIPLE) if (trap_activate(objTraps[osid].id, &dummy)) quick_death = FALSE; osid = objTraps[osid].next; } // if we died not from a trap - then we should clear the player data #ifdef TEST_REBIRTH clear_player_data = FALSE; return FALSE; #else clear_player_data = (quick_death | alternate_death); if (quick_death) { amap_reset(); secret_render_fx=0; gDeadPlayerQuit = TRUE; gPlayingGame = FALSE; // Hop out of the game loop. } return(quick_death | alternate_death); #endif } void regenerate_player(void) { extern void wear_off_drug(int i); extern void regenetron_door_hack(void); int i; for (i = 0; i < NUM_DAMAGE_TYPES; i++) player_struct.hit_points_lost[i] = 0; hud_unset(HUD_RADPOISON|HUD_BIOPOISON); player_struct.curr_target = OBJ_NULL; player_struct.fatigue = 0; // clear physics state player_struct.posture = 0; player_struct.foot_planted=0; player_struct.leanx = 0; player_struct.leany = 0; player_struct.eye_pos = 0; for (i = 0; i < NUM_DRUGZ; i++) wear_off_drug(i); regenetron_door_hack(); } // ----------------------------------------------------------------------------------------- // damage_player() // // returns 0 if player is still alive // returns 1 if player is dead #define CSHIELD_THRESHOLDS(lev) 0 #define MAX_CSHIELD_ABSORB 120 #define NUM_CSHIELD_LEVELS 10 #define CSHIELD_ABSORB_RATES(lev) ((lev) == 0) ? 0 : (MAX_CSHIELD_ABSORB / (NUM_CSHIELD_LEVELS - (lev))) #define MAX_FATIGUE 10000 #define DEATH_TICKS CIT_CYCLE ulong player_death_time = 0; // Something has caused the player to become a fatality // typically this is damage, but can be delayed-death due to craze void player_dies() { extern void physics_zero_all_controls(); extern void clear_digi_fx(); extern short inventory_page; #ifdef AUDIOLOGS extern char secret_pending_hack; secret_pending_hack = 0; #endif // we should play funky death music mai_player_death(); reset_input_system(); chg_set_sta(GL_CHG_2); // disable the input system // clear off hud & prep for funky regen FX // hud_unset(HUD_ALL); physics_zero_all_controls(); // clear edms_state LG_memset(player_struct.edms_state, 0, sizeof(fix)*12); // reset the inventory page inventory_page = 0; // hey no more things to do to player while he's dead // it's really a secret - don't do things to player // while there are cool secret_render_fx going on player_struct.dead = TRUE; set_dmg_percentage(DMG_BLOOD, 100); // 100 is an arbitrary number - we're trying to get it to do red static secret_render_fx=DYING_REND_SFX; clear_digi_fx(); player_struct.num_deaths++; } // ------------------------------------------------------- // damage_player() // #define DAMAGE_DIFFICULTY ((global_fullmap->cyber) ? 3 : 0) ubyte damage_player(int damage, ubyte dtype, ubyte flags) { ubyte *cur_hp; short rawval; bool dead = 0, damage_dealt=FALSE; char dlev, dmg_type=DMG_BLOOD; if (secret_render_fx > 0) return 0; if ((damage <= 0) || (player_struct.hit_points==0)) // 0 hp is already dead, eh? return 0; if (global_fullmap->cyber && !player_struct.difficulty[CYBER_DIFF_INDEX]) return 0; cur_hp= (global_fullmap->cyber) ? &player_struct.cspace_hp : &player_struct.hit_points; shield_used = FALSE; // check if shields should play a role if ((dtype==RADIATION_TYPE)||(dtype==BIO_TYPE)) dmg_type=DMG_RAD; else if (!(flags & NO_SHIELD_ABSORBTION)) { byte absorb_rate, thresh_val; // compensate for difficulty level dlev = player_struct.difficulty[DAMAGE_DIFFICULTY]; if (dlev!=3) damage>>=(2-dlev); if (global_fullmap->cyber) { if (player_struct.softs.defense[SOFTWARE_CSHIELD]) { absorb_rate=CSHIELD_ABSORB_RATES(player_struct.softs.defense[SOFTWARE_CSHIELD]); thresh_val =CSHIELD_THRESHOLDS(player_struct.softs.defense[SOFTWARE_CSHIELD]); } else absorb_rate = thresh_val = 0; } else { absorb_rate=(byte)player_struct.shield_absorb_rate; thresh_val =player_struct.shield_threshold; } damage = shield_absorb_damage(damage,dtype,absorb_rate,thresh_val); } if (damage<=0) return 0; #ifdef WACKY_STATIC_USAGE // Play digi FX should go in here when we have appropriate SFX if ((!global_fullmap->cyber) && (damage > static_pain_base + rand()%static_pain_delta)) { extern char static_density, static_color, static_grouping; // Turn on fullscreen static & turn off any SFX that might be otherwise going on. fr_global_mod_flag(FR_SOLIDFR_STATIC, FR_SOLIDFR_MASK|FR_SFX_MASK); fr_solidfr_time = (static_pain_time); play_digi_fx(SFX_STATIC, -1); } #endif // did we take more damage than hit points?? - eeeegggads! we're dead if ((*cur_hp)<=damage) { damage_dealt=TRUE; { if (global_fullmap->cyber) { // No matter what, the player can't be killed in one shot.....it takes at least two shots player_struct.cspace_hp = (player_struct.cspace_hp == 1) ? 0 : 1; } else // normal (non-cyberspace) damage - player's dead dead dead { if (*cur_hp>0) { #ifdef CRAZE_NODEATH if ((player_struct.drug_status[DRUG_LSD] > 0) && (QUESTVAR_GET(COMBAT_DIFF_QVAR) < 3)) *cur_hp = 1; else #endif { *cur_hp = 0; dead = TRUE; // if we're carrying an object - let's drop it here! if (object_on_cursor != OBJ_NULL) { obj_move_to(object_on_cursor,&objs[PLAYER_OBJ].loc,TRUE); pop_cursor_object(); } // signal for the effects that happen with death player_dies(); } } } } } if (!damage_dealt) { extern int mai_damage_sum; *cur_hp -= damage; mai_damage_sum += damage; } if (*cur_hp == 0) rawval = damage<<8; else rawval = ((damage<<8) / (*cur_hp)); // what is this minmax3/20xff thing? // it's my voodoo - minman if (!shield_used) set_dmg_percentage(dmg_type,(ubyte) min(max(rawval,((damage*3)/2)),0x00FF)); // makes sure gamescreen knows that it should be updated chg_set_flg(VITALS_UPDATE); return(dead); } // -------------------------------------------------- // damage_object() // // return 0 - if object is still alive after damage // return 1 - if object has been destroyed // // also does the appropriate texture map change if // object is destroyed??????? ubyte damage_object(ObjID target_id, int damage, int dtype, ubyte flags) { int obclass = objs[target_id].obclass; int dead = 0; bool tranq=FALSE; bool stun= FALSE; short target_hp = ObjProps[OPNUM(target_id)].hit_points; // If we've already been destroyed, or don't care, thendon't bother us. if ((objs[target_id].info.inst_flags & INDESTRUCT_FLAG) || ((target_id != player_struct.rep) && (objs[target_id].info.current_hp == 0))) return(0); // let the player get his/her own special treatment if (target_id == PLAYER_OBJ) dead = damage_player(damage, (ubyte) PRIMARY_DAMAGE(dtype), flags); else { // are we still alive - then do special stuff that we only care about if // we're still alive, makes too much sense if (objs[target_id].info.current_hp > damage) { // damage object - but it's not dead yet. dead = 0; if (obclass == CLASS_CRITTER) { int pct = (450L * damage)/objs[target_id].info.current_hp; tranq = dtype & TRANQ_FLAG; stun = (flags & STUN_ATTACK); if (tranq) { tranq = FALSE; // okay tranq - only if we've done damage, and we're lucky and the damage we're doing // is a decent amount of the remaining life if (damage) tranq = (RndRange(&damage_rnd, 0, 100) < pct); } else if (stun) { if (objs[target_id].subclass == CRITTER_SUBCLASS_ROBOT) stun = FALSE; else stun = (RndRange(&damage_rnd, 0, 100) < pct); } ai_critter_hit(objs[target_id].specID, damage,tranq, stun); } // get rid of the hit points objs[target_id].info.current_hp -= damage; } else { objs[target_id].info.current_hp = 0; dead = 1; // Check to see whether or not there is cool special stuff to do when this thing // gets destroyed. obj_combat_destroy returns whether or not to go ahead and // continue the destruction process if (obj_combat_destroy(target_id)) ADD_DESTROYED_OBJECT(target_id); if (DESTROY_SOUND_EFFECT(ObjProps[OPNUM(target_id)].destroy_effect)) { extern ObjID damage_sound_id; extern char damage_sound_fx; damage_sound_fx = SFX_CPU_EXPLODE; damage_sound_id = target_id; } } if ((obclass == CLASS_CRITTER) && !global_fullmap->cyber) { ubyte seriousness=0; extern void hud_report_damage(ObjID target, byte seriousness); // marc's desired code if (stun) seriousness=7; else if (tranq) seriousness=6; else if (!object_affect(target_id, dtype)) seriousness=5; else if (damage > target_hp) seriousness=4; else if (damage > (target_hp * 4)/5) seriousness=3; else if (damage > target_hp/5) seriousness=2; else if (damage > 0) seriousness=1; hud_report_damage(target_id,seriousness); } // If we damaged the currently targeted creature, let mfds know... if ((target_id == player_struct.curr_target) && !global_fullmap->cyber) mfd_notify_func(MFD_TARGET_FUNC, MFD_TARGET_SLOT, FALSE, MFD_ACTIVE, TRUE); } return(dead); } // ----------------------------------------------- // simple_damage_object() takes damage of a particular type with particular flags, // and applies it to the object if it is vulnerable to the type. // returns whether the object was destroyed. bool simple_damage_object(ObjID target, int damage, ubyte dtype, ubyte flags) { if (object_affect(target,1 << (dtype-1) )) return damage_object(target,damage,dtype,flags); return FALSE; } #define FIRST_ENRG_PROJ_TYPE 7 #define DAMAGE_PROJ_DISTANCE (fix_make(0,0x6000)) // OBJ_NULL victim means terrain void slow_proj_hit(ObjID id, ObjID victim) { Combat_Pt origin; ObjLoc loc = objs[id].loc; ObjRefID current_ref; ObjID current_id; ubyte affect; ubyte dtype; ubyte proj_power; ubyte special_effect = EFFECT_VAL(ObjProps[OPNUM(id)].destroy_effect); int a; int weapon_triple; current_ref = MAP_GET_XY(OBJ_LOC_BIN_X(objs[id].loc), OBJ_LOC_BIN_Y(objs[id].loc))->objRef; if (objPhysicss[objs[id].specID].owner != PLAYER_OBJ) { if (special_effect) do_special_effect_location(id, special_effect, 0xFF, &loc, 0); return; } a = objPhysicss[objs[id].specID].bullet_triple; if (objs[id].info.type >= FIRST_ENRG_PROJ_TYPE) { weapon_triple = MAKETRIP(CLASS_GUN, GUN_SUBCLASS_BEAMPROJ, TRIP2TY(a)); proj_power = TRIP2CL(a); dtype = BeamprojGunProps[SCTRIP(weapon_triple)].damage_type; } else { weapon_triple = MAKETRIP(CLASS_GUN, GUN_SUBCLASS_SPECIAL, TRIP2TY(a)); proj_power = 100; dtype = SpecialGunProps[SCTRIP(weapon_triple)].damage_type; } if (weapon_triple == RAILGUN_TRIPLE) { do_explosion(loc, id, 0, &(game_explosions[1])); play_digi_fx_obj(SFX_EXPLOSION_1,1,id); } else if(victim == OBJ_NULL) { while (current_ref != OBJ_REF_NULL) { current_id = objRefs[current_ref].obj; if (current_id != id) { affect=object_affect(current_id, dtype); if (affect) { fix dist,deltax,deltay,deltaz; deltax = fix_from_obj_coord(objs[id].loc.x-objs[current_id].loc.x); deltay = fix_from_obj_coord(objs[id].loc.y-objs[current_id].loc.y); deltaz = fix_from_obj_height_val(objs[id].loc.z-objs[current_id].loc.z); dist = fix_mul(deltax,deltax)+fix_mul(deltay,deltay)+fix_mul(deltaz,deltaz); if (dist < DAMAGE_PROJ_DISTANCE) { origin.x = fix_make(-1,0); origin.y = fix_make(-1,0); origin.z = fix_make(-1,0); player_attack_object(current_id,weapon_triple,proj_power,origin); } } } current_ref = objRefs[current_ref].next; } } else { origin.x = fix_from_obj_coord(loc.x); origin.y = fix_from_obj_coord(loc.y); origin.z = fix_from_obj_height(loc.z); player_attack_object(victim, weapon_triple, proj_power, origin); } if (special_effect) do_special_effect_location(id, special_effect, 0xFF, &loc, 0); } // returns whether it is being killed... bool special_terrain_hit(ObjID cobjid) { if (is_obj_destroyed(cobjid)) return TRUE; if (objs[cobjid].obclass==CLASS_GRENADE) { if (objGrenades[objs[cobjid].specID].flags&GREN_ACTIVE_FLAG) ADD_DESTROYED_OBJECT(cobjid); else { return FALSE; // dead grenades stay alive // in the sense that they are not live, but should remain physics live, see } } else if (objs[cobjid].obclass == CLASS_PHYSICS) { // only one terrain hit per turn! if (objPhysicss[objs[cobjid].specID].p3.x) return FALSE; else objPhysicss[objs[cobjid].specID].p3.x = 3; slow_proj_hit(cobjid,OBJ_NULL); EDMS_obey_collisions(objs[cobjid].info.ph); if (PhysicsProps[CPNUM(cobjid)].flags & PROJ_PRESERVE_WALL) return FALSE; // Signal a miss to our controller ai_misses(objs[objPhysicss[objs[cobjid].specID].owner].specID); } ADD_DESTROYED_OBJECT(cobjid); return TRUE; } #define DMG_THRESH 0x60000 #define HACK_THRESH 0x1800 #define SPCL_THRESH 0x80 // HEY COMMENTED OUT PROCEDURE #ifdef CALLS_WERENT_SLOW bool terrain_damage_object(physics_handle ph, fix raw_damage) { bool dead = FALSE; ObjID target = physics_handle_to_id(ph); if (ObjProps[OPNUM(cobjid)].flags & SPCL_TERR_DMG) { if (raw_damage>SPCL_THRESH) { objs[target].info.current_hp = 0; ADD_DESTROYED_OBJECT(target); dead = TRUE; } } else dead = simple_damage_object(target,(raw_damage-HACK_THRESH)>>10,EXPLOSION_FLAG,NO_SHIELD_ABSORBTION); return(dead); } #endif // ------------------------------ // compute_damage() // // computes damage to be inflicted on target // // target ObjID of object that will be damaged // damage_type damage type of the attack example : energy, explosion, physical, needle, etc... // damage_mod raw value of damage inflicted // offense offensive value of the attack // penet penetration value of the attack // power_level percentage of damage to be inflicted // *effect returns the effect number to be played // *effect_row a pointer to the effect row int 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) { int damage = 0; int delta; int modifier; ubyte affect; // AFFECTIVENESS // affect = object_affect(target, damage_type); if (affect) { damage = (damage_mod * power_level * affect) / 100; damage = armor_absorption(damage, ID2TRIP(target), penet); if (!global_fullmap->cyber) { // Compute the CRITICAL HIT affector // delta = random_bell_modifier((target == PLAYER_OBJ)); modifier = (target == PLAYER_OBJ) ? (offense + delta - PLAYER_DEFENSE_VALUE) : ((offense - ObjProps[OPNUM(target)].defense_value) + delta); if (modifier < -3) damage /= SQUARE(modifier+3); // plus because it's negative else if ((modifier > 3) && (ObjProps[OPNUM(target)].defense_value != 0xFF)) { // we are going to do critical damage, but we must check that the defense value // isn't 0xFF (meaning it's invulnerable to critical hits). // just put an upper bound to be safe. if (modifier > 12) modifier = 12; damage = (damage * modifier)/3; } } // TOUGHNESS if (ObjProps[OPNUM(target)].toughness!=3) damage>>=ObjProps[OPNUM(target)].toughness; else damage=0; if (damage < 0) { damage = 0; } else { damage = randomize_damage(damage,target==PLAYER_OBJ); // bound to realistic max if (damage > MAX_DAMAGE) damage = MAX_DAMAGE; } // take either the normal effect, unless we did lots of damage - then play bigger one if ((effect_row != NULL) && (attack_effect_type != SPECIAL_TYPE) && (effect != NULL) && !global_fullmap->cyber) { // check to see if we did damage - if so - do appropriate hit effect // otherwise do a wall hit effect - MEANS NO DAMAGE/EFFECT if (damage) *effect = (damage < (damage_mod*7)/6) ? *(effect_row) : *(effect_row+1); else *effect = effect_matrix[NON_CRITTER_EFFECT][attack_effect_type][0]; } else if (effect != NULL) *effect = 0; } else { // we didn't affect - so do the no effect one! if (effect) *effect = (!global_fullmap->cyber) ? effect_matrix[NON_CRITTER_EFFECT][attack_effect_type][0] : 0; } return(damage); } // -------------------------------------------------------------- // critter_hit_effect() // void critter_hit_effect(ObjID target, ubyte effect,Combat_Pt location, int damage, int max_damage) { fix radius, height; byte ht; ObjLoc loc = objs[target].loc; // temporary - to hit effect_center - will take care of later SET_EFFECT_LOC(target, EFFECT_CENTER); SET_EFFECT_NUM(target, effect); SET_EFFECT_FRAME(target, 0); radius = fix_make(ObjProps[OPNUM(target)].physics_xr,0)/(PHYSICS_RADIUS_UNIT*4); height = fix_from_obj_height_val(loc.z); ht = ((height - location.z)/radius) + 4; if (ht < 1) ht = 1; else if (ht > 7) ht = 7; SET_EFFECT_HEIGHT(target, ht); if (damage < (max_damage/3)) { SET_EFFECT_DUAL(target, 0); SET_EFFECT_SCALE(target, 1); } else if (damage < max_damage) { SET_EFFECT_DUAL(target, 0); SET_EFFECT_SCALE(target, 2); } else { SET_EFFECT_DUAL(target, 1); SET_EFFECT_SCALE(target, 3); } } // --------------------------------------------------------------------------- // get_damage_estimate() // // Returns a number from DAMAGE_MIN to DAMAGE_MAX indicating how wounded // a creature is. int get_damage_estimate(ObjSpecID osid) { ObjID id = objCritters[osid].id; int triple = ID2TRIP(id); return(DAMAGE_MAX - ((objs[id].info.current_hp*DAMAGE_MAX)/ ObjProps[OPTRIP(triple)].hit_points)); } // ------------------------------------------------------- // attack_object() // // target ObjID of object that will be damaged // damage_type damage type of the attack example : energy, explosion, physical, needle, etc... // damage_mod raw value of damage inflicted // offense offensive value of the attack // penet penetration value of the attack // flags flags for the attack (currently just to see if player's shields absorb damage) // power_level percentage of damage to be inflicted // *effect returns the effect number to be played // *effect_row a pointer to the effect row // // returns whether target died ubyte 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) { int damage; char diff; if (effect) *effect = 0; // check to see if we have a valid target if (target == OBJ_NULL) return(0); else if (is_obj_destroyed(target)) return(0); // the next is same as dead! else if ((objs[target].info.current_hp==0) && DESTROY_OBJ_EFFECT(ObjProps[OPNUM(target)].destroy_effect)) return(0); // get the difficulty level diff = (global_fullmap->cyber) ? player_struct.difficulty[CYBER_DIFF_INDEX] : player_struct.difficulty[COMBAT_DIFF_INDEX]; // look combat difficult 0 setting - KILL OBJECTS IN ONE SHOT!!! - if object's toughness isn't 3 if ((ObjProps[OPNUM(target)].toughness!=3) && !diff && (target != PLAYER_OBJ)) { damage = objs[target].info.current_hp; if (effect) *effect = (effect_row) ? *(effect_row+1) : 0; } else { #ifdef SELFRUN // we do max damage if we're in self run damage = ((objs[target].obclass == CLASS_CRITTER) && (target != PLAYER_OBJ)) ? 0xFF : 0; #else damage = compute_damage(target, damage_type, damage_mod, offense, penet, power_level, effect, effect_row, attack_effect_type); #endif } if (damage_inflicted) *damage_inflicted=damage; // okay let's check if we're destroying an object that has a flagged destroy effect // if the high bit is flagged then we will destroy the object during the animation if ((objs[target].info.current_hp <= damage) && DESTROY_OBJ_EFFECT(ObjProps[OPNUM(target)].destroy_effect)) { objs[target].info.current_hp=0; if (effect) *effect = ObjProps[OPNUM(target)].destroy_effect; return(TRUE); } return(damage_object(target, damage, damage_type, flags)); } // ------------------------------------------------------------- // player_attack_object() // #define CRAZE_DAMAGE_MOD 2 ubyte player_attack_object(ObjID target, int wpn_triple, int power_level, Combat_Pt origin) { ubyte offense; int damage_mod; int wpn_class = TRIP2CL(wpn_triple); int dtype; int damage_inflicted; int prop_val; ubyte penet; ubyte effect; ubyte *effect_row; ubyte attack_effect_type; ubyte special_effect = 0; ubyte flags = 0; ObjID effect_id = OBJ_NULL; bool dead = FALSE; bool new_loc = FALSE; ObjLoc loc; ubyte effect_class = (objs[target].obclass == CLASS_CRITTER) ? CritterProps[CPNUM(target)].hit_effect : NON_CRITTER_EFFECT; // Special targeting ware hack if ((objs[target].obclass == CLASS_CRITTER) && (get_player_ware_version(WARE_HARD,HARDWARE_TARGET) > 3) && (player_struct.curr_target == OBJ_NULL) && (!global_fullmap->cyber)) { select_current_target(target,FALSE); } switch (wpn_class) { case (CLASS_GUN): // Beam weapon is the only gun with damage type switch (TRIP2SC(wpn_triple)) { case (GUN_SUBCLASS_HANDTOHAND): prop_val = SCTRIP(wpn_triple); damage_mod = HandtohandGunProps[prop_val].damage_modifier; offense = HandtohandGunProps[prop_val].offense_value; if (player_struct.drug_status[DRUG_LSD] > 0) { damage_mod <<= CRAZE_DAMAGE_MOD; offense += CRAZE_DAMAGE_MOD; } dtype = HandtohandGunProps[prop_val].damage_type; penet = HandtohandGunProps[prop_val].penetration; attack_effect_type = HAND_TYPE; break; case (GUN_SUBCLASS_BEAM): prop_val = SCTRIP(wpn_triple); damage_mod = BeamGunProps[prop_val].damage_modifier; offense = BeamGunProps[prop_val].offense_value; dtype = BeamGunProps[prop_val].damage_type; penet = BeamGunProps[prop_val].penetration; attack_effect_type = BEAM_TYPE; break; case (GUN_SUBCLASS_SPECIAL): prop_val = SCTRIP(wpn_triple); damage_mod = SpecialGunProps[prop_val].damage_modifier; offense = SpecialGunProps[prop_val].offense_value; dtype = SpecialGunProps[prop_val].damage_type; penet = SpecialGunProps[prop_val].penetration; attack_effect_type = SPECIAL_TYPE; special_effect = EFFECT_VAL(ObjProps[OPTRIP(SpecialGunProps[prop_val].proj_triple)].destroy_effect); break; case (GUN_SUBCLASS_BEAMPROJ): prop_val = SCTRIP(wpn_triple); damage_mod = BeamprojGunProps[prop_val].damage_modifier; offense = BeamprojGunProps[prop_val].offense_value; dtype = BeamprojGunProps[prop_val].damage_type; penet = BeamprojGunProps[prop_val].penetration; attack_effect_type = SPECIAL_TYPE; special_effect = EFFECT_VAL(ObjProps[OPTRIP(BeamprojGunProps[prop_val].proj_triple)].destroy_effect); if (BeamprojGunProps[prop_val].flags & 0x02) flags |= STUN_ATTACK; break; } break; case (CLASS_AMMO): damage_mod = AmmoProps[CPTRIP(wpn_triple)].damage_modifier; offense = AmmoProps[CPTRIP(wpn_triple)].offense_value; dtype = AmmoProps[CPTRIP(wpn_triple)].damage_type; penet = AmmoProps[CPTRIP(wpn_triple)].penetration; attack_effect_type = PROJ_TYPE; break; case (CLASS_GRENADE): damage_mod = GrenadeProps[CPTRIP(wpn_triple)].damage_modifier; offense = GrenadeProps[CPTRIP(wpn_triple)].offense_value; dtype = GrenadeProps[CPTRIP(wpn_triple)].damage_type; penet = GrenadeProps[CPTRIP(wpn_triple)].penetration; attack_effect_type = GREN_TYPE; break; default: return(0); break; } effect_row = effect_matrix[effect_class][attack_effect_type]; dead = attack_object(target,dtype,damage_mod,offense,penet,flags,power_level, effect_row, &effect, attack_effect_type, &damage_inflicted); // this is for a slow projectile spang - not for objects exploding if (attack_effect_type == SPECIAL_TYPE) effect = 0; //special_effect; if (dead) { ubyte old_effect = effect; // check to see if we're suppose to play an animation if object is destroyed.... if (EFFECT_VAL(ObjProps[OPNUM(target)].destroy_effect)) effect = ObjProps[OPNUM(target)].destroy_effect; if (old_effect != effect) { new_loc = TRUE; loc = objs[target].loc; } } if (effect != 0) { // if (objs[target].obclass == CLASS_CRITTER) // critter_hit_effect(target, effect, origin, damage_inflicted, damage_mod); //#ifdef REMOVE_OLD_EFFECT // else //#endif { fix deltax, deltay,dist; // if it's a 3-d model - let's get the right place if (ObjProps[OPNUM(target)].render_type==1) { loc = objs[target].loc; if (ObjProps[OPNUM(target)].physics_xr > 20) { // let's randomize the hit by a bit if object is big enough loc.x += (RndRange(&damage_rnd, 0, 0x40)-0x20); loc.y += (RndRange(&damage_rnd, 0, 0x40)-0x20); loc.z += ((RndRange(&damage_rnd, 0, 0x10)-0x08) + obj_height_from_fix(fix_make(ObjProps[OPNUM(target)].physics_xr,0) / PHYSICS_RADIUS_UNIT)); } // and in the end - let's bring the effect up to the center of the object } else if (!new_loc) { // if we've been given bad info - let's just go on if ((origin.x < 0) || (fix_int(origin.y) > 64)) { return(dead); } loc.x = obj_coord_from_fix(origin.x); loc.y = obj_coord_from_fix(origin.y); loc.z = obj_height_from_fix(origin.z); } deltax = OBJ_LOC_VAL_TO_FIX(objs[PLAYER_OBJ].loc.x-loc.x); deltay = OBJ_LOC_VAL_TO_FIX(objs[PLAYER_OBJ].loc.y-loc.y); dist = fix_fast_pyth_dist(deltax, deltay)<<2; // move explosion towards player loc.x += obj_coord_from_fix(fix_div(deltax, dist)); loc.y += obj_coord_from_fix(fix_div(deltay, dist)); effect_id=do_special_effect_location(target, effect, 0xFF, &loc, 0); } } if (attack_effect_type==BEAM_TYPE) { extern ObjID beam_effect_id; if (effect_id!=OBJ_NULL) { beam_effect_id = effect_id; hudobj_set_id(beam_effect_id, TRUE); } } return(dead); }