|
posted 11-20-98 11:47 AM CT (US)
Title: The Torture Chamber
Difficulty: Easy to Implement, Difficult to understand
By: Philip (aka Maj.Bitch)
Email: peblair@gv.net
Date: 11-07-98
Note: Please give credit where credit is due.
======================================================
I put this tutorial
together because I wanted to make yet one
more new weapon with a lot of features. This new weapon
is called the Torture Chamber (because, well... that's what
it is!)
All the chamber's
models are standard issue Q2 so nothing new
is needed client-side.
What is it? Well, it
is a pair of respawn pads with one flipped
over to form a chamber. The inside of the chamber is filled
with a water-fountain of yellow particles and there is a laser
beam shell around the chamber. A pipe sticks out the top of
the chamber out of which flows a green particle substance which
indicates that the chamber is presently targeting. When the
chamber finds a player a beam comes out of the top of the pipe
on top of the chamber and tags the player. Once tagged, the
player gets transported into the center of the chamber and gets
tortured to death (with the associated screams of pain and agony,
of course). Once a player is inside the chamber, the yellow particle
waterfall ceases and a the player's blood comes spurting out the
top of the chamber!! (MAN THIS IS TOO HARD TO DESCRIBE PHYSICALLY!)
You'll have to see it to believe it!! Really an awesome piece of
hardward to add to your arsenal!
HERE ARE SOME HINTS:
(The rest you'll have to get from reading
the code!)
The player who gets
transported into the chamber has their
health increased so that they get tortured longer!!
The only way to kill
the chamber is to kill the antenna/pipe which
sticks up from the top of the chamber. It is very small but very
vulnerable. It has a health of 200 units so it takes quite a bit
to get it to explode (giving you the appropriate 5 frag credits
too) and it has a very small defined BBOX dimension so targeting
accuracy is at a premium. While you are targeting the antenna/pipe
at the top of the chamber, you maybe tagged and teleported into
the chamber and tortured to death! However, the chamber turns
off its targeting mechanism while a player is already in the
chamber! So, if there is a player inside getting tortured to
death, run over and stand next to the chamber and watch the poor
bastard suffer! hehehe
Killing the
chamber's owner destroys all the chambers which belong
to that player (providing that they don't have any players in them
at the moment. If so, then those poor players already inside the
chambers continue to be tortured until they are dead, at which time
the chamber will self-destruct!
The cost of a single
chamber is 100 powercells or 5 frags. Pay the
piper and you can set up your own torture chamber with the single
stroke of your aliased key. You get all the frags that the chamber
makes and you get to watch the bastards die slowly!!
The laser shell
around the chamber is an invincible force field which
keeps the player trapped inside. You can throw anything you want at
the chamber and it won't die (unless, like I mentioned before, you
score a direct hit on the chambers antenna/pipe).. Also, you can't
do anything to save the player inside the chamber (unless they've
got a self-teleport capability, see my INSTA-PORT Tutorial). This
also means that you can't kill the poor bastard inside the chamber
to put them out of their misery. Their only way out is to suicide!
TOO MANY FEATURES TO
DISCUSS. Try this one out for yourself!!
SERVER ADMINS:
I'm sure you can
find plenty of reasons to torture to death
a few of those bastards which, for instance, flood your message
board. Place a chamber on each of your levels for safe measure and
auto-teleport any assholes which are causing you trouble right into
the chamber for safe keeping..
I put this Torture
Chamber together is such a manner that it
can find a lot of different uses within various mods. For
instance, you can have pre-defined chambers placed around a
level which captured players are put into! Or, you can put
the zbot detected players into a preset chamber on a level
and torture the poor bastards for a while. Lots of things
you can do with the preset positioning of the chamber on any
particular level. Use your imagination and read the preset
code below!
I'm not even going
to get into how this things works! If you
would understand how I did this then you'll easily get it from
a quick read of all the comments I've got all over the place!
If you can't then I'd be wasting alot of chars trying to
explain the mechanisms I employed to make this a reality..
Well, I want to quit
all this yapping and get to the code!
Okay? Let's get started..
============================================================
We need to add a new variable to your gclient_s struct. So,
at the bottom of that struct, add this:
int chamber; //
1=ON, 0=OFF
This will be used to
toggle the chamber ON/OFF
============================================================
We need to add
another MOD_* obituary flag. So, inside of
your g_local.h add the MOD_CHAMBER flag as the next integer
in the numerical sequence as shown by example:
#define
MOD_CLUSTER_BOMBS 0x00000024 // Cluster Airstrike
#define MOD_ZYLON_GAS 0x00000025 // Gas Grenade
#define MOD_RAILSTRIKE 0x00000026 // BFG Nuke Airstrike
#define MOD_BFG_NUKE 0x00000027 // Railgun Airstrike
#define MOD_DECOY 0x00000028 // Decoy Blast
#define MOD_LASERDRONE 0x00000029 // LaserDrone Weapon
#define MOD_BATON 0x00000030 // Baton Weapon
#define MOD_CHAMBER 0x00000031 // NEW FLAG HERE
#define MOD_FRIENDLY_FIRE 0x10000000 // DON'T TOUCH THIS ONE!!
============================================================
Okay, let's make
some obituaries for your gameplay. Find your
ClientObituary() function in your p_client.c file and add the
following:
---------- FIND
THESE LINES HERE -------------
case
MOD_TARGET_BLASTER:
message="got blasted";
break;
------------ ADD THESE LINES HERE ------------
case MOD_CHAMBER:
message="was tortured to death";
break;
Further down in this
same function:
-------------- FIND
THESE LINES -----------
case MOD_G_SPLASH:
if (IsFemale(victim))
message="tripped on her own grenade";
else
message="tripped on his own grenade";
break;
----------- ADD
THESE LINES HERE -------------
case MOD_CHAMBER:
message="died under torture";
break;
Still further down
in this same function:
---------- FIND
THESE LINES HERE -------------
case MOD_TELEFRAG:
message="tried to invade";
message2="'s personal space";
break;
--------- ADD THESE
LINES HERE ---------------
case MOD_CHAMBER:
message="died in";
message2="'s torture chamber";
break;
Remember: You should
feel free to change these client
obituaries to whatever you think is more appropriate for
your particular mod..
============================================================
Okay, go into
InitClientPersistant() function in your p_client.c
file and add this line near the bottom:
client->chamber=0;
// default to OFF.
This will set the
default to OFF each time the player enters
the game..
============================================================
Also in your
p_client.c file, go into Player_Die() function
and add this line too:
self->client->chamber=0;
// Turn OFF.
This will kill the
chamber entities when the player dies..
============================================================
Open up your
g_cmds.c file and go near the bottom of your
ClientCommand() function and add the following as shown
by example..
---------- IF-ELSE
STATEMENTS --------
else if (Q_stricmp(cmd, "decoy") == 0 )
SP_Decoy(ent);
else if (Q_stricmp(cmd, "drone") == 0 )
Cmd_LaserDrone_f(ent);
else if (Q_stricmp(cmd, "teleport") == 0)
Cmd_Teleport_f(ent);
----------- ADD THESE LINES ------
else if (Q_stricmp(cmd, "chamber") == 0)
Cmd_Chamber_f(ent);
else if .....
This will create a
Chamber with a single stroke of the player's
aliased key (providing they can pay the costs!).
============================================================
Also, near the top
of you g_cmds.c file add this forward
declaration:
void
Cmd_Chamber_f(edict_t *ent);
============================================================
You are going to need the following set of helper functions.
If you already have them, then you don't need them.. If you
don't already have them, you need them...
Okay, put these
helper function at the bottom of g_utils.c file.
//======================================================
// True if start and end are within radius distance.
//======================================================
qboolean G_Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
for (j=0; j<3; j++)>
eorg[j]=abs(start[j]-end[j]);
return (VectorLength(eorg) < rad);
}
//======================================================
// True if Ent is valid, has client, and edict_t inuse.
//======================================================
qboolean G_EntExists(edict_t *ent) {
return ((ent) && (ent->client) && (ent->inuse));
}
//======================================================
// True if ent is not DEAD or DEAD or DEAD (and BURIED!)
//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean buried=true;
qboolean b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b3=ent->health > 0;
return (b3 || b2 || b1) && (buried);
}
//======================================================
// True if ent is not DEAD and not just did a Respawn.
//======================================================
qboolean G_ClientInGame(edict_t *ent) {
if (!G_EntExists(ent)) return false;
if (!G_ClientNotDead(ent)) return false;
return (ent->client->respawn_time + 5.0 < level.time);
}
//======================================================
// True if start and end are within radius distance.
//======================================================
qboolean G_Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
for (j=0; j<3; j++)>
eorg[j]=abs(start[j]-end[j]);
return (VectorLength(eorg) < rad);
}
--------------------------------------------------------
And, put these
helper functions at the bottom of g_spawn.c
//======================================================
//========== Spawn Temp Entity Functions ===============
//======================================================
/*
Spawns (type) Splash with {count} particles of {color} at {start} moving
in {direction} and Broadcasts to all in Potentially Visible Set from
vector (origin)
TE_LASER_SPARKS -
Splash particles obey gravity
TE_WELDING_SPARKS - Splash particles with flash of light at {origin}
TE_SPLASH - Randomly shaded shower of particles
colors:
/* Color Codes
0xf2f2f0f0,//red
0xd0d1d2d3,//green
0xf3f3f1f1,//blue
0xdcdddedf,//yellow
0xe0e1e2e3,//yellow strobe
0x80818283,//dark purple
0x70717273,//light blue
0x90919293,//different green
0xb0b1b2b3,//purple
0x40414243,//different red
0xe2e5e3e6,//orange
0xd0f1d3f3,//mixture
0xf2f3f0f1,//inner = red, outer = blue
0xf3f2f1f0,//inner = blue, outer = red
0xdad0dcd2,//inner = green, outer = yellow
0xd0dad2dc //inner = yellow, outer = green
*/
//======================================================
void G_Spawn_Splash(int type, int count, int color, vec3_t start, vec3_t
movdir, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WriteByte(count);
gi.WritePosition(start);
gi.WriteDir(movdir);
gi.WriteByte(color);
gi.multicast(origin, MULTICAST_PVS);
}
//======================================================
//======================================================
//======================================================
/*
Spawns a string of successive (type) models of from record (rec_no)
from (start) to (endpos) which are offset by vector (offset) and
Broadcasts to all in Potentially Visible Set from vector (origin)
Type:
TE_GRAPPLE_CABLE - The grappling hook cable (NOT IN 3.19!)
TE_MEDIC_CABLE_ATTACK - NOT IMPLEMENTED IN ENGINE
TE_PARASITE_ATTACK - NOT IMPLEMENTED IN ENGINE
*/
//======================================================
void G_Spawn_Models(int type, short rec_no, vec3_t start, vec3_t endpos,
vec3_t offset, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WriteShort(rec_no);
gi.WritePosition(start);
gi.WritePosition(endpos);
gi.WritePosition(offset);
gi.multicast(origin, MULTICAST_PVS);
}
//======================================================
//======================================================
//======================================================
/*
Spawns a trail of (type) from {start} to {end} and Broadcasts to all
in Potentially Visible Set from vector (origin)
TE_BFG_LASER -
Spawns a green laser
TE_BUBBLETRAIL - Spawns a trail of bubbles
TE_PLASMATRAIL - NOT IMPLEMENTED IN ENGINE
TE_RAILTRAIL - Spawns a blue spiral trail filled with white smoke
*/
//======================================================
void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.WritePosition(endpos);
gi.multicast(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_BLASTER - Spawns
a blaster sparks
TE_BLOOD - Spawns a spurt of red blood
TE_BULLET_SPARKS - Same as TE_SPARKS, with a bullet puff and richochet sound
TE_GREENBLOOD - NOT IMPLEMENTED - Spawns a spurt of green blood
TE_GUNSHOT - Spawns a grey splash of particles, with a bullet puff
TE_SCREEN_SPARKS - Spawns a large green/white splash of sparks
TE_SHIELD_SPARKS - Spawns a large blue/violet splash of sparks
TE_SHOTGUN - Spawns a small grey splash of spark particles, with a bullet
puff
TE_SPARKS - Spawns a red/gold splash of spark particles
*/
//======================================================
void G_Spawn_Sparks(int type, vec3_t start, vec3_t movdir, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.WriteDir(movdir);
gi.multicast(origin, MULTICAST_PVS);
}
//======================================================
//======================================================
//======================================================
/*
Spawns a (type) explosion at (start} and Broadcasts to all Potentially
Visible Sets from {origin}
TE_BFG_BIGEXPLOSION
- Spawns a BFG particle explosion
TE_BFG_EXPLOSION - Spawns a BFG explosion sprite
TE_BOSSTPORT - Spawns a mushroom-cloud particle effect
TE_EXPLOSION1 - Spawns a mid-air-style explosion
TE_EXPLOSION2 - Spawns a nuclear-style explosion
TE_GRENADE_EXPLOSION - Spawns a grenade explosion
TE_GRENADE_EXPLOSION_WATER - Spawns an underwater grenade explosion
TE_ROCKET_EXPLOSION - Spawns a rocket explosion
TE_ROCKET_EXPLOSION_WATER - Spawns an underwater rocket explosion
Note: The last four
EXPLOSION entries overlap to some degree.
TE_GRENADE_EXPLOSION is the same as TE_EXPLOSION2,
TE_ROCKET_EXPLOSION is the same as TE_EXPLOSION1,
and both of the EXPLOSION_WATER entries are the same, visually.
*/
//======================================================
void G_Spawn_Explosion(int type, vec3_t start, vec3_t origin ) {
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
gi.WritePosition(start);
gi.multicast(origin, MULTICAST_PVS);
}
==============================================================
Okay... So far, so
good!
==============================================================
Open up your g_phys.c file and put these lines at the very
bottom of your SV_Physics_Toss() function.
// Reposition
botpad's linked parts
if (ent->mynoise2!=NULL)
Link_All_Together(ent);
And, put these same
lines at the very bottom of your
SV_Physics_Bounce() function. (If you don't have one of these
particular functions, then you absolutely need to do the tutorial
on new bounce physics code! Excellent!!)
// Reposition
botpad's linked parts
if (ent->mynoise2!=NULL)
Link_All_Together(ent);
And, put this
pre-declaration at the top of your g_phys.c file.
void
Link_All_Together(edict_t *bodpad);
Okay.. Moving
along..
===============================================================
Open up your g_combat.c file and put this code at the very top
of your Killed() function:
void Killed(........){
// Was this the
Chamber which just got killed?
if (Q_stricmp(targ->classname, "Chamber")==0) {
// Flag to detonate on next frame.
targ->owner->client->chamber=0;
// Give the attacker 5 frags for killing the Chamber..
if (G_EntExists(attacker) && (attacker!=targ->owner)) {
gi.centerprintf(attacker,"5 Frags for destroying Chamber!\n");
attacker->client->resp.score += 5; }
return; }
NOTE: Some of my
variable names may be different. If so, then
make my variable names the same as yours..
===============================================================
Okay, now comes the easy part. Create a new file called
g_chamber.c and paste in all the code from START to END:
As always, make sure
that the includes match your particular
setup!
---------------- START
HERE ---------------------
#include "xxxxxxxx.h"
//======================================================
//========== PARTICLE TORTURE CHAMBER ==================
//======================================================
#define EF_NONE
0x00000000 // No effects
#define
BURNING1_SOUND gi.soundindex("player/burn1.wav")
#define PAIN50_1_SOUND gi.soundindex("*pain50_1.wav")
#define PAIN75_1_SOUND gi.soundindex("*pain75_1.wav")
#define PAIN100_1_SOUND gi.soundindex("*pain100_1.wav")
#define FRYING_SOUND gi.soundindex("player/fry.wav")
#define TELEPORT_SOUND gi.soundindex("misc/tele1.wav")
#define MASK_OPAQUE
(CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA)
#define MASK_LIQUID (CONTENTS_SLIME|CONTENTS_LAVA)
#define
ITEM_IN_ENTS_INVENTORY ent->client->pers.inventory[index]
void
G_Spawn_Models(int type, short rec_no, vec3_t start, vec3_t endpos, vec3_t
offset, vec3_t origin );
void G_Spawn_Splash(int type, int count, int color, vec3_t start, vec3_t
movdir, vec3_t origin);
void G_Spawn_Trails(int type, vec3_t start, vec3_t endpos, vec3_t origin);
void G_Spawn_Sparks(int type, vec3_t start, vec3_t movdir, vec3_t origin);
void G_Spawn_Explosion(int type, vec3_t start, vec3_t origin);
qboolean
G_ClientInGame(edict_t *ent);
qboolean G_EntExists(edict_t *ent);
qboolean G_ClientNotDead(edict_t *ent);
void
Search_For_Player(edict_t *particle);
//======================================================
// Returns true if clear path from spot1 to spot2
//======================================================
qboolean Clear_Path(vec3_t spot1, vec3_t spot2) {
trace_t tr;
tr=gi.trace(spot1, NULL, NULL, spot2, NULL, MASK_OPAQUE);
return (tr.fraction == 1.0);
}
//======================================================
// Need dummy touch because BBOX hits world surfaces..
//======================================================
void Dummy_Touch(edict_t *a, edict_t *b, cplane_t *c, csurface_t *d)
{}
//======================================================
// Explode the chamber at particle's origin and
// then free up all the chamber entities..
//======================================================
void Chamber_Explode(edict_t *particle) {
// Destroy the
Chamber in a big fireball.
G_Spawn_Explosion(TE_EXPLOSION2, particle->s.origin,
particle->s.origin);
// Throw chamber
debris all over the place...
ThrowDebris(particle, DEBRIS3_MODEL, 3.50, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 2.50, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 1.50, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 4.50, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 3.75, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 2.30, particle->s.origin);
ThrowDebris(particle, DEBRIS3_MODEL, 1.00, particle->s.origin);
// Assign any Radius
Damage frags to the particle's owner..
T_RadiusDamage(particle, particle->owner, particle->dmg, NULL,
particle->dmg_radius, MOD_SPLASH);
// Free these in
reverse order.
G_FreeEdict(particle->activator->chain); // Pipe Entity
G_FreeEdict(particle->activator->mynoise); // Laser Entity
G_FreeEdict(particle->activator->mynoise2);// Top Respawn Pad
G_FreeEdict(particle->activator); // Bot Respawn Pad
G_FreeEdict(particle); // Particle Entity
}
//==========================================================
// Player is linked to Particle as particle->goalentity.
// Chamber is linked to Particle as particle->activator.
//==========================================================
void Torture_The_Bastard(edict_t *particle) {
float dmg=10.0;
int sound_index;
vec3_t zvec=(0,0,0);
// Player died? or Player
has escaped!! (by self teleport)
if ((particle->goalentity->health <= 0)
||
(!G_Within_Radius(particle->s.origin,particle->goalentity->s.origin,10)))
{
particle->goalentity = NULL;
// NOTE: Chamber won't self destruct with player inside!
if (particle->delay < level.time)
particle->think = Chamber_Explode;
else
// Go search for another victim!
particle->think = Search_For_Player;
particle->nextthink = level.time + 0.1; // On next frame..
return; }
// If last torture
cycle, then
// Use with exploding body parts tutorial!
if (particle->goalentity->health - dmg <= 0)
dmg *= 3;
else {
// Pick a painful sound to play
switch ((((int)(random()*10+0.5))%6)+1) {
case 1: sound_index=PAIN100_1_SOUND;break;
case 2: sound_index=PAIN50_1_SOUND; break;
case 3: sound_index=PAIN75_1_SOUND; break;
default:sound_index=BURNING1_SOUND; }
// Play randomly selected pain sound!
gi.sound(particle->goalentity, CHAN_VOICE, sound_index, 1, ATTN_NORM, 0);
}
// Damage this
player in decrements of 'dmg' units per cycle.
T_Damage(particle->goalentity, particle, particle->owner, zvec,
particle->s.origin, NULL, dmg, 0, 0, MOD_CHAMBER);
// Cycle every 1 sec
until player dead.
// Need time for scream sound to finish..
particle->think = Torture_The_Bastard;
particle->nextthink = level.time + 1.0; // 1 sec intervals.
}
//===============================================================
// Teleport particle->goalentity into Particle Chamber..
//===============================================================
void Beam_Player_Into_Chamber(edict_t *particle) {
vec3_t start,zvec=(0,0,0);
// Turn OFF particle
effect.
particle->s.effects = EF_NONE;
// Tell Pipe to
spurt Red sparks..
particle->activator->chain->noise_index2 = 0;
// Play teleport
sound effects.
gi.sound(particle->goalentity, CHAN_VOICE, TELEPORT_SOUND, 1, ATTN_NORM,
0);
// Temp storage of
player's previous location.
VectorCopy(particle->goalentity->s.origin, start);
// Physically
relocate this player into Chamber.
VectorCopy(particle->s.origin, particle->goalentity->s.origin);
// Large colored
particle effect at player's old spot.
G_Spawn_Splash(TE_LASER_SPARKS, 24, 0xd0d1d2d3, start, zvec, start);
// Maximize health
to make'em suffer longer!
particle->goalentity->health = particle->goalentity->max_health;
// Start Torture
effect on next frame
particle->think = Torture_The_Bastard;
particle->nextthink = level.time + 0.1;
}
//==========================================================
// Search for first visible player with targeted radius units
// then teleport player into Particle Chamber..
//==========================================================
void Search_For_Player(edict_t *particle) {
int i=0;
trace_t tr;
edict_t *player=NULL;
vec3_t start;
// Time to self
destruct? OR, Owner has died?
if ((particle->delay < level.time)
||(particle->owner->client->chamber==0)) {
particle->think = Chamber_Explode; // Self destruct..
particle->nextthink = level.time + 0.1; // On next frame.
return; }
// Chamber makes a
frying/hissing sound!!
gi.sound(particle, CHAN_VOICE, FRYING_SOUND, 1, ATTN_STATIC, 0);
// Turn ON particle
effect when nobody in chamber.
particle->s.effects = EF_TELEPORTER;
// Tell Pipe to
spurt Green sparks (nobody in chamber)..
particle->activator->chain->noise_index2 = 1;
// Get starting
vector at very top of pipe
VectorCopy(particle->activator->chain->s.origin, start);
start[2] += (65+12); // Beam starts from top of pipe!!
// Is top of pipe in
solid?
if (gi.pointcontents(start) & MASK_SHOT)
VectorCopy(particle->s.origin, start); // Make an adjustment
// Grab each visible
player in 1000 unit radius!
for(i=0;i < game.maxclients;i++) {
player=g_edicts+i+1;
if (!G_ClientInGame(player)) continue;
if (player==particle->owner) continue; // Exempt owner??
if (!player->takedamage) continue;
if (!Clear_Path(start,player->s.origin)) continue;
if (!G_Within_Radius(start, player->s.origin, 1000)) continue;
// Laser Beam reaches out and teleports player into chamber.
tr=gi.trace(start, NULL, NULL, player->s.origin, particle->activator,
MASK_SHOT);
G_Spawn_Trails(TE_BFG_LASER, start, tr.endpos, tr.endpos);
particle->goalentity=player; // Linked to particle entity
particle->think=Beam_Player_Into_Chamber;
particle->nextthink = level.time + 0.2; // Give time to teleport.
return;
} // end for
// Continue
searching on next frame.
particle->think = Search_For_Player;
particle->nextthink = level.time + 0.1;
}
//==========================================================
// Particle communicates to 'Stove Pipe' thru noise_index2
//==========================================================
void StovePipe_Think(edict_t *pipe) {
vec3_t start;
// Place effect at
end of pipe.
VectorCopy(pipe->s.origin, start);
start[2] += 12;
if
(pipe->noise_index2 > 0)
// Green sparks coming out of Top Pipe - Nobody in Chamber
G_Spawn_Splash(TE_LASER_SPARKS, 12, 0xd0d1d2d3, start, NULL, start);
else
// Red blood spurting out of Top Pipe - Chamber Occupied
G_Spawn_Splash(TE_LASER_SPARKS, 12, 0xd0d1d2d3, start, NULL, start);
// More effects
every other frame.
pipe->nextthink = level.time + 0.2; // No need every frame.
}
//==========================================================
// Keeps the laser beam and top particle effect active..
//==========================================================
void Laser_Think(edict_t *laser) {
trace_t tr;
vec3_t end={0,0,0};
VectorMA(laser->s.origin,
70, laser->movedir, end);
tr=gi.trace(laser->s.origin, NULL, NULL, end, laser, MASK_ALL);
VectorCopy(tr.endpos, laser->s.old_origin);
laser->nextthink
= level.time + 0.1; // Every frame else it flashes!!
}
//==========================================================
// Place the 'stove pipe' on top of Particle Chamber.
// ONLY way to kill chamber is to 'Smoke' its Stack!
//==========================================================
void Create_StovePipe(edict_t *botpad) {
edict_t *pipe=NULL;
//
------------------------------------------
// -------- Stove Pipe Entity ---------------
// ------------------------------------------
pipe = G_Spawn();
botpad->chain=pipe; // botpad's link to pipe
pipe->owner = botpad->owner; // For killing!!
pipe->classname = "Chamber";
pipe->takedamage = DAMAGE_YES;
pipe->health=200;
pipe->max_health=200;
pipe->noise_index2=1; // 1=GREEN, 0=RED
pipe->deadflag = DEAD_DEAD; // So, Not in game..
pipe->clipmask = MASK_SHOT;
VectorClear(pipe->velocity);
pipe->movetype = MOVETYPE_NONE;
pipe->solid = SOLID_BBOX;
pipe->s.effects = EF_NONE;
VectorSet(pipe->mins,-2,-2,-8); // Size of BBOX
VectorSet(pipe->maxs, 2, 2, 8); // Size of BBOX
pipe->s.modelindex =
gi.modelindex("models/objects/minelite/light1/tris.md2");
pipe->s.modelindex2 = 0;
// ------ Position
Relative to BotPad -------
VectorCopy(botpad->s.origin, pipe->s.origin);
pipe->s.origin[2] += 65;
VectorCopy(pipe->s.origin,pipe->s.old_origin);
// ------------------------------------------
pipe->touch=Dummy_Touch;
pipe->think=StovePipe_Think;
pipe->nextthink = level.time + 0.5; // Wait to start sparks effect.
gi.linkentity(pipe);
}
--------- SEE PART
II ------------
|