ctf: Add game monsters

This commit is contained in:
Denis Pauk 2023-11-02 00:38:33 +02:00
parent 2fe5162add
commit d68c31e0e4
11 changed files with 2213 additions and 82 deletions

View file

@ -1480,8 +1480,6 @@ CTF_OBJS_ = \
src/common/shared/flash.o \
src/common/shared/rand.o \
src/common/shared/shared.o \
src/game/g_newai.o \
src/game/g_newtrig.o \
src/ctf/g_ai.o \
src/ctf/g_chase.o \
src/ctf/g_cmds.o \
@ -1489,11 +1487,18 @@ CTF_OBJS_ = \
src/game/g_ctf.o \
src/ctf/g_func.o \
src/ctf/g_items.o \
src/game/g_newai.o \
src/game/g_newdm.o \
src/game/g_newfnc.o \
src/game/g_newtarg.o \
src/game/g_newtrig.o \
src/game/g_newweap.o \
src/ctf/g_main.o \
src/ctf/g_misc.o \
src/ctf/g_monster.o \
src/ctf/g_phys.o \
src/ctf/g_save.o \
src/game/g_sphere.o \
src/ctf/g_spawn.o \
src/ctf/g_svcmds.o \
src/ctf/g_target.o \
@ -1501,7 +1506,39 @@ CTF_OBJS_ = \
src/game/g_utils.o \
src/game/g_weapon.o \
src/game/menu/menu.o \
src/game/dm/ball.o \
src/game/dm/tag.o \
src/game/monster/berserker/berserker.o \
src/game/monster/boss2/boss2.o \
src/game/monster/boss3/boss3.o \
src/game/monster/boss3/boss31.o \
src/game/monster/boss3/boss32.o \
src/game/monster/boss5/boss5.o \
src/game/monster/brain/brain.o \
src/game/monster/carrier/carrier.o \
src/game/monster/chick/chick.o \
src/game/monster/fixbot/fixbot.o \
src/game/monster/flipper/flipper.o \
src/game/monster/float/float.o \
src/game/monster/flyer/flyer.o \
src/game/monster/gekk/gekk.o \
src/game/monster/gladiator/gladb.o \
src/game/monster/gladiator/gladiator.o \
src/game/monster/gunner/gunner.o \
src/game/monster/hover/hover.o \
src/game/monster/infantry/infantry.o \
src/game/monster/insane/insane.o \
src/game/monster/medic/medic.o \
src/game/monster/misc/move.o \
src/game/monster/mutant/mutant.o \
src/game/monster/parasite/parasite.o \
src/game/monster/soldier/soldier.o \
src/game/monster/stalker/stalker.o \
src/game/monster/supertank/supertank.o \
src/game/monster/tank/tank.o \
src/game/monster/turret/turret.o \
src/game/monster/widow/widow2.o \
src/game/monster/widow/widow.o \
src/ctf/player/client.o \
src/ctf/player/hud.o \
src/ctf/player/trail.o \

View file

@ -811,3 +811,178 @@ T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
}
}
void
T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage,
edict_t *ignore, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
float len;
float killzone, killzone2;
trace_t tr;
float dist;
killzone = radius;
killzone2 = radius * 2.0;
if (!inflictor || !attacker || !ignore)
{
return;
}
while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL)
{
/* ignore nobody */
if (ent == ignore)
{
continue;
}
if (!ent->takedamage)
{
continue;
}
if (!ent->inuse)
{
continue;
}
if (!(ent->client || (ent->svflags & SVF_MONSTER) ||
(ent->svflags & SVF_DAMAGEABLE)))
{
continue;
}
VectorAdd(ent->mins, ent->maxs, v);
VectorMA(ent->s.origin, 0.5, v, v);
VectorSubtract(inflictor->s.origin, v, v);
len = VectorLength(v);
if (len <= killzone)
{
if (ent->client)
{
ent->flags |= FL_NOGIB;
}
points = 10000;
}
else if (len <= killzone2)
{
points = (damage / killzone) * (killzone2 - len);
}
else
{
points = 0;
}
if (points > 0)
{
if (ent->client)
{
ent->client->nuke_framenum = level.framenum + 20;
}
VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
mod);
}
}
/* skip the worldspawn */
ent = g_edicts + 1;
/* cycle through players */
while (ent)
{
if ((ent->client) &&
(ent->client->nuke_framenum != level.framenum + 20) && (ent->inuse))
{
tr = gi.trace(inflictor->s.origin, NULL, NULL, ent->s.origin,
inflictor, MASK_SOLID);
if (tr.fraction == 1.0)
{
ent->client->nuke_framenum = level.framenum + 20;
}
else
{
dist = realrange(ent, inflictor);
if (dist < 2048)
{
ent->client->nuke_framenum = max(ent->client->nuke_framenum,
level.framenum + 15);
}
else
{
ent->client->nuke_framenum = max(ent->client->nuke_framenum,
level.framenum + 10);
}
}
ent++;
}
else
{
ent = NULL;
}
}
}
/*
* Like T_RadiusDamage, but ignores
* anything with classname=ignoreClass
*/
void
T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage,
char *ignoreClass, float radius, int mod)
{
float points;
edict_t *ent = NULL;
vec3_t v;
vec3_t dir;
if (!inflictor || !attacker || !ignoreClass)
{
return;
}
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
{
if (ent->classname && !strcmp(ent->classname, ignoreClass))
{
continue;
}
if (!ent->takedamage)
{
continue;
}
VectorAdd(ent->mins, ent->maxs, v);
VectorMA(ent->s.origin, 0.5, v, v);
VectorSubtract(inflictor->s.origin, v, v);
points = damage - 0.5 * VectorLength(v);
if (ent == attacker)
{
points = points * 0.5;
}
if (points > 0)
{
if (CanDamage(ent, inflictor))
{
VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
mod);
}
}
}
}

View file

