mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2025-02-07 07:41:10 +00:00
578 lines
14 KiB
C
578 lines
14 KiB
C
|
// g_target.c
|
||
|
|
||
|
#include "g_local.h"
|
||
|
|
||
|
|
||
|
/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Fire an origin based temp entity event to the clients.
|
||
|
"style" type byte
|
||
|
*/
|
||
|
void Use_Target_Tent(edict_t *ent, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
gi.WriteByte(svc_temp_entity);
|
||
|
gi.WriteByte(ent->style);
|
||
|
gi.WritePosition(ent->s.origin);
|
||
|
gi.multicast(ent->s.origin, MULTICAST_PVS);
|
||
|
}
|
||
|
|
||
|
void SP_target_temp_entity(edict_t *ent)
|
||
|
{
|
||
|
ent->use = Use_Target_Tent;
|
||
|
|
||
|
//CW++
|
||
|
ent->svflags |= SVF_NOCLIENT;
|
||
|
//CW--
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
|
||
|
"noise" wav file to play
|
||
|
"attenuation"
|
||
|
-1 = none, send to whole level
|
||
|
1 = normal fighting sounds
|
||
|
2 = idle sound level
|
||
|
3 = ambient sound level
|
||
|
"volume" 0.0 to 1.0
|
||
|
|
||
|
Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers.
|
||
|
|
||
|
Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off.
|
||
|
Multiple identical looping sounds will just increase volume without any speed cost.
|
||
|
*/
|
||
|
void Use_Target_Speaker(edict_t *ent, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
int chan;
|
||
|
|
||
|
if (ent->spawnflags & 3)
|
||
|
{ // looping sound toggles
|
||
|
if (ent->s.sound)
|
||
|
ent->s.sound = 0; // turn it off
|
||
|
else
|
||
|
ent->s.sound = ent->noise_index; // start it
|
||
|
}
|
||
|
else
|
||
|
{ // normal sound
|
||
|
if (ent->spawnflags & 4)
|
||
|
chan = CHAN_VOICE | CHAN_RELIABLE;
|
||
|
else
|
||
|
chan = CHAN_VOICE;
|
||
|
// use a positioned_sound, because this entity won't normally be
|
||
|
// sent to any clients because it is invisible
|
||
|
gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SP_target_speaker(edict_t *ent)
|
||
|
{
|
||
|
char buffer[MAX_QPATH];
|
||
|
|
||
|
if (!st.noise)
|
||
|
{
|
||
|
gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
|
||
|
return;
|
||
|
}
|
||
|
if (!strstr (st.noise, ".wav"))
|
||
|
Com_sprintf(buffer, sizeof(buffer), "%s.wav", st.noise);
|
||
|
else
|
||
|
strncpy(buffer, st.noise, sizeof(buffer));
|
||
|
ent->noise_index = gi.soundindex (buffer);
|
||
|
|
||
|
if (!ent->volume)
|
||
|
ent->volume = 1.0;
|
||
|
|
||
|
if (!ent->attenuation)
|
||
|
ent->attenuation = 1.0;
|
||
|
else if (ent->attenuation == -1) // use -1 so 0 defaults to 1
|
||
|
ent->attenuation = 0;
|
||
|
|
||
|
// check for prestarted looping sound
|
||
|
if (ent->spawnflags & 1)
|
||
|
ent->s.sound = ent->noise_index;
|
||
|
|
||
|
ent->use = Use_Target_Speaker;
|
||
|
|
||
|
// must link the entity so we get areas and clusters so
|
||
|
// the server can determine who to send updates to
|
||
|
gi.linkentity(ent);
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Spawns an explosion temporary entity when used.
|
||
|
|
||
|
"delay" wait this long before going off
|
||
|
"dmg" how much radius damage should be done, defaults to 0
|
||
|
*/
|
||
|
void target_explosion_explode(edict_t *self)
|
||
|
{
|
||
|
float save;
|
||
|
|
||
|
gi.WriteByte(svc_temp_entity);
|
||
|
gi.WriteByte(TE_EXPLOSION1);
|
||
|
gi.WritePosition(self->s.origin);
|
||
|
gi.multicast(self->s.origin, MULTICAST_PHS);
|
||
|
T_RadiusDamage(self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
|
||
|
|
||
|
save = self->delay;
|
||
|
self->delay = 0.0;
|
||
|
G_UseTargets(self, self->activator);
|
||
|
self->delay = save;
|
||
|
}
|
||
|
|
||
|
void use_target_explosion(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
self->activator = activator;
|
||
|
|
||
|
if (!self->delay)
|
||
|
{
|
||
|
target_explosion_explode(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self->think = target_explosion_explode;
|
||
|
self->nextthink = level.time + self->delay;
|
||
|
}
|
||
|
|
||
|
void SP_target_explosion(edict_t *ent)
|
||
|
{
|
||
|
ent->use = use_target_explosion;
|
||
|
ent->svflags = SVF_NOCLIENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Changes level to "map" when fired
|
||
|
*/
|
||
|
void use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
// ignore if already activated
|
||
|
if (level.intermissiontime)
|
||
|
return;
|
||
|
|
||
|
// if noexit, do a ton of damage to other
|
||
|
if (!((int)dmflags->value & DF_ALLOW_EXIT) && (other != world)) //CW
|
||
|
{
|
||
|
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// let everyone know who hit the exit
|
||
|
if (activator && activator->client) //CW
|
||
|
gi_bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
|
||
|
|
||
|
// if going to a new unit, clear cross triggers
|
||
|
if (strstr(self->map, "*"))
|
||
|
game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
|
||
|
|
||
|
BeginIntermission(self);
|
||
|
}
|
||
|
|
||
|
void SP_target_changelevel(edict_t *ent)
|
||
|
{
|
||
|
if (!ent->map)
|
||
|
{
|
||
|
gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
|
||
|
G_FreeEdict(ent);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// ugly hack because *SOMEBODY* screwed up their map
|
||
|
if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
|
||
|
ent->map = "fact3$secret1";
|
||
|
|
||
|
ent->use = use_target_changelevel;
|
||
|
ent->svflags = SVF_NOCLIENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Creates a particle splash effect when used.
|
||
|
|
||
|
Set "sounds" to one of the following:
|
||
|
1) sparks
|
||
|
2) blue water
|
||
|
3) brown water
|
||
|
4) slime
|
||
|
5) lava
|
||
|
6) blood
|
||
|
|
||
|
"count" how many pixels in the splash
|
||
|
"dmg" if set, does a radius damage at this location when it splashes
|
||
|
useful for lava/sparks
|
||
|
*/
|
||
|
|
||
|
void use_target_splash(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
gi.WriteByte(svc_temp_entity);
|
||
|
gi.WriteByte(TE_SPLASH);
|
||
|
gi.WriteByte(self->count);
|
||
|
gi.WritePosition(self->s.origin);
|
||
|
gi.WriteDir(self->movedir);
|
||
|
gi.WriteByte(self->sounds);
|
||
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
||
|
|
||
|
if (self->dmg)
|
||
|
T_RadiusDamage(self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
|
||
|
}
|
||
|
|
||
|
void SP_target_splash(edict_t *self)
|
||
|
{
|
||
|
self->use = use_target_splash;
|
||
|
G_SetMovedir(self->s.angles, self->movedir);
|
||
|
|
||
|
if (!self->count)
|
||
|
self->count = 32;
|
||
|
|
||
|
self->svflags = SVF_NOCLIENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Set target to the type of entity you want spawned.
|
||
|
Useful for spawning monsters and gibs in the factory levels.
|
||
|
|
||
|
For monsters:
|
||
|
Set direction to the facing you want it to have.
|
||
|
|
||
|
For gibs:
|
||
|
Set direction if you want it moving and
|
||
|
speed how fast it should be moving otherwise it
|
||
|
will just be dropped
|
||
|
*/
|
||
|
void ED_CallSpawn(edict_t *ent);
|
||
|
|
||
|
void use_target_spawner(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
edict_t *ent;
|
||
|
|
||
|
ent = G_Spawn();
|
||
|
ent->classname = self->target;
|
||
|
VectorCopy(self->s.origin, ent->s.origin);
|
||
|
VectorCopy(self->s.angles, ent->s.angles);
|
||
|
ED_CallSpawn(ent);
|
||
|
gi.unlinkentity(ent);
|
||
|
KillBox(ent);
|
||
|
gi.linkentity(ent);
|
||
|
|
||
|
if (self->speed)
|
||
|
VectorCopy(self->movedir, ent->velocity);
|
||
|
}
|
||
|
|
||
|
void SP_target_spawner(edict_t *self)
|
||
|
{
|
||
|
self->use = use_target_spawner;
|
||
|
self->svflags = SVF_NOCLIENT;
|
||
|
if (self->speed)
|
||
|
{
|
||
|
G_SetMovedir(self->s.angles, self->movedir);
|
||
|
VectorScale(self->movedir, self->speed, self->movedir);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
|
||
|
Fires a blaster bolt in the set direction when triggered.
|
||
|
|
||
|
dmg default is 15
|
||
|
speed default is 1000
|
||
|
*/
|
||
|
|
||
|
void use_target_blaster(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
int effect;
|
||
|
|
||
|
if (self->spawnflags & 2)
|
||
|
effect = 0;
|
||
|
else if (self->spawnflags & 1)
|
||
|
effect = EF_HYPERBLASTER;
|
||
|
else
|
||
|
effect = EF_BLASTER;
|
||
|
|
||
|
Fire_Blaster(self, self->s.origin, self->movedir, self->dmg, self->speed, effect); //CW
|
||
|
gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
||
|
}
|
||
|
|
||
|
void SP_target_blaster(edict_t *self)
|
||
|
{
|
||
|
self->use = use_target_blaster;
|
||
|
G_SetMovedir(self->s.angles, self->movedir);
|
||
|
self->noise_index = gi.soundindex("weapons/laser2.wav");
|
||
|
|
||
|
if (!self->dmg)
|
||
|
self->dmg = 15;
|
||
|
if (!self->speed)
|
||
|
self->speed = 1000.0;
|
||
|
|
||
|
self->svflags = SVF_NOCLIENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
|
||
|
When triggered, fires a laser. You can either set a target
|
||
|
or a direction.
|
||
|
*/
|
||
|
|
||
|
void target_laser_think(edict_t *self)
|
||
|
{
|
||
|
edict_t *ignore;
|
||
|
trace_t tr;
|
||
|
vec3_t start;
|
||
|
vec3_t end;
|
||
|
vec3_t point;
|
||
|
vec3_t last_movedir;
|
||
|
int count;
|
||
|
|
||
|
if (self->spawnflags & 0x80000000)
|
||
|
count = 8;
|
||
|
else
|
||
|
count = 4;
|
||
|
|
||
|
if (self->enemy)
|
||
|
{
|
||
|
VectorCopy(self->movedir, last_movedir);
|
||
|
VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
|
||
|
VectorSubtract(point, self->s.origin, self->movedir);
|
||
|
VectorNormalize(self->movedir);
|
||
|
if (!VectorCompare(self->movedir, last_movedir))
|
||
|
self->spawnflags |= 0x80000000;
|
||
|
}
|
||
|
|
||
|
ignore = self;
|
||
|
VectorCopy(self->s.origin, start);
|
||
|
VectorMA(start, 2048.0, 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))
|
||
|
//CW++
|
||
|
{
|
||
|
if (tr.ent->client && (tr.ent->client->agm_enemy != NULL))
|
||
|
T_Damage(tr.ent, self, tr.ent->client->agm_enemy, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_AGM_TARG_LASER);
|
||
|
else
|
||
|
//CW--
|
||
|
T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
|
||
|
}
|
||
|
|
||
|
// 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(count);
|
||
|
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 + FRAMETIME;
|
||
|
}
|
||
|
|
||
|
void target_laser_on(edict_t *self)
|
||
|
{
|
||
|
if (!self->activator)
|
||
|
self->activator = self;
|
||
|
|
||
|
self->spawnflags |= 0x80000001;
|
||
|
self->svflags &= ~SVF_NOCLIENT;
|
||
|
target_laser_think(self);
|
||
|
}
|
||
|
|
||
|
void target_laser_off(edict_t *self)
|
||
|
{
|
||
|
self->spawnflags &= ~1;
|
||
|
self->svflags |= SVF_NOCLIENT;
|
||
|
self->nextthink = 0.0;
|
||
|
}
|
||
|
|
||
|
void target_laser_use(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
self->activator = activator;
|
||
|
if (self->spawnflags & 1)
|
||
|
target_laser_off (self);
|
||
|
else
|
||
|
target_laser_on (self);
|
||
|
}
|
||
|
|
||
|
void target_laser_start(edict_t *self)
|
||
|
{
|
||
|
edict_t *ent;
|
||
|
|
||
|
self->movetype = MOVETYPE_NONE;
|
||
|
self->solid = SOLID_NOT;
|
||
|
self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
|
||
|
self->s.modelindex = 1; // must be non-zero
|
||
|
|
||
|
// set the beam diameter
|
||
|
if (self->spawnflags & 64)
|
||
|
self->s.frame = 16;
|
||
|
else
|
||
|
self->s.frame = 4;
|
||
|
|
||
|
// set the color
|
||
|
if (self->spawnflags & 2)
|
||
|
self->s.skinnum = 0xf2f2f0f0;
|
||
|
else if (self->spawnflags & 4)
|
||
|
self->s.skinnum = 0xd0d1d2d3;
|
||
|
else if (self->spawnflags & 8)
|
||
|
self->s.skinnum = 0xf3f3f1f1;
|
||
|
else if (self->spawnflags & 16)
|
||
|
self->s.skinnum = 0xdcdddedf;
|
||
|
else if (self->spawnflags & 32)
|
||
|
self->s.skinnum = 0xe0e1e2e3;
|
||
|
|
||
|
if (!self->enemy)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
self->use = target_laser_use;
|
||
|
self->think = target_laser_think;
|
||
|
|
||
|
if (!self->dmg)
|
||
|
self->dmg = 1;
|
||
|
|
||
|
VectorSet(self->mins, -8.0, -8.0, -8.0);
|
||
|
VectorSet(self->maxs, 8.0, 8.0, 8.0);
|
||
|
gi.linkentity(self);
|
||
|
|
||
|
if (self->spawnflags & 1)
|
||
|
target_laser_on(self);
|
||
|
else
|
||
|
target_laser_off(self);
|
||
|
}
|
||
|
|
||
|
void SP_target_laser (edict_t *self)
|
||
|
{
|
||
|
self->think = target_laser_start;
|
||
|
self->nextthink = level.time + 1.0; // let everything else get spawned before we start firing
|
||
|
}
|
||
|
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
When triggered, this initiates a level-wide earthquake.
|
||
|
All players and monsters are affected.
|
||
|
"speed" severity of the quake (default:200)
|
||
|
"count" duration of the quake (default:5)
|
||
|
*/
|
||
|
|
||
|
void target_earthquake_think(edict_t *self)
|
||
|
{
|
||
|
edict_t *e;
|
||
|
int i;
|
||
|
|
||
|
if (self->last_move_time < level.time)
|
||
|
{
|
||
|
gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
|
||
|
self->last_move_time = level.time + 0.5;
|
||
|
}
|
||
|
|
||
|
for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
|
||
|
{
|
||
|
if (!e->inuse)
|
||
|
continue;
|
||
|
if (!e->client)
|
||
|
continue;
|
||
|
if (!e->groundentity)
|
||
|
continue;
|
||
|
|
||
|
e->groundentity = NULL;
|
||
|
e->velocity[0] += crandom()* 150.0;
|
||
|
e->velocity[1] += crandom()* 150.0;
|
||
|
e->velocity[2] = self->speed * (100.0 / e->mass);
|
||
|
}
|
||
|
|
||
|
if (level.time < self->timestamp)
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
}
|
||
|
|
||
|
void target_earthquake_use(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
self->timestamp = level.time + self->count;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
self->activator = activator;
|
||
|
self->last_move_time = 0.0;
|
||
|
}
|
||
|
|
||
|
void SP_target_earthquake(edict_t *self)
|
||
|
{
|
||
|
if (!self->targetname)
|
||
|
gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
|
||
|
|
||
|
if (!self->count)
|
||
|
self->count = 5;
|
||
|
|
||
|
if (!self->speed)
|
||
|
self->speed = 200.0;
|
||
|
|
||
|
self->svflags |= SVF_NOCLIENT;
|
||
|
self->think = target_earthquake_think;
|
||
|
self->use = target_earthquake_use;
|
||
|
|
||
|
self->noise_index = gi.soundindex("world/quake.wav");
|
||
|
}
|
||
|
|
||
|
//==========================================================
|
||
|
|
||
|
/*QUAKED target_victory (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
ASSAULT: When triggered, ends the game with a victory for the attacking team.
|
||
|
*/
|
||
|
void target_victory_use(edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
G_UseTargets(self, activator);
|
||
|
asltgame.victory = true;
|
||
|
}
|
||
|
|
||
|
void SP_target_victory(edict_t *self)
|
||
|
{
|
||
|
if (sv_gametype->value != G_ASLT)
|
||
|
{
|
||
|
G_FreeEdict(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self->classname = "assault_victory";
|
||
|
self->svflags |= SVF_NOCLIENT;
|
||
|
self->use = target_victory_use;
|
||
|
}
|
||
|
//CW--
|