heretic2-sdk/Toolkit/Programming/GameCode/game/g_misc.c
1999-03-18 00:00:00 +00:00

2328 lines
56 KiB
C

// g_misc.c
#include "q_shared.h"
#include "g_DefaultMessageHandler.h"
#include "g_misc.h"
#include "g_local.h"
#include "FX.h"
#include "random.h"
#include "g_monster.h"
#include "vector.h"
#include "g_teleport.h"
#include "g_HitLocation.h"
#include "m_stats.h"
#include "g_playstats.h"
void ED_CallSpawn (edict_t *ent);
extern vec3_t mins;
#define CAMERA_SCRIPTED 2
int AndoriaSoundID[AS_MAX] =
{
AS_NOTHING,
AS_SMALLFOUNTAIN,
AS_LARGEFOUNTAIN,
AS_SEWERWATER,
AS_OUTSIDEWATERWAY,
AS_WINDCHIME,
};
int CloudSoundID[AS_MAX] =
{
AS_NOTHING,
AS_CAULDRONBUBBLE,
AS_WINDEERIE,
AS_WINDNOISY,
AS_WINDSOFTHI,
AS_WINDSOFTLO,
AS_WINDSTRONG1,
AS_WINDSTRONG2,
AS_WINDWHISTLE,
};
int HiveSoundID[AS_MAX] =
{
AS_NOTHING,
AS_GONG,
AS_WINDEERIE,
AS_WINDNOISY,
AS_WINDSOFTHI,
AS_WINDSOFTLO,
AS_WINDSTRONG1,
AS_WINDSTRONG2,
AS_WINDWHISTLE,
};
int MineSoundID[AS_MAX] =
{
AS_NOTHING,
AS_MUDPOOL,
AS_ROCKS,
AS_WINDEERIE,
AS_WINDSOFTLO,
AS_CONVEYOR,
AS_BUCKETCONVEYOR,
AS_CAVECREAK,
};
int SilverSpringSoundID[AS_MAX] =
{
AS_NOTHING,
AS_FIRE,
AS_WATERLAPPING,
AS_SEAGULLS,
AS_OCEAN,
AS_BIRDS,
AS_CRICKETS,
AS_FROGS,
AS_CRYING,
AS_MOSQUITOES,
AS_BUBBLES,
AS_BELL,
AS_FOOTSTEPS,
AS_MOANS,
AS_SEWERDRIPS,
AS_WATERDRIPS,
AS_HEAVYDRIPS,
AS_CAULDRONBUBBLE,
AS_SPIT,
};
int SwampCanyonSoundID[AS_MAX] =
{
AS_NOTHING,
AS_BIRD1,
AS_BIRD2,
AS_HUGEWATERFALL,
AS_MUDPOOL,
AS_WINDEERIE,
AS_WINDNOISY,
AS_WINDSOFTHI,
AS_WINDSOFTLO,
AS_WINDSTRONG1,
AS_WINDSTRONG2,
AS_WINDWHISTLE,
};
typedef struct DebrisSound
{
char *Name;
} DebrisSound_t;
DebrisSound_t DebrisSound [NUM_MAT]=
{
"misc/breakstone.wav", // MAT_STONE
"misc/breakstone.wav", // MAT_GREYSTONE
"misc/tearcloth.wav", // MAT_CLOTH
"misc/metalbreak.wav", // MAT_METAL
"misc/fleshbreak.wav", // MAT_FLESH
"misc/potbreak.wav", // MAT_POTTERY
"misc/glassbreak2.wav", // MAT_GLASS
"misc/breakstone.wav", // MAT_LEAF FIXME
"misc/breakwood.wav", // MAT_WOOD
"misc/breakstone.wav", // MAT_BROWNSTONE
"misc/bushbreak.wav", // MAT_NONE
NULL, // MAT_INSECT
};
/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience.
*/
//=====================================================
void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator)
{
ent->count ^= 1; // toggle state
// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count);
gi.SetAreaPortalState (ent->style, ent->count);
}
/*QUAKED func_areaportal (0 0 0) ?
This is a non-visible object that divides the world into
areas that are seperated when this portal is not activated.
Usually enclosed in the middle of a door.
*/
void SP_func_areaportal (edict_t *ent)
{
ent->use = Use_Areaportal;
ent->count = 0; // allways start closed;
}
//=====================================================
/*
=================
Misc functions
=================
*/
void VelocityForDamage (int damage, vec3_t v)
{
v[0] = flrand(-100.0F, 100.0F);
v[1] = flrand(-100.0F, 100.0F);
v[2] = flrand(200.0F, 300.0F);
if (damage < 50)
VectorScale (v, 0.7, v);
else
VectorScale (v, 1.2, v);
}
void ClipGibVelocity (edict_t *ent)
{
if (ent->velocity[0] < -300)
ent->velocity[0] = -300;
else if (ent->velocity[0] > 300)
ent->velocity[0] = 300;
if (ent->velocity[1] < -300)
ent->velocity[1] = -300;
else if (ent->velocity[1] > 300)
ent->velocity[1] = 300;
if (ent->velocity[2] < 200)
ent->velocity[2] = 200; // always some upwards
else if (ent->velocity[2] > 500)
ent->velocity[2] = 500;
}
/*
=================
gibs
=================
*/
void gib_think (edict_t *self)
{
self->s.frame++;
self->nextthink = level.time + FRAMETIME;
if (self->s.frame == 10)
{
self->think = G_FreeEdict;
self->nextthink = level.time + flrand(8.0F, 18.0F);
}
}
void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t normal_angles, right;
if (!self->groundentity)
return;
self->touch = NULL;
if (plane)
{
// gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
vectoangles (plane->normal, normal_angles);
AngleVectors (normal_angles, NULL, right, NULL);
vectoangles (right, self->s.angles);
if (self->s.modelindex == sm_meat_index)
{
self->s.frame++;
self->think = gib_think;
self->nextthink = level.time + FRAMETIME;
}
}
}
int gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
G_FreeEdict(self);
return(0);
}
void ThrowGib (edict_t *self, char *gibname, int damage, int type)
{
edict_t *gib;
vec3_t vd;
vec3_t origin;
vec3_t size;
float vscale;
gib=G_Spawn();
VectorScale (self->size, 0.5, size);
VectorAdd (self->absmin, size, origin);
VectorRandomAdd(origin, size, gib->s.origin);
gi.setmodel (gib, gibname);
gib->solid = SOLID_NOT;
gi.CreateEffect(&gib->s,FX_GIB_TRAIL,CEF_OWNERS_ORIGIN,gib->s.origin,NULL);
gib->flags |= FL_NO_KNOCKBACK;
gib->takedamage = DAMAGE_YES;
gib->die = gib_die;
if (type == GIB_ORGANIC)
{
gib->movetype = PHYSICSTYPE_STEP;
gib->touch = gib_touch;
vscale = 0.5;
}
else
{
gib->movetype = PHYSICSTYPE_STEP;
vscale = 1.0;
}
VelocityForDamage (damage, vd);
VectorMA (self->velocity, vscale, vd, gib->velocity);
ClipGibVelocity (gib);
gib->avelocity[0] = flrand(-600, 600);
gib->avelocity[1] = flrand(-600, 600);
gib->avelocity[2] = flrand(-600, 600);
gib->think = G_FreeEdict;
gib->nextthink = level.time + flrand(10.0, 20.0);
gi.linkentity (gib);
}
void BecomeExplosion1 (edict_t *self)
{
gi.CreateEffect(NULL, FX_EXPLOSION1, 0, self->s.origin, NULL);
G_FreeEdict (self);
}
void BecomeExplosion2 (edict_t *self)
{
gi.CreateEffect(NULL, FX_EXPLOSION2, 0, self->s.origin, NULL);
G_FreeEdict (self);
}
void SpawnDebris(edict_t *self, float size, vec3_t origin)
{
byte sizeb, magb;
vec3_t halfsize;
float mag;
int flags = 0;
int violence=VIOLENCE_DEFAULT;
size /=10;
sizeb = Clamp(size, 1.0, 255.0);
VectorScale(self->size, 0.5, halfsize);
mag = VectorLength(halfsize);
magb = Clamp(mag, 1.0, 255.0);
if(self->fire_damage_time > level.time || self->svflags&SVF_ONFIRE)
flags |= CEF_FLAG6;
if (self->materialtype == MAT_FLESH || self->materialtype == MAT_INSECT)
{
if (blood_level)
violence = blood_level->value;
if(violence < VIOLENCE_NORMAL)
{
size /= 10;
sizeb = Clamp(size, 1.0, 255.0);
gi.CreateEffect(NULL,
FX_DEBRIS,
flags, origin,
"bbdb",
sizeb,
MAT_STONE,
halfsize, magb);
}
else
{
if(violence > VIOLENCE_NORMAL)
{
sizeb *= (violence - VIOLENCE_NORMAL);
if(sizeb > 255)
sizeb = 255;
}
if(self->materialtype == MAT_INSECT)
flags |= CEF_FLAG8;
if(!stricmp(self->classname, "monster_tcheckrik_male"))
flags |= CEF_FLAG7;//use male insect skin on chunks
gi.CreateEffect(NULL,
FX_FLESH_DEBRIS,
flags, origin,
"bdb",
sizeb,
halfsize, magb);
}
}
else
{
if(self->s.renderfx & RF_REFLECTION)
flags |= CEF_FLAG8;
gi.CreateEffect(NULL,
FX_DEBRIS,
flags, origin,
"bbdb",
sizeb,
(byte)self->materialtype,
halfsize, magb);
}
if(self->classID == CID_OBJECT)
if(!strcmp(self->classname, "obj_larvabrokenegg") || !strcmp(self->classname, "obj_larvaegg"))
self->materialtype = MAT_POTTERY;
if (DebrisSound[self->materialtype].Name)
gi.sound (self, CHAN_VOICE, gi.soundindex(DebrisSound[self->materialtype].Name) ,
2, ATTN_NORM, 0);
}
void BecomeDebris2(edict_t *self, float damage)
{
float size;
int violence=VIOLENCE_DEFAULT;
if (blood_level)
violence = blood_level->value;
if (violence > VIOLENCE_BLOOD)
{
if(!(self->svflags & SVF_PARTS_GIBBED))
{//haven't yet thrown parts
if(self->monsterinfo.dismember)
{//FIXME:have a generic GibParts effect that throws flesh and several body parts- much cheaper?
int i, num_limbs;
num_limbs = irand(3, 10);
if(violence > VIOLENCE_NORMAL)
num_limbs *= (violence - VIOLENCE_NORMAL);
for(i = 0; i < num_limbs; i++)
{
if(self->svflags&SVF_MONSTER)
self->monsterinfo.dismember(self, flrand(80, 160), irand(hl_Head, hl_LegLowerRight) | hl_MeleeHit);
}
self->svflags |= SVF_PARTS_GIBBED;
self->think = BecomeDebris;
self->nextthink = level.time + 0.1;
return;
}
}
}
// Set my message handler to the special message handler for dead entities.
self->msgHandler=DeadMsgHandler;
//What the hell is this???
if (self->spawnflags & 4 && !(self->svflags&SVF_MONSTER))
{ // Need to create an explosion effect for this
if(self->owner)
{
T_DamageRadius(self, self->owner, self, 60.0,
self->dmg, self->dmg/2, DAMAGE_NORMAL|DAMAGE_AVOID_ARMOR,MOD_DIED);
}
else
{
T_DamageRadius(self, self, self, 60.0,
self->dmg, self->dmg/2, DAMAGE_NORMAL|DAMAGE_AVOID_ARMOR,MOD_DIED);
}
}
// A zero mass is well and truly illegal!
if (self->mass<0)
gi.dprintf("ERROR: %s needs a mass to generate debris",self->classname);
// Create a chunk-spitting client effect and remove me now that I've been chunked.
// This only yields 4, 8, 12, or 16 chunks, generally seems to yield 16
if(self->svflags&SVF_MONSTER && self->classID != CID_MOTHER)
{
size = VectorLength(self->size);
size *= 100;
assert(size >= 0);
}
else
{
if (Vec3IsZero(self->s.origin))
{// Set this brush up as if it were an object so the debris will be thrown properly
// If I'm a BModel (and therefore don't have an origin), calculate one to use instead and
// slap that into my origin.
// self->solid = SOLID_NOT; // This causes the breakable brushes to generate a sound
VectorMA(self->absmin,0.5,self->size,self->s.origin);
}
size = VectorLength(self->size) * 3;
if(self->solid == SOLID_BSP)
size *= 3;
else if(self->classID == CID_MOTHER)
size *= 10;
if (!self->mass)
self->mass = size / 10;
}
SpawnDebris(self, size, self->s.origin);
self->s.modelindex = 0;
self->solid = SOLID_NOT;
self->deadflag = DEAD_DEAD;
G_SetToFree(self);
self->nextthink = level.time + 2;
}
void BecomeDebris(edict_t *self)
{
if(self->health<0)
BecomeDebris2(self, abs(self->health)+10.0f);
else
BecomeDebris2(self, 10.0f);
}
void SprayDebris(edict_t *self, vec3_t spot, byte NoOfChunks, float damage)
{
byte magb, mat;
float mag, size;
int flags = 0;
int violence=VIOLENCE_DEFAULT;
mag = VectorLength(self->mins);
mat = (byte)(self->materialtype);
magb = Clamp(mag, 1.0, 255.0);
if(mat == MAT_FLESH || mat == MAT_INSECT)
{
if (blood_level)
violence = blood_level->value;
if (violence < VIOLENCE_NORMAL)
{
mat = MAT_STONE;
}
else if(violence > VIOLENCE_NORMAL)
{
NoOfChunks *= (violence - VIOLENCE_NORMAL);
if(NoOfChunks > 255)
NoOfChunks = 255;
}
}
if(mat == MAT_FLESH || mat == MAT_INSECT)
{
if(self->materialtype == MAT_INSECT)
{
flags |= CEF_FLAG8;
if(!stricmp(self->classname, "monster_tcheckrik_male"))
flags |= CEF_FLAG7;//use male insect skin on chunks
}
if(self->fire_damage_time > level.time || self->svflags&SVF_ONFIRE)
flags |= CEF_FLAG6;
gi.CreateEffect(NULL,
FX_FLESH_DEBRIS,
flags,
spot,
"bdb",
NoOfChunks, self->mins, magb);
}
else
{
size = (float)(NoOfChunks/100);
NoOfChunks = Clamp(size, 1.0, 255.0);
gi.CreateEffect(NULL,
FX_DEBRIS,
flags, spot,
"bbdb",
NoOfChunks,
MAT_STONE,
self->mins, magb);
}
}
void DefaultObjectDieHandler(edict_t *self, G_Message_t *msg)
{
edict_t *inflictor;
ParseMsgParms(msg, "ee", &inflictor, &inflictor);
G_UseTargets(self, inflictor);
if (self->target_ent)
BecomeDebris(self->target_ent);
BecomeDebris(self);
}
void harpy_take_head(edict_t *self, edict_t *victim, int BodyPart, int frame, int flags);
void ThrowBodyPart(edict_t *self, vec3_t *spot, int BodyPart, float damage, int frame)
{//add blood spew to sever loc and blood trail on flying part
float ke = 0;
vec3_t spot2;
int flags;
if (damage)
{
if (damage>255)
{
damage = 255;
}
gi.sound (self, CHAN_VOICE, gi.soundindex("misc/fleshbreak.wav") , 1, ATTN_NORM, 0);
}
VectorAdd(self->s.origin, *spot, spot2);
if(self->fire_damage_time > level.time || self->svflags&SVF_ONFIRE)
flags = CEF_FLAG6;
else
flags = 0;
if(self->materialtype == MAT_INSECT)
flags |= CEF_FLAG8;
if(give_head_to_harpy && take_head_from == self)
{
harpy_take_head(give_head_to_harpy, self, BodyPart, frame, flags);
SprayDebris(self, *spot, 5, damage);
return;
}
gi.CreateEffect(NULL,//owner
FX_BODYPART,//type
flags,//can't mess with this, sends only 1st byte and effects message
spot2,//spot,
"ssbbb",//int int float byte
(short)frame,//only 1 frame, sorry no anim
(short)BodyPart,//bitwise - node(s) to leave on
(byte)damage,//speed
self->s.modelindex,//my modelindex
self->s.number);//my number
}
void ThrowWeapon(edict_t *self, vec3_t *spot, int BodyPart, float damage, int frame)
{//same but no blood and metal sound when hits ground
vec3_t spot2;
if (damage>255)
{
damage = 255;
}
VectorAdd(self->s.origin, *spot, spot2);
gi.CreateEffect(NULL,//owner
FX_THROWWEAPON,//type
0,//can't mess with this, sends only 1st byte and effects message
spot2,//spot,
"ssbbb",// int int float byte
(short)frame,//only 1 frame, sorry no anim
(short)BodyPart,//bitwise - node(s) to leave on
(byte)damage,//speed
self->s.modelindex,//my modelindex
self->s.number);//my number
}
/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
---------KEYS---------------
target - target name of next path corner
pathtarget - used when an entity that has this path_corner targeted touches it
angles - used to make the brush rotate. The brush MUST have an origin brush in it. It is an
accumulative value so if the value is 0 40 0 the brush rotate an additional 40 degrees along
the y axis before it reaches this path corner.
wait - -1 makes trains stop until retriggered
-3 trains explode upon reaching this path corner
noise - file to play when corner is hit (trains only)
-objects/bucket.wav
*/
void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t v;
edict_t *next;
if (other->movetarget != self)
return;
if (other->enemy)
return;
if (self->pathtarget)
{
char *savetarget;
savetarget = self->target;
self->target = self->pathtarget;
G_UseTargets (self, other);
self->target = savetarget;
}
if (self->target)
next = G_PickTarget(self->target);
else
next = NULL;
if ((next) && (next->spawnflags & MSF_AMBUSH))
{
VectorCopy (next->s.origin, v);
v[2] += next->mins[2];
v[2] -= other->mins[2];
VectorCopy (v, other->s.origin);
next = G_PickTarget(next->target);
}
other->goalentity = other->movetarget = next;
if (self->wait)
{
other->monsterinfo.pausetime = level.time + self->wait;
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
// M_GiveOrder(ORD_STAND,other,other,PR_LOW, 0,0,0,0,0,0);
// other->monsterinfo.stand (other);
return;
}
if (!other->movetarget)
{
other->monsterinfo.pausetime = level.time + 100000000;
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
// M_GiveOrder(ORD_STAND,other,other,PR_LOW, 0,0,0,0,0,0);
// other->monsterinfo.stand (other);
}
else
{
VectorSubtract (other->goalentity->s.origin, other->s.origin, v);
other->ideal_yaw = vectoyaw (v);
}
}
void SP_path_corner (edict_t *self)
{
if (!self->targetname)
{
gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
G_FreeEdict (self);
return;
}
if (st.noise)
self->moveinfo.sound_middle = gi.soundindex (st.noise);
self->solid = SOLID_TRIGGER;
self->movetype = PHYSICSTYPE_NONE;
self->touch = path_corner_touch;
VectorSet (self->mins, -8, -8, -8);
VectorSet (self->maxs, 8, 8, 8);
self->svflags |= SVF_NOCLIENT;
gi.linkentity (self);
}
/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
Makes this the target of a monster and it will head here
when first activated before going after the activator. If
hold is selected, it will stay here.
NOTE FROM MG: Probably still works... will test
*/
void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *activator;
if (other->movetarget != self)
return;//wasn't purposely heading for me
if (self->target)
{
other->target = self->target;
other->goalentity = other->movetarget = G_PickTarget(other->target);
if (!other->goalentity)
{
gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
other->movetarget = self;
}
self->target = NULL;
}
else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY)))
{//HOLD
other->spawnflags |= MSF_FIXED;//stay here forever now
QPostMessage(other, MSG_STAND, PRI_DIRECTIVE, NULL);
}
if (other->movetarget == self)
{
other->target = NULL;
other->movetarget = NULL;
other->goalentity = other->enemy;
other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
}
if (self->pathtarget)
{
char *savetarget;
savetarget = self->target;
self->target = self->pathtarget;
if (other->enemy && other->enemy->client)
activator = other->enemy;
else if (other->oldenemy && other->oldenemy->client)
activator = other->oldenemy;
else if (other->activator && other->activator->client)
activator = other->activator;
else
activator = other;
G_UseTargets (self, activator);
self->target = savetarget;
}
}
void SP_point_combat (edict_t *self)
{
self->solid = SOLID_TRIGGER;
self->touch = point_combat_touch;
VectorSet (self->mins, -8, -8, -16);
VectorSet (self->maxs, 8, 8, 16);
self->svflags = SVF_NOCLIENT;
gi.linkentity (self);
};
/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for spotlights, etc.
*/
void SP_info_null (edict_t *self)
{
G_FreeEdict (self);
};
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for lightning.
*/
void SP_info_notnull (edict_t *self)
{
VectorCopy (self->s.origin, self->absmin);
VectorCopy (self->s.origin, self->absmax);
self->solid = SOLID_TRIGGER;
self->movetype = PHYSICSTYPE_NONE;
};
/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
This is just a solid wall if not inhibited
TRIGGER_SPAWN the wall will not be present until triggered
it will then blink in to existance; it will
kill anything that was in it's way
TOGGLE only valid for TRIGGER_SPAWN walls
this allows the wall to be turned on and off
START_ON only valid for TRIGGER_SPAWN walls
the wall will initially be present
*/
void func_wall_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->solid == SOLID_NOT)
{
self->solid = SOLID_BSP;
self->svflags &= ~SVF_NOCLIENT;
KillBox (self);
}
else
{
self->solid = SOLID_NOT;
self->svflags |= SVF_NOCLIENT;
}
gi.linkentity (self);
if (!(self->spawnflags & 2))
self->use = NULL;
}
void SP_func_wall (edict_t *self)
{
self->movetype = PHYSICSTYPE_PUSH;
gi.setmodel (self, self->model);
if (self->spawnflags & 8)
self->s.effects |= EF_ANIM_ALL;
if (self->spawnflags & 16)
self->s.effects |= EF_ANIM_ALLFAST;
// just a wall
if ((self->spawnflags & 7) == 0)
{
self->solid = SOLID_BSP;
gi.linkentity (self);
return;
}
// it must be TRIGGER_SPAWN
if (!(self->spawnflags & 1))
{
// gi.dprintf("func_wall missing TRIGGER_SPAWN\n");
self->spawnflags |= 1;
}
// yell if the spawnflags are odd
if (self->spawnflags & 4)
{
if (!(self->spawnflags & 2))
{
gi.dprintf("func_wall START_ON without TOGGLE\n");
self->spawnflags |= 2;
}
}
self->use = func_wall_use;
if (self->spawnflags & 4)
{
self->solid = SOLID_BSP;
}
else
{
self->solid = SOLID_NOT;
self->svflags |= SVF_NOCLIENT;
}
gi.linkentity (self);
}
/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
This is solid bmodel that will fall if it's support it removed.
*/
void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
// only squash thing we fall on top of
if (!plane)
return;
if (plane->normal[2] < 1.0)
return;
if (other->takedamage == DAMAGE_NO)
return;
T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, DAMAGE_AVOID_ARMOR,MOD_DIED);
}
void func_object_release (edict_t *self)
{
self->movetype = PHYSICSTYPE_STEP;
self->touch = func_object_touch;
}
void func_object_use (edict_t *self, edict_t *other, edict_t *activator)
{
self->solid = SOLID_BSP;
self->svflags &= ~SVF_NOCLIENT;
self->use = NULL;
KillBox (self);
func_object_release (self);
}
void SP_func_object (edict_t *self)
{
gi.setmodel (self, self->model);
self->mins[0] += 1;
self->mins[1] += 1;
self->mins[2] += 1;
self->maxs[0] -= 1;
self->maxs[1] -= 1;
self->maxs[2] -= 1;
if (!self->dmg)
self->dmg = 100;
if (self->spawnflags == 0)
{
self->solid = SOLID_BSP;
self->movetype = PHYSICSTYPE_PUSH;
self->think = func_object_release;
self->nextthink = level.time + 2 * FRAMETIME;
}
else
{
self->solid = SOLID_NOT;
self->movetype = PHYSICSTYPE_PUSH;
self->use = func_object_use;
self->svflags |= SVF_NOCLIENT;
}
if (self->spawnflags & 2)
self->s.effects |= EF_ANIM_ALL;
if (self->spawnflags & 4)
self->s.effects |= EF_ANIM_ALLFAST;
self->clipmask = MASK_MONSTERSOLID;
gi.linkentity (self);
}
void ItemSpitterSpit(edict_t *self,edict_t *owner,edict_t *attacker)
{
int i1;
gitem_t *item;
edict_t *newitem;
vec3_t forward, holdangles,holdorigin;
float delta;
if ((!self->target) || (!self->style))
{
return;
}
self->style = 0; // Show spitter has been used
delta =(float) 360 / self->count;
VectorCopy(owner->s.angles,holdangles);
holdangles[YAW]= 0;
for (i1 = 0;i1 < self->count;++i1)
{
item = P_FindItemByClassname(self->target);
if (!item) // Must be an object not an item
{
newitem = G_Spawn();
newitem->classname = ED_NewString(self->target);
AngleVectors(holdangles,forward,NULL,NULL);
VectorCopy(self->s.origin,newitem->s.origin);
VectorMA(newitem->s.origin, self->dmg_radius, forward, newitem->s.origin);
if (self->mass)
newitem->spawnflags |= self->mass;
ED_CallSpawn(newitem);
VectorCopy(newitem->s.origin,holdorigin);
if (!(self->spawnflags & 1))
gi.CreateEffect(NULL, FX_PICKUP, 0, holdorigin, "");
}
else
{
newitem = G_Spawn();
newitem->movetype = PHYSICSTYPE_STEP;
AngleVectors(holdangles,forward,NULL,NULL);
VectorCopy(self->s.origin,newitem->s.origin);
VectorMA(newitem->s.origin, self->dmg_radius, forward, newitem->s.origin);
if (self->mass)
newitem->spawnflags |= self->mass;
SpawnItem(newitem, item);
VectorCopy(newitem->s.origin,holdorigin);
if (!(self->spawnflags & 1))
gi.CreateEffect(NULL, FX_PICKUP, 0, holdorigin, "");
}
holdangles[YAW] += delta;
}
}
/*QUAKED item_spitter (0 .5 .8) (-4 -4 -4) (4 4 4) NOFLASH
When targeted it will spit out an number of items in various directions
-------SPAWN FLAGS -----------
NOFLASH - no flash is created when item is 'spit out'
---------KEYS-----------------
target - classname of item or object being spit out
count - number of items being spit out (default 1)
radius - distance from item_spitter origin that items will be spawned
spawnflags2 - the spawnflags for the item being created.
*/
void SP_item_spitter(edict_t *self)
{
self->style = 1; // To show it hasn't been used yet
self->movetype = PHYSICSTYPE_NONE;
VectorSet(self->mins,-4,-4,-4);
VectorSet(self->maxs,4,4,4);
self->use = ItemSpitterSpit;
self->solid = SOLID_NOT;
if (!self->count)
self->count = 1;
self->dmg_radius = st.radius;
if (st.spawnflags2)
self->mass = st.spawnflags2;
else
self->mass = 0;
gi.linkentity (self);
}
//=================================================================================
// update the spawner so that we will rematerialise in a different position
void respawner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *spot = NULL;
// if we aren't a player, forget it
if (!other->client)
return;
while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL)
{
if (!game.spawnpoint[0] && !spot->targetname)
break;
if (!game.spawnpoint[0] || !spot->targetname)
continue;
if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
break;
}
if (!spot)
{
if (!game.spawnpoint[0])
{ // there wasn't a spawnpoint without a target, so use any
spot = G_Find (spot, FOFS(classname), "info_player_start");
}
if (!spot)
gi.error ("Couldn't find spawn point %s\n", game.spawnpoint);
}
VectorSet(spot->s.origin, self->mins[0]+((self->size[0]) /2), self->mins[1]+((self->size[1]) /2) ,self->mins[2]+self->size[2]);
VectorCopy (self->s.angles, spot->s.angles);
G_FreeEdict (self);
}
/*QUAKED misc_update_spawner (.5 .5 .5) ?
This creates the spawner update entity, which upates the spawner position when triggered
*/
void misc_update_spawner (edict_t *ent)
{
ent->movetype = PHYSICSTYPE_NONE;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_TRIGGER;
ent->touch = respawner_touch;
gi.setmodel(ent,ent->model);
gi.linkentity (ent);
}
void Teleporter_Deactivate(edict_t *self, G_Message_t *msg)
{
self->touch = NULL;
// if there's an effect out there, kill it
if (self->enemy)
{
gi.RemoveEffects(&self->enemy->s, FX_TELEPORT_PAD);
if (self->enemy->PersistantCFX)
{
gi.RemovePersistantEffect(self->enemy->PersistantCFX, REMOVE_TELEPORT_PAD);
self->enemy->PersistantCFX = 0;
}
self->enemy = NULL;
}
}
void Teleporter_Activate(edict_t *self, G_Message_t *msg)
{
vec3_t real_origin;
edict_t *effect;
self->touch = teleporter_touch;
// if there's no effect already, create a new one
if (!self->enemy)
{
effect = G_Spawn();
VectorCopy(self->maxs, effect->maxs);
VectorCopy(self->mins, effect->mins);
effect->solid = SOLID_NOT;
effect->s.effects |= EF_NODRAW_ALWAYS_SEND|EF_ALWAYS_ADD_EFFECTS;
self->enemy = effect;
gi.linkentity (effect);
real_origin[0] = ((self->maxs[0] - self->mins[0]) / 2.0) + self->mins[0];
real_origin[1] = ((self->maxs[1] - self->mins[1]) / 2.0) + self->mins[1];
real_origin[2] = ((self->maxs[2] - self->mins[2]) / 2.0) + self->mins[2];
if (!(self->spawnflags & 1))
effect->PersistantCFX = gi.CreatePersistantEffect(&effect->s, FX_TELEPORT_PAD, CEF_BROADCAST, real_origin, "");
}
}
void TeleporterStaticsInit()
{
classStatics[CID_TELEPORTER].msgReceivers[G_MSG_SUSPEND] = Teleporter_Deactivate;
classStatics[CID_TELEPORTER].msgReceivers[G_MSG_UNSUSPEND] = Teleporter_Activate;
}
/*QUAKED misc_teleporter (1 0 0) ? NO_MODEL DEATHMATCH_RANDOM START_OFF MULT_DEST
This creates the teleporter disc that will send us places
Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
------- FIELDS ------------------
NO_MODEL - makes teleporter invisible
DEATHMATCH_RANDOM - makes the teleporter dump you at random spawn points in deathmatch
START_OFF - Pad has no effect, and won't teleport you anywhere till its activated
MULT_DEST - pad is targeted at more than one destination
---------- KEYS -----------------
style - number of destinations this pad has.
*/
void SP_misc_teleporter (edict_t *ent)
{
vec3_t real_origin;
edict_t *effect;
if (!ent->target && (!ent->spawnflags&DEATHMATCH_RANDOM || !deathmatch->value))
{
gi.dprintf ("teleporter without a target.\n");
G_FreeEdict (ent);
return;
}
ent->msgHandler = DefaultMsgHandler;
ent->classID = CID_TELEPORTER;
ent->movetype = PHYSICSTYPE_NONE;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_TRIGGER;
gi.setmodel(ent, ent->model);
gi.linkentity (ent);
// if we don't have mult dests - probably redundant
if (!(ent->spawnflags & 8))
ent->style = 0;
// if we want an effect on spawn, create it.
if (!(ent->spawnflags & 4))
{
ent->touch = teleporter_touch;
effect = G_Spawn();
VectorCopy(ent->maxs, effect->maxs);
VectorCopy(ent->mins, effect->mins);
effect->solid = SOLID_NOT;
effect->s.effects |= EF_NODRAW_ALWAYS_SEND|EF_ALWAYS_ADD_EFFECTS;
ent->enemy = effect;
gi.linkentity (effect);
real_origin[0] = ((ent->maxs[0] - ent->mins[0]) / 2.0) + ent->mins[0];
real_origin[1] = ((ent->maxs[1] - ent->mins[1]) / 2.0) + ent->mins[1];
real_origin[2] = ((ent->maxs[2] - ent->mins[2]) / 2.0) + ent->mins[2];
if (!(ent->spawnflags & 1))
effect->PersistantCFX = gi.CreatePersistantEffect(&effect->s, FX_TELEPORT_PAD, CEF_BROADCAST, real_origin, "");
}
}
/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
Point teleporters at these.
*/
void SP_misc_teleporter_dest (edict_t *ent)
{
trace_t tr;
vec3_t endpos;
ent->s.skinnum = 0;
ent->solid = SOLID_NOT;
VectorSet (ent->mins, -32, -32, -24);
VectorSet (ent->maxs, 32, 32, -16);
gi.linkentity (ent);
VectorCopy(ent->s.origin, endpos);
endpos[2] -= 500;
gi.trace (ent->s.origin, vec3_origin, vec3_origin, endpos, NULL, CONTENTS_WORLD_ONLY|MASK_PLAYERSOLID,&tr);
VectorCopy(tr.endpos,ent->last_org);
ent->last_org[2] -= mins[2];
}
extern void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator);
void misc_magic_portal_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *ent=NULL;
if (level.time < self->touch_debounce_time)
return;
if (!other->client) // Not a player
return;
ent = G_Find (ent, FOFS(targetname), self->target);
if (!ent)
{ // No target. Don't do anything.
// Com_Printf("Portal has no target.\n");
}
else
{
use_target_changelevel(ent, self, other);
}
self->touch_debounce_time = level.time + 4.0;
return;
}
void misc_magic_portal_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (level.time < self->impact_debounce_time)
return;
if (self->solid == SOLID_NOT)
{ // We aren't engaged yet. Make solid and start the effect.
self->solid = SOLID_TRIGGER;
self->touch = misc_magic_portal_touch;
self->PersistantCFX = gi.CreatePersistantEffect(&self->s, FX_MAGIC_PORTAL, CEF_BROADCAST, self->s.origin,
"vbb", self->s.angles, (byte)self->style, (byte)self->count);
self->s.effects &= ~EF_DISABLE_EXTRA_FX;
}
else
{ // We were on, now turn it off.
self->solid = SOLID_NOT;
self->touch = NULL;
// remove the persistant effect
if (self->PersistantCFX)
{
gi.RemovePersistantEffect(self->PersistantCFX, REMOVE_PORTAL);
self->PersistantCFX = 0;
}
self->s.effects |= EF_DISABLE_EXTRA_FX;
}
gi.linkentity(self);
self->impact_debounce_time = level.time + 4.0;
}
#define START_OFF 1
/*QUAKED misc_magic_portal (1 .5 0) (-16 -16 -32) (16 16 32) START_OFF
A magical glowing portal. Triggerable.
------- FIELDS ------------------
START_OFF - portal will start off
-----------------------------------
angles - manipulates the facing of the effect as normal.
style - 0-blue, 1-red, 2-green
count - Close after 1-255 seconds. 0 means stay until triggered.
In order to be functional as a world teleport,
it must target a target_changelevel
*/
void SP_misc_magic_portal (edict_t *self)
{
// Set up the basics.
VectorSet(self->mins, -16, -16, -32);
VectorSet(self->maxs, 16, 16, 32);
self->s.scale = 1;
self->mass = 250;
self->friction = 0;
self->gravity = 0;
self->s.effects |= EF_ALWAYS_ADD_EFFECTS;
self->svflags |= SVF_ALWAYS_SEND;
self->movetype = PHYSICSTYPE_NONE;
self->solid = SOLID_NOT;
self->touch = NULL;
self->use = misc_magic_portal_use;
if (!self->spawnflags & START_OFF)
{ // Set up the touch function, since this baby is live.
misc_magic_portal_use(self, NULL, NULL);
}
gi.linkentity(self);
}
void flame_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other->damage_debounce_time < level.time)
{
other->damage_debounce_time = level.time + 0.2;
T_Damage (other, world, world, other->s.origin, vec3_origin, vec3_origin, 10, 0, DAMAGE_SPELL|DAMAGE_AVOID_ARMOR,MOD_DIED);//Fixme: propper params.
}
}
void flame_think (edict_t *self)
{
// self->s.frame++;
// if (self->s.frame > 5)
// self->s.frame = 0;
// self->nextthink = level.time + FRAMETIME;
self->nextthink = level.time + 200;
}
void soundambient_think(edict_t *self)
{
byte style,wait,attenuation,volume;
attenuation = Q_ftol(self->attenuation);
volume = Q_ftol(self->volume * 255);
self->s.sound_data = (volume & ENT_VOL_MASK) | attenuation;
// if its a looping sound, create it on this entity
switch((int)(self->style))
{
case AS_FIRE:
self->s.sound = gi.soundindex("ambient/fireplace.wav");
break;
case AS_WATERLAPPING:
self->s.sound = gi.soundindex("ambient/waterlap.wav");
break;
case AS_OCEAN:
self->s.sound = gi.soundindex("ambient/ocean.wav");
break;
case AS_SMALLFOUNTAIN:
self->s.sound = gi.soundindex("ambient/smallfountain.wav");
break;
case AS_LARGEFOUNTAIN:
self->s.sound = gi.soundindex("ambient/fountainloop.wav");
break;
case AS_SEWERWATER:
self->s.sound = gi.soundindex("ambient/sewerflow.wav");
break;
case AS_OUTSIDEWATERWAY:
self->s.sound = gi.soundindex("ambient/river.wav");
break;
case AS_CAULDRONBUBBLE:
self->s.sound = gi.soundindex("ambient/cauldronbubble.wav");
break;
case AS_HUGEWATERFALL:
self->s.sound = gi.soundindex("ambient/hugewaterfall.wav");
break;
case AS_MUDPOOL:
self->s.sound = gi.soundindex("ambient/mudpool.wav");
break;
case AS_WINDEERIE:
self->s.sound = gi.soundindex("ambient/windeerie.wav");
break;
case AS_WINDNOISY:
self->s.sound = gi.soundindex("ambient/windnoisy.wav");
break;
case AS_WINDSOFTHI:
self->s.sound = gi.soundindex("ambient/windsofthi.wav");
break;
case AS_WINDSOFTLO:
self->s.sound = gi.soundindex("ambient/windsoftlow.wav");
break;
case AS_WINDSTRONG1:
self->s.sound = gi.soundindex("ambient/windstrong1.wav");
break;
case AS_WINDSTRONG2:
self->s.sound = gi.soundindex("ambient/windstrong2.wav");
break;
case AS_WINDWHISTLE:
self->s.sound = gi.soundindex("ambient/windwhistle.wav");
break;
case AS_CONVEYOR:
self->s.sound = gi.soundindex("objects/conveyor.wav");
break;
case AS_BUCKETCONVEYOR:
self->s.sound = gi.soundindex("objects/bucketconveyor.wav");
break;
case AS_SPIT:
self->s.sound = gi.soundindex("objects/spit.wav");
break;
default:
style = Q_ftol(self->style);
wait = Q_ftol(self->wait);
gi.CreatePersistantEffect(&self->s,
FX_SOUND,
CEF_BROADCAST | CEF_OWNERS_ORIGIN,
self->s.origin,
"bbbb",
style,attenuation,volume,wait);
break;
}
self->count = 1; // This is just a flag to show it's on
self->think = NULL;
}
void sound_ambient_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->count) // This is just a flag to show it's on
{
self->count = 0;
gi.RemoveEffects(&self->s,0);
}
else
soundambient_think(self);
}
void sound_ambient_init(edict_t *self)
{
VectorSet(self->mins,-4,-4,-4);
VectorSet(self->maxs,4,4,4);
self->movetype = PHYSICSTYPE_NONE;
if (self->attenuation <= 0.01)
self->attenuation = 1;
if (self->volume <= 0.01)
self->volume = .5;
if (self->wait<1)
self->wait = 10;
self->s.effects |= EF_NODRAW_ALWAYS_SEND;
if (!(self->spawnflags & 2))
{
self->nextthink = level.time + 2.5;
self->think = soundambient_think;
}
// if we are asked to do a sound of type zero, free this edict, since its obviously bogus
if (!self->style)
{
gi.dprintf("Bogus ambient sound at x:%f y:%f z:%f\n",self->s.origin[0], self->s.origin[1],self->s.origin[2]);
G_SetToFree(self);
return;
}
// if we are non local, clear the origin of this object
if (self->spawnflags & 1)
{
VectorClear(self->s.origin);
}
else
// if we are here, then this ambient sound should have an origin
assert(Vec3NotZero(self->s.origin));
self->use = sound_ambient_use;
gi.linkentity(self);
}
/*QUAKED sound_ambient_cloudfortress (1 0 0) (-4 -4 0) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for cloud fortress levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style
1 - Cauldron bubbling (looping sound)
2 - wind, low, eerie (looping)
3 - wind, low, noisy (looping)
4 - wind, high, soft (looping)
5 - wind, low, soft (looping)
6 - wind, low, strong (looping)
7 - wind, high, strong (looping)
8 - wind, whistling, strong (looping)
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_cloudfortress (edict_t *self)
{
sound_ambient_init(self);
self->style = CloudSoundID[self->style];
}
/*QUAKED sound_ambient_mine (1 0 0) (-4 -4 0) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for mine levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style
1 - Mud pool bubbling (looping)
2 - Rocks falling (3 sounds)
3 - wind, low, eerie (looping)
4 - wind, low, soft (looping)
5 - conveyor belt (looping)
6 - bucket conveyor belt (looping)
7 - three different creaks of heavy timbers
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_mine (edict_t *self)
{
sound_ambient_init(self);
self->style = MineSoundID[self->style];
}
/*QUAKED sound_ambient_hive (1 0 0) (-4 -4 0) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for hive levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style
1 - gong
2 - wind, low, eerie (looping)
3 - wind, low, noisy (looping)
4 - wind, high, soft (looping)
5 - wind, low, soft (looping)
6 - wind, low, strong (looping)
7 - wind, high, strong (looping)
8 - wind, whistling, strong (looping)
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_hive (edict_t *self)
{
sound_ambient_init(self);
self->style = HiveSoundID[self->style];
}
/*QUAKED sound_ambient_andoria (1 0 0) (-4 -4 0) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for andoria levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style
1 - small fountain (constant loop)
2 - large fountain (constant loop)
3 - water running out of sewer (constant loop)
4 - rushing waterway outside (constant loop)
5 - wind chime
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_andoria (edict_t *self)
{
sound_ambient_init(self);
self->style = AndoriaSoundID[self->style];
}
/*QUAKED sound_ambient_swampcanyon (1 0 0) (-4 -4 0) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for swamp or canyon levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style
1 - bird, quick, high pitch
2 - bird, low, medium pitch
3 - huge waterfall
4 - mud pool bubbling (looping)
5 - wind, low, eerie (looping)
6 - wind, low, noisy (looping)
7 - wind, high, soft (looping)
8 - wind, low, soft (looping)
9 - wind, low, strong (looping)
10 - wind, high, strong (looping)
11 - wind, whistling, strong (looping)
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_swampcanyon (edict_t *self)
{
self->style = SwampCanyonSoundID[self->style];
sound_ambient_init(self);
}
/*QUAKED sound_ambient_silverspring (1 0 0) (-4 -4 -4) (4 4 4) NON_LOCAL START_OFF
Generates an ambient sound for silverspring levels
------- FLAGS ------------------
NON_LOCAL - sound occurs everywhere in the level - attenuation is not operative with this type of sound
wait amount of seconds to wait + or - 50% before spawning sound again (default is 10 seconds)
START_OFF - starts off, can be triggered on
------- KEYS ------------------
style :
1 - fire (looping)
2 - water lapping (looping)
3 - seagulls (2 random calls)
4 - ocean
5 - birds (10 random bird calls)
6 - crickets (3 random chirps)
7 - frogs (2 random ribbets)
8 - distant women/children crying (4 total)
9 - mosquitoes (2 random sounds)
10 - bubbles
11 - bell tolling
12 - footsteps (3 random sounds)
13 - moans/screams/coughing (5 random sounds)
14 - Sewer drips (3 random sounds)
15 - Water drips (3 random sounds)
16 - Solid drips - heavy liquid (3 random sounds)
17 - Cauldron bubbling (looping sound)
18 - Creaking for the spit that's holding the elf over a fire
attenuation (how quickly sound drops off from origin)
0 - heard over entire level (default)
1 -
2 -
3 - diminish very rapidly with distance
volume range of .1 to 1 (default .5)
0 - silent
1 - full volume
-----------------------------------
*/
void SP_sound_ambient_silverspring (edict_t *self)
{
self->style = SilverSpringSoundID[self->style];
sound_ambient_init(self);
}
/*QUAKED misc_remote_camera (0 0.5 0.8) (-4 -4 -4) (4 4 4) ACTIVATING SCRIPTED NO_DELETE
pathtarget - holds the name of the camera's owner entity (if any).
target - holds the name of the entity to be looked at.
spawnflags - 1 only the activating client will see the remote camera view.
- 2 this is a scripted camera
- 4 don't delete camera
*/
void remove_camera(edict_t *Self)
{
if(Self->spawnflags&1)
{
// Just for the activator.
Self->activator->client->RemoteCameraLockCount--;
}
else
{
// For all clients.
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
cl_ent->client->RemoteCameraLockCount--;
}
}
if(!(Self->spawnflags&4))
{
G_FreeEdict(Self);
}
}
void misc_remote_camera_think(edict_t *Self)
{
// ********************************************************************************************
// Attempt to find my owner entity (i.e. what I'm fixed to). If nothing is found, then my
// position will remain unchanged.
// ********************************************************************************************
if(Self->pathtarget)
Self->enemy=G_Find(NULL,FOFS(targetname),Self->pathtarget);
if(Self->enemy||(Self->spawnflags&2))
{
// I am attatched to another (possibly moving) entity, so update my position.
if(Self->enemy)
{
VectorCopy(Self->enemy->s.origin,Self->s.origin);
}
// Update the position on client(s).
if(Self->spawnflags&1)
{
// Just for the activator.
if(Self->activator->client->RemoteCameraNumber==Self->s.number)
{
int i;
for(i=0;i<3;i++)
Self->activator->client->ps.remote_vieworigin[i]=Self->s.origin[i]*8.0;
}
}
else
{
// For all clients.
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
if(cl_ent->client->RemoteCameraNumber==Self->s.number)
{
int j;
for(j=0;j<3;j++)
cl_ent->client->ps.remote_vieworigin[j]=Self->s.origin[j]*8.0;
}
}
}
}
// ********************************************************************************************
// Find my target entity and then orientate myself to look at it.
// ********************************************************************************************
if(Self->targetEnt=G_Find(NULL,FOFS(targetname),Self->target))
{
// Calculate the angles from myself to my target.
vec3_t Forward;
VectorSubtract(Self->targetEnt->s.origin,Self->s.origin,Forward);
VectorNormalize(Forward);
vectoangles(Forward,Self->s.angles);
Self->s.angles[PITCH]=-Self->s.angles[PITCH];
// Update the angles on client(s).
if(Self->spawnflags&1)
{
// Just for the activator.
if(Self->activator->client->RemoteCameraNumber==Self->s.number)
{
int i;
for(i=0;i<3;i++)
Self->activator->client->ps.remote_viewangles[i]=Self->s.angles[i];
}
}
else
{
// For all clients.
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
if(cl_ent->client->RemoteCameraNumber==Self->s.number)
{
int j;
for(j=0;j<3;j++)
cl_ent->client->ps.remote_viewangles[j]=Self->s.angles[j];
}
}
}
}
// Think again or remove myself?
if (Self->spawnflags & 2)
{
Self->nextthink = level.time+FRAMETIME;
}
else
{
Self->delay-=0.1;
if (Self->delay >= 0.0)
{
Self->nextthink=level.time+0.1;
}
else
{
remove_camera(Self);
}
}
}
void Use_misc_remote_camera(edict_t *Self,edict_t *Other,edict_t *Activator)
{
vec3_t Forward;
// ********************************************************************************************
// If I am already active, just return, else flag that I am active.
// ********************************************************************************************
if(Self->count)
{
if(Self->spawnflags&2)
{
// I am a scripted camera, so free myself before returning.
remove_camera(Self);
}
return;
}
Self->count=1;
// ********************************************************************************************
// Signal to client(s) that a remote camera view is active,
// ********************************************************************************************
if(Self->spawnflags&1)
{
// Signal to just the activator (i.e. person who was ultimately responsible for triggering the
// remote camera) that their camera view has changed to a remote camera view..
Self->activator=Activator;
Self->activator->client->RemoteCameraLockCount++;
Self->activator->client->RemoteCameraNumber=Self->s.number;
}
else
{
// Signal to all clients that their camera view has changed to a remote camera view..
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
cl_ent->client->RemoteCameraLockCount++;
cl_ent->client->RemoteCameraNumber=Self->s.number;
}
}
// ********************************************************************************************
// Attempt to find my owner entity (i.e. what I'm fixed to). If nothing is found, then I am a
// static camera so set up my position here (it will remain unchanged hereafter).
// ********************************************************************************************
if(!Self->pathtarget)
{
// I am static, so set up my position (which will not change hereafter).
if(Self->spawnflags&1)
{
// Just for the activator.
int i;
Self->enemy=NULL;
for(i=0;i<3;i++)
Self->activator->client->ps.remote_vieworigin[i]=Self->s.origin[i]*8.0;
}
else
{
// For all clients.
int i,j;
edict_t *cl_ent;
Self->enemy=NULL;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
for(j=0;j<3;j++)
cl_ent->client->ps.remote_vieworigin[j]=Self->s.origin[j]*8.0;
}
}
}
else
{
Self->enemy=G_Find(NULL,FOFS(targetname),Self->pathtarget);
if(Self->enemy||(Self->spawnflags&2))
{
// I am attatched to another (possibly moving) entity, so update my position.
if(Self->enemy)
{
VectorCopy(Self->enemy->s.origin,Self->s.origin);
}
// Update the position on client(s).
if(Self->spawnflags&1)
{
// Just for the activator.
if(Self->activator->client->RemoteCameraNumber==Self->s.number)
{
int i;
for(i=0;i<3;i++)
Self->activator->client->ps.remote_vieworigin[i]=Self->s.origin[i]*8.0;
}
}
else
{
// For all clients.
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
if(cl_ent->client->RemoteCameraNumber==Self->s.number)
{
int j;
for(j=0;j<3;j++)
cl_ent->client->ps.remote_vieworigin[j]=Self->s.origin[j]*8.0;
}
}
}
}
}
// ********************************************************************************************
// Find my target entity and then orientate myself to look at it.
// ********************************************************************************************
Self->targetEnt=G_Find(NULL,FOFS(targetname),Self->target);
VectorSubtract(Self->targetEnt->s.origin,Self->s.origin,Forward);
VectorNormalize(Forward);
vectoangles(Forward,Self->s.angles);
Self->s.angles[PITCH]=-Self->s.angles[PITCH];
// Update the angles on client(s).
if(Self->spawnflags&1)
{
// Just for the activator.
if(Self->activator->client->RemoteCameraNumber==Self->s.number)
{
int i;
for(i=0;i<3;i++)
Self->activator->client->ps.remote_viewangles[i]=Self->s.angles[i];
}
}
else
{
// For all clients.
int i;
edict_t *cl_ent;
for(i=0;i<game.maxclients;i++)
{
cl_ent=g_edicts+1+i;
if(!cl_ent->inuse)
continue;
if(cl_ent->client->RemoteCameraNumber==Self->s.number)
{
int j;
for(j=0;j<3;j++)
cl_ent->client->ps.remote_viewangles[j]=Self->s.angles[j];
}
}
}
// ********************************************************************************************
// Setup next think stuff.
// ********************************************************************************************
Self->think=misc_remote_camera_think;
Self->nextthink=level.time + FRAMETIME;
}
void SP_misc_remote_camera(edict_t *Self)
{
Self->enemy=Self->targetEnt=NULL;
if(!Self->target)
{
gi.dprintf("Object 'misc_remote_camera' without a target.\n");
G_FreeEdict(Self);
return;
}
Self->movetype = PHYSICSTYPE_NONE;
Self->solid=SOLID_NOT;
VectorSet(Self->mins,-4,-4,-4);
VectorSet(Self->maxs,4,4,4);
Self->count=0;
Self->use=Use_misc_remote_camera;
gi.linkentity(Self);
}
// Spawns a client model animation
// spawnflags & 2 is a designer flag whether to animate or not
// If the model is supposed to animate, the hi bit of the type is set
// If the model is static, then the default frame stored on the client is used
// Valid scale ranges from 1/50th to 5
void SpawnClientAnim(edict_t *self, byte type, char *sound)
{
int scale, skin;
if (self->spawnflags & 2) // Animate it
{
type |= 0x80;
if(sound)
{
self->s.sound = gi.soundindex(sound);
self->s.sound_data = (255 & ENT_VOL_MASK) | ATTN_STATIC;
}
}
scale = (byte)(self->s.scale * 50);
assert((scale > 0) && (scale < 255));
skin = (byte)self->s.skinnum;
// self->svflags |= SVF_ALWAYS_SEND;
self->PersistantCFX = gi.CreatePersistantEffect(&self->s,
FX_ANIMATE,
CEF_BROADCAST,
self->s.origin,
"bbbv", type, scale, skin, self->s.angles);
self->s.effects |= EF_ALWAYS_ADD_EFFECTS;
}
//A check to see if ent should reflect
qboolean EntReflecting(edict_t *ent, qboolean checkmonster, qboolean checkplayer)
{
if(!ent)
{
return false;
}
if(checkmonster)
{
if(ent->svflags & SVF_MONSTER && ent->svflags & SVF_REFLECT)
{
return true;
}
}
if(checkplayer)
{
if(ent->client)
{
if(ent->client->playerinfo.reflect_timer > level.time)
{
return true;
}
// possibly, we might want to reflect this if the player has gold armor
else
if((ent->client->playerinfo.pers.armortype == ARMOR_TYPE_GOLD) && (ent->client->playerinfo.pers.armor_count) && (irand(0,100) < 30))
return true;
}
}
return false;
}
void SkyFlyCheck(edict_t *self)
{
if(self->s.origin[2]>3900)
G_FreeEdict(self);
else
self->nextthink = level.time + 0.1;
}
void SkyFly (edict_t *self)
{
/* if(deathmatch->value)
{*/
G_SetToFree(self);
return;
/* }
//They're not being drawn, even after this is set- why not? Did they stop?
self->svflags |= SVF_ALWAYS_SEND;
self->movetype = PHYSICSTYPE_NOCLIP;
self->solid = SOLID_NOT;
self->touch = NULL;
self->isBlocked = NULL;
self->isBlocking = NULL;
self->bounced = NULL;
//or just remove self after a time
self->think = SkyFlyCheck;
self->nextthink = level.time + 0.1;
*/
}
void fire_spark_think (edict_t *self)
{
if(self->delay && self->delay < level.time)
{
G_FreeEdict(self);
return;
}
self->think = fire_spark_think;
self->nextthink = level.time + 0.1;//self->wait;
}
void fire_spark_gone (edict_t *self, edict_t *other, edict_t *activator)
{
self->use = NULL;
gi.RemoveEffects(&self->s, FX_SPARKS);
G_FreeEdict(self);
}
void fire_spark_use (edict_t *self, edict_t *other, edict_t *activator)
{
gi.CreateEffect(&self->s, FX_SPARKS, CEF_FLAG6|CEF_FLAG7|CEF_FLAG8, self->s.origin, "d", vec3_up);
self->use = fire_spark_gone;
self->think = fire_spark_think;
self->nextthink = level.time + 0.1;
}
/*QUAKED misc_fire_sparker (0 0 0) (-4 -4 0) (4 4 8) FIREBALL
FIREBALL - more of a poofy fireball trail
Fires of sparks when used...
used a second time removes it
"delay" - how long to live for... (default is forever)
*/
void SP_misc_fire_sparker (edict_t *self)
{
if(self->spawnflags & 1)
self->s.effects |= EF_MARCUS_FLAG1;
self->svflags |= SVF_ALWAYS_SEND;
self->solid = SOLID_NOT;
self->movetype = PHYSICSTYPE_NOCLIP;
self->clipmask = 0;
self->use = fire_spark_use;
}
// end