@ -27,8 +27,12 @@
#include "header/local.h"
#define HEALTH_IGNORE_MAX 1
#define HEALTH_TIMED 2
qboolean Pickup_Weapon(edict_t *ent, edict_t *other);
void Use_Weapon(edict_t *ent, gitem_t *inv);
void Use_Weapon2(edict_t *ent, gitem_t *inv);
void Drop_Weapon(edict_t *ent, gitem_t *inv);
void Weapon_Blaster(edict_t *ent);
@ -42,10 +46,21 @@ void Weapon_Grenade(edict_t *ent);
void Weapon_GrenadeLauncher(edict_t *ent);
void Weapon_Railgun(edict_t *ent);
void Weapon_BFG(edict_t *ent);
void Weapon_ChainFist(edict_t *ent);
void Weapon_Disintegrator(edict_t *ent);
void Weapon_ETF_Rifle(edict_t *ent);
void Weapon_Heatbeam(edict_t *ent);
void Weapon_Prox(edict_t *ent);
void Weapon_Tesla(edict_t *ent);
void Weapon_ProxLauncher(edict_t *ent);
gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
void Weapon_Ionripper(edict_t *ent);
void Weapon_Phalanx(edict_t *ent);
void Weapon_Trap(edict_t *ent);
static gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
static gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
static gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
static int jacket_armor_index;
static int combat_armor_index;
@ -57,6 +72,7 @@ static int power_shield_index;
#define HEALTH_TIMED 2
void Use_Quad(edict_t *ent, gitem_t *item);
void Use_QuadFire(edict_t *ent, gitem_t *item);
static int quad_drop_timeout_hack;
@ -1503,13 +1519,14 @@ SpawnItem(edict_t *ent, gitem_t *item)
/* ====================================================================== */
gitem_t itemlist[] = {
static const gitem_t gameitemlist[] = {
{
NULL
},
}, /* leave index 0 alone */
/*
* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
{
"item_armor_body",
@ -2675,12 +2692,19 @@ gitem_t itemlist[] = {
{NULL}
};
gitem_t itemlist[MAX_ITEMS];
/*
* QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
void
SP_item_health(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
{
G_FreeEdict(self);
@ -2694,11 +2718,16 @@ SP_item_health(edict_t *self)
}
/*
* QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
void
SP_item_health_small(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
{
G_FreeEdict(self);
@ -2713,11 +2742,16 @@ SP_item_health_small(edict_t *self)
}
/*
* QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
void
SP_item_health_large(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
{
G_FreeEdict(self);
@ -2731,11 +2765,16 @@ SP_item_health_large(edict_t *self)
}
/*
* QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16)
* QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
*/
void
SP_item_health_mega(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
{
G_FreeEdict(self);
@ -2774,7 +2813,9 @@ SP_item_foodcube(edict_t *self)
void
InitItems(void)
{
game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1;
memset(itemlist, 0, sizeof(itemlist));
memcpy(itemlist, gameitemlist, sizeof(gameitemlist));
game.num_items = sizeof(gameitemlist) / sizeof(gameitemlist[0]) - 1;
}
/*
@ -2799,3 +2840,72 @@ SetItemNames(void)
power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
}
void
SP_xatrix_item(edict_t *self)
{
gitem_t *item;
int i;
char *spawnClass = NULL;
if (!self)
{
return;
}
if (!self->classname)
{
return;
}
if (!strcmp(self->classname, "ammo_magslug"))
{
spawnClass = "ammo_flechettes";
}
else if (!strcmp(self->classname, "ammo_trap"))
{
spawnClass = "weapon_proxlauncher";
}
else if (!strcmp(self->classname, "item_quadfire"))
{
float chance;
chance = random();
if (chance < 0.2)
{
spawnClass = "item_sphere_hunter";
}
else if (chance < 0.6)
{
spawnClass = "item_sphere_vengeance";
}
else
{
spawnClass = "item_sphere_defender";
}
}
else if (!strcmp(self->classname, "weapon_boomer"))
{
spawnClass = "weapon_etf_rifle";
}
else if (!strcmp(self->classname, "weapon_phalanx"))
{
spawnClass = "weapon_plasmabeam";
}
/* check item spawn functions */
for (i = 0, item = itemlist; i < game.num_items; i++, item++)
{
if (!item->classname)
{
continue;
}
if (!strcmp(item->classname, spawnClass))
{
/* found it */
SpawnItem(self, item);
return;
}
}
}

View file

@ -1,12 +1,37 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
* Licensed under the GNU General Public License 2.0.
*/
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* Miscellaneos entities, functs and functions.
*
* =======================================================================
*/
#include "header/local.h"
int debristhisframe;
int gibsthisframe;
extern void M_WorldEffects(edict_t *ent);
/*
* QUAKED func_group (0 0 0) ?
* Used to group brushes together just for editor convenience.
@ -15,8 +40,13 @@ int gibsthisframe;
/* ===================================================== */
void
Use_Areaportal(edict_t *ent, edict_t *other, edict_t *activator)
Use_Areaportal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!ent)
{
return;
}
ent->count ^= 1; /* toggle state */
gi.SetAreaPortalState(ent->style, ent->count);
}
@ -31,8 +61,13 @@ Use_Areaportal(edict_t *ent, edict_t *other, edict_t *activator)
void
SP_func_areaportal(edict_t *ent)
{
if (!ent)
{
return;
}
ent->use = Use_Areaportal;
ent->count = 0; /* allways start closed; */
ent->count = 0; /* always start closed; */
}
/* ===================================================== */
@ -57,6 +92,11 @@ VelocityForDamage(int damage, vec3_t v)
void
ClipGibVelocity(edict_t *ent)
{
if (!ent)
{
return;
}
if (ent->velocity[0] < -300)
{
ent->velocity[0] = -300;
@ -85,9 +125,16 @@ ClipGibVelocity(edict_t *ent)
}
}
/* ===================================================== */
void
gib_think(edict_t *self)
{
if (!self)
{
return;
}
self->s.frame++;
self->nextthink = level.time + FRAMETIME;
@ -99,10 +146,15 @@ gib_think(edict_t *self)
}
void
gib_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane, csurface_t *surf /* unused */)
{
vec3_t normal_angles, right;
if (!self)
{
return;
}
if (!self->groundentity)
{
return;
@ -112,7 +164,8 @@ gib_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
if (plane)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0);
gi.sound(self, CHAN_VOICE, gi.soundindex(
"misc/fhit3.wav"), 1, ATTN_NORM, 0);
vectoangles(plane->normal, normal_angles);
AngleVectors(normal_angles, NULL, right, NULL);
@ -128,9 +181,14 @@ gib_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
}
void
gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
int damage, vec3_t point)
gib_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
int damage /* unused */, vec3_t point /* unused */)
{
if (!self)
{
return;
}
G_FreeEdict(self);
}
@ -237,13 +295,141 @@ ThrowHead(edict_t *self, char *gibname, int damage, int type)
gi.linkentity(self);
}
void
ThrowGibACID(edict_t *self, char *gibname, int damage, int type)
{
edict_t *gib;
vec3_t vd;
vec3_t origin;
vec3_t size;
float vscale;
if (!self || !gibname)
{
return;
}
gibsthisframe++;
if (gibsthisframe > MAX_GIBS)
{
return;
}
gib = G_Spawn();
VectorScale(self->size, 0.5, size);
VectorAdd(self->absmin, size, origin);
gib->s.origin[0] = origin[0] + crandom() * size[0];
gib->s.origin[1] = origin[1] + crandom() * size[1];
gib->s.origin[2] = origin[2] + crandom() * size[2];
/* gi.setmodel (gib, gibname); */
gib->s.modelindex = gi.modelindex(gibname);
gib->clipmask = MASK_SHOT;
gib->solid = SOLID_BBOX;
gib->s.effects |= EF_GREENGIB;
/* note to self check this */
gib->s.renderfx |= RF_FULLBRIGHT;
gib->flags |= FL_NO_KNOCKBACK;
gib->takedamage = DAMAGE_YES;
gib->die = gib_die;
gib->dmg = 2;
gib->health = 250;
if (type == GIB_ORGANIC)
{
gib->movetype = MOVETYPE_TOSS;
vscale = 3.0;
}
else
{
gib->movetype = MOVETYPE_BOUNCE;
vscale = 1.0;
}
VelocityForDamage(damage, vd);
VectorMA(self->velocity, vscale, vd, gib->velocity);
ClipGibVelocity(gib);
gib->avelocity[0] = random() * 600;
gib->avelocity[1] = random() * 600;
gib->avelocity[2] = random() * 600;
gib->think = G_FreeEdict;
gib->nextthink = level.time + 10 + random() * 10;
gi.linkentity(gib);
}
void
ThrowHeadACID(edict_t *self, char *gibname, int damage, int type)
{
vec3_t vd;
float vscale;
if (!self || !gibname)
{
return;
}
self->s.skinnum = 0;
self->s.frame = 0;
VectorClear(self->mins);
VectorClear(self->maxs);
self->s.modelindex2 = 0;
gi.setmodel(self, gibname);
self->clipmask = MASK_SHOT;
self->solid = SOLID_BBOX;
self->s.effects |= EF_GREENGIB;
self->s.effects &= ~EF_FLIES;
self->s.effects |= RF_FULLBRIGHT;
self->s.sound = 0;
self->flags |= FL_NO_KNOCKBACK;
self->svflags &= ~SVF_MONSTER;
self->takedamage = DAMAGE_YES;
self->die = gib_die;
self->dmg = 2;
if (type == GIB_ORGANIC)
{
self->movetype = MOVETYPE_TOSS;
vscale = 0.5;
}
else
{
self->movetype = MOVETYPE_BOUNCE;
vscale = 1.0;
}
VelocityForDamage(damage, vd);
VectorMA(self->velocity, vscale, vd, self->velocity);
ClipGibVelocity(self);
self->avelocity[YAW] = crandom() * 600;
self->think = G_FreeEdict;
self->nextthink = level.time + 10 + random() * 10;
gi.linkentity(self);
}
void
ThrowClientHead(edict_t *self, int damage)
{
vec3_t vd;
char *gibname;
if (rand() & 1)
if (!self)
{
return;
}
if (randk() & 1)
{
gibname = "models/objects/gibs/head2/tris.md2";
self->s.skinnum = 1; /* second skin is player */
@ -275,14 +461,26 @@ ThrowClientHead(edict_t *self, int damage)
self->client->anim_priority = ANIM_DEATH;
self->client->anim_end = self->s.frame;
}
else
{
self->think = NULL;
self->nextthink = 0;
}
gi.linkentity(self);
}
/* ===================================================== */
void
debris_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
int damage, vec3_t point)
debris_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
int damage /* unused */, vec3_t point /* unused */)
{
if (!self)
{
return;
}
G_FreeEdict(self);
}
@ -325,6 +523,11 @@ ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin)
void
BecomeExplosion1(edict_t *self)
{
if (!self)
{
return;
}
/* flags are important */
if (strcmp(self->classname, "item_flag_team1") == 0)
{
@ -360,6 +563,11 @@ BecomeExplosion1(edict_t *self)
void
BecomeExplosion2(edict_t *self)
{
if (!self)
{
return;
}
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION2);
gi.WritePosition(self->s.origin);
@ -367,21 +575,27 @@ BecomeExplosion2(edict_t *self)
G_FreeEdict(self);
}
/* ===================================================== */
/*
* QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
*
* Target: next path corner
* Pathtarget: gets used when an entity that has
* this path_corner targeted touches it
*/
void
path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane,
csurface_t *surf)
path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */)
{
vec3_t v;
edict_t *next;
if (!self || !other)
{
return;
}
if (other->movetarget != self)
{
return;
@ -418,6 +632,7 @@ path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane,
v[2] -= other->mins[2];
VectorCopy(v, other->s.origin);
next = G_PickTarget(next->target);
other->s.event = EV_OTHER_TELEPORT;
}
other->goalentity = other->movetarget = next;
@ -444,6 +659,11 @@ path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane,
void
SP_path_corner(edict_t *self)
{
if (!self)
{
return;
}
if (!self->targetname)
{
gi.dprintf("path_corner with no targetname at %s\n",
@ -460,18 +680,26 @@ SP_path_corner(edict_t *self)
gi.linkentity(self);
}
/* ===================================================== */
/*
* QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
*
* Makes this the target of a monster and it will head here
* when first activated before going after the activator. If
* hold is selected, it will stay here.
*/
void
point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane,
csurface_t *surf)
point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
csurface_t *surf /* unused */)
{
edict_t *activator;
if (!self || !other)
{
return;
}
if (other->movetarget != self)
{
return;
@ -484,8 +712,10 @@ point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane,
if (!other->goalentity)
{
gi.dprintf("%s at %s target %s does not exist\n", self->classname,
vtos(self->s.origin), self->target);
gi.dprintf("%s at %s target %s does not exist\n",
self->classname,
vtos(self->s.origin),
self->target);
other->movetarget = self;
}
@ -538,6 +768,11 @@ point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane,
void
SP_point_combat(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value)
{
G_FreeEdict(self);
@ -552,8 +787,11 @@ SP_point_combat(edict_t *self)
gi.linkentity(self);
}
/* ===================================================== */
/*
* QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
*
* Just for the debugging level. Don't use
*/
static int robotron[4];
@ -577,6 +815,11 @@ TH_viewthing(edict_t *ent)
void
SP_viewthing(edict_t *ent)
{
if (!ent)
{
return;
}
gi.dprintf("viewthing spawned\n");
ent->movetype = MOVETYPE_NONE;
@ -591,41 +834,60 @@ SP_viewthing(edict_t *ent)
return;
}
/* ===================================================== */
/*
* QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
*
* Used as a positional target for spotlights, etc.
*/
void
SP_info_null(edict_t *self)
{
if (!self)
{
return;
}
G_FreeEdict(self);
}
/*
* QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
* Used as a positional target for lightning.
*
* Used as a positional target for lighting.
*/
void
SP_info_notnull(edict_t *self)
{
if (!self)
{
return;
}
VectorCopy(self->s.origin, self->absmin);
VectorCopy(self->s.origin, self->absmax);
}
#define START_OFF 1
/*
* QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
*
* Non-displayed light.
* Default light value is 300.
* Default style is 0.
* If targeted, will toggle between on and off.
* Default _cone value is 10 (used to set size of light for spotlights)
*/
#define START_OFF 1
void
light_use(edict_t *self, edict_t *other, edict_t *activator)
light_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
{
if (!self)
{
return;
}
if (self->spawnflags & START_OFF)
{
gi.configstring(CS_LIGHTS + self->style, "m");
@ -641,6 +903,11 @@ light_use(edict_t *self, edict_t *other, edict_t *activator)
void
SP_light(edict_t *self)
{
if (!self)
{
return;
}
/* no targeted lights in deathmatch, because they cause global messages */
if (!self->targetname || deathmatch->value)
{
@ -663,6 +930,8 @@ SP_light(edict_t *self)
}
}
/* ===================================================== */
/*
* QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
* This is just a solid wall if not inhibited

View file

@ -1,5 +1,6 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* 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
@ -27,12 +28,19 @@
#include "header/local.h"
/* monster weapons */
void monster_start_go(edict_t *self);
/* Monster weapons */
void
monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
int kick, int hspread, int vspread, int flashtype)
{
if (!self)
{
return;
}
fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
gi.WriteByte(svc_muzzleflash2);
@ -45,6 +53,11 @@ void
monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
int kick, int hspread, int vspread, int count, int flashtype)
{
if (!self)
{
return;
}
fire_shotgun(self, start, aimdir, damage, kick, hspread,
vspread, count, MOD_UNKNOWN);
@ -55,9 +68,14 @@ monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
}
void
monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir,
int damage, int speed, int flashtype, int effect)
monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, int flashtype, int effect)
{
if (!self)
{
return;
}
fire_blaster(self, start, dir, damage, speed, effect, false);
gi.WriteByte(svc_muzzleflash2);
@ -66,10 +84,251 @@ monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir,
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, int flashtype, int effect)
{
if (!self)
{
return;
}
fire_blueblaster(self, start, dir, damage, speed, effect);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(MZ_BLUEHYPERBLASTER);
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, int flashtype, int effect)
{
if (!self)
{
return;
}
fire_blaster2(self, start, dir, damage, speed, effect, false);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(flashtype);
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, int flashtype, int effect)
{
if (!self)
{
return;
}
fire_ionripper(self, start, dir, damage, speed, effect);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(flashtype);
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, int flashtype)
{
if (!self)
{
return;
}
fire_heat(self, start, dir, damage, speed, damage, damage);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(flashtype);
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage,
int speed, edict_t *enemy, int flashtype)
{
if (!self || !enemy)
{
return;
}
fire_tracker(self, start, dir, damage, speed, enemy);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(flashtype);
gi.multicast(start, MULTICAST_PVS);
}
void
monster_fire_heatbeam(edict_t *self, vec3_t start, vec3_t dir, vec3_t offset,
int damage, int kick, int flashtype)
{
if (!self)
{
return;
}
fire_heatbeam(self, start, dir, offset, damage, kick, true);
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
gi.WriteByte(flashtype);
gi.multicast(start, MULTICAST_PVS);
}
void
dabeam_hit(edict_t *self)
{
edict_t *ignore;
vec3_t start;
vec3_t end;
trace_t tr;
if (!self)
{
return;
}
ignore = self;
VectorCopy(self->s.origin, start);
VectorMA(start, 2048, self->movedir, end);
while (1)
{
tr = gi.trace(start, NULL, NULL, end, ignore,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
if (!tr.ent)
{
break;
}
/* hurt it if we can */
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) &&
(tr.ent != self->owner))
{
T_Damage(tr.ent, self, self->owner, self->movedir, tr.endpos,
vec3_origin, self->dmg, skill->value, DAMAGE_ENERGY,
MOD_TARGET_LASER);
}
if (self->dmg < 0) /* healer ray */
{
/* when player is at 100 health
just undo health fix */
if (tr.ent->client && (tr.ent->health > 100))
{
tr.ent->health += self->dmg;
}
}
/* if we hit something that's not a monster or
player or is immune to lasers, we're done */
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
{
if (self->spawnflags & 0x80000000)
{
self->spawnflags &= ~0x80000000;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_LASER_SPARKS);
gi.WriteByte(10);
gi.WritePosition(tr.endpos);
gi.WriteDir(tr.plane.normal);
gi.WriteByte(self->s.skinnum);
gi.multicast(tr.endpos, MULTICAST_PVS);
}
break;
}
ignore = tr.ent;
VectorCopy(tr.endpos, start);
}
VectorCopy(tr.endpos, self->s.old_origin);
self->nextthink = level.time + 0.1;
self->think = G_FreeEdict;
}
void
monster_dabeam(edict_t *self)
{
vec3_t last_movedir;
vec3_t point;
if (!self)
{
return;
}
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
self->s.modelindex = 1;
self->s.frame = 2;
if (self->owner->monsterinfo.aiflags & AI_MEDIC)
{
self->s.skinnum = 0xf3f3f1f1;
}
else
{
self->s.skinnum = 0xf2f2f0f0;
}
if (self->enemy)
{
VectorCopy(self->movedir, last_movedir);
VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
if (self->owner->monsterinfo.aiflags & AI_MEDIC)
{
point[0] += sin(level.time) * 8;
}
VectorSubtract(point, self->s.origin, self->movedir);
VectorNormalize(self->movedir);
if (!VectorCompare(self->movedir, last_movedir))
{
self->spawnflags |= 0x80000000;
}
}
else
{
G_SetMovedir(self->s.angles, self->movedir);
}
self->think = dabeam_hit;
self->nextthink = level.time + 0.1;
VectorSet(self->mins, -8, -8, -8);
VectorSet(self->maxs, 8, 8, 8);
gi.linkentity(self);
self->spawnflags |= 0x80000001;
self->svflags &= ~SVF_NOCLIENT;
}
void
monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir,
int damage, int speed, int flashtype)
{
if (!self)
{
return;
}
fire_grenade(self, start, aimdir, damage, speed, 2.5, damage + 40);
gi.WriteByte(svc_muzzleflash2);
@ -82,6 +341,11 @@ void
monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir,
int damage, int speed, int flashtype)
{
if (!self)
{
return;
}
fire_rocket(self, start, dir, damage, speed, damage + 20, damage);
gi.WriteByte(svc_muzzleflash2);
@ -94,7 +358,15 @@ void
monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir,
int damage, int kick, int flashtype)
{
fire_rail(self, start, aimdir, damage, kick);
if (!self)
{
return;
}
if (!(gi.pointcontents(start) & MASK_SOLID))
{
fire_rail(self, start, aimdir, damage, kick);
}
gi.WriteByte(svc_muzzleflash2);
gi.WriteShort(self - g_edicts);
@ -104,9 +376,14 @@ monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir,
void
monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir,
int damage, int speed, int kick, float damage_radius,
int damage, int speed, int kick /* unused */, float damage_radius,
int flashtype)
{
if (!self)
{
return;
}
fire_bfg(self, start, aimdir, damage, speed, damage_radius);
gi.WriteByte(svc_muzzleflash2);
@ -115,11 +392,18 @@ monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir,
gi.multicast(start, MULTICAST_PVS);
}
/* ================================================================== */
/* Monster utility functions */
void
M_FliesOff(edict_t *self)
{
if (!self)
{
return;
}
self->s.effects &= ~EF_FLIES;
self->s.sound = 0;
}
@ -127,6 +411,11 @@ M_FliesOff(edict_t *self)
void
M_FliesOn(edict_t *self)
{
if (!self)
{
return;
}
if (self->waterlevel)
{
return;
@ -141,6 +430,11 @@ M_FliesOn(edict_t *self)
void
M_FlyCheck(edict_t *self)
{
if (!self)
{
return;
}
if (self->waterlevel)
{
return;
@ -158,6 +452,11 @@ M_FlyCheck(edict_t *self)
void
AttackFinished(edict_t *self, float time)
{
if (!self)
{
return;
}
self->monsterinfo.attack_finished = level.time + time;
}
@ -167,12 +466,17 @@ M_CheckGround(edict_t *ent)
vec3_t point;
trace_t trace;
if (!ent)
{
return;
}
if (ent->flags & (FL_SWIM | FL_FLY))
{
return;
}
if (ent->velocity[2] > 100)
if ((ent->velocity[2] * ent->gravityVector[2]) < -100)
{
ent->groundentity = NULL;
return;
@ -182,7 +486,7 @@ M_CheckGround(edict_t *ent)
is solid the entity is on ground */
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] - 0.25;
point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]);
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point,
ent, MASK_MONSTERSOLID);
@ -209,10 +513,15 @@ M_CatagorizePosition(edict_t *ent)
vec3_t point;
int cont;
if (!ent)
{
return;
}
/* get waterlevel */
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
point[0] = (ent->absmax[0] + ent->absmin[0])/2;
point[1] = (ent->absmax[1] + ent->absmin[1])/2;
point[2] = ent->absmin[2] + 2;
cont = gi.pointcontents(point);
if (!(cont & MASK_WATER))
@ -247,6 +556,11 @@ M_WorldEffects(edict_t *ent)
{
int dmg;
if (!ent)
{
return;
}
if (ent->health > 0)
{
if (!(ent->flags & FL_SWIM))
@ -303,7 +617,8 @@ M_WorldEffects(edict_t *ent)
{
if (ent->flags & FL_INWATER)
{
gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
gi.sound(ent, CHAN_BODY, gi.soundindex(
"player/watr_out.wav"), 1, ATTN_NORM, 0);
ent->flags &= ~FL_INWATER;
}
@ -315,18 +630,18 @@ M_WorldEffects(edict_t *ent)
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 0.2;
T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
10 * ent->waterlevel, 0, 0, MOD_LAVA);
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
vec3_origin, 10 * ent->waterlevel, 0, 0, MOD_LAVA);
}
}
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME) && !(ent->svflags & SVF_DEADMONSTER))
{
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 1;
T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
4 * ent->waterlevel, 0, 0, MOD_SLIME);
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
vec3_origin, 4 * ent->waterlevel, 0, 0, MOD_SLIME);
}
}
@ -370,12 +685,17 @@ M_droptofloor(edict_t *ent)
vec3_t end;
trace_t trace;
if (!ent)
{
return;
}
ent->s.origin[2] += 1;
VectorCopy(ent->s.origin, end);
end[2] -= 256;
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent,
MASK_MONSTERSOLID);
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end,
ent, MASK_MONSTERSOLID);
if ((trace.fraction == 1) || trace.allsolid)
{
@ -392,8 +712,15 @@ M_droptofloor(edict_t *ent)
void
M_SetEffects(edict_t *ent)
{
ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN);
ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE);
int remaining;
if (!ent)
{
return;
}
ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT);
ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE);
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
{
@ -418,6 +745,48 @@ M_SetEffects(edict_t *ent)
ent->s.renderfx |= RF_SHELL_GREEN;
}
}
if (ent->monsterinfo.quad_framenum > level.framenum)
{
remaining = ent->monsterinfo.quad_framenum - level.framenum;
if ((remaining > 30) || (remaining & 4))
{
ent->s.effects |= EF_QUAD;
}
}
else
{
ent->s.effects &= ~EF_QUAD;
}
if (ent->monsterinfo.double_framenum > level.framenum)
{
remaining = ent->monsterinfo.double_framenum - level.framenum;
if ((remaining > 30) || (remaining & 4))
{
ent->s.effects |= EF_DOUBLE;
}
}
else
{
ent->s.effects &= ~EF_DOUBLE;
}
if (ent->monsterinfo.invincible_framenum > level.framenum)
{
remaining = ent->monsterinfo.invincible_framenum - level.framenum;
if ((remaining > 30) || (remaining & 4))
{
ent->s.effects |= EF_PENT;
}
}
else
{
ent->s.effects &= ~EF_PENT;
}
}
void
@ -426,6 +795,11 @@ M_MoveFrame(edict_t *self)
mmove_t *move;
int index;
if (!self)
{
return;
}
move = self->monsterinfo.currentmove;
self->nextthink = level.time + FRAMETIME;
@ -438,6 +812,9 @@ M_MoveFrame(edict_t *self)
}
else
{
/* prevent nextframe from leaking into a future move */
self->monsterinfo.nextframe = 0;
if (self->s.frame == move->lastframe)
{
if (move->endfunc)
@ -499,6 +876,11 @@ M_MoveFrame(edict_t *self)
void
monster_think(edict_t *self)
{
if (!self)
{
return;
}
M_MoveFrame(self);
if (self->linkcount != self->monsterinfo.linkcount)
@ -517,8 +899,13 @@ monster_think(edict_t *self)
* at the current activator
*/
void
monster_use(edict_t *self, edict_t *other, edict_t *activator)
monster_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
if (self->enemy)
{
return;
@ -544,11 +931,14 @@ monster_use(edict_t *self, edict_t *other, edict_t *activator)
FoundTarget(self);
}
void monster_start_go(edict_t *self);
void
monster_triggered_spawn(edict_t *self)
{
if (!self)
{
return;
}
self->s.origin[2] += 1;
KillBox(self);
@ -572,8 +962,13 @@ monster_triggered_spawn(edict_t *self)
}
void
monster_triggered_spawn_use(edict_t *self, edict_t *other, edict_t *activator)
monster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
/* we have a one frame delay here so we
don't telefrag the guy who activated us */
self->think = monster_triggered_spawn;
@ -590,6 +985,11 @@ monster_triggered_spawn_use(edict_t *self, edict_t *other, edict_t *activator)
void
monster_triggered_start(edict_t *self)
{
if (!self)
{
return;
}
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
@ -598,12 +998,17 @@ monster_triggered_start(edict_t *self)
}
/*
* When a monster dies, it fires all of its
* targets with the current enemy as activator.
* When a monster dies, it fires all of its targets
* with the current enemy as activator.
*/
void
monster_death_use(edict_t *self)
{
if (!self)
{
return;
}
self->flags &= ~(FL_FLY | FL_SWIM);
self->monsterinfo.aiflags &= AI_GOOD_GUY;
@ -626,11 +1031,16 @@ monster_death_use(edict_t *self)
G_UseTargets(self, self->enemy);
}
/* ============================================================================ */
/* ================================================================== */
qboolean
monster_start(edict_t *self)
{
if (!self)
{
return false;
}
if (deathmatch->value)
{
G_FreeEdict(self);
@ -654,7 +1064,12 @@ monster_start(edict_t *self)
self->takedamage = DAMAGE_AIM;
self->air_finished = level.time + 12;
self->use = monster_use;
self->max_health = self->health;
if(!self->max_health)
{
self->max_health = self->health;
}
self->clipmask = MASK_MONSTERSOLID;
self->s.skinnum = 0;
@ -683,10 +1098,15 @@ monster_start(edict_t *self)
if (self->monsterinfo.currentmove)
{
self->s.frame = self->monsterinfo.currentmove->firstframe +
(rand() % (self->monsterinfo.currentmove->lastframe -
(randk() % (self->monsterinfo.currentmove->lastframe -
self->monsterinfo.currentmove->firstframe + 1));
}
self->monsterinfo.base_height = self->maxs[2];
self->monsterinfo.quad_framenum = 0;
self->monsterinfo.double_framenum = 0;
self->monsterinfo.invincible_framenum = 0;
return true;
}
@ -695,6 +1115,11 @@ monster_start_go(edict_t *self)
{
vec3_t v;
if (!self)
{
return;
}
if (self->health <= 0)
{
return;
@ -796,6 +1221,11 @@ monster_start_go(edict_t *self)
void
walkmonster_start_go(edict_t *self)
{
if (!self)
{
return;
}
if (!(self->spawnflags & 2) && (level.time < 1))
{
M_droptofloor(self);
@ -815,19 +1245,29 @@ walkmonster_start_go(edict_t *self)
self->yaw_speed = 20;
}
self->viewheight = 25;
monster_start_go(self);
if (!self->viewheight)
{
self->viewheight = 25;
}
if (self->spawnflags & 2)
{
monster_triggered_start(self);
}
else
{
monster_start_go(self);
}
}
void
walkmonster_start(edict_t *self)
{
if (!self)
{
return;
}
self->think = walkmonster_start_go;
monster_start(self);
}
@ -835,6 +1275,11 @@ walkmonster_start(edict_t *self)
void
flymonster_start_go(edict_t *self)
{
if (!self)
{
return;
}
if (!M_walkmove(self, 0, 0))
{
gi.dprintf("%s in solid at %s\n", self->classname, vtos(self->s.origin));
@ -845,19 +1290,29 @@ flymonster_start_go(edict_t *self)
self->yaw_speed = 10;
}
self->viewheight = 25;
monster_start_go(self);
if (!self->viewheight)
{
self->viewheight = 25;
}
if (self->spawnflags & 2)
{
monster_triggered_start(self);
}
else
{
monster_start_go(self);
}
}
void
flymonster_start(edict_t *self)
{
if (!self)
{
return;
}
self->flags |= FL_FLY;
self->think = flymonster_start_go;
monster_start(self);
@ -866,29 +1321,152 @@ flymonster_start(edict_t *self)
void
swimmonster_start_go(edict_t *self)
{
if (!self)
{
return;
}
if (!self->yaw_speed)
{
self->yaw_speed = 10;
}
self->viewheight = 10;
monster_start_go(self);
if (!self->viewheight)
{
self->viewheight = 10;
}
if (self->spawnflags & 2)
{
monster_triggered_start(self);
}
else
{
monster_start_go(self);
}
}
void
swimmonster_start(edict_t *self)
{
if (!self)
{
return;
}
self->flags |= FL_SWIM;
self->think = swimmonster_start_go;
monster_start(self);
}
void stationarymonster_start_go(edict_t *self);
void
stationarymonster_triggered_spawn(edict_t *self)
{
if (!self)
{
return;
}
KillBox(self);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NONE;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12;
gi.linkentity(self);
monster_start_go(self);
if (self->enemy && !(self->spawnflags & 1) &&
!(self->enemy->flags & FL_NOTARGET))
{
if (!(self->enemy->flags & FL_DISGUISED))
{
FoundTarget(self);
}
else
{
self->enemy = NULL;
}
}
else
{
self->enemy = NULL;
}
}
void
stationarymonster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
{
if (!self || !activator)
{
return;
}
/* we have a one frame delay here so we don't telefrag the guy who activated us */
self->think = stationarymonster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
if (activator->client)
{
self->enemy = activator;
}
self->use = monster_use;
}
void
stationarymonster_triggered_start(edict_t *self)
{
if (!self)
{
return;
}
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = stationarymonster_triggered_spawn_use;
}
void
stationarymonster_start_go(edict_t *self)
{
if (!self)
{
return;
}
if (!self->yaw_speed)
{
self->yaw_speed = 20;
}
if (self->spawnflags & 2)
{
stationarymonster_triggered_start(self);
}
else
{
monster_start_go(self);
}
}
void
stationarymonster_start(edict_t *self)
{
if (!self)
{
return;
}
self->think = stationarymonster_start_go;
monster_start(self);
}
void
monster_done_dodge(edict_t *self)

View file

@ -27,6 +27,12 @@
#include "header/local.h"
#define LEG_WAIT_TIME 1
#define MAX_LEGSFRAME 23
#define SPAWNGROW_LIFESPAN 0.3
#define STEPSIZE 18
typedef struct
{
char *name;
@ -125,6 +131,7 @@ void SP_misc_easterchick2(edict_t *self);
void SP_monster_berserk(edict_t *self);
void SP_monster_gladiator(edict_t *self);
void SP_monster_gunner(edict_t *self);
void SP_monster_guncmdr(edict_t *self);
void SP_monster_infantry(edict_t *self);
void SP_monster_soldier_light(edict_t *self);
void SP_monster_soldier(edict_t *self);
@ -150,7 +157,7 @@ void SP_turret_breach(edict_t *self);
void SP_turret_base(edict_t *self);
void SP_turret_driver(edict_t *self);
spawn_t spawns[] = {
static spawn_t spawns[] = {
{"item_health", SP_item_health},
{"item_health_small", SP_item_health_small},
{"item_health_large", SP_item_health_large},
@ -246,8 +253,39 @@ spawn_t spawns[] = {
{NULL, NULL}
};
static qboolean
Spawn_CheckCoop_MapHacks(edict_t *ent)
{
if(!coop->value || !ent)
{
return false;
}
if(!Q_stricmp(level.mapname, "xsewer1"))
{
if(ent->classname && !Q_stricmp(ent->classname, "trigger_relay") && ent->target && !Q_stricmp(ent->target, "t3") && ent->targetname && !Q_stricmp(ent->targetname, "t2"))
{
return true;
}
if(ent->classname && !Q_stricmp(ent->classname, "func_button") && ent->target && !Q_stricmp(ent->target, "t16") && ent->model && !Q_stricmp(ent->model, "*71"))
{
ent->message = "Overflow valve maintenance\nhatch A opened.";
return false;
}
if(ent->classname && !Q_stricmp(ent->classname, "trigger_once") && ent->model && !Q_stricmp(ent->model, "*3"))
{
ent->message = "Overflow valve maintenance\nhatch B opened.";
return false;
}
}
return false;
}
/*
* Finds the spawn function for the entity and calls it
* Finds the spawn function for
* the entity and calls it
*/
void
ED_CallSpawn(edict_t *ent)
@ -256,12 +294,37 @@ ED_CallSpawn(edict_t *ent)
gitem_t *item;
int i;
if (!ent)
{
return;
}
if (!ent->classname)
{
gi.dprintf("ED_CallSpawn: NULL classname\n");
G_FreeEdict(ent);
return;
}
ent->gravityVector[0] = 0.0;
ent->gravityVector[1] = 0.0;
ent->gravityVector[2] = -1.0;
if (!strcmp(ent->classname, "weapon_nailgun"))
{
ent->classname = (FindItem("ETF Rifle"))->classname;
}
if (!strcmp(ent->classname, "ammo_nails"))
{
ent->classname = (FindItem("Flechettes"))->classname;
}
if (!strcmp(ent->classname, "weapon_heatbeam"))
{
ent->classname = (FindItem("Plasma Beam"))->classname;
}
/* check item spawn functions */
for (i = 0, item = itemlist; i < game.num_items; i++, item++)
{
@ -760,7 +823,13 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH);
}
ent->gravityVector[0] = 0.0;
ent->gravityVector[1] = 0.0;
ent->gravityVector[2] = -1.0;
ED_CallSpawn(ent);
ent->s.renderfx |= RF_IR_VISIBLE;
}
gi.dprintf("%i entities inhibited.\n", inhibit);
@ -908,6 +977,11 @@ char *dm_statusbar =
void
SP_worldspawn(edict_t *ent)
{
if (!ent)
{
return;
}
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
ent->inuse = true; /* since the world doesn't use G_Spawn() */
@ -915,7 +989,8 @@ SP_worldspawn(edict_t *ent)
/* --------------- */
/* reserve some spots for dead player bodies for coop / deathmatch */
/* reserve some spots for dead
player bodies for coop / deathmatch */
InitBodyQue();
/* set configstrings for items */
@ -989,7 +1064,7 @@ SP_worldspawn(edict_t *ent)
gi.cvar_set("sv_gravity", st.gravity);
}
snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */
snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */
PrecacheItem(FindItem("Blaster"));
@ -1055,6 +1130,7 @@ SP_worldspawn(edict_t *ent)
gi.soundindex("misc/h2ohit1.wav"); /* landing splash */
gi.soundindex("items/damage.wav");
gi.soundindex("misc/ddamage1.wav");
gi.soundindex("items/protect.wav");
gi.soundindex("items/protect4.wav");
gi.soundindex("weapons/noammo.wav");
@ -1069,7 +1145,8 @@ SP_worldspawn(edict_t *ent)
gi.modelindex("models/objects/gibs/skull/tris.md2");
gi.modelindex("models/objects/gibs/head2/tris.md2");
/* Setup light animation tables. 'a' is total darkness, 'z' is doublebright. */
/* Setup light animation tables. 'a'
is total darkness, 'z' is doublebright. */
/* 0 normal */
gi.configstring(CS_LIGHTS + 0, "m");
@ -1113,3 +1190,527 @@ SP_worldspawn(edict_t *ent)
gi.configstring(CS_LIGHTS + 63, "a");
}
/*
* Monster spawning code:
* Used by the carrier, the medic_commander, and the black widow
*
* The sequence to create a flying monster is:
* FindSpawnPoint - tries to find suitable spot to spawn the monster in
* CreateFlyMonster - this verifies the point as good and creates the monster
*
* To create a ground walking monster:
* FindSpawnPoint - same thing
* CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable
*/
edict_t *
CreateMonster(vec3_t origin, vec3_t angles, char *classname)
{
edict_t *newEnt;
if (!classname)
{
return NULL;
}
newEnt = G_Spawn();
VectorCopy(origin, newEnt->s.origin);
VectorCopy(angles, newEnt->s.angles);
newEnt->classname = ED_NewString(classname);
newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
VectorSet(newEnt->gravityVector, 0, 0, -1);
ED_CallSpawn(newEnt);
newEnt->s.renderfx |= RF_IR_VISIBLE;
return newEnt;
}
edict_t *
CreateFlyMonster(vec3_t origin, vec3_t angles, vec3_t mins,
vec3_t maxs, char *classname)
{
if (!classname)
{
return NULL;
}
if (!mins || !maxs ||
VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin))
{
DetermineBBox(classname, mins, maxs);
}
if (!CheckSpawnPoint(origin, mins, maxs))
{
return NULL;
}
return CreateMonster(origin, angles, classname);
}
edict_t *
CreateGroundMonster(vec3_t origin, vec3_t angles, vec3_t entMins,
vec3_t entMaxs, char *classname, int height)
{
edict_t *newEnt;
vec3_t mins, maxs;
if (!classname)
{
return NULL;
}
/* if they don't provide us a bounding box, figure it out */
if (!entMins || !entMaxs || VectorCompare(entMins,
vec3_origin) || VectorCompare(entMaxs, vec3_origin))
{
DetermineBBox(classname, mins, maxs);
}
else
{
VectorCopy(entMins, mins);
VectorCopy(entMaxs, maxs);
}
/* check the ground to make sure it's there, it's relatively flat, and it's not toxic */
if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1))
{
return NULL;
}
newEnt = CreateMonster(origin, angles, classname);
if (!newEnt)
{
return NULL;
}
return newEnt;
}
qboolean
FindSpawnPoint(vec3_t startpoint, vec3_t mins, vec3_t maxs,
vec3_t spawnpoint, float maxMoveUp)
{
trace_t tr;
vec3_t top;
tr = gi.trace(startpoint, mins, maxs, startpoint,
NULL, MASK_MONSTERSOLID | CONTENTS_PLAYERCLIP);
if ((tr.startsolid || tr.allsolid) || (tr.ent != world))
{
VectorCopy(startpoint, top);
top[2] += maxMoveUp;
tr = gi.trace(top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID);
if (tr.startsolid || tr.allsolid)
{
return false;
}
else
{
VectorCopy(tr.endpos, spawnpoint);
return true;
}
}
else
{
VectorCopy(startpoint, spawnpoint);
return true;
}
}
qboolean
CheckSpawnPoint(vec3_t origin, vec3_t mins, vec3_t maxs)
{
trace_t tr;
if (!mins || !maxs ||
VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin))
{
return false;
}
tr = gi.trace(origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID);
if (tr.startsolid || tr.allsolid)
{
return false;
}
if (tr.ent != world)
{
return false;
}
return true;
}
qboolean
CheckGroundSpawnPoint(vec3_t origin, vec3_t entMins, vec3_t entMaxs,
float height, float gravity)
{
trace_t tr;
vec3_t start, stop;
vec3_t mins, maxs;
int x, y;
float mid, bottom;
if (!CheckSpawnPoint(origin, entMins, entMaxs))
{
return false;
}
VectorCopy(origin, stop);
stop[2] = origin[2] + entMins[2] - height;
tr = gi.trace(origin, entMins, entMaxs, stop,
NULL, MASK_MONSTERSOLID | MASK_WATER);
if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID))
{
/* first, do the midpoint trace */
VectorAdd(tr.endpos, entMins, mins);
VectorAdd(tr.endpos, entMaxs, maxs);
/* first, do the easy flat check */
if (gravity > 0)
{
start[2] = maxs[2] + 1;
}
else
{
start[2] = mins[2] - 1;
}
for (x = 0; x <= 1; x++)
{
for (y = 0; y <= 1; y++)
{
start[0] = x ? maxs[0] : mins[0];
start[1] = y ? maxs[1] : mins[1];
if (gi.pointcontents(start) != CONTENTS_SOLID)
{
goto realcheck;
}
}
}
/* if it passed all four above checks, we're done */
return true;
realcheck:
/* check it for real */
start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
start[2] = mins[2];
tr = gi.trace(start, vec3_origin, vec3_origin,
stop, NULL, MASK_MONSTERSOLID);
if (tr.fraction == 1.0)
{
return false;
}
if (gravity < 0)
{
start[2] = mins[2];
stop[2] = start[2] - STEPSIZE - STEPSIZE;
mid = bottom = tr.endpos[2] + entMins[2];
}
else
{
start[2] = maxs[2];
stop[2] = start[2] + STEPSIZE + STEPSIZE;
mid = bottom = tr.endpos[2] - entMaxs[2];
}
for (x = 0; x <= 1; x++)
{
for (y = 0; y <= 1; y++)
{
start[0] = stop[0] = x ? maxs[0] : mins[0];
start[1] = stop[1] = y ? maxs[1] : mins[1];
tr = gi.trace(start, vec3_origin, vec3_origin,
stop, NULL, MASK_MONSTERSOLID);
if (gravity > 0)
{
if ((tr.fraction != 1.0) && (tr.endpos[2] < bottom))
{
bottom = tr.endpos[2];
}
if ((tr.fraction == 1.0) || (tr.endpos[2] - mid > STEPSIZE))
{
return false;
}
}
else
{
if ((tr.fraction != 1.0) && (tr.endpos[2] > bottom))
{
bottom = tr.endpos[2];
}
if ((tr.fraction == 1.0) || (mid - tr.endpos[2] > STEPSIZE))
{
return false;
}
}
}
}
return true; /* we can land on it, it's ok */
}
/* otherwise, it's either water (bad) or not
* there (too far) if we're here, it's bad below */
return false;
}
void
DetermineBBox(char *classname, vec3_t mins, vec3_t maxs)
{
edict_t *newEnt;
if (!classname)
{
return;
}
newEnt = G_Spawn();
VectorCopy(vec3_origin, newEnt->s.origin);
VectorCopy(vec3_origin, newEnt->s.angles);
newEnt->classname = ED_NewString(classname);
newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
ED_CallSpawn(newEnt);
VectorCopy(newEnt->mins, mins);
VectorCopy(newEnt->maxs, maxs);
G_FreeEdict(newEnt);
}
void
spawngrow_think(edict_t *self)
{
int i;
if (!self)
{
return;
}
for (i = 0; i < 2; i++)
{
self->s.angles[0] = rand() % 360;
self->s.angles[1] = rand() % 360;
self->s.angles[2] = rand() % 360;
}
if ((level.time < self->wait) && (self->s.frame < 2))
{
self->s.frame++;
}
if (level.time >= self->wait)
{
if (self->s.effects & EF_SPHERETRANS)
{
G_FreeEdict(self);
return;
}
else if (self->s.frame > 0)
{
self->s.frame--;
}
else
{
G_FreeEdict(self);
return;
}
}
self->nextthink += FRAMETIME;
}
void
SpawnGrow_Spawn(vec3_t startpos, int size)
{
edict_t *ent;
int i;
float lifespan;
ent = G_Spawn();
VectorCopy(startpos, ent->s.origin);
for (i = 0; i < 2; i++)
{
ent->s.angles[0] = rand() % 360;
ent->s.angles[1] = rand() % 360;
ent->s.angles[2] = rand() % 360;
}
ent->solid = SOLID_NOT;
ent->s.renderfx = RF_IR_VISIBLE;
ent->movetype = MOVETYPE_NONE;
ent->classname = "spawngro";
if (size <= 1)
{
lifespan = SPAWNGROW_LIFESPAN;
ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2");
}
else if (size == 2)
{
ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
lifespan = 2;
}
else
{
ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2");
lifespan = SPAWNGROW_LIFESPAN;
}
ent->think = spawngrow_think;
ent->wait = level.time + lifespan;
ent->nextthink = level.time + FRAMETIME;
if (size != 2)
{
ent->s.effects |= EF_SPHERETRANS;
}
gi.linkentity(ent);
}
void
widowlegs_think(edict_t *self)
{
vec3_t offset;
vec3_t point;
vec3_t f, r, u;
if (!self)
{
return;
}
if (self->s.frame == 17)
{
VectorSet(offset, 11.77, -7.24, 23.31);
AngleVectors(self->s.angles, f, r, u);
G_ProjectSource2(self->s.origin, offset, f, r, u, point);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL);
ThrowSmallStuff(self, point);
}
if (self->s.frame < MAX_LEGSFRAME)
{
self->s.frame++;
self->nextthink = level.time + FRAMETIME;
return;
}
else if (self->wait == 0)
{
self->wait = level.time + LEG_WAIT_TIME;
}
if (level.time > self->wait)
{
AngleVectors(self->s.angles, f, r, u);
VectorSet(offset, -65.6, -8.44, 28.59);
G_ProjectSource2(self->s.origin, offset, f, r, u, point);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL);
ThrowSmallStuff(self, point);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2",
80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2",
80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
VectorSet(offset, -1.04, -51.18, 7.04);
G_ProjectSource2(self->s.origin, offset, f, r, u, point);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL);
ThrowSmallStuff(self, point);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2",
80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2",
80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2",
80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
G_FreeEdict(self);
return;
}
if ((level.time > (self->wait - 0.5)) && (self->count == 0))
{
self->count = 1;
AngleVectors(self->s.angles, f, r, u);
VectorSet(offset, 31, -88.7, 10.96);
G_ProjectSource2(self->s.origin, offset, f, r, u, point);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL);
VectorSet(offset, -12.67, -4.39, 15.68);
G_ProjectSource2(self->s.origin, offset, f, r, u, point);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL);
self->nextthink = level.time + FRAMETIME;
return;
}
self->nextthink = level.time + FRAMETIME;
}
void
Widowlegs_Spawn(vec3_t startpos, vec3_t angles)
{
edict_t *ent;
ent = G_Spawn();
VectorCopy(startpos, ent->s.origin);
VectorCopy(angles, ent->s.angles);
ent->solid = SOLID_NOT;
ent->s.renderfx = RF_IR_VISIBLE;
ent->movetype = MOVETYPE_NONE;
ent->classname = "widowlegs";
ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2");
ent->think = widowlegs_think;
ent->nextthink = level.time + FRAMETIME;
gi.linkentity(ent);
}

