|
I took my previous torture chamber tutorial and revised it for v3.20 source
and I made a few modifications.
What is it? The chamber
is made up of 2 respawn pads with one at the bottom and another one flipped
upside down at the top to form a chamber (with enough room for a player to
stand up in-between the two pads). A translucent see-thru green shell extends
from the top pad to the bottom pad encasing an area between the two pads
where the player gets tortured. A small pipe (standard Q2 light model) sticks
out above the top of the chamber. When the chamber finds a player, a
laserbeam shoots from the top of the pipe at the top of the chamber to the
targeted player's location and beams the poor bastard inside the chamber and
then tortures the guy to death!
Note that my previous
torture chamber tutorial was set up so that players could spawn a chamber if
they had 100 cells (and they got the frags too). What I did here was to
create an autolauncher entity which puts a single chamber into the game at
some random location on the map. Once spawned, the chamber scans around
looking for the first player to come within its search radius (adjustable).
When it finds a player, a beam shoots out the top of the pipe on top of the
chamber and hits the player and beams that player inside the chamber (with
some teleport effects and sounds occurring at the player's last location).
Once inside, the chamber slowly tortures the poor bastard to death (with the
usual screaming sounds, blood, etc..) and there is nothing the guy can do to
escape (except suicide!) because the green shell cannot be penetrated. Also,
while the player is inside the chamber being tortured, blood comes spurting
out the top of the pipe sticking up from the top of the chamber! Cool
effects!
The chamber won't search
again until the current victim is dead otherwise the chamber is in constant
search mode. So while the chamber is searching, STAY AWAY!! However, while
there is somebody inside the chamber (and thus the chamber is no longer in
search mode) you can run up to it and watch the action (if you've got the
stomach for it) because you can see the player inside through the outer
shell.
At all times, the chamber
itself can be destroyed! It is most vulnerable when there is a player inside
because it won't search for anybody during that interval. The chamber can be
destroyed by killing the pipe on top of the chamber. The pipe has 200 health
so you have to really hit it hard with some real firepower. But, the pipe
entity has a relatively small bbox size so you must be pretty accurate with
your shots to get a direct hit (or use a bfg!). When the pipe's health falls
to zero, the chamber explodes into a huge fireball which by the way will have
the effect of incinerating everybody nearby so be sure to kill it from a
distance! The player who destroys the chamber gets 5 frags!
After the chamber has
been destroyed, the chamber's autolauncher entity re-activates itself and in
another 30 seconds (adustable) it spawns another chamber some other random
location on the map. Then, it starts searching again for another player to
beam inside and torture to death! Unless the chamber is destroyed, it will
continuously search, beam and torture! Check it out!
BTW, I put this together
at work so I haven't tested it out! But I've been over the source and
everything looks fine. So, if you encounter problems, please post. Hopefully,
I'll be running this in the next day or so for myself.
Also, this tutorial
teaches you how to create multiple cross-linked entities with various think
functions being turned On/Off at different intervals to do things in a
certain order. I've spent alot of time commenting this source for others to
see what is happening throughout. So, START AT THE BOTTOM and read thru the
source and see for yourself what each routine does. Shouldn't be difficult to
understand.
Here it is:
<C&NBSP;CODE>
First, prototype this function in one of your header files..
void Init_Chamber_Launcher(edict_t *);
Then, place a call to this function at the bottom of SpawnEntities() function
in your g_spawn.c file:
Init_Chamber_Launcher(NULL);
Now, create a new file called g_chamber.c and paste in all this source:
//================== CUT HERE ==========================
#include "g_local.h"
//======================================================
//================= TORTURE CHAMBER ====================
//======================================================
void Search_For_Player(edict_t *);
//======================================================
// NOTE: If you already have a G_ClearPath then delete
// this one and change G_ClearPath2 to G_ClearPath
//======================================================
qboolean G_ClearPath2(vec3_t spot1,vec3_t spot2) {
return (gi.trace(spot1,NULL,NULL,spot2,NULL,MASK_OPAQUE).fraction == 1.0);
}
//======================================================
void Dummy_Touch(edict_t *a,edict_t *b,cplane_t *c,csurface_t *d)
{ // Do nothing when chamber is touched
}
//======================================================
void Chamber_Explode(edict_t *torturer) {
edict_t *botpad;
//============================================
// Ent retrieval done ONLY to avoid confusion
//============================================
botpad=torturer->activator; // retrieve botpad entity
// Make crosslinked ents disappear in reverse order.
G_FreeEdict(botpad->mynoise2); // free Pipe Entity
G_FreeEdict(botpad->mynoise); // free Shell Entity
G_FreeEdict(botpad->movetarget);// free Top Pad
G_FreeEdict(botpad); // now free Bottom Pad
// Display huge bfg fireball at this location
fire_bfg(torturer,torturer->s.origin,zvec,200,0,1000);
// Start autolauncher again on next frame
torturer->think=Init_Chamber_Launcher;
torturer->nextthink=level.time + FRAMETIME;
}
//======================================================
void Torture_The_Bastard(edict_t *torturer) {
float damage=10; // ADJUSTABLE - change to adjust torture duration
edict_t *victim;
victim=torturer->target_ent; // retrieve victim entity
if (!G_ClientInGame(victim)) { // Victim died or left the game?
torturer->target_ent=NULL;
torturer->think=Search_For_Player; // Start searching again
torturer->nextthink=level.time + FRAMETIME;
return; }
// Test if this is last cycle for victim?
if (victim->health-damage <= 0)
damage=50; // make victim explode! - see player_die()
else
if (rand()&1) // make victim scream loudly
gi.sound(victim,2,gi.soundindex("player/burn1.wav"),1,1,0);
else
gi.sound(victim,2,gi.soundindex("player/burn2.wav"),1,1,0);
// Damage victim a total of 'damage' units - Nobody gets the frags
T_Damage(victim,torturer,torturer,zvec,torturer->s.origin,zvec,damage,0,0,MOD_CHAMBER);
torturer->think=Torture_The_Bastard;
torturer->nextthink=level.time + 3.0; // Allow for gi.sound() to finish playing
}
//======================================================
void Beam_Player_Into_Chamber(edict_t *torturer) {
edict_t *botpad;
edict_t *pipe;
edict_t *victim;
//============================================
// Ent retrieval done ONLY to avoid confusion
//============================================
victim=torturer->target_ent;// retrieve victim entity
botpad=torturer->activator; // retrieve botpad entity
pipe=botpad->mynoise2; // retrieve pipe entity
pipe->noise_index2=1; // start spurting blood - see Pipe_Think()
// Show laserbeam from top of pipe to victim's origin
G_Spawn_Trails(TE_BFG_LASER,pipe->pos1,victim->s.origin);
// Do teleport effect and play teleport sound at victim's origin
G_MuzzleFlash((short)(victim-g_edicts),victim->s.origin,MZ_LOGIN);
gi.sound(victim,2,gi.soundindex("world/amb10.wav"),1.0,1,0);
// Physically relocate victim to Chamber Center
VectorCopy(torturer->s.origin,victim->s.origin);
// Start torture cycle on next frame
torturer->think=Torture_The_Bastard;
torturer->nextthink=level.time + FRAMETIME;
}
//======================================================
void Search_For_Player(edict_t *torturer) {
edict_t *victim=NULL;
edict_t *pipe,*botpad;
vec3_t start;
botpad=torturer->activator;// retrieve botpad entity
pipe=botpad->mynoise2; // retrieve pipe entity
pipe->noise_index2=0; // Turn OFF blood spurts - see Pipe_Think()
VectorCopy(torturer->s.origin,start); // search from center of chamber
// Beam FIRST victim found into center of Torture Chamber
while ((victim=findradius(victim,start,1000))!=NULL) { // search radius - ADJUSTABLE
if (!G_ClientInGame(victim)) continue; // Skip dead,respawners,spectators
if (!G_ClearPath2(start,victim->s.origin)) continue; // straight line to victim?
//-------- Okay! Found one within radius! ----------
torturer->target_ent=victim; // player becomes victim
torturer->think=Beam_Player_Into_Chamber; // relocate player into chamber
torturer->nextthink=level.time + FRAMETIME; // move player on next frame
//--------------------------------------------------
return; }
torturer->think=Search_For_Player; // search again in 1 second
torturer->nextthink=level.time + 1.0;
}
//======================================================
void Pipe_Die(edict_t *pipe,edict_t *inflictor,edict_t *attacker,int damage,vec3_t point) {
edict_t *botpad;
edict_t *torturer;
// Valid Attacker gets frags for Killing Pipe Entity!
if (G_ClientInGame(attacker) && (attacker!=pipe->owner)) {
gi_centerprintf(attacker,"5 Frags for destroying Torture Chamber!\n");
attacker->client->resp.score += 5; } // frag award - ADJUSTABLE
botpad=pipe->activator; // retrieve botpad entity - for clarity
torturer=botpad->goalentity;// retrieve torturer entity
// Make chamber explode on next frame
torturer->think=Chamber_Explode;
torturer->nextthink=level.time + FRAMETIME;
}
//======================================================
void Pipe_Think(edict_t *pipe) {
// Chamber Occupied! Make Blood spurt out top of Pipe!
if (pipe->noise_index2==1) {
G_Spawn_Splash(TE_LASER_SPARKS,12,0xf2f2f0f0,pipe->pos1,zvec);
pipe->nextthink=level.time + 0.5; // spurt every 1/2 sec
return; }
pipe->nextthink=level.time + FRAMETIME; // check again next frame
}
//======================================================
// Place the 'stove pipe' on top of Torture Chamber.
// ONLY way to kill Chamber is to 'smoke' its stack!
//======================================================
void Create_StovePipe(edict_t *botpad) {
edict_t *pipe;
pipe=G_Spawn();
pipe->owner=world;
pipe->activator=botpad; // crosslink pipe to bottom pad
botpad->mynoise2=pipe; // crosslink bottom pad to pipe
pipe->s.modelindex=gi.modelindex("models/objects/minelite/light1/tris.md2");
VectorSet(pipe->mins,-2,-2,-8); // don't touch!
VectorSet(pipe->maxs,+2,+2,+8);
pipe->movetype=MOVETYPE_NONE;
//----- Pipe Is Killable --------
pipe->solid=SOLID_BBOX; // touchable
pipe->clipmask=MASK_SHOT; // shootable
pipe->takedamage=DAMAGE_YES; // damageable
pipe->health=200; // 200 health
pipe->max_health=200;
//-------------------------------
pipe->noise_index2=0; // No blood spurting yet
VectorCopy(botpad->s.origin,pipe->s.origin);
pipe->s.origin[2] += 65; // don't touch!
VectorCopy(pipe->s.origin,pipe->pos1);
pipe->pos1[2] += 12; // approx. top of pipe
pipe->touch=Dummy_Touch;
pipe->die=Pipe_Die; // do this when Killed()
pipe->think=Pipe_Think;
pipe->nextthink=level.time + 0.5;
gi.linkentity(pipe);
}
//======================================================
void Create_Torturer_Entity(edict_t *botpad) {
edict_t *torturer;
torturer=G_Spawn();
torturer->owner=world;
torturer->activator=botpad; // crosslink Torturer to bottom pad
botpad->goalentity=torturer; // crosslink bottom pad to Torturer
torturer->svflags=SVF_NOCLIENT; // this ent is completely invisible
VectorCopy(botpad->s.origin,torturer->s.origin);
torturer->s.origin[2] += 7; // approx. center of chamber
torturer->think=Search_For_Player; // Start seeking players
torturer->nextthink=level.time + 5.0; // Activate search in 5 secs
gi.linkentity(torturer);
}
//======================================================
void Shell_Think(edict_t *shell) {
vec3_t end;
trace_t tr;
// Must repeatedly be updated else laserbeam shell flashes
VectorMA(shell->s.origin,70,tv(0,0,1),end);
tr=gi.trace(shell->s.origin,NULL,NULL,end,shell,MASK_SHOT);
VectorCopy(tr.endpos,shell->s.old_origin);
shell->nextthink=level.time + FRAMETIME;
gi.linkentity(shell); // update engine
}
//======================================================
void Create_Chamber_Shell(edict_t *botpad) {
edict_t *shell;
shell=G_Spawn();
shell->owner=world;
botpad->mynoise=shell; // crosslink bottom pad to Shell
VectorSet(shell->mins,-20,-20,-60); // don't touch!
VectorSet(shell->maxs,+20,+20,+60);
shell->s.renderfx |= (RF_BEAM|RF_TRANSLUCENT|RF_GLOW);
shell->movetype=MOVETYPE_NONE;
shell->solid=SOLID_BBOX; // touchable by player
shell->clipmask=MASK_SHOT; // shootable by weapons
shell->takedamage=DAMAGE_NO; // but not damageable
shell->s.modelindex=1; // must==1 - world
shell->s.frame=58; // width of beam
shell->s.skinnum=0xd0d1d2d3; // beam color is Green
VectorCopy(botpad->s.origin,shell->s.origin);
shell->s.origin[2] -= 15;
VectorCopy(shell->s.origin,shell->s.old_origin);
shell->touch=Dummy_Touch;
shell->think=Shell_Think; // Beam must be constantly refreshed
shell->nextthink=level.time + FRAMETIME;
gi.linkentity(shell);
}
//======================================================
void Create_Toppad(edict_t *botpad) {
edict_t *toppad;
toppad=G_Spawn();
toppad->owner=world;
botpad->movetarget=toppad; // crosslink bottom pad to top pad
toppad->s.modelindex=gi.modelindex("models/objects/dmspot/tris.md2");
VectorSet(toppad->mins,-20,-20,-5); // don't touch!
VectorSet(toppad->maxs,+20,+20,+5);
toppad->movetype=MOVETYPE_NONE;
toppad->clipmask=MASK_SHOT; // shootable by weapons
toppad->solid=SOLID_BBOX; // touchable by player
toppad->takedamage=DAMAGE_NO;// but not damageable
VectorCopy(botpad->s.origin,toppad->s.origin);
toppad->s.origin[2] += 32;
toppad->s.angles[ROLL]+=180; // flip upside down..
toppad->touch=Dummy_Touch;
gi.linkentity(toppad);
}
//======================================================
// Keep checking if bottom spawnpad stopped bouncing yet
//======================================================
void botpad_Think(edict_t *botpad) {
// If bottom spawnpad bounced into Lava/Slime,then we need to do this.
if (gi.pointcontents(botpad->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) {
botpad->svflags = SVF_NOCLIENT; // Make pad invisible
botpad->think=Init_Chamber_Launcher;// Make Pad initialize launcher
botpad->nextthink=level.time + 10.0;// Start launcher in 10 seconds
return; }
// Pad stopped moving! Now make full chamber appear!
if (VectorLengthSqr(botpad->velocity) <= 1.0) {
botpad->think=NULL; // no more thinking needed
botpad->nextthink=level.time; // reset thinking timer
//---- Create Torture Chamber -------------------------------
Create_Toppad(botpad); // Make top pad entity
Create_StovePipe(botpad); // Put StovePipe on top
Create_Chamber_Shell(botpad); // Activate Chamber Laser Shell.
Create_Torturer_Entity(botpad);// Create Torturer Entity
//-----------------------------------------------------------
return; }
botpad->think=botpad_Think; // Check again on next frame
botpad->nextthink=level.time + FRAMETIME;
}
//======================================================
void Create_Botpad(edict_t *launcher) {
edict_t *botpad;
botpad=G_Spawn(); // botpad is chamber's primary entity
botpad->owner=world;
botpad->s.modelindex=gi.modelindex("models/objects/dmspot/tris.md2");
VectorSet(botpad->mins,-20,-20,-30); // don't touch!
VectorSet(botpad->maxs,+20,+20,+10);
botpad->clipmask=MASK_SHOT; // traceable
botpad->solid=SOLID_BBOX; // touchable
botpad->takedamage=DAMAGE_NO; // not damageable - see T_Damage()
botpad->movetype=MOVETYPE_BOUNCE;// tell G_RunEntity() that pad bounces around
VectorSet(botpad->velocity,100*crandom(),100*crandom(),0); // randomize velocity in (x,y)
VectorCopy(launcher->pos1,botpad->s.origin); // start bouncing from this location
botpad->touch=Dummy_Touch;
botpad->think=botpad_Think;
botpad->nextthink=level.time + FRAMETIME;
gi.linkentity(botpad);
G_FreeEdict(launcher); // don't need launcher for now
}
//======================================================
edict_t *GetValidOrigin(edict_t *launcher) {
// Grab origin of first player in game
for (int i=1; i<=game.maxclients; i++)
if (G_ClientInGame(&g_edicts[i]))
return &g_edicts[i]; // Got one!
return NULL; // Nobody in game yet.
}
//======================================================
void Launcher_Think(edict_t *launcher) {
edict_t *ent;
if ((ent=GetValidOrigin(launcher))!=NULL) {
VectorCopy(ent->s.origin,launcher->pos1);// locate chamber at this ent's origin
launcher->think=Create_Botpad; // delay creating chamber so ent can move
launcher->nextthink=level.time + 10.0; // 10 second delay should be enough..
return; }
launcher->nextthink=level.time + 10.0; // try again in 10 secs
}
//======================================================
void Init_Chamber_Launcher(edict_t *ent) {
edict_t *launcher;
launcher=G_Spawn();
launcher->owner=world;
launcher->svflags=SVF_NOCLIENT; // invisible
launcher->think=Launcher_Think; // Find a location to spawn Chamber
launcher->nextthink=level.time + 30.0; // Give time for player's to connect
gi.linkentity(launcher);
if (ent) G_FreeEdict(ent); // now free previous entity!!
}
</C&NBSP;CODE>
//================ STOP
CUTTING HERE =======================
Be sure to add your new g_chamber.c file to your project then compile. That's
it!
At the start of each
game, the autolauncher will activate itself at the bottom of SpawnEntities()
after all the map's entities and items have been put into the game. After 30
seconds, it will create a torture chamber at some random location in the map.
After 5 seconds, it will begin scanning around for players. So, if you see
one of these babies suddenly appear nearby your location, GET THE HELL OUT OF
THERE!!
Have fun!
Maj.Bitch
|