Files
questshock/engine/src/GameSrc/damage.c
T
ml a5ec8db5f7
build / build (push) Failing after 5s
Vendor the Shockolate engine and build it via a Docker image with every
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont)
prebuilt, so compiling the engine needs no network access - just the
image and the engine source. Add res/assets/extract_assets.sh to pull
the game's data files out of a purchased GOG installer, and a Makefile
that assembles a runnable dist/ from the two.

Also add `make package`, which builds a redistributable tarball that
omits the proprietary game assets (shipping res/GET_ASSETS.txt instead)
plus license information for both the MIT tooling and the GPLv3 engine,
and a Gitea Actions workflow that builds and publishes it to
dl.ladkau.de on every push.
2026-07-19 15:43:38 +02:00

1246 lines
42 KiB
C

/*
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 <http://www.gnu.org/licenses/>.
*/
/*
* $Source: r:/prj/cit/src/RCS/damage.c $
* $Revision: 1.206 $
* $Author: xemu $
* $Date: 1994/10/27 04:56:12 $
*/
#include <stdlib.h>
#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);
}