Files
IPUDOOM/src/p_inter.c

796 lines
18 KiB
C

//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
//
// 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 2
// 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.
//
// DESCRIPTION:
// Handling interactions (i.e., collisions).
//
#include <stddef.h>
#include "am_map.h"
#include "d_englsh.h"
#include "d_items.h"
#include "d_mode.h"
// Data.
#include "doomdef.h"
#include "doomstat.h"
#include "i_system.h"
#include "info.h"
#include "m_fixed.h"
#include "m_random.h"
#include "p_inter.h"
#include "p_local.h"
#include "p_mobj.h"
#include "r_defs.h"
#include "r_main.h"
#include "s_sound.h"
#include "sounds.h"
#include "tables.h"
#define BONUSADD 6
// a weapon is found with two clip loads,
// a big item has five clip loads
int maxammo[NUMAMMO] = {200, 50, 300, 50};
int clipammo[NUMAMMO] = {10, 4, 20, 1};
//
// GET STUFF
//
//
// P_GiveAmmo
// Num is the number of clip loads,
// not the individual count (0= 1/2 clip).
// Returns false if the ammo can't be picked up at all
//
boolean P_GiveAmmo(player_t *player, ammotype_t ammo, int num) {
int oldammo;
if (ammo == am_noammo)
return false;
if (ammo > NUMAMMO)
I_Error("P_GiveAmmo: bad type %i", ammo);
if (player->ammo[ammo] == player->maxammo[ammo])
return false;
if (num)
num *= clipammo[ammo];
else
num = clipammo[ammo] / 2;
if (gameskill == sk_baby || gameskill == sk_nightmare) {
// give double ammo in trainer mode,
// you'll need in nightmare
num <<= 1;
}
oldammo = player->ammo[ammo];
player->ammo[ammo] += num;
if (player->ammo[ammo] > player->maxammo[ammo])
player->ammo[ammo] = player->maxammo[ammo];
// If non zero ammo,
// don't change up weapons,
// player was lower on purpose.
if (oldammo)
return true;
// We were down to zero,
// so select a new weapon.
// Preferences are not user selectable.
switch (ammo) {
case am_clip:
if (player->readyweapon == wp_fist) {
if (player->weaponowned[wp_chaingun])
player->pendingweapon = wp_chaingun;
else
player->pendingweapon = wp_pistol;
}
break;
case am_shell:
if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol) {
if (player->weaponowned[wp_shotgun])
player->pendingweapon = wp_shotgun;
}
break;
case am_cell:
if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol) {
if (player->weaponowned[wp_plasma])
player->pendingweapon = wp_plasma;
}
break;
case am_misl:
if (player->readyweapon == wp_fist) {
if (player->weaponowned[wp_missile])
player->pendingweapon = wp_missile;
}
default:
break;
}
return true;
}
//
// P_GiveWeapon
// The weapon name may have a MF_DROPPED flag ored in.
//
boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropped) {
boolean gaveammo;
boolean gaveweapon;
if (netgame && (deathmatch != 2) && !dropped) {
// leave placed weapons forever on net games
if (player->weaponowned[weapon])
return false;
player->bonuscount += BONUSADD;
player->weaponowned[weapon] = true;
if (deathmatch)
P_GiveAmmo(player, weaponinfo[weapon].ammo, 5);
else
P_GiveAmmo(player, weaponinfo[weapon].ammo, 2);
player->pendingweapon = weapon;
if (player == &players[consoleplayer])
S_StartSound(NULL, sfx_wpnup);
return false;
}
if (weaponinfo[weapon].ammo != am_noammo) {
// give one clip with a dropped weapon,
// two clips with a found weapon
if (dropped)
gaveammo = P_GiveAmmo(player, weaponinfo[weapon].ammo, 1);
else
gaveammo = P_GiveAmmo(player, weaponinfo[weapon].ammo, 2);
} else
gaveammo = false;
if (player->weaponowned[weapon])
gaveweapon = false;
else {
gaveweapon = true;
player->weaponowned[weapon] = true;
player->pendingweapon = weapon;
}
return (gaveweapon || gaveammo);
}
//
// P_GiveBody
// Returns false if the body isn't needed at all
//
boolean P_GiveBody(player_t *player, int num) {
if (player->health >= MAXHEALTH)
return false;
player->health += num;
if (player->health > MAXHEALTH)
player->health = MAXHEALTH;
player->mo->health = player->health;
return true;
}
//
// P_GiveArmor
// Returns false if the armor is worse
// than the current armor.
//
boolean P_GiveArmor(player_t *player, int armortype) {
int hits;
hits = armortype * 100;
if (player->armorpoints >= hits)
return false; // don't pick up
player->armortype = armortype;
player->armorpoints = hits;
return true;
}
//
// P_GiveCard
//
void P_GiveCard(player_t *player, card_t card) {
if (player->cards[card])
return;
player->bonuscount = BONUSADD;
player->cards[card] = 1;
}
//
// P_GivePower
//
boolean P_GivePower(player_t *player, int /*powertype_t*/ power) {
if (power == pw_invulnerability) {
player->powers[power] = INVULNTICS;
return true;
}
if (power == pw_invisibility) {
player->powers[power] = INVISTICS;
player->mo->flags |= MF_SHADOW;
return true;
}
if (power == pw_infrared) {
player->powers[power] = INFRATICS;
return true;
}
if (power == pw_ironfeet) {
player->powers[power] = IRONTICS;
return true;
}
if (power == pw_strength) {
P_GiveBody(player, 100);
player->powers[power] = 1;
return true;
}
if (player->powers[power])
return false; // already got it
player->powers[power] = 1;
return true;
}
//
// P_TouchSpecialThing
//
void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) {
player_t *player;
int i;
fixed_t delta;
int sound;
delta = special->z - toucher->z;
if (delta > toucher->height || delta < -8 * FRACUNIT) {
// out of reach
return;
}
sound = sfx_itemup;
player = toucher->player;
// Dead thing touching.
// Can happen with a sliding player corpse.
if (toucher->health <= 0)
return;
// Identify by sprite.
switch (special->sprite) {
// armor
case SPR_ARM1:
// TODO: replace 1 below with GREEN armor constant
if (!P_GiveArmor(player, 1))
return;
player->message = (GOTARMOR);
break;
case SPR_ARM2:
// TODO: replace 2 below with BLUE armor constant
if (!P_GiveArmor(player, 2))
return;
player->message = (GOTMEGA);
break;
// bonus items
case SPR_BON1:
player->health++; // can go over 100%
// TODO: replace 200 below with a constant
if (player->health > 200)
player->health = 200;
player->mo->health = player->health;
player->message = (GOTHTHBONUS);
break;
case SPR_BON2:
player->armorpoints++; // can go over 100%
if (player->armorpoints > 200)
player->armorpoints = 200;
if (!player->armortype)
player->armortype = 1;
player->message = (GOTARMBONUS);
break;
case SPR_SOUL:
player->health += 100;
if (player->health > 100)
player->health = 100;
player->mo->health = player->health;
player->message = (GOTSUPER);
sound = sfx_getpow;
break;
case SPR_MEGA:
if (gamemode != commercial)
return;
player->health = 200;
player->mo->health = player->health;
P_GiveArmor(player, 2);
player->message = (GOTMSPHERE);
sound = sfx_getpow;
break;
// cards
// leave cards for everyone
case SPR_BKEY:
if (!player->cards[it_bluecard])
player->message = (GOTBLUECARD);
P_GiveCard(player, it_bluecard);
if (!netgame)
break;
return;
case SPR_YKEY:
if (!player->cards[it_yellowcard])
player->message = (GOTYELWCARD);
P_GiveCard(player, it_yellowcard);
if (!netgame)
break;
return;
case SPR_RKEY:
if (!player->cards[it_redcard])
player->message = (GOTREDCARD);
P_GiveCard(player, it_redcard);
if (!netgame)
break;
return;
case SPR_BSKU:
if (!player->cards[it_blueskull])
player->message = (GOTBLUESKUL);
P_GiveCard(player, it_blueskull);
if (!netgame)
break;
return;
case SPR_YSKU:
if (!player->cards[it_yellowskull])
player->message = (GOTYELWSKUL);
P_GiveCard(player, it_yellowskull);
if (!netgame)
break;
return;
case SPR_RSKU:
if (!player->cards[it_redskull])
player->message = (GOTREDSKULL);
P_GiveCard(player, it_redskull);
if (!netgame)
break;
return;
// medikits, heals
case SPR_STIM:
if (!P_GiveBody(player, 10))
return;
player->message = (GOTSTIM);
break;
case SPR_MEDI:
if (!P_GiveBody(player, 25))
return;
if (player->health < 25)
player->message = (GOTMEDINEED);
else
player->message = (GOTMEDIKIT);
break;
// power ups
case SPR_PINV:
if (!P_GivePower(player, pw_invulnerability))
return;
player->message = (GOTINVUL);
sound = sfx_getpow;
break;
case SPR_PSTR:
if (!P_GivePower(player, pw_strength))
return;
player->message = (GOTBERSERK);
if (player->readyweapon != wp_fist)
player->pendingweapon = wp_fist;
sound = sfx_getpow;
break;
case SPR_PINS:
if (!P_GivePower(player, pw_invisibility))
return;
player->message = (GOTINVIS);
sound = sfx_getpow;
break;
case SPR_SUIT:
if (!P_GivePower(player, pw_ironfeet))
return;
player->message = (GOTSUIT);
sound = sfx_getpow;
break;
case SPR_PMAP:
if (!P_GivePower(player, pw_allmap))
return;
player->message = (GOTMAP);
sound = sfx_getpow;
break;
case SPR_PVIS:
if (!P_GivePower(player, pw_infrared))
return;
player->message = (GOTVISOR);
sound = sfx_getpow;
break;
// ammo
case SPR_CLIP:
if (special->flags & MF_DROPPED) {
if (!P_GiveAmmo(player, am_clip, 0))
return;
} else {
if (!P_GiveAmmo(player, am_clip, 1))
return;
}
player->message = (GOTCLIP);
break;
case SPR_AMMO:
if (!P_GiveAmmo(player, am_clip, 5))
return;
player->message = (GOTCLIPBOX);
break;
case SPR_ROCK:
if (!P_GiveAmmo(player, am_misl, 1))
return;
player->message = (GOTROCKET);
break;
case SPR_BROK:
if (!P_GiveAmmo(player, am_misl, 5))
return;
player->message = (GOTROCKBOX);
break;
case SPR_CELL:
if (!P_GiveAmmo(player, am_cell, 1))
return;
player->message = (GOTCELL);
break;
case SPR_CELP:
if (!P_GiveAmmo(player, am_cell, 5))
return;
player->message = (GOTCELLBOX);
break;
case SPR_SHEL:
if (!P_GiveAmmo(player, am_shell, 1))
return;
player->message = (GOTSHELLS);
break;
case SPR_SBOX:
if (!P_GiveAmmo(player, am_shell, 5))
return;
player->message = (GOTSHELLBOX);
break;
case SPR_BPAK:
if (!player->backpack) {
for (i = 0; i < NUMAMMO; i++)
player->maxammo[i] *= 2;
player->backpack = true;
}
for (i = 0; i < NUMAMMO; i++)
P_GiveAmmo(player, i, 1);
player->message = (GOTBACKPACK);
break;
// weapons
case SPR_BFUG:
if (!P_GiveWeapon(player, wp_bfg, false))
return;
player->message = (GOTBFG9000);
sound = sfx_wpnup;
break;
case SPR_MGUN:
if (!P_GiveWeapon(player, wp_chaingun, (special->flags & MF_DROPPED) != 0))
return;
player->message = (GOTCHAINGUN);
sound = sfx_wpnup;
break;
case SPR_CSAW:
if (!P_GiveWeapon(player, wp_chainsaw, false))
return;
player->message = (GOTCHAINSAW);
sound = sfx_wpnup;
break;
case SPR_LAUN:
if (!P_GiveWeapon(player, wp_missile, false))
return;
player->message = (GOTLAUNCHER);
sound = sfx_wpnup;
break;
case SPR_PLAS:
if (!P_GiveWeapon(player, wp_plasma, false))
return;
player->message = (GOTPLASMA);
sound = sfx_wpnup;
break;
case SPR_SHOT:
if (!P_GiveWeapon(player, wp_shotgun, (special->flags & MF_DROPPED) != 0))
return;
player->message = (GOTSHOTGUN);
sound = sfx_wpnup;
break;
case SPR_SGN2:
if (!P_GiveWeapon(player, wp_supershotgun,
(special->flags & MF_DROPPED) != 0))
return;
player->message = (GOTSHOTGUN2);
sound = sfx_wpnup;
break;
default:
I_Error("P_SpecialThing: Unknown gettable thing");
}
if (special->flags & MF_COUNTITEM)
player->itemcount++;
P_RemoveMobj(special);
player->bonuscount += BONUSADD;
if (player == &players[consoleplayer])
S_StartSound(NULL, sound);
}
//
// KillMobj
//
void P_KillMobj(mobj_t *source, mobj_t *target) {
mobjtype_t item;
mobj_t *mo;
target->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY);
if (target->type != MT_SKULL)
target->flags &= ~MF_NOGRAVITY;
target->flags |= MF_CORPSE | MF_DROPOFF;
target->height >>= 2;
if (source && source->player) {
// count for intermission
if (target->flags & MF_COUNTKILL)
source->player->killcount++;
if (target->player)
source->player->frags[target->player - players]++;
} else if (!netgame && (target->flags & MF_COUNTKILL)) {
// count all monster deaths,
// even those caused by other monsters
players[0].killcount++;
}
if (target->player) {
// count environment kills against you
if (!source)
target->player->frags[target->player - players]++;
target->flags &= ~MF_SOLID;
target->player->playerstate = PST_DEAD;
P_DropWeapon(target->player);
if (target->player == &players[consoleplayer] && automapactive) {
// don't die in auto map,
// switch view prior to dying
AM_Stop();
}
}
if (target->health < -target->info->spawnhealth &&
target->info->xdeathstate) {
P_SetMobjState(target, target->info->xdeathstate);
} else
P_SetMobjState(target, target->info->deathstate);
target->tics -= P_Random() & 3;
if (target->tics < 1)
target->tics = 1;
// I_StartSound (&actor->r, actor->info->deathsound);
// In Chex Quest, monsters don't drop items.
if (gameversion == exe_chex) {
return;
}
// Drop stuff.
// This determines the kind of object spawned
// during the death frame of a thing.
switch (target->type) {
case MT_WOLFSS:
case MT_POSSESSED:
item = MT_CLIP;
break;
case MT_SHOTGUY:
item = MT_SHOTGUN;
break;
case MT_CHAINGUY:
item = MT_CHAINGUN;
break;
default:
return;
}
mo = P_SpawnMobj(target->x, target->y, ONFLOORZ, item);
mo->flags |= MF_DROPPED; // special versions of items
}
//
// P_DamageMobj
// Damages both enemies and players
// "inflictor" is the thing that caused the damage
// creature or missile, can be NULL (slime, etc)
// "source" is the thing to target after taking damage
// creature or NULL
// Source and inflictor are the same for melee attacks.
// Source can be NULL for slime, barrel explosions
// and other environmental stuff.
//
void P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source,
int damage) {
unsigned ang;
int saved;
player_t *player;
fixed_t thrust;
int temp;
if (!(target->flags & MF_SHOOTABLE))
return; // shouldn't happen...
if (target->health <= 0)
return;
if (target->flags & MF_SKULLFLY) {
target->momx = target->momy = target->momz = 0;
}
player = target->player;
if (player && gameskill == sk_baby)
damage >>= 1; // take half damage in trainer mode
// Some close combat weapons should not
// inflict thrust and push the victim out of reach,
// thus kick away unless using the chainsaw.
if (inflictor && !(target->flags & MF_NOCLIP) &&
(!source || !source->player ||
source->player->readyweapon != wp_chainsaw)) {
ang = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y);
thrust = damage * (FRACUNIT >> 3) * 100 / target->info->mass;
// make fall forwards sometimes
if (damage < 40 && damage > target->health &&
target->z - inflictor->z > 64 * FRACUNIT && (P_Random() & 1)) {
ang += ANG180;
thrust *= 4;
}
ang >>= ANGLETOFINESHIFT;
target->momx += FixedMul(thrust, finecosine[ang]);
target->momy += FixedMul(thrust, finesine[ang]);
}
// player specific
if (player) {
// end of game hell hack
if (target->subsector->sector->special == 11 && damage >= target->health) {
damage = target->health - 1;
}
// Below certain threshold,
// ignore damage in GOD mode, or with INVUL power.
if (damage < 1000 &&
((player->cheats & CF_GODMODE) || player->powers[pw_invulnerability])) {
return;
}
if (player->armortype) {
if (player->armortype == 1)
saved = damage / 3;
else
saved = damage / 2;
if (player->armorpoints <= saved) {
// armor is used up
saved = player->armorpoints;
player->armortype = 0;
}
player->armorpoints -= saved;
damage -= saved;
}
player->health -= damage; // mirror mobj health here for Dave
if (player->health < 0)
player->health = 0;
player->attacker = source;
player->damagecount += damage; // add damage after armor / invuln
if (player->damagecount > 100)
player->damagecount = 100; // teleport stomp does 10k points...
temp = damage < 100 ? damage : 100;
if (player == &players[consoleplayer])
I_Tactile(40, 10, 40 + temp * 2);
}
// do the damage
target->health -= damage;
if (target->health <= 0) {
P_KillMobj(source, target);
return;
}
if ((P_Random() < target->info->painchance) &&
!(target->flags & MF_SKULLFLY)) {
target->flags |= MF_JUSTHIT; // fight back!
P_SetMobjState(target, target->info->painstate);
}
target->reactiontime = 0; // we're awake now...
if ((!target->threshold || target->type == MT_VILE) && source &&
source != target && source->type != MT_VILE) {
// if not intent on another player,
// chase after this one
target->target = source;
target->threshold = BASETHRESHOLD;
if (target->state == &states[target->info->spawnstate] &&
target->info->seestate != S_NULL)
P_SetMobjState(target, target->info->seestate);
}
}