thirtyflightsofloving/missionpack/g_newtarg.c

3274 lines
89 KiB
C
Raw Normal View History

#include "g_local.h"
qboolean FindTarget (edict_t *self);
//==========================================================
/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8)
Creates a steam effect (particles w/ velocity in a line).
speed = velocity of particles (default 50)
count = number of particles (default 32)
sounds = color of particles (default 8 for steam)
the color range is from this color to this color + 6
wait = seconds to run before stopping (overrides default
value derived from func_timer)
best way to use this is to tie it to a func_timer that "pokes"
it every second (or however long you set the wait time, above)
note that the width of the base is proportional to the speed
good colors to use:
6-9 - varying whites (darker to brighter)
224 - sparks
176 - blue water
80 - brown water
208 - slime
232 - blood
*/
void use_target_steam (edict_t *self, edict_t *other, edict_t *activator)
{
// FIXME - this needs to be a global
static int nextid;
vec3_t point;
if (nextid > 20000)
nextid = nextid %20000;
nextid++;
// automagically set wait from func_timer unless they set it already, or
// default to 1000 if not called by a func_timer (eek!)
if (!self->wait)
if (other)
self->wait = other->wait * 1000;
else
self->wait = 1000;
if (self->enemy)
{
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
VectorSubtract (point, self->s.origin, self->movedir);
VectorNormalize (self->movedir);
}
VectorMA (self->s.origin, self->plat2flags*0.5, self->movedir, point);
if (self->wait > 100)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_STEAM);
gi.WriteShort (nextid);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds&0xff);
gi.WriteShort ( (short int)(self->plat2flags) );
gi.WriteLong ( (int)(self->wait) );
gi.multicast (self->s.origin, MULTICAST_PVS);
}
else
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_STEAM);
gi.WriteShort ((short int)-1);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds&0xff);
gi.WriteShort ( (short int)(self->plat2flags) );
gi.multicast (self->s.origin, MULTICAST_PVS);
}
}
void target_steam_start (edict_t *self)
{
edict_t *ent;
self->use = use_target_steam;
if (self->target)
{
ent = G_Find (NULL, FOFS(targetname), self->target);
if (!ent)
gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
self->enemy = ent;
}
else
{
G_SetMovedir (self->s.angles, self->movedir);
}
if (!self->count)
self->count = 32;
if (!self->plat2flags)
self->plat2flags = 75;
if (!self->sounds)
self->sounds = 8;
if (self->wait)
self->wait *= 1000; // we want it in milliseconds, not seconds
// paranoia is good
self->sounds &= 0xff;
self->count &= 0xff;
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
}
void SP_target_steam (edict_t *self)
{
self->plat2flags = self->speed;
if (self->target)
{
self->think = target_steam_start;
self->nextthink = level.time + 1;
}
else
target_steam_start (self);
}
//==========================================================
// target_anger
//==========================================================
void target_anger_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *target;
edict_t *t;
t = NULL;
target = G_Find (t, FOFS(targetname), self->killtarget);
if (target && self->target)
{
// Make whatever a "good guy" so the monster will try to kill it!
target->monsterinfo.aiflags |= AI_GOOD_GUY;
target->svflags |= SVF_MONSTER;
//Knightmare- removed
if (target->health == 0)
target->health = 300;
t = NULL;
while ((t = G_Find (t, FOFS(targetname), self->target)))
{
if (t == self)
{
gi.dprintf ("WARNING: entity used itself.\n");
}
else
{
if (t->use)
{
if (t->health < 0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("target_anger with dead monster!\n");
return;
}
t->enemy = target;
t->monsterinfo.aiflags |= AI_TARGET_ANGER;
FoundTarget (t);
}
}
if (!self->inuse)
{
gi.dprintf("entity was removed while using targets\n");
return;
}
}
}
}
/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8)
This trigger will cause an entity to be angry at another entity when a player touches it. Target the
entity you want to anger, and killtarget the entity you want it to be angry at.
target - entity to piss off
killtarget - entity to be pissed off at
*/
void SP_target_anger (edict_t *self)
{
if(deathmatch->value)
{
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.dprintf("target_anger without target!\n");
G_FreeEdict (self);
return;
}
if (!self->killtarget)
{
gi.dprintf("target_anger without killtarget!\n");
G_FreeEdict (self);
return;
}
self->use = target_anger_use;
self->svflags = SVF_NOCLIENT;
}
// target_monsterbattle serves the same purpose as target_anger, but
// ends up turning a dmgteam group of monsters against another dmgteam
void use_target_monsterbattle(edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *grouch, *grouchmate;
edict_t *target, *targetmate;
grouch = G_Find(NULL,FOFS(targetname),self->target);
if(!grouch) return;
if(!grouch->inuse) return;
target = G_Find(NULL,FOFS(targetname),self->killtarget);
if(!target) return;
if(!target->inuse) return;
if(grouch->dmgteam)
{
grouchmate = G_Find(NULL,FOFS(dmgteam),grouch->dmgteam);
while(grouchmate)
{
grouchmate->monsterinfo.aiflags2 |= AI2_FREEFORALL;
grouchmate = G_Find(grouchmate,FOFS(dmgteam),grouch->dmgteam);
}
}
if(target->dmgteam)
{
targetmate = G_Find(NULL,FOFS(dmgteam),target->dmgteam);
while(targetmate)
{
targetmate->monsterinfo.aiflags2 |= AI2_FREEFORALL;
targetmate = G_Find(targetmate,FOFS(dmgteam),target->dmgteam);
}
}
grouch->enemy = target;
grouch->monsterinfo.aiflags |= AI_TARGET_ANGER;
FoundTarget(grouch);
self->count--;
if(self->count == 0)
{
self->think = G_FreeEdict;
self->nextthink = level.time + 1;
}
}
void SP_target_monsterbattle(edict_t *self)
{
if(deathmatch->value)
{
G_FreeEdict(self);
return;
}
if(!self->target)
{
gi.dprintf("target_monsterbattle with no target set at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if(!self->killtarget)
{
gi.dprintf("target_monsterbattle with no killtarget set at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->use = use_target_monsterbattle;
}
// ================
// target_spawn
// ================
/*
extern edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname);
void target_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *newEnt;
newEnt = CreateMonster (self->s.origin, self->s.angles, "monster_infantry");
if(newEnt)
newEnt->enemy = other;
}
*/
/*Q U AKED target_spawn (1 0 0) (-32 -32 -24) (32 32 72)
*/
/*
void SP_target_spawn (edict_t *self)
{
self->use = target_spawn_use;
self->svflags = SVF_NOCLIENT;
}
*/
// ***********************************
// target_killplayers
// ***********************************
void target_killplayers_use (edict_t *self, edict_t *other, edict_t *activator)
{
int i;
edict_t *ent, *player;
// kill the players
for (i=0 ; i<game.maxclients ; i++)
{
player = &g_edicts[1+i];
if (!player->inuse)
continue;
// nail it
T_Damage (player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
}
// kill any visible monsters
for (ent = g_edicts; ent < &g_edicts[globals.num_edicts] ; ent++)
{
if (!ent->inuse)
continue;
if (ent->health < 1)
continue;
if (!ent->takedamage)
continue;
for(i=0;i<game.maxclients ; i++)
{
player = &g_edicts[1+i];
if(!player->inuse)
continue;
if(visible(player, ent))
{
T_Damage (ent, self, self, vec3_origin, ent->s.origin, vec3_origin,
ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
break;
}
}
}
}
/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this will kill all the players on the map.
*/
void SP_target_killplayers (edict_t *self)
{
self->use = target_killplayers_use;
self->svflags = SVF_NOCLIENT;
}
/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24)
Pulsing black light with sphere in the center
*/
void blacklight_think (edict_t *self)
{
self->s.angles[0] = rand()%360;
self->s.angles[1] = rand()%360;
self->s.angles[2] = rand()%360;
self->nextthink = level.time + 0.1;
}
void SP_target_blacklight(edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
VectorClear (ent->mins);
VectorClear (ent->maxs);
ent->s.effects |= (EF_TRACKERTRAIL|EF_TRACKER);
ent->think = blacklight_think;
ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
ent->s.frame = 1;
ent->nextthink = level.time + 0.1;
gi.linkentity (ent);
}
/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24)
Translucent pulsing orb with speckles
*/
void orb_think (edict_t *self)
{
self->s.angles[0] = rand()%360;
self->s.angles[1] = rand()%360;
self->s.angles[2] = rand()%360;
// self->s.effects |= (EF_TRACKERTRAIL|EF_DOUBLE);
self->nextthink = level.time + 0.1;
}
void SP_target_orb(edict_t *ent)
{
if (deathmatch->value)
{ // auto-remove for deathmatch
G_FreeEdict (ent);
return;
}
VectorClear (ent->mins);
VectorClear (ent->maxs);
// ent->s.effects |= EF_TRACKERTRAIL;
ent->think = orb_think;
ent->nextthink = level.time + 0.1;
ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
ent->s.frame = 2;
ent->s.effects |= EF_SPHERETRANS;
gi.linkentity (ent);
}
/*
===============================================
MAPPACK AND LAZARUS ADDITIONS
===============================================
*/
/*QUAKED target_command (1 0 0) (-8 -8 -8) (8 8 8)
Sends a command to the console which gets executed imediately,
unless it requires a restart of the server.
message - command to send
*/
void target_command_use (edict_t *self, edict_t *activator, edict_t *other)
{
gi.WriteByte (11);
gi.WriteString (self->message);
gi.unicast (self, true);
}
void SP_target_command (edict_t *self)
{
if(!self->message)
{
gi.dprintf("target_command with no command, target name is %s at %s", self->targetname, vtos(self->s.origin));
G_FreeEdict (self);
return;
}
self->use = target_command_use;
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
}
#define ON 1
#define OFF 2
#define TOGGLE 4
/*QUAKED target_set_effect (1 0 0) (-8 -8 -8) (8 8 8)
Changes the effects of the targeted entities.
"target" - set the properties to this entity
"style" -
0 = Set
1 = Remove
2 = XOR (toggle)
"alpha" : Change the alpha value of the target (between 0 and 1)
"effects"
1: ROTATE Rotate like a weapon
2: GIB
8: BLASTER Yellowish orange glow plus particles
16: ROCKET Rocket trail
32: GRENADE Grenade trail
64: HYPERBLASTER BLASTER w/o the particles
128: BFG Big green ball
256: COLOR_SHELL
512: POWERSCREEN Green power shield
16384: FLIES Ewwww
32768: QUAD Blue shell
65536: PENT Red shell
131072: TELEPORTER Teleporter particles
262144: FLAG1 Red glow
524288: FLAG2 Blue glow
1048576: IONRIPPER
2097152: GREENGIB
4194304: BLUE_HB Blue hyperblaster glow
8388608: SPINNING_LIGHTS Red spinning lights
16777216: PLASMA
33554432: TRAP
67108864: TRACKER
134217728: DOUBLE Yellow shell
268435456: SPHERETRANS Transparent
536870912: TAGTRAIL
1073741824: HALF_DAMAGE
2147483648: TRACKER_TRAIL
"renderfx"
1: MINLIGHT Never completely dark
2: VIEWERMODEL
4: WEAPONMODEL
8: FULLBRIGHT
16: DEPTHHACK
32: TRANSLUCENT Transparent
64: FRAMELERP
128: BEAM
512: GLOW Pulsating glow of normal Q2 pickup items
1024: SHELL_RED
2048: SHELL_GREEN
4096: SHELL_BLUE
32768: IR_VISIBLE
65536: SHELL_DOUBLE
131072: SHELL_HALF_DAMAGE White shell
262144: USE_DISGUISE
*/
void target_set_effect_use (edict_t *self, edict_t *activator, edict_t *other)
{
edict_t *target;
target = G_Find (NULL, FOFS(targetname), self->target);
while(target)
{
if(self->style == 1)
{
target->s.effects &= ~self->effects;
target->s.renderfx &= ~self->renderfx;
}
else if(self->style == 2)
{
target->s.effects ^= self->effects;
target->s.renderfx ^= self->renderfx;
}
else
{
target->s.effects = self->effects;
target->s.renderfx = self->renderfx;
}
#ifdef KMQUAKE2_ENGINE_MOD //Knightmare added
if ((self->alpha >= 0.0) && (self->alpha <= 1.0))
target->s.alpha = self->alpha;
#endif
gi.linkentity(target);
target = G_Find(target,FOFS(targetname),self->target);
}
}
void SP_target_set_effect (edict_t *self)
{
if(!self->target)
{
gi.dprintf("target_set_effect w/o a target at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->use = target_set_effect_use;
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
}
/*QUAKED target_global_text (1 0 0) (-8 -8 -8) (8 8 8)
Send a string to all clients to be printed.
message - what to print.
*/
void target_global_text_use (edict_t *self, edict_t *activator, edict_t *other)
{
//FIXME : Send this to all clients notjust the activator.
//gi.centerprintf (activator, "%s", self->message);
gi.bprintf (PRINT_CHAT, "%s\n", self->message);
}
void SP_target_global_text (edict_t *self)
{
if (!self->message)
{
gi.dprintf("target_global_text at %s with no message\n", vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if (!self->targetname)
{
gi.dprintf("target_global_text at %s with no targetname\n", vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->use = target_global_text_use;
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
}
/*QUAKED target_ignore_player (1 0 0) (-8 -8 -8) (8 8 8) OFF
Switches monsters ignoring the player on and off.
OFF - switch the effect off.
target - monster to switch
*/
/*#define IGNORE_CLIENT 64
void target_ignore_player_use (edict_t *self, edict_t *activator, edict_t *other)
{
if (self->spawnflags & 1)
self->enemy->monsterinfo.aiflags &= ~IGNORE_CLIENT;
else
self->enemy->monsterinfo.aiflags |= IGNORE_CLIENT;
}
void SP_target_ignore_player (edict_t *self)
{
if (!self->target)
{
gi.dprintf("target_ignore_player with out target at %s", vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->svflags = SVF_NOCLIENT;
self->think = VerifyTarget;
self->nextthink = 1;
self->use = target_ignore_player_use;
self->svflags |= SVF_NOCLIENT;
gi.linkentity (self);
}*/
/*QUAKED target_effect (1 0 0) (-8 -8 -8) (8 8 8) LoopOn LoopOff
Calls an effect when used
"target" ent aimed at
"sounds" splash or pallete index
"count" pixels/splash (1-255)
"wait" steam duration
"speed" steam speed
"style" Select from the list below
0 : Gunshot
1 : Blood
2 : Blaster
3 : Railtrail
4 : Shotgun
5 : Explosion1
6 : Explosion2
7 : Rocket explosion
8 : Grenade explosion
9 : Sparks
10 : Splash
11 : Bubbletrail
12 : Screen sparks
13 : Shield sparks
14 : Bullet sparks
15 : Laser sparks
16 : Parasite attack
17 : Rocket expl (water)
18 : Grenade expl (water)
19 : Medic cable attack
20 : BFG explosion
21 : BFG big explosion
22 : BossTport
23 : BFG laser
24 : Grapple cable
25 : Welding sparks
26 : GreenBlood
28 : Plasma explosion
29 : Tunnel sparks
30 : Blaster2
33 : Lightning
34 : Debugtrail
35 : Plain explosion
36 : Flashlight
38 : Heatbeam
39 : Monster heatbeam
40 : Steam
41 : Bubbletrail2
42 : MoreBlood
43 : Heatbeam sparks
44 : Heatbeam steam
45 : Chainfist smoke
46 : Electric sparks
47 : Tracker explosion
48 : Teleport effect
49 : DBall goal
50 : WidowBeamOut
51 : NukeBlast
52 : WidowSplash
53 : Explosion1 Big
54 : Explosion1 NP
55 : Flechette
*/
/*====================================================================================
TARGET_EFFECT
======================================================================================*/
/* Unknowns or not supported
TE_FLAME, 32 Rogue flamethrower, never implemented
TE_FORCEWALL, 37 ??
*/
//=========================================================================
/* Spawns an effect at the entity origin
TE_FLASHLIGHT 36
*/
void target_effect_at (edict_t *self, edict_t *activator)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (self->style);
gi.WritePosition (self->s.origin);
gi.WriteShort (self - g_edicts);
gi.multicast (self->s.origin, MULTICAST_PVS);
}
/* Poor man's target_steam
TE_STEAM 40
*/
void target_effect_steam (edict_t *self, edict_t *activator)
{
static int nextid;
int wait;
if(self->wait)
wait = self->wait*1000;
else
wait = 0;
if (nextid > 20000)
nextid = nextid %20000;
nextid++;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (self->style);
gi.WriteShort (nextid);
gi.WriteByte (self->count);
gi.WritePosition (self->s.origin);
gi.WriteDir (self->movedir);
gi.WriteByte (self->sounds&0xff);
gi.WriteShort ( (int)(self->speed) );
gi.WriteLong ( (int)(wait) );
gi.multicast (self->s.origin, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectSteam (self->s.origin,self->movedir,self->count,self->sounds,(int)(self->speed),wait,nextid);
}
//=========================================================================
/*
Spawns (style) Splash with (count) particles of (sounds) color at (origin)
moving in (movedir) direction.
TE_SPLASH 10 Randomly shaded shower of particles
TE_LASER_SPARKS 15 Splash particles obey gravity
TE_WELDING_SPARKS 25 Splash particles with flash of light at {origin}
*/
//=========================================================================
void target_effect_splash (edict_t *self, edict_t *activator)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(self->style);
gi.WriteByte(self->count);
gi.WritePosition(self->s.origin);
gi.WriteDir(self->movedir);
gi.WriteByte(self->sounds);
gi.multicast(self->s.origin, MULTICAST_PVS);
}
//======================================================
/*
Spawns a trail of (type) from (start) to (end) and Broadcasts to all
in Potentially Visible Set from vector (origin)
TE_RAILTRAIL 3 Spawns a blue spiral trail filled with white smoke
TE_BUBBLETRAIL 11 Spawns a trail of bubbles
TE_PARASITE_ATTACK 16
TE_MEDIC_CABLE_ATTACK 19
TE_BFG_LASER 23 Spawns a green laser
TE_GRAPPLE_CABLE 24
TE_RAILTRAIL2 31 NOT IMPLEMENTED IN ENGINE
TE_DEBUGTRAIL 34
TE_HEATBEAM, 38 Requires Rogue model
TE_MONSTER_HEATBEAM, 39 Requires Rogue model
TE_BUBBLETRAIL2 41
*/
//======================================================
void target_effect_trail (edict_t *self, edict_t *activator)
{
edict_t *target;
if(!self->target) return;
target = G_Find(NULL,FOFS(targetname),self->target);
if(!target) return;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(self->style);
if ((self->style == TE_PARASITE_ATTACK) || (self->style==TE_MEDIC_CABLE_ATTACK) ||
(self->style == TE_HEATBEAM) || (self->style==TE_MONSTER_HEATBEAM) ||
(self->style == TE_GRAPPLE_CABLE) )
gi.WriteShort(self-g_edicts);
gi.WritePosition(self->s.origin);
gi.WritePosition(target->s.origin);
if (self->style == TE_GRAPPLE_CABLE) {
gi.WritePosition(vec3_origin);
}
gi.multicast(self->s.origin, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
{
if((self->style == TE_RAILTRAIL) || (self->style == TE_BUBBLETRAIL) ||
(self->style == TE_BFG_LASER) || (self->style == TE_DEBUGTRAIL) ||
(self->style == TE_BUBBLETRAIL2))
ReflectTrail(self->style,self->s.origin,target->s.origin);
}
}
//===========================================================================
/* TE_LIGHTNING 33 Lightning bolt
Similar but slightly different syntax to trail stuff */
void target_effect_lightning(edict_t *self, edict_t *activator)
{
edict_t *target;
if (!self->target)
return;
target = G_Find(NULL,FOFS(targetname),self->target);
if (!target)
return;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (self->style);
gi.WriteShort (target - g_edicts); // destination entity
gi.WriteShort (self - g_edicts); // source entity
gi.WritePosition (target->s.origin);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PVS);
}
//===========================================================================
/*
Spawns sparks of (type) from (start) in direction of (movdir) and
Broadcasts to all in Potentially Visible Set from vector (origin)
TE_GUNSHOT 0 Spawns a grey splash of particles, with a bullet puff
TE_BLOOD 1 Spawns a spurt of red blood
TE_BLASTER 2 Spawns a blaster sparks
TE_SHOTGUN 4 Spawns a small grey splash of spark particles, with a bullet puff
TE_SPARKS 9 Spawns a red/gold splash of spark particles
TE_SCREEN_SPARKS 12 Spawns a large green/white splash of sparks
TE_SHIELD_SPARKS 13 Spawns a large blue/violet splash of sparks
TE_BULLET_SPARKS 14 Same as TE_SPARKS, with a bullet puff and richochet sound
TE_GREENBLOOD 26 Spurt of green (actually kinda yellow) blood
TE_BLUEHYPERBLASTER 27 NOT IMPLEMENTED
TE_BLASTER2 30 Green/white sparks with a yellow/white flash
TE_MOREBLOOD 42
TE_HEATBEAM_SPARKS 43
TE_HEATBEAM_STEAM 44
TE_CHAINFIST_SMOKE 45
TE_ELECTRIC_SPARKS 46
TE_FLECHETTE 55
*/
//======================================================
void target_effect_sparks (edict_t *self, edict_t *activator)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(self->style);
gi.WritePosition(self->s.origin);
if (self->style != TE_CHAINFIST_SMOKE)
gi.WriteDir(self->movedir);
gi.multicast(self->s.origin, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectSparks(self->style,self->s.origin,self->movedir);
}
//======================================================
/*
Spawns a (type) effect at (start} and Broadcasts to all in the
Potentially Hearable set from vector (origin)
TE_EXPLOSION1 5 airburst
TE_EXPLOSION2 6 ground burst
TE_ROCKET_EXPLOSION 7 rocket explosion
TE_GRENADE_EXPLOSION 8 grenade explosion
TE_ROCKET_EXPLOSION_WATER 17 underwater rocket explosion
TE_GRENADE_EXPLOSION_WATER 18 underwater grenade explosion
TE_BFG_EXPLOSION 20 BFG explosion sprite
TE_BFG_BIGEXPLOSION 21 BFG particle explosion
TE_BOSSTPORT 22
TE_PLASMA_EXPLOSION 28
TE_PLAIN_EXPLOSION 35
TE_TRACKER_EXPLOSION 47
TE_TELEPORT_EFFECT 48
TE_DBALL_GOAL 49 Identical to TE_TELEPORT_EFFECT?
TE_NUKEBLAST 51
TE_WIDOWSPLASH 52
TE_EXPLOSION1_BIG 53 Works, but requires Rogue models/objects/r_explode2
TE_EXPLOSION1_NP 54
*/
//==============================================================================
void target_effect_explosion (edict_t *self, edict_t *activator)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(self->style);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS);
// Lazarus reflections
if (level.num_reflectors)
ReflectExplosion (self->style, self->s.origin);
}
//===============================================================================
/* TE_TUNNEL_SPARKS 29
Similar to other splash effects, but Xatrix does some funky things with
the origin so we'll do the same */
void target_effect_tunnel_sparks (edict_t *self, edict_t *activator)
{
vec3_t origin;
int i;
VectorCopy(self->s.origin,origin);
for (i=0; i<self->count; i++)
{
origin[2] += (self->speed * 0.01) * (i + random());
gi.WriteByte (svc_temp_entity);
gi.WriteByte (self->style);
gi.WriteByte (1);
gi.WritePosition (origin);
gi.WriteDir (vec3_origin);
gi.WriteByte (self->sounds + (rand()&7)); // color
gi.multicast (self->s.origin, MULTICAST_PVS);
}
}
//===============================================================================
/* TE_WIDOWBEAMOUT 50
*/
void target_effect_widowbeam(edict_t *self, edict_t *activator)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_WIDOWBEAMOUT);
gi.WriteShort (20001);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PVS);
}
//===============================================================================
void target_effect_use(edict_t *self, edict_t *other, edict_t *activator)
{
if(self->spawnflags & 1) {
// currently looped on - turn it off
self->spawnflags &= ~1;
self->spawnflags |= 2;
self->nextthink = 0;
return;
}
if(self->spawnflags & 2) {
// currently looped off - turn it on
self->spawnflags &= ~2;
self->spawnflags |= 1;
self->nextthink = level.time + self->wait;
}
if(self->spawnflags & 4) {
// "if_moving" set. If movewith target isn't moving,
// don't play
edict_t *mover;
if(!self->movewith) return;
mover = G_Find(NULL,FOFS(targetname),self->movewith);
if(!mover) return;
if(!VectorLength(mover->velocity)) return;
}
self->play(self,activator);
}
void target_effect_think(edict_t *self)
{
self->play(self,NULL);
self->nextthink = level.time + self->wait;
}
//===============================================================================
void SP_target_effect (edict_t *self)
{
self->class_id = ENTITY_TARGET_EFFECT;
if(self->movewith)
self->movetype = MOVETYPE_PUSH;
else
self->movetype = MOVETYPE_NONE;
switch (self->style )
{
case TE_FLASHLIGHT:
self->play = target_effect_at;
break;
case TE_STEAM:
self->play = target_effect_steam;
G_SetMovedir (self->s.angles, self->movedir);
if(!self->count)
self->count = 32;
if(!self->sounds)
self->sounds = 8;
if(!self->speed)
self->speed = 75;
break;
case TE_SPLASH:
case TE_LASER_SPARKS:
case TE_WELDING_SPARKS:
self->play = target_effect_splash;
G_SetMovedir (self->s.angles, self->movedir);
if(!self->count)
self->count = 32;
break;
case TE_RAILTRAIL:
case TE_BUBBLETRAIL:
case TE_PARASITE_ATTACK:
case TE_MEDIC_CABLE_ATTACK:
case TE_BFG_LASER:
case TE_GRAPPLE_CABLE:
case TE_DEBUGTRAIL:
case TE_HEATBEAM:
case TE_MONSTER_HEATBEAM:
case TE_BUBBLETRAIL2:
if(!self->target)
{
gi.dprintf("%s at %s with style=%d needs a target\n",self->classname,vtos(self->s.origin),self->style);
G_FreeEdict(self);
} else
self->play = target_effect_trail;
break;
case TE_LIGHTNING:
if(!self->target)
{
gi.dprintf("%s at %s with style=%d needs a target\n",self->classname,vtos(self->s.origin),self->style);
G_FreeEdict(self);
}
else
self->play = target_effect_lightning;
break;
case TE_GUNSHOT:
case TE_BLOOD:
case TE_BLASTER:
case TE_SHOTGUN:
case TE_SPARKS:
case TE_SCREEN_SPARKS:
case TE_SHIELD_SPARKS:
case TE_BULLET_SPARKS:
case TE_GREENBLOOD:
case TE_BLASTER2:
case TE_MOREBLOOD:
case TE_HEATBEAM_SPARKS:
case TE_HEATBEAM_STEAM:
case TE_CHAINFIST_SMOKE:
case TE_ELECTRIC_SPARKS:
case TE_FLECHETTE:
self->play = target_effect_sparks;
G_SetMovedir (self->s.angles, self->movedir);
break;
case TE_EXPLOSION1:
case TE_EXPLOSION2:
case TE_ROCKET_EXPLOSION:
case TE_GRENADE_EXPLOSION:
case TE_ROCKET_EXPLOSION_WATER:
case TE_GRENADE_EXPLOSION_WATER:
case TE_BFG_EXPLOSION:
case TE_BFG_BIGEXPLOSION:
case TE_BOSSTPORT:
case TE_PLASMA_EXPLOSION:
case TE_PLAIN_EXPLOSION:
case TE_TRACKER_EXPLOSION:
case TE_TELEPORT_EFFECT:
case TE_DBALL_GOAL:
case TE_NUKEBLAST:
case TE_WIDOWSPLASH:
case TE_EXPLOSION1_BIG:
case TE_EXPLOSION1_NP:
self->play = target_effect_explosion;
break;
case TE_TUNNEL_SPARKS:
if(!self->count)
self->count = 32;
if(!self->sounds)
self->sounds = 116; // Light blue, same color used by Xatrix
self->play = target_effect_tunnel_sparks;
break;
case TE_WIDOWBEAMOUT:
self->play = target_effect_widowbeam;
G_SetMovedir (self->s.angles, self->movedir);
break;
default:
gi.dprintf("%s at %s: bad style %d\n",self->classname,vtos(self->s.origin),self->style);
}
self->use = target_effect_use;
self->think = target_effect_think;
if(self->spawnflags & 1)
self->nextthink = level.time + 1;
}
/*QUAKED target_movewith (1 0 0) (-8 -8 -8) (8 8 8) DETACH
Change or remove an entity's movewith field. Useful for
attaching or detaching it from a parent entity (usually a train).
DETACH: Just remove its movewith field
target : Targetname of entity to attach/detach.
pathtarget : Targetname of parent entity to attach to. Not needed to detach.
count : Number of times it can be used
*/
void movewith_init (edict_t *ent);
void target_movewith_use (edict_t *self, edict_t *activator, edict_t *other)
{
edict_t *t = NULL;
if (!self->target)
return;
while ((t = G_Find (t, FOFS(targetname), self->target)))
{
if (self->spawnflags & 1)
{
if (t->oldmovetype) // restore movetype
t->movetype = t->oldmovetype;
t->movewith_ent = NULL;
t->movewith = NULL;
VectorClear(t->movewith_offset);
t->movewith_set = 0;
if (t->svflags & SVF_MONSTER) // toss monsters up a little bit so they won't be stuck
t->s.origin[2] += 2;
}
else if (self->pathtarget)
t->movewith = self->pathtarget;
gi.linkentity(t);
}
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
}
void SP_target_movewith (edict_t *self)
{
if (!self->targetname)
gi.dprintf("target_movewith without a targetname at %s\n", vtos(self->s.origin));
if (!self->target)
gi.dprintf("target_movewith without a target at %s\n", vtos(self->s.origin));
if (!self->pathtarget && !(self->spawnflags & 1))
gi.dprintf("target_movewith without a pathtarget at %s\n", vtos(self->s.origin));
self->svflags |= SVF_NOCLIENT;
self->use = target_movewith_use;
gi.linkentity (self);
}
/*QUAKED target_change (1 0 0) (-8 -8 -8) (8 8 8) Spawnflags Spawnflags Spawnflags Spawnflags Spawnflags Spawnflags Spawnflags Spawnflags
An entity changer
"newtargetname" The new targetname value you may wish to assign to the targeted entity.
"target" Two values are valid here; the first is the targetname of the entity whose keyvalue(s) you wish to alter,
and the second (optional) value is the new value for "target" that you may wish to assign to the targeted entity.
If two values are used, then they should be separated by a comma.
Syntax: targeted_entity_name,new_target_value.
"targetname" The name of the specific target_change.
SPAWNFLAGS Virtually any other keyvalue may be assigned, assuming the targeted entity can use it.
*/
void target_change_use (edict_t *self, edict_t *activator, edict_t *other)
{
char *buffer;
char *target;
char *newtarget;
int L;
int newteams=0;
edict_t *target_ent;
if(!self->target)
return;
L = (int)strlen(self->target);
buffer = (char *)gi.TagMalloc(L+1, TAG_LEVEL);
strcpy(buffer, self->target);
newtarget = strstr(buffer,",");
if(newtarget)
{
*newtarget = 0;
newtarget++;
}
target = buffer;
target_ent = G_Find(NULL,FOFS(targetname),target);
while(target_ent)
{
if(newtarget && strlen(newtarget))
target_ent->target = G_CopyString(newtarget);
if(self->newtargetname && strlen(self->newtargetname))
target_ent->targetname = G_CopyString(self->newtargetname);
if(self->team && strlen(self->team))
{
target_ent->team = G_CopyString(self->team);
newteams++;
}
if(VectorLength(self->s.angles))
{
VectorCopy (self->s.angles, target_ent->s.angles);
if(target_ent->solid == SOLID_BSP)
G_SetMovedir (target_ent->s.angles, target_ent->movedir);
}
if(self->deathtarget && strlen(self->deathtarget))
target_ent->deathtarget = G_CopyString(self->deathtarget);
if(self->pathtarget && strlen(self->pathtarget))
target_ent->pathtarget = G_CopyString(self->pathtarget);
if(self->killtarget && strlen(self->killtarget))
target_ent->killtarget = G_CopyString(self->killtarget);
if(self->message && strlen(self->message))
target_ent->message = G_CopyString(self->message);
if(self->delay > 0)
target_ent->delay = self->delay;
if(self->dmg > 0)
target_ent->dmg = self->dmg;
if(self->health > 0)
target_ent->health = self->health;
if(self->mass > 0)
target_ent->mass = self->mass;
if(self->pitch_speed > 0)
target_ent->pitch_speed = self->pitch_speed;
if(self->random > 0)
target_ent->random = self->random;
if(self->roll_speed > 0)
target_ent->roll_speed = self->roll_speed;
if(self->wait > 0)
target_ent->wait = self->wait;
if(self->yaw_speed > 0)
target_ent->yaw_speed = self->yaw_speed;
if(self->noise_index)
{
if(target_ent->s.sound == target_ent->noise_index)
target_ent->s.sound = target_ent->noise_index = self->noise_index;
else
target_ent->noise_index = self->noise_index;
}
#ifdef LOOP_SOUND_ATTENUATION
if(self->attenuation)
{
if(target_ent->s.attenuation == target_ent->attenuation)
target_ent->s.attenuation = target_ent->attenuation = self->attenuation;
else
target_ent->attenuation = self->attenuation;
}
#endif
if(self->spawnflags)
{
target_ent->spawnflags = self->spawnflags;
// special cases:
if(!Q_stricmp(target_ent->classname,"model_train"))
{
if(target_ent->spawnflags & 32)
{
target_ent->spawnflags &= ~32;
target_ent->spawnflags |= 8;
}
if(target_ent->spawnflags & 64)
{
target_ent->spawnflags &= ~64;
target_ent->spawnflags |= 16;
}
}
}
gi.linkentity(target_ent);
target_ent = G_Find(target_ent,FOFS(targetname),target);
}
gi.TagFree(buffer);
if(newteams)
G_FindTeams();
}
void SP_target_change (edict_t *self)
{
if (!self->targetname)
gi.dprintf("target_change without a targetname at %s\n", vtos(self->s.origin));
if (!self->target)
gi.dprintf("target_change without a target at %s\n", vtos(self->s.origin));
self->svflags |= SVF_NOCLIENT;
self->use = target_change_use;
if (st.noise) //David Hyde's code
self->noise_index = gi.soundindex(st.noise);
gi.linkentity (self);
}
#define ROTATION_NO_LOOP 1
#define ROTATION_RANDOM 2
/*QUAKED target_rotation (.5 .5 .5) (-8 -8 -8) (8 8 8) NO_LOOP RANDOM
A target "cycler." Every time you trigger it, it picks from among its string of targets, either in order, or randomly depending on the spawnflag setting.
"target" Comma-separated targets to choose from, e.g. "targ1,targ2,targ3" (Do not include the quotation marks)
"count" how many times it can be used
*/
void target_rotation_use (edict_t *self, edict_t *activator, edict_t *other)
{
edict_t *target;
int i, pick;
char *p1, *p2;
char targetname[256];
if(self->spawnflags & 2) {
// random pick
pick = self->sounds * random();
if(pick == self->sounds) pick--;
} else {
pick = self->mass;
if(pick == self->sounds) {
if(self->spawnflags & 1) // no loop
return;
else
pick = 0;
}
self->mass = pick+1;
}
p1 = self->target;
p2 = targetname;
memset(targetname,0,sizeof(targetname));
// skip over pick commas
for(i=0; i<pick; i++) {
p1 = strstr(p1,",");
if(p1)
p1++;
else
return; // should never happen
}
while(*p1 != 0 && *p1 != ',') {
*p2 = *p1;
p1++;
p2++;
}
target = G_Find(NULL,FOFS(targetname),targetname);
while(target) {
if(target->inuse && target->use)
target->use(target,other,activator);
target = G_Find(target,FOFS(targetname),targetname);
}
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
/* char *target_ent = NULL;
char *savetarget;
int i = 0;
if (self->spawnflags & ROTATION_RANDOM) //random cycling
{
int j = rand() % self->dmg + 1;
for ( target_ent = strtok(self->target,","); target_ent != NULL; target_ent = strtok(NULL, ",") )
{
i++;
if (i == j) break;
}
}
else //fire target enumerated by self->health
{
for ( target_ent = strtok(self->target,","); target_ent != NULL; target_ent = strtok(NULL, ",") )
{
i++;
if (i == self->health) break;
}
self->health++;
if (self->health > self->dmg) //if at end of string
{
if (self->spawnflags & ROTATION_NO_LOOP) //if no looping, remove
{
self->use = NULL;
self->think = G_FreeEdict;
self->nextthink = level.time + FRAMETIME;
}
else //start over
self->health = 1;
}
}
//now fire selected target
savetarget = self->target;
self->target = target_ent;
G_UseTargets (self, self->activator);
self->target = savetarget;*/
}
void SP_target_rotation (edict_t *self)
{
char *p;
if(!self->target)
{
gi.dprintf("target_rotation without a target at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if( (self->spawnflags & 3) == 3)
{
gi.dprintf("target_rotation at %s: NO_LOOP and RANDOM are mutually exclusive.\n");
self->spawnflags = 2;
}
self->use = target_rotation_use;
self->svflags = SVF_NOCLIENT;
self->mass = 0; // index of currently selected target
self->sounds = 0; // number of comma-delimited targets in target string
p = self->target;
while( (p = strstr(p,",")) != NULL)
{
self->sounds++;
p++;
}
self->sounds++;
/* if (!self->targetname)
gi.dprintf("target_rotation without a targetname at %s\n", vtos(self->s.origin));
if (!self->target)
gi.dprintf("target_rotation without a target at %s\n", vtos(self->s.origin));
if (!strstr(self->target, ","))
{
gi.dprintf("target_rotation with less than 2 targets at %s\n", vtos(self->s.origin));
return;
}
else //Count how many different targets we have
{
char *target1 = NULL;
self->dmg = 0; //dmg is number of targets
self->health = 1; //start at target 1
for ( target1 = strtok(self->target,","); target1 != NULL; target1 = strtok(NULL, ",") )
{
self->dmg++;
}
}
self->svflags |= SVF_NOCLIENT;
self->use = target_rotation_use;
gi.linkentity (self);*/
}
/*QUAKED target_cd (1 0 0) (-8 -8 -8) (8 8 8)
A CD/OGG track player
"count" number of times it can be used
"sounds" CD track number; default = 2
"musictrack" name of OGG track or CD track number, overrides "sounds"
"dmg" times to loop; default=1
*/
void target_cd_use (edict_t *self, edict_t *activator, edict_t *other)
{
if (self->musictrack && strlen(self->musictrack))
gi.configstring (CS_CDTRACK, self->musictrack);
else
gi.configstring (CS_CDTRACK, va("%d", self->sounds) );
if ((self->dmg > 0) && (!deathmatch->value) && (!coop->value))
stuffcmd(&g_edicts[1],va("cd_loopcount %d\n",self->dmg));
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + 1;
self->think = G_FreeEdict;
}
}
void SP_target_cd (edict_t *self)
{
if (!self->targetname)
gi.dprintf("target_cd without a targetname at %s\n", vtos(self->s.origin));
if (!self->sounds)
self->sounds = 2;
if (!self->dmg)
self->dmg = lazarus_cd_loop->value;
self->svflags |= SVF_NOCLIENT;
self->use = target_cd_use;
gi.linkentity (self);
}
/*QUAKED target_skill (1 0 0) (-8 -8 -8) (8 8 8)
Change skill level on the fly
"targetname" when triggered
"count" number of times it can be used
"style"
0 : Easy
1 : Normal (Default)
2 : Hard
3 : Nightmare
*/
void use_target_skill (edict_t *self, edict_t *other, edict_t *activator)
{
level.next_skill = self->style + 1;
self->count--;
if(self->count == 0)
G_FreeEdict(self);
}
void SP_target_skill (edict_t *self)
{
self->use = use_target_skill;
}
/*QUAKED target_sky (1 0 0) (-8 -8 -8) (8 8 8)
Change the level's environment map
"sky" env map name
"count" number of times it can be used
*/
void target_sky_use (edict_t *self, edict_t *activator, edict_t *other)
{
gi.configstring(CS_SKY,self->pathtarget);
stuffcmd(&g_edicts[1],va("sky %s\n",self->pathtarget));
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
}
void SP_target_sky (edict_t *self)
{
if(!st.sky || !*st.sky)
{
gi.dprintf("Target_sky with no sky string at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->pathtarget = gi.TagMalloc(strlen(st.sky)+1,TAG_LEVEL);
strcpy(self->pathtarget, st.sky);
self->svflags |= SVF_NOCLIENT;
self->use = target_sky_use;
gi.linkentity (self);
}
/*=============================================================================
TARGET_FADE fades the screen to a color
color = R,G,B components of fade color, 0-1
alpha = opacity of fade. 0=no effect, 1=solid color
fadein = time in seconds from trigger until full alpha
fadeout = time in seconds after fadein+holdtime from full alpha to clear screen
holdtime = time to hold the effect at full alpha value. -1 = permanent
=============================================================================*/
void use_target_fade (edict_t *self, edict_t *other, edict_t *activator)
{
if(!activator->client)
return;
activator->client->fadestart= level.framenum;
activator->client->fadein = level.framenum + self->fadein*10;
activator->client->fadehold = activator->client->fadein + self->holdtime*10;
activator->client->fadeout = activator->client->fadehold + self->fadeout*10;
activator->client->fadecolor[0] = self->color[0];
activator->client->fadecolor[1] = self->color[1];
activator->client->fadecolor[2] = self->color[2];
activator->client->fadealpha = self->alpha;
self->count--;
if(self->count == 0)
{
self->think = G_FreeEdict;
self->nextthink = level.time + 1;
}
}
void SP_target_fade (edict_t *self)
{
self->use = use_target_fade;
if(self->fadein < 0)
self->fadein = 0;
if(self->holdtime < 0)
{
self->count = 1;
self->holdtime = 10000;
}
if(self->fadeout < 0)
self->fadeout = 0;
}
/*QUAKED target_rocks (.5 .5 .5) (-8 -8 -8) (8 8 8)
Causes a rock slide when fired
"count" Number of times you can use it.
"angles" Angle to throw rocks
"mass" Debris amount. Default=500
"speed" How fast the rocks come down in units/sec. Default=400
*/
/*void rock_touch (edict_t *rock, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int damage;
damage = rock->mass * VectorLength(rock->velocity) * 0.0001;
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (rock);
return;
}
if (plane->normal && rock->sounds == 1)
gi.sound (rock, CHAN_AUTO, gi.soundindex("zer/brik_hit.wav"), 1.0, ATTN_NORM, 0);
//if (other->client || other->svflags & SVF_MONSTER)
if (other->takedamage)
T_Damage (other, rock, rock, rock->velocity, rock->s.origin, plane->normal, damage, 0, 0, MOD_FALLING_ROCKS);
}*/
//void spawn_rock1 (edict_t *self, float speed)
void ThrowRock (edict_t *self, char *modelname, float speed, vec3_t origin, vec3_t size, int mass)
{
edict_t *rock;
vec_t var = speed/5;
rock = G_Spawn();
VectorCopy (origin, rock->s.origin);
gi.setmodel (rock, modelname);
VectorCopy(size,rock->maxs);
VectorScale(rock->maxs,0.5,rock->maxs);
VectorNegate(rock->maxs,rock->mins);
rock->velocity[0] = speed * self->movedir[0] + var * crandom();
rock->velocity[1] = speed * self->movedir[1] + var * crandom();
rock->velocity[2] = speed * self->movedir[2] + var * crandom();
rock->avelocity[0] = random()*600;
rock->avelocity[1] = random()*600;
rock->avelocity[2] = random()*600;
rock->movetype = MOVETYPE_DEBRIS;
rock->attenuation = 0.5;
rock->solid = SOLID_NOT;
rock->mass = mass;
// rock->touch = rock_touch;
rock->nextthink = level.time + 15 + random()*15;
rock->think = gib_fade;
rock->classname = "debris";
gi.linkentity (rock);
}
/*void spawn_rock2 (edict_t *self, float speed)
{
edict_t *rock;
vec3_t dir;
vec3_t forward, right, up;
vec_t var = speed/5;
vectoangles (self->s.angles, dir);
AngleVectors (dir, forward, right, up);
rock = G_Spawn();
VectorCopy (self->s.origin, rock->s.origin);
// VectorScale (self->movedir, speed, rock->velocity);
rock->velocity[0] = speed * self->movedir[0] + var * crandom();
rock->velocity[1] = speed * self->movedir[1] + var * crandom();
rock->velocity[2] = speed * self->movedir[2] + var * crandom();
// VectorMA (rock->velocity, crandom() * 10.0, forward, rock->velocity);
// VectorMA (rock->velocity, crandom() * 10.0, right, rock->velocity);
// VectorSet (rock->avelocity, 300, 300, 300);
rock->avelocity[0] = random()*600;
rock->avelocity[1] = random()*600;
rock->avelocity[2] = random()*600;
rock->movetype = MOVETYPE_DEBRIS;
rock->clipmask = MASK_SHOT;
rock->solid = SOLID_BBOX;
VectorClear (rock->mins);
VectorClear (rock->maxs);
rock->mass = 25;
rock->sounds = self->sounds;
rock->s.modelindex = gi.modelindex ("models/objects/rock2/tris.md2");
rock->touch = rock_touch;
rock->nextthink = level.time + 10 + random()*10;
rock->think = gib_fade;
rock->classname = "rock";
gi.linkentity (rock);
SV_AddGravity (rock);
}*/
void target_rocks_use (edict_t *self, edict_t *activator, edict_t *other)
{
vec3_t chunkorigin;
vec3_t size, source;
vec_t mass;
int count;
char modelname[64];
// int r1, r2, i;
// r1 = min (16, (self->mass / 100));
// r2 = min (16, (self->mass / 25));
VectorSet(source,8,8,8);
mass = self->mass;
//set movedir here- if we're
G_SetMovedir2 (self->s.angles, self->movedir);
// if (self->sounds == 1)
// gi.sound (self, CHAN_AUTO, gi.soundindex("zer/wall01.wav"), 1.0, ATTN_NORM, 0);
// big chunks
if (mass >= 100)
{
Com_sprintf(modelname, sizeof(modelname), "models/objects/rock%d/tris.md2",self->style*2+1);
count = mass / 100;
if (count > 16)
count = 16;
VectorSet(size,8,8,8);
while(count--)
{
chunkorigin[0] = self->s.origin[0] + crandom() * source[0];
chunkorigin[1] = self->s.origin[1] + crandom() * source[1];
chunkorigin[2] = self->s.origin[2] + crandom() * source[2];
ThrowRock (self, modelname, self->speed, chunkorigin, size, 100);
}
}
// small chunks
count = mass / 25;
Com_sprintf(modelname, sizeof(modelname), "models/objects/rock%d/tris.md2",self->style*2+2);
if (count > 16)
count = 16;
VectorSet(size,4,4,4);
while(count--)
{
chunkorigin[0] = self->s.origin[0] + crandom() * source[0];
chunkorigin[1] = self->s.origin[1] + crandom() * source[1];
chunkorigin[2] = self->s.origin[2] + crandom() * source[2];
ThrowRock (self, modelname, self->speed, chunkorigin, size, 25);
}
if (self->dmg)
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
/* for (i = 0; i < r1; i++)
spawn_rock1 (self, self->speed);
for (i = 0; i < r2; i++)
spawn_rock2 (self, self->speed);*/
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
}
void SP_target_rocks (edict_t *self)
{
if (!self->targetname)
gi.dprintf("target_rocks without a targetname at %s\n", vtos(self->s.origin));
if (!self->mass)
self->mass = 500;
if (!self->speed)
self->speed = 400;
// G_SetMovedir (self->s.angles, self->movedir);
self->svflags |= SVF_NOCLIENT;
self->use = target_rocks_use;
gi.linkentity (self);
}
/*QUAKED target_clone (1 0 0) (-8 -8 -8) (8 8 8) START_ON SET_MOVEDIR
Clones entities, works the same as target_bmodel_spawner
START_ON: It will spawn the entity clone at map load.
SET_MOVEDIR: Set the door, button, etc's movedir using move_angles instead of copying it from the parent entity.
"targetname" Name of the specific target_bmodel_spawner.
"source" Targetname of the brush model entity which is to be used as a model reference.
"count" When non-zero, specifies the number of times the target_bmodel_spawner will be called before being auto-killtargeted. Default=0.
"angle" facing angle of the spawned brush entity on the XY plane, relative to its referenced parent model. Default=0.
"angles" facing angle of the spawned brush entity in 3 dimensions, relative to its referenced parent model, as defined by pitch, yaw, and roll. Default=0 0 0.
"move_angles" Movement angle for the spawned brush entity (pitch, yaw, roll[ignored])
"alpha" Alpha value for the spawned entity (between 0 and 1)
"target" value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not have a target.
"followtarget" Followtarget for new bmodel, specific to func_door_swinging. If no value is given to this key, then the clone will not have a followtarget.
"deathtarget" value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not have a deathtarget.
"pathtarget" value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not have a pathtarget.
"killtarget" value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not have a killtarget.
"newtargetname" Targetname value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not have a targetname.
"newteam" Team value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not be on a team. (This should be used with START_ON, so that there's no chance one teammate can be in the process of moving while another teammate is being spawned).
"team" value which will be inherited by the spawned clone. If no value is given to this key, then the clone will not be on a team. (This should be used with START_ON, so that there's no chance one teammate can be in the process of moving while another teammate is being spawned).
*/
//void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
//void Think_CalcMoveSpeed (edict_t *self);
//void Think_SpawnDoorTrigger (edict_t *ent);
//void func_train_find (edict_t *self);
void clone (edict_t *self)
{
edict_t *ent;
edict_t *source;
self->nextthink = 0;
source = G_Find (NULL, FOFS(targetname), self->source);
if(!source)
{
gi.dprintf("%s at %s, source not found\n", self->classname, vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if(!source->classname)
{
gi.dprintf("%s at %s, source at %s has no classname\n",
self->classname, vtos(self->s.origin), vtos(source->s.origin));
G_FreeEdict(self);
return;
}
if(!source->s.modelindex)
{
gi.dprintf("%s at %s, source %s at %s not a model entity\n",
self->classname, vtos(self->s.origin), source->classname, vtos(source->s.origin));
G_FreeEdict(self);
return;
}
//spawn and copy origin and angles
ent = G_Spawn();
VectorCopy(self->s.origin, ent->s.origin);
VectorCopy(self->s.angles, ent->s.angles);
if (self->spawnflags & 2)
G_SetMovedir2 (self->move_angles, ent->movedir);
else
VectorCopy(source->movedir,ent->movedir);
ent->model = source->model;
ent->s.modelindex = source->s.modelindex;
//copy fields from source
ent->classname = gi.TagMalloc(strlen(source->classname)+1,TAG_LEVEL);
strcpy(ent->classname,source->classname);
// ent->classname = source->classname;
ent->spawnflags = source->spawnflags;
ent->svflags = source->svflags;
VectorCopy(source->mins,ent->mins);
VectorCopy(source->maxs,ent->maxs);
VectorCopy(source->size,ent->size);
ent->solid = source->solid;
ent->clipmask = source->clipmask;
ent->movetype = source->movetype;
ent->mass = source->mass;
ent->speed = source->speed;
ent->accel = source->accel;
ent->decel = source->decel;
ent->lip = source->lip;
ent->distance = source->distance;
ent->health = source->health;
ent->max_health = source->max_health;
ent->takedamage = source->takedamage;
ent->dmg = source->dmg;
ent->count = source->count;
ent->sounds = source->sounds;
ent->noise_index = source->noise_index;
ent->noise_index2 = source->noise_index2;
ent->wait = source->wait;
ent->delay = source->delay;
ent->random = source->random;
ent->style = source->style;
ent->flags = source->flags;
ent->blocked = source->blocked;
ent->touch = source->touch;
ent->use = source->use;
ent->pain = source->pain;
ent->die = source->die;
ent->renderfx = source->renderfx;
ent->s.effects = source->s.effects;
#ifdef KMQUAKE2_ENGINE_MOD //Knightmare added
ent->s.alpha = source->s.alpha;
#endif
ent->s.skinnum = source->s.skinnum;
ent->skinnum = source->skinnum;
ent->gib_type = source->gib_type;
ent->item = source->item;
ent->moveinfo.sound_start = source->moveinfo.sound_start;
ent->moveinfo.sound_middle = source->moveinfo.sound_middle;
ent->moveinfo.sound_end = source->moveinfo.sound_end;
ent->pitch_speed = source->pitch_speed;
ent->yaw_speed = source->yaw_speed;
ent->roll_speed = source->roll_speed;
if(VectorLength(ent->s.angles) != 0)
{
if(ent->s.angles[YAW] == 90 || ent->s.angles[YAW] == 270)
{ // We're correct for these angles, not even gonna bother with others
vec_t temp;
temp = ent->size[0];
ent->size[0] = ent->size[1];
ent->size[1] = temp;
temp = ent->mins[0];
if(ent->s.angles[YAW] == 90)
{
ent->mins[0] = -ent->maxs[1];
ent->maxs[1] = ent->maxs[0];
ent->maxs[0] = -ent->mins[1];
ent->mins[1] = temp;
}
else
{
ent->mins[0] = ent->mins[1];
ent->mins[1] = -ent->maxs[0];
ent->maxs[0] = ent->maxs[1];
ent->maxs[1] = -temp;
}
}
vectoangles(ent->movedir,ent->movedir);
ent->movedir[PITCH] += ent->s.angles[PITCH];
ent->movedir[YAW] += ent->s.angles[YAW];
ent->movedir[ROLL] += ent->s.angles[ROLL];
if(ent->movedir[PITCH] > 360)
ent->movedir[PITCH] -= 360;
if(ent->movedir[YAW] > 360)
ent->movedir[YAW] -= 360;
if(ent->movedir[ROLL] > 360)
ent->movedir[ROLL] -= 360;
AngleVectors(ent->movedir,ent->movedir,NULL,NULL);
}
VectorAdd(ent->s.origin,ent->mins,ent->absmin);
VectorAdd(ent->s.origin,ent->maxs,ent->absmax);
//fields from bmodel spawner
if (self->target)
ent->target = G_CopyString(self->target);
if (self->followtarget)
ent->followtarget = G_CopyString(self->followtarget);
if (self->deathtarget)
ent->deathtarget = G_CopyString(self->deathtarget);
if (self->pathtarget)
ent->pathtarget = G_CopyString(self->pathtarget);
if (self->killtarget)
ent->killtarget = G_CopyString(self->killtarget);
if (self->newtargetname)
ent->targetname = G_CopyString(self->newtargetname);
if (self->team)
ent->team = G_CopyString(self->team);
if (self->newteam)
ent->team = G_CopyString(self->newteam);
#ifdef KMQUAKE2_ENGINE_MOD //Knightmare added
if ((self->alpha >= 0.0) && (self->alpha <= 1.0))
ent->s.alpha = self->alpha;
#endif
ent->svflags |= SVF_CLONED; //mark this entity as cloned
ReInitialize_Entity(ent); //call its spawn function
//set up orgin offset
VectorAdd(ent->absmin, ent->absmax, ent->origin_offset);
VectorScale(ent->origin_offset, 0.5, ent->origin_offset);
VectorSubtract(ent->origin_offset, ent->s.origin, ent->origin_offset);
//Kill anything in the way
gi.unlinkentity(ent);
//Knightmare- only killbox if spawned entity is solid
if (ent->solid == SOLID_BSP || ent->solid == SOLID_BBOX)
KillBox(ent);
gi.linkentity(ent);
self->count--;
if (self->count == 0)
{
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
}
void target_clone_use (edict_t *self, edict_t *activator, edict_t *other)
{
clone (self);
}
void SP_target_clone (edict_t *self)
{
if (!self->targetname && !(self->spawnflags & 1))
{
gi.dprintf("%s without a targetname and without start_on at %s\n", self->classname, vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if (!self->source)
{
gi.dprintf("%s without a source at %s\n", self->classname, vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->svflags |= SVF_NOCLIENT;
self->use = target_clone_use;
gi.linkentity (self);
if (self->spawnflags & 1) //START_ON
{
self->think = clone;
self->nextthink = level.time + FRAMETIME;
}
}
/*=====================================================================================
TARGET_ATTRACTOR - pulls target entity towards its origin
target - Targetname of entity to attract. Ignored if PLAYER spawnflag is set
pathtarget - Entity or entities to "use" when distance criteria is met.
speed - Minimum speed to pull target with. Must use a value > sv_gravity/10
to overcome gravity when pulling up.
distance - When target is within "distance" units of target_attractor, attraction
is shut off. Use a value < 0 to hold target in place. 0 will be reset
to 1.
sounds - effect to use. ONLY VALID for PLAYER or MONSTER, not target. If sounds
is non-zero, SINGLE_TARGET and SIGHT are automatically set
0 = none
1 = medic cable
2 = green laser
Spawnflags: 1 - START_ON
2 - PLAYER (attract player, ignore "target"
4 - NO_GRAVITY - turns off gravity for target. W/O this flag you'll
get an annoying jitter when pulling players up.
8 - MONSTER - attract ALL monsters, ignore "target"
16 - SIGHT - must have LOS to target to attract it
32 - SINGLE_TARGET - will select best target
64 - PATHTARGET_FIRE - used internally only
=======================================================================================*/
#define ATTRACTOR_ON 1
#define ATTRACTOR_PLAYER 2
#define ATTRACTOR_NO_GRAVITY 4
#define ATTRACTOR_MONSTER 8
#define ATTRACTOR_SIGHT 16
#define ATTRACTOR_SINGLE 32
#define ATTRACTOR_PATHTARGET 64
void target_attractor_think_single(edict_t *self)
{
edict_t *ent, *target, *previous_target;
trace_t tr;
vec3_t dir, targ_org;
vec_t dist, speed;
vec_t best_dist;
vec3_t forward, right;
int i;
int num_targets = 0;
if ( !(self->spawnflags & ATTRACTOR_ON) ) return;
previous_target = self->target_ent;
target = NULL;
best_dist = WORLD_SIZE; // was 8192
if(self->spawnflags & ATTRACTOR_PLAYER)
{
for(i=1, ent=&g_edicts[i]; i<=game.maxclients; i++, ent++)
{
if(!ent->inuse) continue;
if(ent->health <= 0) continue;
num_targets++;
VectorSubtract(self->s.origin,ent->s.origin,dir);
dist = VectorLength(dir);
if(dist > self->moveinfo.distance) continue;
if(self->spawnflags & ATTRACTOR_SIGHT)
{
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,ent->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
if(tr.ent != ent) continue;
}
if(dist < best_dist)
{
best_dist = dist;
target = ent;
}
}
}
if(self->spawnflags & ATTRACTOR_MONSTER)
{
for(i=1, ent=&g_edicts[i]; i<=globals.num_edicts; i++, ent++)
{
if(!ent->inuse) continue;
if(ent->health <= 0) continue;
if(!(ent->svflags & SVF_MONSTER)) continue;
num_targets++;
VectorSubtract(self->s.origin,ent->s.origin,dir);
dist = VectorLength(dir);
if(dist > self->moveinfo.distance) continue;
if(self->spawnflags & ATTRACTOR_SIGHT)
{
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,ent->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
if(tr.ent != ent) continue;
}
if(dist < best_dist)
{
best_dist = dist;
target = ent;
}
}
}
if(!(self->spawnflags & (ATTRACTOR_PLAYER | ATTRACTOR_MONSTER)))
{
ent = G_Find(NULL,FOFS(targetname),self->target);
while(ent)
{
if(!ent->inuse) continue;
num_targets++;
VectorAdd(ent->s.origin,ent->origin_offset,targ_org);
VectorSubtract(self->s.origin,targ_org,dir);
dist = VectorLength(dir);
if(dist > self->moveinfo.distance) continue;
if(self->spawnflags & ATTRACTOR_SIGHT)
{
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,targ_org,NULL,MASK_OPAQUE | MASK_SHOT);
if(tr.ent != ent) continue;
}
if(dist < best_dist)
{
best_dist = dist;
target = ent;
}
ent = G_Find(ent,FOFS(targetname),self->target);
}
}
self->target_ent = target;
if(!target)
{
if(num_targets > 0) self->nextthink = level.time + FRAMETIME;
return;
}
if(target != previous_target)
self->moveinfo.speed = 0;
if(self->moveinfo.speed != self->speed)
{
if(self->speed > 0)
self->moveinfo.speed = min(self->speed, self->moveinfo.speed + self->accel);
else
self->moveinfo.speed = max(self->speed, self->moveinfo.speed + self->accel);
}
VectorAdd(target->s.origin,target->origin_offset,targ_org);
VectorSubtract(self->s.origin,targ_org,dir);
dist = VectorLength(dir);
if(readout->value) gi.dprintf("distance=%g, pull speed=%g\n",dist,self->moveinfo.speed);
if((self->pathtarget) && (self->spawnflags & ATTRACTOR_PATHTARGET))
{
if(dist == 0)
{
// fire pathtarget when close
ent = G_Find(NULL,FOFS(targetname),self->pathtarget);
while(ent) {
if(ent->use)
ent->use(ent,self,self);
ent = G_Find(ent,FOFS(targetname),self->pathtarget);
}
self->spawnflags &= ~ATTRACTOR_PATHTARGET;
}
}
VectorNormalize(dir);
speed = VectorNormalize(target->velocity);
speed = max(fabs(self->moveinfo.speed),speed);
if(self->moveinfo.speed < 0) speed = -speed;
if(speed > dist*10) {
speed = dist*10;
VectorScale(dir,speed,target->velocity);
// if NO_GRAVITY is NOT set, and target would normally be affected by gravity,
// counteract gravity during the last move
if( !(self->spawnflags & ATTRACTOR_NO_GRAVITY) )
{
if( (target->movetype == MOVETYPE_BOUNCE ) ||
(target->movetype == MOVETYPE_PUSHABLE) ||
(target->movetype == MOVETYPE_STEP ) ||
(target->movetype == MOVETYPE_TOSS ) ||
(target->movetype == MOVETYPE_DEBRIS ) )
{
target->velocity[2] += target->gravity * sv_gravity->value * FRAMETIME;
}
}
} else
VectorScale(dir,speed,target->velocity);
// Add attractor velocity in case it's a movewith deal
VectorAdd(target->velocity,self->velocity,target->velocity);
if(target->client)
{
float scale;
if(target->groundentity || target->waterlevel > 1)
{
if(target->groundentity)
scale = 0.75;
else
scale = 0.375;
// Players - add movement stuff so he MAY be able to escape
AngleVectors (target->client->v_angle, forward, right, NULL);
for (i=0 ; i<3 ; i++)
target->velocity[i] += scale * forward[i] * target->client->ucmd.forwardmove +
scale * right[i] * target->client->ucmd.sidemove;
target->velocity[2] += scale * target->client->ucmd.upmove;
}
}
// If target is on the ground and attractor is overhead, give 'em a little nudge.
// This is only really necessary for players
if(target->groundentity && (self->s.origin[2] > target->absmax[2]))
{
target->s.origin[2] += 1;
target->groundentity = NULL;
}
if(self->sounds)
{
vec3_t new_origin;
if(target->client)
VectorCopy(target->s.origin,new_origin);
else
VectorMA(targ_org,FRAMETIME,target->velocity,new_origin);
switch(self->sounds)
{
case 1:
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_MEDIC_CABLE_ATTACK);
gi.WriteShort(self-g_edicts);
gi.WritePosition(self->s.origin);
gi.WritePosition(new_origin);
gi.multicast(self->s.origin, MULTICAST_PVS);
break;
case 2:
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_BFG_LASER);
gi.WritePosition(self->s.origin);
gi.WritePosition(new_origin);
gi.multicast(self->s.origin, MULTICAST_PVS);
}
}
if(self->spawnflags & ATTRACTOR_NO_GRAVITY)
target->gravity_debounce_time = level.time + 2*FRAMETIME;
gi.linkentity(target);
if(!num_targets)
{
// shut 'er down
self->spawnflags &= ~ATTRACTOR_ON;
}
else
{
self->nextthink = level.time + FRAMETIME;
}
}
void target_attractor_think(edict_t *self)
{
edict_t *ent, *target;
trace_t tr;
vec3_t dir, targ_org;
vec_t dist, speed;
vec3_t forward, right;
int i;
int ent_start;
int num_targets = 0;
if ( !(self->spawnflags & ATTRACTOR_ON) ) return;
if(self->moveinfo.speed != self->speed)
{
if(self->speed > 0)
self->moveinfo.speed = min(self->speed, self->moveinfo.speed + self->accel);
else
self->moveinfo.speed = max(self->speed, self->moveinfo.speed + self->accel);
}
target = NULL;
ent_start = 1;
while(true)
{
if(self->spawnflags & (ATTRACTOR_PLAYER | ATTRACTOR_MONSTER))
{
target = NULL;
for(i=ent_start, ent=&g_edicts[ent_start];i<globals.num_edicts && !target; i++, ent++)
{
if((self->spawnflags & ATTRACTOR_PLAYER) && ent->client && ent->inuse)
{
target = ent;
ent_start = i+1;
continue;
}
if((self->spawnflags & ATTRACTOR_MONSTER) && (ent->svflags & SVF_MONSTER) && (ent->inuse))
{
target = ent;
ent_start = i+1;
}
}
}
else
target = G_Find(target,FOFS(targetname),self->target);
if(!target) break;
if(!target->inuse) continue;
if( ((target->client) || (target->svflags & SVF_MONSTER)) && (target->health <= 0)) continue;
num_targets++;
VectorAdd(target->s.origin,target->origin_offset,targ_org);
VectorSubtract(self->s.origin,targ_org,dir);
dist = VectorLength(dir);
if(self->spawnflags & ATTRACTOR_SIGHT)
{
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
if(tr.ent != target) continue;
}
if(readout->value) gi.dprintf("distance=%g, pull speed=%g\n",dist,self->moveinfo.speed);
if(dist > self->moveinfo.distance)
continue;
if((self->pathtarget) && (self->spawnflags & ATTRACTOR_PATHTARGET))
{
if(dist == 0)
{
// fire pathtarget when close
ent = G_Find(NULL,FOFS(targetname),self->pathtarget);
while(ent)
{
if(ent->use)
ent->use(ent,self,self);
ent = G_Find(ent,FOFS(targetname),self->pathtarget);
}
self->spawnflags &= ~ATTRACTOR_PATHTARGET;
}
}
VectorNormalize(dir);
speed = VectorNormalize(target->velocity);
speed = max(fabs(self->moveinfo.speed),speed);
if(self->moveinfo.speed < 0) speed = -speed;
if(speed > dist*10) {
speed = dist*10;
VectorScale(dir,speed,target->velocity);
// if NO_GRAVITY is NOT set, and target would normally be affected by gravity,
// counteract gravity during the last move
if( !(self->spawnflags & ATTRACTOR_NO_GRAVITY) )
{
if( (target->movetype == MOVETYPE_BOUNCE ) ||
(target->movetype == MOVETYPE_PUSHABLE) ||
(target->movetype == MOVETYPE_STEP ) ||
(target->movetype == MOVETYPE_TOSS ) ||
(target->movetype == MOVETYPE_DEBRIS ) )
{
target->velocity[2] += target->gravity * sv_gravity->value * FRAMETIME;
}
}
}
else
VectorScale(dir,speed,target->velocity);
// Add attractor velocity in case it's a movewith deal
VectorAdd(target->velocity,self->velocity,target->velocity);
if(target->client)
{
float scale;
if(target->groundentity || target->waterlevel > 1)
{
if(target->groundentity)
scale = 0.75;
else
scale = 0.375;
// Players - add movement stuff so he MAY be able to escape
AngleVectors (target->client->v_angle, forward, right, NULL);
for (i=0 ; i<3 ; i++)
target->velocity[i] += scale * forward[i] * target->client->ucmd.forwardmove +
scale * right[i] * target->client->ucmd.sidemove;
target->velocity[2] += scale * target->client->ucmd.upmove;
}
}
// If target is on the ground and attractor is overhead, give 'em a little nudge.
// This is only really necessary for players
if(target->groundentity && (self->s.origin[2] > target->absmax[2]))
{
target->s.origin[2] += 1;
target->groundentity = NULL;
}
if(self->spawnflags & ATTRACTOR_NO_GRAVITY)
target->gravity_debounce_time = level.time + 2*FRAMETIME;
gi.linkentity(target);
}
if(!num_targets)
{
// shut 'er down
self->spawnflags &= ~ATTRACTOR_ON;
}
else
{
self->nextthink = level.time + FRAMETIME;
}
}
void use_target_attractor(edict_t *self, edict_t *other, edict_t *activator)
{
if(self->spawnflags & ATTRACTOR_ON)
{
self->count--;
if (self->count == 0)
{
self->think = G_FreeEdict;
self->nextthink = level.time + 1;
}
else
{
self->spawnflags &= ~ATTRACTOR_ON;
self->s.sound = 0;
self->target_ent = NULL;
self->nextthink = 0;
}
}
else
{
self->spawnflags |= (ATTRACTOR_ON + ATTRACTOR_PATHTARGET);
self->s.sound = self->noise_index;
#ifdef LOOP_SOUND_ATTENUATION
self->s.attenuation = self->attenuation;
#endif
if(self->spawnflags & ATTRACTOR_SINGLE)
self->think = target_attractor_think_single;
else
self->think = target_attractor_think;
self->moveinfo.speed = 0;
gi.linkentity(self);
self->think(self);
}
}
void SP_target_attractor(edict_t *self)
{
if(!self->target && !(self->spawnflags & ATTRACTOR_PLAYER) &&
!(self->spawnflags & ATTRACTOR_MONSTER))
{
gi.dprintf("target_attractor without a target at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->class_id = ENTITY_TARGET_ATTRACTOR;
if(self->sounds)
{
// if((self->spawnflags & ATTRACTOR_PLAYER) || (self->spawnflags & ATTRACTOR_MONSTER)) {
self->spawnflags |= (ATTRACTOR_SIGHT | ATTRACTOR_SINGLE);
// } else {
// gi.dprintf("Target_attractor sounds key is only valid\n"
// "for PLAYER or MONSTER. Setting sounds=0\n");
// }
}
if (self->distance)
st.distance = self->distance;
if(st.distance)
self->moveinfo.distance = st.distance;
else
self->moveinfo.distance = WORLD_SIZE; // was 8192
self->solid = SOLID_NOT;
if(self->movewith)
self->movetype = MOVETYPE_PUSH;
else
self->movetype = MOVETYPE_NONE;
self->use = use_target_attractor;
if(st.noise)
self->noise_index = gi.soundindex(st.noise);
else
self->noise_index = 0;
if(!self->speed)
self->speed = 100;
if(!self->accel)
self->accel = self->speed;
else
{
self->accel *= 0.1;
if(self->accel > self->speed)
self->accel = self->speed;
}
if(self->spawnflags & ATTRACTOR_ON)
{
if(self->spawnflags & ATTRACTOR_SINGLE)
self->think = target_attractor_think_single;
else
self->think = target_attractor_think;
if(self->sounds)
self->nextthink = level.time + 2*FRAMETIME;
else
self->think(self);
}
}
/*QUAKED target_text (1 0 0) (-8 -8 -8) (8 8 8) FILE
General text display. You can enter text using the "message" key, or you can refer to a text file.
"message" string or path/file.txt
*/
//Note: this is a temporary implementation
/*void target_text_use (edict_t *self, edict_t *activator, edict_t *other)
{
if ((self->message) && !(activator->svflags & SVF_MONSTER))
{
gi.centerprintf (activator, "%s", self->message);
gi.dprintf ("%s", self->message);
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
}
}
void SP_target_text (edict_t *self)
{
self->svflags |= SVF_NOCLIENT;
self->use = target_text_use;
gi.linkentity (self);
}*/
/*===================================================================
TARGET_MONITOR
Move the player's viewpoint to the target_monitor origin,
gives the target_monitor angles to the player, and freezes him
for "wait" seconds (default=3). If wait < 0, target_monitor
must be targeted a second time to free the player.
===================================================================*/
#define SF_MONITOR_CHASECAM 1
#define SF_MONITOR_EYEBALL 2 // Same as CHASECAM, but viewpoint is at the target
// entity's viewheight, and target entity is made
// invisible while in use
#define SF_MONITOR_CAMERAEFFECT 4 // Knightmare- camera effect
#define SF_MONITOR_LETTERBOX 8 // Knightmare- letterboxing
void target_monitor_off (edict_t *self)
{
int i;
edict_t *faker;
edict_t *player;
player = self->child;
if(!player) return;
if(self->spawnflags & SF_MONITOR_EYEBALL)
{
if(self->target_ent)
self->target_ent->svflags &= ~SVF_NOCLIENT;
}
faker = player->client->camplayer;
VectorCopy(faker->s.origin,player->s.origin);
gi.TagFree(faker->client);
G_FreeEdict (faker);
player->client->ps.pmove.origin[0] = player->s.origin[0]*8;
player->client->ps.pmove.origin[1] = player->s.origin[1]*8;
player->client->ps.pmove.origin[2] = player->s.origin[2]*8;
for (i=0 ; i<3 ; i++)
player->client->ps.pmove.delta_angles[i] =
ANGLE2SHORT(player->client->org_viewangles[i] - player->client->resp.cmd_angles[i]);
VectorCopy(player->client->org_viewangles, player->client->resp.cmd_angles);
VectorCopy(player->client->org_viewangles, player->s.angles);
VectorCopy(player->client->org_viewangles, player->client->ps.viewangles);
VectorCopy(player->client->org_viewangles, player->client->v_angle);
player->client->ps.gunindex = gi.modelindex(player->client->pers.weapon->view_model);
player->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
player->client->ps.pmove.pm_type = PM_NORMAL;
#ifdef KMQUAKE2_ENGINE_MOD
player->client->ps.rdflags &= ~(RDF_CAMERAEFFECT|RDF_LETTERBOX); // Knightmare- letterboxing
#endif
player->svflags &= ~SVF_NOCLIENT;
player->clipmask = MASK_PLAYERSOLID;
player->solid = SOLID_BBOX;
player->viewheight = 22;
player->client->camplayer = NULL;
player->target_ent = NULL;
gi.unlinkentity(player);
KillBox(player);
gi.linkentity(player);
if(self->noise_index)
gi.sound (player, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
// if we were previously in third person view, restore it
if(tpp->value)
Cmd_Chasecam_Toggle (player);
self->child = NULL;
gi.linkentity(self);
self->count--;
if (self->count == 0)
{
self->use = NULL;
self->think = G_FreeEdict;
self->nextthink = level.time + 1;
}
}
void target_monitor_move (edict_t *self)
{
// "chase cam"
trace_t trace;
vec3_t forward, o, goal;
if(!self->target_ent || !self->target_ent->inuse)
{
if(self->wait)
{
self->think = target_monitor_off;
self->nextthink = self->monsterinfo.attack_finished;
}
return;
}
if( (self->monsterinfo.attack_finished > 0) &&
(level.time > self->monsterinfo.attack_finished))
{
target_monitor_off(self);
return;
}
AngleVectors(self->target_ent->s.angles,forward,NULL,NULL);
VectorMA(self->target_ent->s.origin, -self->moveinfo.distance, forward, o);
o[2] += self->viewheight;
VectorSubtract(o,self->s.origin,o);
VectorMA(self->s.origin,0.2,o,o);
trace = gi.trace(self->target_ent->s.origin, NULL, NULL, o, self, MASK_SOLID);
VectorCopy(trace.endpos, goal);
VectorMA(goal, 2, forward, goal);
// pad for floors and ceilings
VectorCopy(goal, o);
o[2] += 6;
trace = gi.trace(goal, NULL, NULL, o, self, MASK_SOLID);
if (trace.fraction < 1) {
VectorCopy(trace.endpos, goal);
goal[2] -= 6;
}
VectorCopy(goal, o);
o[2] -= 6;
trace = gi.trace(goal, NULL, NULL, o, self, MASK_SOLID);
if (trace.fraction < 1) {
VectorCopy(trace.endpos, goal);
goal[2] += 6;
}
VectorCopy(goal, self->s.origin);
self->nextthink = level.time + FRAMETIME;
gi.linkentity(self);
}
void use_target_monitor (edict_t *self, edict_t *other, edict_t *activator)
{
int i;
edict_t *faker;
edict_t *monster;
gclient_t *cl;
if(!activator->client)
return;
if(self->child)
{
if(self->wait < 0)
target_monitor_off(self);
return;
}
if(self->target)
self->target_ent = G_Find(NULL,FOFS(targetname),self->target);
// if this is a CHASE_CAM target_monitor and the target no longer
// exists, remove this target_monitor and exit
if(self->spawnflags & SF_MONITOR_CHASECAM)
{
if(!self->target_ent || !self->target_ent->inuse)
{
G_FreeEdict(self);
return;
}
}
// save current viewangles
VectorCopy(activator->client->v_angle,activator->client->org_viewangles);
// create a fake player to stand in real player's position
faker = activator->client->camplayer = G_Spawn();
faker->s.frame = activator->s.frame;
VectorCopy (activator->s.origin, faker->s.origin);
VectorCopy (activator->velocity, faker->velocity);
VectorCopy (activator->s.angles, faker->s.angles);
faker->s = activator->s;
faker->takedamage = DAMAGE_NO; // so monsters won't attack
faker->flags |= FL_NOTARGET; // ... just to make sure
// faker->movetype = MOVETYPE_WALK;
faker->movetype = MOVETYPE_TOSS;
faker->groundentity = activator->groundentity;
faker->viewheight = activator->viewheight;
faker->inuse = true;
faker->classname = "camplayer";
faker->mass = activator->mass;
faker->solid = SOLID_BBOX;
faker->deadflag = DEAD_NO;
faker->clipmask = MASK_PLAYERSOLID;
faker->health = 100000; // invulnerable
faker->light_level = activator->light_level;
faker->think = faker_animate;
faker->nextthink = level.time + FRAMETIME;
VectorCopy(activator->mins,faker->mins);
VectorCopy(activator->maxs,faker->maxs);
// create a client so you can pick up items/be shot/etc while in camera
cl = (gclient_t *) gi.TagMalloc(sizeof(gclient_t), TAG_LEVEL);
faker->client = cl;
faker->target_ent = activator;
gi.linkentity (faker);
if(self->target_ent && self->target_ent->inuse)
{
if(self->spawnflags & SF_MONITOR_EYEBALL)
VectorCopy(self->target_ent->s.angles,activator->client->ps.viewangles);
else
{
vec3_t dir;
VectorSubtract(self->target_ent->s.origin,self->s.origin,dir);
vectoangles(dir,activator->client->ps.viewangles);
}
}
else
VectorCopy (self->s.angles, activator->client->ps.viewangles);
VectorCopy (self->s.origin, activator->s.origin);
activator->client->ps.pmove.origin[0] = self->s.origin[0]*8;
activator->client->ps.pmove.origin[1] = self->s.origin[1]*8;
activator->client->ps.pmove.origin[2] = self->s.origin[2]*8;
activator->client->ps.pmove.pm_type = PM_FREEZE;
#ifdef KMQUAKE2_ENGINE_MOD
// Knightmare- camera effect and letterboxing
if (self->spawnflags & SF_MONITOR_CAMERAEFFECT)
activator->client->ps.rdflags |= RDF_CAMERAEFFECT;
if (self->spawnflags & SF_MONITOR_LETTERBOX)
activator->client->ps.rdflags |= RDF_LETTERBOX;
#endif
activator->svflags |= SVF_NOCLIENT;
activator->solid = SOLID_NOT;
activator->viewheight = 0;
if(activator->client->chasetoggle)
{
Cmd_Chasecam_Toggle (activator);
activator->client->chasetoggle = 1;
}
else
activator->client->chasetoggle = 0;
activator->clipmask = 0;
VectorClear(activator->velocity);
activator->client->ps.gunindex = 0;
activator->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
gi.linkentity(activator);
gi.unlinkentity(faker);
KillBox(faker);
gi.linkentity(faker);
// check to see if player is the enemy of any monster.
for(i=maxclients->value+1, monster=g_edicts+i; i<globals.num_edicts; i++, monster++) {
if(!monster->inuse) continue;
if(!(monster->svflags & SVF_MONSTER)) continue;
if(monster->enemy == activator)
{
monster->enemy = NULL;
monster->oldenemy = NULL;
if(monster->goalentity == activator)
monster->goalentity = NULL;
if(monster->movetarget == activator)
monster->movetarget = NULL;
monster->monsterinfo.attack_finished = level.time + 1;
FindTarget(monster);
}
}
activator->target_ent = self;
self->child = activator;
if(self->noise_index)
gi.sound (activator, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
if(self->spawnflags & SF_MONITOR_CHASECAM)
{
if(self->wait > 0)
self->monsterinfo.attack_finished = level.time + self->wait;
else
self->monsterinfo.attack_finished = 0;
if(self->spawnflags & SF_MONITOR_EYEBALL)
{
self->viewheight = self->target_ent->viewheight;
self->target_ent->svflags |= SVF_NOCLIENT;
}
VectorCopy(self->target_ent->s.origin,self->s.origin);
self->think = target_monitor_move;
self->think(self);
}
else if(self->wait > 0)
{
self->think = target_monitor_off;
self->nextthink = level.time + self->wait;
}
}
void SP_target_monitor (edict_t *self)
{
char buffer[MAX_QPATH];
if(!self->wait)
self->wait = 3;
self->use = use_target_monitor;
self->movetype = MOVETYPE_NOCLIP;
if(st.noise)
{
if (!strstr (st.noise, ".wav"))
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
else
strncpy (buffer, st.noise, sizeof(buffer));
self->noise_index = gi.soundindex (buffer);
}
if(self->spawnflags & SF_MONITOR_EYEBALL)
self->spawnflags |= SF_MONITOR_CHASECAM;
if(self->spawnflags & SF_MONITOR_CHASECAM)
{ // chase cam
if(self->spawnflags & SF_MONITOR_EYEBALL)
{
self->moveinfo.distance = 0;
self->viewheight = 0;
}
else
{
if (self->distance)
st.distance = self->distance;
if(st.distance)
self->moveinfo.distance = st.distance;
else
self->moveinfo.distance = 128;
if (self->height)
st.height = self->height;
if(st.height)
self->viewheight = st.height;
else
self->viewheight = 16;
}
// MUST have target
if(!self->target)
{
gi.dprintf("CHASECAM target_monitor with no target at %s\n",vtos(self->s.origin));
self->spawnflags &= ~(SF_MONITOR_CHASECAM | SF_MONITOR_EYEBALL);
}
else if(self->movewith)
{
gi.dprintf("CHASECAM target_monitor cannot use 'movewith'\n");
self->spawnflags &= ~(SF_MONITOR_CHASECAM | SF_MONITOR_EYEBALL);
}
}
gi.linkentity(self);
}
/*====================================================================================
TARGET_ANIMATION causes the target entity to use the animation frames
"startframe" through "startframe" + "framenumbers" - 1.
Spawnflags:
ACTIVATOR = 1 - target_animation acts on it's activator rather than
it's target
"message" - specifies allowable classname to animate. This prevents
animating entities with inapplicable frame numbers
=====================================================================================*/
void target_animate (edict_t *ent)
{
if( (ent->s.frame < ent->monsterinfo.currentmove->firstframe) ||
(ent->s.frame >= ent->monsterinfo.currentmove->lastframe ) )
{
if(ent->monsterinfo.currentmove->endfunc)
{
ent->think = ent->monsterinfo.currentmove->endfunc;
ent->nextthink = level.time + FRAMETIME;
}
else if(ent->svflags & SVF_MONSTER)
{
// Hopefully we don't get here, but if we DO then we definitely
// need for monsters/actors to turn their brains back on.
ent->think = monster_think;
ent->nextthink = level.time + FRAMETIME;
}
else
{
ent->think = NULL;
ent->nextthink = 0;
}
ent->monsterinfo.currentmove = ent->monsterinfo.savemove;
return;
}
ent->s.frame++;
ent->nextthink = level.time + FRAMETIME;
gi.linkentity(ent);
}
void target_animation_use (edict_t *self, edict_t *other, edict_t *activator)
{
edict_t *target = NULL;
if(level.time < self->touch_debounce_time)
return;
if(self->spawnflags & 1)
{
if(activator && activator->client)
return;
if(self->message && Q_stricmp(self->message, activator->classname))
return;
if(!self->target)
target = activator;
}
if(!target)
{
if(!self->target)
return;
target = G_Find(NULL,FOFS(targetname),self->target);
if(!target)
return;
}
// Don't allow target to be animated if ALREADY under influence of
// another target_animation
if(target->think == target_animate)
return;
self->monsterinfo.currentmove->firstframe = self->startframe;
self->monsterinfo.currentmove->lastframe = self->startframe + self->framenumbers - 1;
self->monsterinfo.currentmove->frame = NULL;
self->monsterinfo.currentmove->endfunc = target->think;
target->s.frame = self->startframe;
target->think = target_animate;
target->monsterinfo.savemove = target->monsterinfo.currentmove;
target->monsterinfo.currentmove = self->monsterinfo.currentmove;
target->nextthink = level.time + FRAMETIME;
gi.linkentity(target);
self->count--;
if(self->count == 0)
G_FreeEdict(self);
else
self->touch_debounce_time = level.time + (self->framenumbers+1)*FRAMETIME;
}
void SP_target_animation (edict_t *self)
{
mmove_t *move;
if(!self->target && !(self->spawnflags & 1))
{
gi.dprintf("target_animation w/o a target at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
}
switch(self->sounds)
{
case 1:
// actor jump
self->startframe = 66;
self->framenumbers = 6;
break;
case 2:
// actor flip
self->startframe = 72;
self->framenumbers = 12;
break;
case 3:
// actor salute
self->startframe = 84;
self->framenumbers = 11;
break;
case 4:
// actor taunt
self->startframe = 95;
self->framenumbers = 17;
break;
case 5:
// actor wave
self->startframe = 112;
self->framenumbers = 11;
break;
case 6:
// actor point
self->startframe = 123;
self->framenumbers = 12;
break;
default:
if(!self->framenumbers)
self->framenumbers = 1;
}
self->use = target_animation_use;
move = gi.TagMalloc(sizeof(mmove_t), TAG_LEVEL);
self->monsterinfo.currentmove = move;
}
/*===================================================================================
TARGET_FAILURE - Halts the game, fades the screen to black and displays
a message explaining to the player how he screwed up.
Optionally plays a sound.
====================================================================================*/
void target_failure_wipe (edict_t *self)
{
edict_t *player;
player = &g_edicts[1]; // Gotta be, since this is SP only
if (player->client->textdisplay) Text_Close(player);
}
void target_failure_player_die (edict_t *player)
{
int n;
// player_die w/o... umm... dying
if (player->client->chaseactive)
{
ChasecamRemove (player);
player->client->chasetoggle = 1;
}
else
player->client->chasetoggle = 0;
player->client->pers.spawn_landmark = false; // paranoia check
player->client->pers.spawn_levelchange = false;
player->client->zooming = 0;
player->client->zoomed = false;
SetSensitivities(player,true);
if(player->client->spycam)
camera_off(player);
VectorClear (player->avelocity);
player->takedamage = DAMAGE_NO;
player->movetype = MOVETYPE_NONE;
player->s.modelindex2 = 0; // remove linked weapon model
player->s.sound = 0;
player->client->weapon_sound = 0;
player->svflags |= SVF_DEADMONSTER;
player->client->respawn_time = level.time + 1.0;
player->client->ps.gunindex = 0;
// clear inventory
for (n = 0; n < game.num_items; n++)
{
player->client->pers.inventory[n] = 0;
}
// remove powerups
player->client->quad_framenum = 0;
player->client->invincible_framenum = 0;
player->client->breather_framenum = 0;
player->client->enviro_framenum = 0;
player->flags &= ~(FL_POWER_SHIELD|FL_POWER_SCREEN);
player->client->flashlight_active = false;
player->deadflag = DEAD_FROZEN;
gi.linkentity (player);
}
void target_failure_think (edict_t *self)
{
target_failure_player_die(self->target_ent);
self->target_ent = NULL;
self->think = target_failure_wipe;
self->nextthink = level.time + 10;
}
void target_failure_fade_lights (edict_t *self)
{
char lightvalue[2];
char values[] = "abcdefghijklm";
lightvalue[0] = values[self->flags];
lightvalue[1] = 0;
gi.configstring(CS_LIGHTS+0, lightvalue);
if(self->flags)
{
self->flags--;
self->nextthink = level.time + 0.2;
}
else
{
target_failure_player_die(self->target_ent);
self->target_ent = NULL;
self->think = target_failure_wipe;
self->nextthink = level.time + 10;
}
}
void Use_Target_Text(edict_t *self, edict_t *other, edict_t *activator);
void use_target_failure (edict_t *self, edict_t *other, edict_t *activator)
{
if(!activator->client)
return;
if(self->target_ent)
return;
if (self->message && strlen(self->message))
Use_Target_Text (self,other,activator);
if(self->noise_index)
gi.sound (activator, CHAN_VOICE|CHAN_RELIABLE, self->noise_index, 1, ATTN_NORM, 0);
self->target_ent = activator;
if (Q_stricmp(vid_ref->string,"gl") && Q_stricmp(vid_ref->string,"kmgl"))
{
self->flags = 12;
self->think = target_failure_fade_lights;
self->nextthink = level.time + FRAMETIME;
}
else
{
activator->client->fadestart = level.framenum;
activator->client->fadein = level.framenum + 40;
activator->client->fadehold = activator->client->fadein + 100000;
activator->client->fadeout = 0;
activator->client->fadecolor[0] = 0;
activator->client->fadecolor[1] = 0;
activator->client->fadecolor[2] = 0;
activator->client->fadealpha = 1.0;
self->think = target_failure_think;
self->nextthink = level.time + 4;
}
activator->deadflag = DEAD_FROZEN;
gi.linkentity(activator);
}
void SP_target_failure (edict_t *self)
{
if (deathmatch->value || coop->value)
{ // SP only
G_FreeEdict (self);
return;
}
self->use = use_target_failure;
if (st.noise)
self->noise_index = gi.soundindex(st.noise);
}
// DWH
//
// Tremor stuff follows
//
//
// target_locator can be used to move entities to a random selection
// from a series of path_corners. Move takes place at level start ONLY.
//
void target_locator_init(edict_t *self)
{
int num_points=0;
int i, N, nummoves;
qboolean looped;
edict_t *tgt0, *tgtlast, *target, *next;
edict_t *move;
move = NULL;
move = G_Find(move,FOFS(targetname),self->target);
if(!move)
{
gi.dprintf("Target of target_locator (%s) not found.\n",
self->target);
G_FreeEdict(self);
return;
}
target = G_Find(NULL,FOFS(targetname),self->pathtarget);
if(!target)
{
gi.dprintf("Pathtarget of target_locator (%s) not found.\n",
self->pathtarget);
G_FreeEdict(self);
return;
}
srand(time(NULL));
tgt0 = target;
next = NULL;
target->spawnflags &= 0x7FFE;
while(next != tgt0)
{
if(target->target)
{
next = G_Find(NULL,FOFS(targetname),target->target);
if((!next) || (next==tgt0)) tgtlast = target;
if(!next)
{
gi.dprintf("Target %s of path_corner at %s not found.\n",
target->target,vtos(target->s.origin));
break;
}
target = next;
target->spawnflags &= 0x7FFE;
num_points++;
}
else
{
next = tgt0;
tgtlast = target;
}
}
if(!num_points) num_points=1;
nummoves = 1;
while(move)
{
if(nummoves > num_points) break; // more targets than path_corners
N = rand() % num_points;
i = 0;
next = tgt0;
looped = false;
while(i<=N)
{
target = next;
if(!(target->spawnflags & 1)) i++;
if(target==tgtlast)
{
// We've looped thru all path_corners, but not
// reached the target number yet. This can only
// happen in the case of multiple targets. Use the
// next available path_corner.
looped = true;
}
if(looped && !(target->spawnflags & 1)) i = N+1;
next = G_Find(NULL,FOFS(targetname),target->target);
}
target->spawnflags |= 1;
// Assumptions here: SOLID_BSP entities are assumed to be brush models,
// all others are point ents
if(move->solid == SOLID_BSP)
{
vec3_t origin;
VectorAdd(move->absmin,move->absmax,origin);
VectorScale(origin,0.5,origin);
VectorSubtract(target->s.origin,origin,move->s.origin);
}
else {
VectorCopy(target->s.origin,move->s.origin);
VectorCopy(target->s.angles,move->s.angles);
}
M_droptofloor(move);
gi.linkentity(move);
move = G_Find(move,FOFS(targetname),self->target);
nummoves++;
}
// All done, go away
G_FreeEdict(self);
}
void SP_target_locator(edict_t *self)
{
if(!self->target)
{
gi.dprintf("target_locator w/o target at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
if(!self->pathtarget)
{
gi.dprintf("target_locator w/o pathtarget at %s\n",vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->think = target_locator_init;
self->nextthink = level.time + 2*FRAMETIME;
gi.linkentity(self);
}