/* 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 "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 "frflags.h" #include "gamerend.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 "cutsloop.h" #define SQUARE(x) ((x) * (x)) #define PLAYER_DEFENSE_VALUE 4 #define MAX_DAMAGE 2500 short destroyed_obj_count = 0; ObjID destroyed_ids[MAX_DESTROYED_OBJS]; //---------------- // Internal Prototypes //---------------- int random_bell_modifier(uchar attack_on_player); int randomize_damage(int damage, uchar 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); void player_dies(); ubyte damage_player(int damage, ubyte dtype, ubyte flags); 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; uchar change_target = FALSE; uchar dupe; ObjID id; if (destroyed_obj_count != 0) { for (i = 0; i < destroyed_obj_count; i++) { // Look through the rest of the list for duplicates // if so, don't delete us now since we'll get what's coming // to us later... // Yeah, yeah, so this is an N-squared algorithm, but hopefully the size of // the list is pretty damn short... dupe = FALSE; id = destroyed_ids[i]; for (j = i + 1; j < destroyed_obj_count; j++) { if (id == destroyed_ids[j]) { dupe = TRUE; break; } } if (!dupe) { { // check if the physics object contributed to the lighting of the map if (objs[id].obclass == CLASS_PHYSICS) { ObjSpecID osid = objs[id].specID; if (objPhysicss[osid].p2.x) { MapElem *mmp; mmp = MAP_GET_XY(OBJ_LOC_TO_LIGHT_LOC(objPhysicss[osid].p1.x), OBJ_LOC_TO_LIGHT_LOC(objPhysicss[osid].p1.y)); me_rend3_set(mmp, me_bits_rend3(mmp) - 1); objPhysicss[osid].p2.x = 0; } } else if (objs[id].obclass == CLASS_GRENADE) { do_grenade_explosion(id, TRUE); } // If we destroyed a creature which was being targeted, we // need to switch the current target if (destroyed_ids[i] == player_struct.curr_target) change_target = TRUE; obj_destroy(destroyed_ids[i]); if (change_target) { toggle_current_target(); change_target = FALSE; } } } // object_dead(); } chg_set_flg(DEMOVIEW_UPDATE); // "clear" the list destroyed_obj_count = 0; } } // ------------------------------------------- // is_obj_destroyed() // uchar is_obj_destroyed(ObjID id) { int i; uchar found = FALSE; for (i = 0; i < destroyed_obj_count; i++) { if (destroyed_ids[i] == id) { found = TRUE; break; } } return (found); } // --------------------------------------------- // random_bell_modifier() // int random_bell_modifier(uchar attack_on_player) { int rtotal; int i; int rval; int retval; ubyte dies, die_value, handicap; ubyte difficulty = (global_fullmap->cyber) ? 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; i < dies; i++) { rval = RndRange(&damage_rnd, 0, die_value); rtotal += rval; } if (rtotal <= 2) retval = -12; // 00-02 else if (rtotal <= 8) retval = -8; // 03-08 else if (rtotal <= 16) retval = -6; // 09-16 else if (rtotal <= 28) retval = -3; // 17-28 else if (rtotal <= 40) retval = -1; // 29-40 else if (rtotal <= 60) retval = 0; // 41-60 else if (rtotal <= 72) retval = 1; // 61-72 else if (rtotal <= 84) retval = 3; // 73-84 else if (rtotal <= 92) retval = 6; // 85-92 else if (rtotal <= 98) retval = 8; // 93-98 else retval = 12; // 99-100 return (retval); } // ------------------------------------------- // randomize_damage() // int randomize_damage(int damage, uchar attack_on_player) { int dtotal; ubyte iterations; int i; ubyte difficulty = (global_fullmap->cyber) ? 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; i < iterations; i++) dtotal += RndRange(&damage_rnd, 1, 7); // if we're playing on difficulty 3 - reduce damage by a third if (!attack_on_player && (difficulty == 3)) dtotal = (dtotal << 1) / 3; return (dtotal); } // --------------------------------------------- // object_affect() // // there has been a hit, now we check how much an object is affected // ubyte object_affect(ObjID target_id, short dtype) { ubyte affected = 0; int resis; if (!target_id) { return 0; } resis = ObjProps[OPNUM(target_id)].resistances; affected = (resis & dtype & DAMAGE_TYPE_FIELD) ? 1 : 0; if (affected) { // are the super damage the same, and we have a non-zero primary damage if ((SUPER_DAMAGE(resis) == SUPER_DAMAGE(dtype)) && (SUPER_DAMAGE(resis))) { ubyte difficulty = (global_fullmap->cyber) ? 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; uchar shield_used; int shield_absorb_damage(int damage, ubyte dtype, 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); } uchar alternate_death = FALSE; // kill_player() // kills the player, checks for traps and stuff, so on // returns true if player is really dead dead dead uchar kill_player(void) { ObjSpecID osid; uchar quick_death = TRUE; uchar dummy; extern uchar clear_player_data; INFO("Player died!\n"); // 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; play_cutscene(DEATH_CUTSCENE, FALSE); } 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 extern uchar weapon_button_up; weapon_button_up = TRUE; // 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; uchar 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)lg_min(lg_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; uchar tranq = FALSE; uchar 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. uchar 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... uchar 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 uchar terrain_damage_object(physics_handle ph, fix raw_damage) { uchar 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 if (attack_effect_type != SPECIAL_TYPE) { // 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; } else { // Special damage type with no damage = no effect. if (effect) { *effect = 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; uchar dead = FALSE; uchar 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); }