The Torture Chamber (other Version)


Posted by Maj.Bitch (12.145.208.*) at 11:02 AM, 3/15/2001:


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