View file

@ -29,11 +29,77 @@
#include "../monster/misc/player.h"
#include <limits.h>
#define PLAYER_NOISE_SELF 0
#define PLAYER_NOISE_IMPACT 1
#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)
#define GRENADE_TIMER 3.0
#define GRENADE_MINSPEED 400
#define GRENADE_MAXSPEED 800
#define CHAINFIST_REACH 64
#define HEATBEAM_DM_DMG 15
#define HEATBEAM_SP_DMG 15
#define TRAP_TIMER 5.0
#define TRAP_MINSPEED 300
#define TRAP_MAXSPEED 700
static qboolean is_quad;
static qboolean is_quadfire;
static byte damage_multiplier;
static byte is_silenced;
void weapon_grenade_fire(edict_t *ent, qboolean held);
void weapon_trap_fire(edict_t *ent, qboolean held);
byte
P_DamageModifier(edict_t *ent)
{
is_quad = 0;
damage_multiplier = 1;
if (!ent)
{
return 0;
}
if (ent->client->quad_framenum > level.framenum)
{
damage_multiplier *= 4;
is_quad = 1;
/* if we're quad and DF_NO_STACK_DOUBLE is on, return now. */
if (((int)(dmflags->value) & DF_NO_STACK_DOUBLE))
{
return damage_multiplier;
}
}
if (ent->client->double_framenum > level.framenum)
{
if ((deathmatch->value) || (damage_multiplier == 1))
{
damage_multiplier *= 2;
is_quad = 1;
}
}
if (ent->client->quadfire_framenum > level.framenum)
{
if ((deathmatch->value) || (damage_multiplier == 1))
{
damage_multiplier *= 2;
is_quadfire = 1;
}
}
return damage_multiplier;
}
void
P_ProjectSource(edict_t *ent, vec3_t distance,
@ -77,6 +143,47 @@ P_ProjectSource(edict_t *ent, vec3_t distance,
}
}
void
P_ProjectSource2(edict_t *ent, vec3_t point, vec3_t distance, vec3_t forward,
vec3_t right, vec3_t up, vec3_t result)
{
gclient_t *client = ent->client;
vec3_t _distance;
if (!client)
{
return;
}
VectorCopy(distance, _distance);
if (client->pers.hand == LEFT_HANDED)
{
_distance[1] *= -1;
}
else if (client->pers.hand == CENTER_HANDED)
{
_distance[1] = 0;
}
G_ProjectSource2(point, _distance, forward, right, up, result);
// Berserker: fix - now the projectile hits exactly where the scope is pointing.
if (aimfix->value)
{
vec3_t start, end;
VectorSet(start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight);
VectorMA(start, 8192, forward, end);
trace_t tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT);
if (tr.fraction < 1)
{
VectorSubtract(tr.endpos, result, forward);
VectorNormalize(forward);
}
}
}
/*
* Each player can have two noise objects associated with it:
* a personal noise (jumping, pain, weapon firing), and a weapon
@ -85,11 +192,115 @@ P_ProjectSource(edict_t *ent, vec3_t distance,
* Monsters that don't directly see the player can move
* to a noise in hopes of seeing the player from there.
*/
static edict_t *
PlayerNoise_Spawn(edict_t *who, int type)
{
edict_t *noise;
if (!who)
{
return NULL;
}
noise = G_SpawnOptional();
if (!noise)
{
return NULL;
}
noise->classname = "player_noise";
noise->spawnflags = type;
VectorSet (noise->mins, -8, -8, -8);
VectorSet (noise->maxs, 8, 8, 8);
noise->owner = who;
noise->svflags = SVF_NOCLIENT;
return noise;
}
static void
PlayerNoise_Verify(edict_t *who)
{
edict_t *e;
edict_t *n1;
edict_t *n2;
if (!who)
{
return;
}
n1 = who->mynoise;
n2 = who->mynoise2;
if (n1 && !n1->inuse)
{
n1 = NULL;
}
if (n2 && !n2->inuse)
{
n2 = NULL;
}
if (n1 && n2)
{
return;
}
for (e = g_edicts + 1 + game.maxclients; e < &g_edicts[globals.num_edicts]; e++)
{
if (!e->inuse || strcmp(e->classname, "player_noise") != 0)
{
continue;
}
if (e->owner && e->owner != who)
{
continue;
}
e->owner = who;
if (!n2 && (e->spawnflags == PLAYER_NOISE_IMPACT || n1))
{
n2 = e;
}
else
{
n1 = e;
}
if (n1 && n2)
{
break;
}
}
if (!n1)
{
n1 = PlayerNoise_Spawn(who, PLAYER_NOISE_SELF);
}
if (!n2)
{
n2 = PlayerNoise_Spawn(who, PLAYER_NOISE_IMPACT);
}
who->mynoise = n1;
who->mynoise2 = n2;
}
void
PlayerNoise(edict_t *who, vec3_t where, int type)
{
edict_t *noise;
if (!who || !who->client)
{
return;
}
if (type == PNOISE_WEAPON)
{
if (who->client->silencer_shots)
@ -154,6 +365,11 @@ Pickup_Weapon(edict_t *ent, edict_t *other)
int index;
gitem_t *ammo;
if (!ent || !other)
{
return false;
}
index = ITEM_INDEX(ent->item);
if ((((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) &&
@ -198,6 +414,7 @@ Pickup_Weapon(edict_t *ent, edict_t *other)
if (coop->value)
{
ent->flags |= FL_RESPAWN;
ent->flags |= FL_COOP_TAKEN;
}
}
}
@ -222,6 +439,11 @@ ChangeWeapon(edict_t *ent)
{
int i;
if (!ent)
{
return;
}
if (ent->client->grenade_time)
{
ent->client->grenade_time = level.time;
@ -262,6 +484,7 @@ ChangeWeapon(edict_t *ent)
if (!ent->client->pers.weapon)
{
/* dead */
ent->client->ps.gunindex = 0;
return;
}
@ -288,6 +511,11 @@ ChangeWeapon(edict_t *ent)
void
NoAmmoWeaponChange(edict_t *ent)
{
if (!ent)
{
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] &&
ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))])
{
@ -295,6 +523,27 @@ NoAmmoWeaponChange(edict_t *ent)
return;
}
if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] >= 2) &&
ent->client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))])
{
ent->client->newweapon = FindItem("Plasma Beam");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("flechettes"))] &&
ent->client->pers.inventory[ITEM_INDEX(FindItem("etf rifle"))])
{
ent->client->newweapon = FindItem("etf rifle");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] > 1 &&
ent->client->pers.inventory[ITEM_INDEX(FindItem("ionripper"))])
{
ent->client->newweapon = FindItem("ionripper");
return;
}
if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] &&
ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))])
{
@ -339,6 +588,11 @@ NoAmmoWeaponChange(edict_t *ent)
void
Think_Weapon(edict_t *ent)
{
if (!ent)
{
return;
}
/* if just died, put the weapon away */
if (ent->health < 1)
{
@ -373,6 +627,11 @@ Use_Weapon(edict_t *ent, gitem_t *item)
int ammo_index;
gitem_t *ammo_item;
if (!ent || !item)
{
return;
}
/* see if we're already using it */
if (item == ent->client->pers.weapon)
{
@ -403,11 +662,100 @@ Use_Weapon(edict_t *ent, gitem_t *item)
ent->client->newweapon = item;
}
void
Use_Weapon2(edict_t *ent, gitem_t *item)
{
int ammo_index;
gitem_t *ammo_item;
gitem_t *nextitem;
int index;
if (!ent || !item)
{
return;
}
if (strcmp(item->pickup_name, "HyperBlaster") == 0)
{
if (item == ent->client->pers.weapon)
{
item = FindItem("Ionripper");
index = ITEM_INDEX(item);
if (!ent->client->pers.inventory[index])
{
item = FindItem("HyperBlaster");
}
}
}
else if (strcmp(item->pickup_name, "Railgun") == 0)
{
ammo_item = FindItem(item->ammo);
ammo_index = ITEM_INDEX(ammo_item);
if (!ent->client->pers.inventory[ammo_index])
{
nextitem = FindItem("Phalanx");
ammo_item = FindItem(nextitem->ammo);
ammo_index = ITEM_INDEX(ammo_item);
if (ent->client->pers.inventory[ammo_index])
{
item = FindItem("Phalanx");
index = ITEM_INDEX(item);
if (!ent->client->pers.inventory[index])
{
item = FindItem("Railgun");
}
}
}
else if (item == ent->client->pers.weapon)
{
item = FindItem("Phalanx");
index = ITEM_INDEX(item);
if (!ent->client->pers.inventory[index])
{
item = FindItem("Railgun");
}
}
}
/* see if we're already using it */
if (item == ent->client->pers.weapon)
{
return;
}
if (item->ammo)
{
ammo_item = FindItem(item->ammo);
ammo_index = ITEM_INDEX(ammo_item);
if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value)
{
gi.cprintf(ent, PRINT_HIGH, "No %s for %s.\n",
ammo_item->pickup_name, item->pickup_name);
return;
}
}
/* change to this weapon when down */
ent->client->newweapon = item;
}
void
Drop_Weapon(edict_t *ent, gitem_t *item)
{
int index;
if (!ent || !item)
{
return;
}
if ((int)(dmflags->value) & DF_WEAPONS_STAY)
{
return;

View file

@ -20,7 +20,8 @@
*
* =======================================================================
*
* Monster utility functions.
* Monster utility functions. While unused by the CTF code they must
* persist here since most of the other game codes has ties to it.
*
* =======================================================================
*/

View file

@ -91,6 +91,7 @@ void SP_target_crosslevel_trigger(edict_t *ent);
void SP_target_crosslevel_target(edict_t *ent);
void SP_target_laser(edict_t *self);
void SP_target_help(edict_t *ent);
void SP_target_actor(edict_t *ent);
void SP_target_lightramp(edict_t *self);
void SP_target_earthquake(edict_t *ent);
void SP_target_music(edict_t *ent);
@ -112,6 +113,7 @@ void SP_point_combat(edict_t *self);
void SP_misc_explobox(edict_t *self);
void SP_misc_banner(edict_t *self);
void SP_misc_satellite_dish(edict_t *self);
void SP_misc_actor(edict_t *self);
void SP_misc_gib_arm(edict_t *self);
void SP_misc_gib_leg(edict_t *self);
void SP_misc_gib_head(edict_t *self);
@ -1309,6 +1311,7 @@ SP_worldspawn(edict_t *ent)
gi.modelindex("#w_hyperblaster.md2");
gi.modelindex("#w_railgun.md2");
gi.modelindex("#w_bfg.md2");
gi.modelindex("#w_grapple.md2");
gi.modelindex("#w_disrupt.md2");
gi.modelindex("#w_etfrifle.md2");
gi.modelindex("#w_plasma.md2");

View file

@ -760,7 +760,7 @@ G_SpawnOptional(void)
}
e = &g_edicts[globals.num_edicts++];
G_InitEdict (e);
G_InitEdict(e);
return e;
}
@ -893,6 +893,14 @@ G_TouchSolids(edict_t *ent)
}
}
/*
* ==============================================================================
*
* Kill box
*
* ==============================================================================
*/
/*
* Kills all entities that would touch the
* proposed new positioning of ent. Ent s

View file

@ -440,7 +440,8 @@ Pickup_Weapon(edict_t *ent, edict_t *other)
if ((other->client->pers.weapon != ent->item) &&
(other->client->pers.inventory[index] == 1) &&
(!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))))
(!deathmatch->value ||
(other->client->pers.weapon == FindItem("blaster"))))
{
other->client->newweapon = ent->item;
}