Torture Chamber (bug-free)


Posted by Maj.Bitch (24.95.222.*) at 10:56 AM, 3/18/2001:


I'm reposting the Torture Chamber tutorial because I made some fixes to the shell and I changed the way it free's itself, modified the way the bfg fireball starts, and I fixed a bug which was causing it to crash when the pipe took damage.

Okay, if you are having a problem with the game crashing when the pipe takes a direct hit with a rocket, (or something that causes T_Damage to run), make sure that both your CheckArmor() and CheckPowerArmor() functions have a test at the very to to make sure that ent is a client. So, if (!ent->client) return; out of both of these functions!

So.. EVERYTHING WORKS JUST FINE now..

Here is the rest of the source:


#include "g_local.h"

//======================================================
//================= TORTURE CHAMBER ====================
//======================================================

//======================================================
void Dummy_Touch(edict_t *a,edict_t *b,cplane_t *c,csurface_t *d)
{}

//======================================================
void Chamber_Explode(edict_t *torturer) {
vec3_t start;

  VectorCopy(torturer->s.origin,start);
  start[2] += 64;

  // Display huge bfg fireball at this location
  fire_bfg(torturer,start,tv(0,0,-1),200,100,300);

  // Restart autolauncher in 10 seconds
  torturer->think=Init_Chamber_Launcher;
  torturer->nextthink=level.time + 10.0;
}

//======================================================
void Chamber_Free(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

  // Explode Chamber on next frame
  torturer->think=Chamber_Explode;
  torturer->nextthink=level.time + FRAMETIME;
}

void Search_For_Player(edict_t *);

//======================================================
void Torture_The_Bastard(edict_t *torturer) {
float damage=10; // ADJUSTABLE - torture duration
edict_t *victim;

  victim=torturer->target_ent; // retrieve victim entity

   // Victim died or left the game?
  if (!G_ClientInGame(victim)) {
    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 + 1.5; // Allow time for gi.sound() to finish
}

//======================================================
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,1,0);

  // Physically relocate victim to Chamber's origin
  VectorCopy(torturer->s.origin,victim->s.origin);

  // Start torturing on very 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 Chamber
  while ((victim=findradius(victim,start,1000))!=NULL) { // search radius - ADJUSTABLE
    if (!G_ClientInGame(victim)) continue;               // Skip dead,respawners,spectators
    if (!G_ClearPath(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;          // do this on next frame
    //--------------------------------------------------
    return; }

  torturer->nextthink=level.time + 1.0; // search again in 1 second
}

//======================================================
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)) {
    gi_centerprintf(attacker,"5 Frags for destroying Torture Chamber!\n");
    attacker->client->resp.score += 5; } // frag award - ADJUSTABLE

  botpad=pipe->activator;      // retrieve botpad entity
  torturer=botpad->goalentity; // retrieve torturer entity

  // Free chamber on next frame
  torturer->think=Chamber_Free;
  torturer->nextthink=level.time + FRAMETIME;
}

//======================================================
void Pipe_Think(edict_t *pipe) {

  // 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_Pipe(edict_t *botpad) {
edict_t *pipe;

gi.dprintf("CHAMBER SPAWNED\n");

  pipe=G_Spawn();
  pipe->owner=world;

  pipe->activator=botpad; // crosslink pipe to bottom pad
  botpad->mynoise2=pipe;  // crosslink bottom pad to pipe

  gi.setmodel(pipe,"models/objects/minelite/light1/tris.md2");
  VectorSet(pipe->mins,-2,-2,-8);
  VectorSet(pipe->maxs,+2,+2,+8);

//---- Make Pipe Is Killable --------
  pipe->movetype=MOVETYPE_NONE;
  pipe->solid=SOLID_BBOX;      // touchable
  pipe->clipmask=MASK_SHOT;    // shootable
  pipe->takedamage=DAMAGE_YES; // damageable
  pipe->health=200;            // 200 health
  pipe->max_health=pipe->health;
//-----------------------------------

  pipe->noise_index2=0; // No blood spurting yet
  VectorCopy(botpad->s.origin,pipe->s.origin);
  pipe->s.origin[2] += 65;

  VectorCopy(pipe->s.origin,pipe->pos1);
  pipe->pos1[2] += 12; // pos1 is top of pipe

  pipe->touch=Dummy_Touch;
  pipe->die=Pipe_Die; // do this when Killed()

  pipe->think=Pipe_Think; // handle blood spurting
  pipe->nextthink=level.time + FRAMETIME;

  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; // completely invisible in game

  VectorCopy(botpad->s.origin,torturer->s.origin);
  torturer->s.origin[2] += 7; // approx center of chamber

  torturer->think=Search_For_Player;    // Start looking for 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 shell flashes
  VectorCopy(shell->s.origin,end);
  end[2] -= 63;
  tr=gi.trace(shell->s.origin,shell->mins,shell->maxs,end,NULL,0);
  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);
  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] += 48;
  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

  gi.setmodel(toppad,"models/objects/dmspot/tris.md2");
  VectorSet(toppad->mins,-20,-20,-5);
  VectorSet(toppad->maxs,+20,+20,+5);

  toppad->movetype=MOVETYPE_NONE;
  toppad->clipmask=MASK_SHOT;   // shootable
  toppad->solid=SOLID_BBOX;     // touchable
  toppad->takedamage=DAMAGE_NO; // but not damageable
  toppad->s.angles[2]+=180;  // flip upside down..

  VectorCopy(botpad->s.origin,toppad->s.origin);
  toppad->s.origin[2] += 32;

  toppad->touch=Dummy_Touch;

  // no thinking required.

  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);         // Create top spawn pad
    Create_Pipe(botpad);           // Put Pipe model on top
    Create_Chamber_Shell(botpad);  // Activate 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;

  gi.setmodel(botpad,"models/objects/dmspot/tris.md2");
  VectorSet(botpad->mins,-20,-20,-30);
  VectorSet(botpad->maxs,+20,+20,+10);

  botpad->clipmask=MASK_SHOT;      // shootable
  botpad->solid=SOLID_BBOX;        // touchable
  botpad->takedamage=DAMAGE_NO;    // but not damageable - see T_Damage()
  botpad->movetype=MOVETYPE_BOUNCE;// pad bounces around - see G_RunEntity()

  VectorCopy(launcher->pos1,botpad->s.origin); // start bouncing from this location
  botpad->s.origin[2] += 40;
  botpad->touch=Dummy_Touch;

  botpad->think=botpad_Think; // check when stop moving
  botpad->nextthink=level.time + FRAMETIME;

  gi.linkentity(botpad);

  G_FreeEdict(launcher); // free launcher entity
}

//======================================================
edict_t *GetValidOrigin(edict_t *launcher) {

  // Grab origin of first player we find
  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) {// found a valid origin to put chamber?
    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 + 1.0;   // 10 sec delay should be enough time..
    return; }

  launcher->nextthink=level.time + 10.0; // try again in 10 secs
}

//======================================================
void Init_Chamber_Launcher(edict_t *prev_ent) {
edict_t *launcher;

  launcher=G_Spawn();
  launcher->owner=world;
  launcher->svflags=SVF_NOCLIENT; // completely invisible in game

  launcher->think=Launcher_Think;        // Find a location to spawn Chamber
  launcher->nextthink=level.time + 30.0; // Give time for players to connect

  gi.linkentity(launcher);

  if (prev_ent)
    G_FreeEdict(prev_ent); // free previous entity
}
 

Just cut and paste this overtop the code in your g_chamber.c file and re-compile..

Have Fun!

Maj.Bitch