Quake DeveLS - Player Decoy

Author: Gregg Reno
Home Page: The Reno Brothers Weapons Factory
Difficulty: Hard

Do you remember using the "Holo-Duke" in Duke Nukem 3D?  Well, this tutorial will show you how to create a player decoy that looks just like you.  It's a great deathmatch mod - create your decoy and hide in the corner while your enemies attack the decoy, thinking it's you.  Depending on your settings, the decoy will be either male or female, and use the same skin as you.

We will be making modifications to the following files:

And you will be creating one new file:

Note: Lines starting with the "+" sign are new lines that should be added. Lines without the "+" should be left alone. The exception is the wf_decoy.c where all lines should be added.


g_local.h   (modification)

First, we will need some variables to keep track of our decoy. Find the edict_s structure definition. Go to the end of the structure and add the following lines:

      // common data blocks
      moveinfo_t      moveinfo;
      monsterinfo_t   monsterinfo;

+     // WF - Decoy variables
+     edict_t *decoy;    //Pointer to decoy
+     edict_t *creator;  //Who created this entity (used by decoy)

};

Now add a function prototype for the decoy function we will be creating. Search for "g_main.c" and add the following lines:

      //
      // g_main.c
      //
      void SaveClientData (void);
      void FetchClientEntData (edict_t *ent);

+     // WF - decoy prototype
+     void SP_Decoy(edict_t *self);


g_cmds.c   (modification)

Lets add a new "cmd decoy" command to control our decoy. Go to the ClientCommand function and add the following lines:

      else if (Q_stricmp (cmd, "wave") == 0)
		Cmd_Wave_f (ent);

+     // 'decoy' command
+     else if (Q_stricmp (cmd, "decoy") == 0)
+        SP_Decoy (ent);	

      else if (Q_stricmp (cmd, "gameversion") == 0)
      {
We'll put the SP_Decoy function in a new file "wf_decoy.c". One thing I try to do is minimize the amount of code I put in the original Q2 source code. That way when it changes (like with a point release), I'll have less work to do in merging in my changes.
g_monster.c   (modification)

Next, we will make a small but very important change. In id's first release, they did not allow monsters in deathmatch. We'll fix that problem by commenting out the code preventing deathmatch monsters. Go to the monster_start function and comment out the following lines:

    qboolean monster_start (edict_t *self)
    {
++      /* WF - let monsters into deathmatch
        if (deathmatch->value || nomonsters->value)
        {
            G_FreeEdict (self);
            return false;
        }
++      */  //WF

	if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
	{
What does our decoy mod have to do with monsters? Well, we'll be using some of the monster code to help give some personallity to our decoy (like making it walk and shoot).
wf_decoy.c   (new file)

OK, now we're ready for the code that puts the decoy in the game. A bit of warning though - the wf_decoy.c is large. Instead of typing, cutting and pasting all of this, you can download it directly by clicking the wf_decoy.c link. Netscape users should right-click on the link, the select the "Save link as..." option.

I've broken the code into a number of sections. Look for explainations at the end of each section. Here we go:

Initialization
First, let's do our initial setup for the modification.

    /*==============================================================================
    The Weapons Factory - 
    Decoy Mod
    Original code by Gregg Reno
    ==============================================================================*/

    #include "g_local.h"
    #include "m_player.h"

    static int	sound_idle;
    static int	sound_sight1;
    static int	sound_sight2;
    static int	sound_pain;
    static int	sound_death;
Explaination: We include the m_player.h file because it defines names for each frame of the player model. Next, we allocate some static variables to hold the decoy sound indexes.

Frame Animations
Most of the code in wf_decoy.c file is just setting up the player animations. I used the m_soldier.c code as an example on how to do the character animations.

// STAND frames
void decoy_idle (edict_t *self)
{
	if (random() > 0.8)
		gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}

void decoy_stand (edict_t *self);

mframe_t decoy_frames_stand1 [] =
{
    ai_stand, 0, decoy_idle,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,

    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,

    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,

    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL
};
mmove_t decoy_move_stand1 = {FRAME_stand01, FRAME_stand40, decoy_frames_stand1, decoy_stand};

void decoy_stand (edict_t *self)
{
    self->monsterinfo.currentmove = &decoy_move_stand1;
}


// TAUNT frames
void decoy_taunt (edict_t *self);

mframe_t decoy_frames_taunt1 [] =
{
    ai_stand, 0, decoy_idle,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,

    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL,
    ai_stand, 0, NULL
};
mmove_t decoy_move_taunt1 = {FRAME_taunt01, FRAME_taunt17, decoy_frames_taunt1, decoy_taunt};

void decoy_taunt (edict_t *self)
{
    self->monsterinfo.currentmove = &decoy_move_taunt1;
}


//
// RUN frames
//
void decoy_run (edict_t *self);
mframe_t decoy_frames_run [] =
{
    ai_run, 10, NULL,
    ai_run, 11, NULL,
    ai_run, 11, NULL,
    ai_run, 16, NULL,
    ai_run, 10, NULL,
    ai_run, 15, NULL
};
mmove_t decoy_move_run = {FRAME_run1, FRAME_run6, decoy_frames_run, decoy_run};

void decoy_run (edict_t *self)
{
    if (self->monsterinfo.aiflags & AI_STAND_GROUND)
    {
        self->monsterinfo.currentmove = &decoy_move_stand1;
        return;
    }

    self->monsterinfo.currentmove = &decoy_move_run;
}


//
// PAIN frames
//
mframe_t decoy_frames_pain1 [] =
{
	ai_move, -3, NULL,
	ai_move, 4,  NULL,
	ai_move, 1,  NULL,
	ai_move, 0,  NULL
};
mmove_t decoy_move_pain1 = {FRAME_pain101, FRAME_pain104, decoy_frames_pain1, decoy_run};

void decoy_pain (edict_t *self, edict_t *other, float kick, int damage)
{
    if (level.time < self->pain_debounce_time)
        return;

    self->pain_debounce_time = level.time + 3;
    gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
    self->monsterinfo.currentmove = &decoy_move_pain1;
}


//
// ATTACK frames
//
static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8};
static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8};
static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8};

void decoy_fire (edict_t *self, int flash_number)
{
    vec3_t  start;
    vec3_t  forward, right, up;
    vec3_t  aim;
    vec3_t  dir;
    vec3_t  end;
    float   r, u;
    int     flash_index;

    flash_index = shotgun_flash[flash_number];

    AngleVectors (self->s.angles, forward, right, NULL);
    G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start);

    if (flash_number == 5 || flash_number == 6)
    {
        VectorCopy (forward, aim);
    }
    else
    {
        VectorCopy (self->enemy->s.origin, end);
        end[2] += self->enemy->viewheight;
        VectorSubtract (end, start, aim);
        vectoangles (aim, dir);
        AngleVectors (dir, forward, right, up);

        r = crandom()*1000;
        u = crandom()*500;
        VectorMA (start, 8192, forward, end);
        VectorMA (end, r, right, end);
        VectorMA (end, u, up, end);

        VectorSubtract (end, start, aim);
        VectorNormalize (aim);
    }

    monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index);
}

// Fire weapon
void decoy_fire1 (edict_t *self)
{
    decoy_fire (self, 0);
}

mframe_t decoy_frames_attack1 [] =
{
    ai_charge, 0,  NULL,
    ai_charge, 0,  NULL,
    ai_charge, 0,  decoy_fire1,
    ai_charge, 0,  NULL,
    ai_charge, 0,  NULL,
    ai_charge, 0,  NULL,
    ai_charge, 0,  NULL,
    ai_charge, 0,  NULL
};
mmove_t decoy_move_attack1 = {FRAME_attack1, FRAME_attack8, decoy_frames_attack1, decoy_run};

void decoy_attack(edict_t *self)
{
    self->monsterinfo.currentmove = &decoy_move_attack1;
}

Explaination: Phew! Now lets take a look how the animations are done. All the animation sequences are basically the same. Let's look at one in particular - the pain animation. We must create a mframe_t structure (called decoy_frames_pain1) to hold information about each frame of the animation. Each frame has a line that looks like this:
	ai_move, -3, NULL,
	ai_move, 4,  NULL,
Each frame line has 3 components - an ai function, the distance to move, and an optional function to execute. I'm using the ai_move function in g_ai.c to make the decoy act like a Q2 monster. For example, it will test to see if it needs to change state from standing to attacking.

The second parameter is the distance to move. A postive number moves the decoy forward, and a negative number moves it back. Notice in this case, when the decoy receives pain, it first moves back -3 (from a blast), them moves forward 4.

The third parameter is an optional function to call on that frame. For example, we may want to play a sound when the animation hits the last frame.

The mmove_t structure defines information about the animation as a whole. It contains the position of the first and last frame of the animation, the name of the structure holding the frame information, and a pointer to the function used to start the animation.

Next, we need to create a function that can be called to start the animation. If you look at the decoy_pain, it does a couple of things. First, it checks to see if the pain animation is already running. If it is, don't start it again for 3 seconds. Next it plays the pain sound. Finally, it starts the pain animation by setting self->monsterinfo.currentmove.

Decoy Behavior
The next bit of code determines some of the decoy behavior:

  //
  // SIGHT logic
  //
  void decoy_sight(edict_t *self, edict_t *other)
	{
	if (random() < 0.5)
		gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
	else
		gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
	}
Explaination: This code will play a random "I saw you!" sound when the decoy sees an enemy.
  //
  // DEATH sequence
  //
  void decoy_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
	{
	//	int		n;

	if (self->deadflag == DEAD_DEAD)
		return;

	// regular death
	self->deadflag = DEAD_DEAD;
	self->takedamage = DAMAGE_YES;

	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);

	//do a BFG kind of explosion where the decoy was
	gi.WriteByte (svc_temp_entity);
	gi.WriteByte (TE_BFG_EXPLOSION);
	gi.WritePosition (self->s.origin);
	gi.multicast (self->s.origin, MULTICAST_PVS);

	//Clear pointer of owner
	self->creator->decoy = NULL;

	//Remove entity instead of playing death sequence
	G_FreeEdict (self);

	}
Explaination: This code plays the death sound, then does a little BFG flash where the decoy stands. This let's people know it was a decoy they killed and not a real person. Before the decoy dies, it needs to clear the decoy pointer for the player who created it.

Creating the Decoy
We're in the home stretch now! Lets spawn the sucker:

  //
  // SPAWN
  //
  void spawn_decoy (edict_t *owner)
  {
      edict_t *self;
      vec3_t forward;

      self = G_Spawn();

      // Place decoy 100 units forward of our position
      AngleVectors(owner->client->v_angle, forward, NULL, NULL);
      VectorMA(owner->s.origin, 100, forward, self->s.origin);
 
      //Link two entities together
      owner->decoy = self;	//for the owner, this is a pointer to the decoy
      self->creator = owner;	//for the decoy, this is a pointer to the owner

      //Use same model and skin as the person creating decoy
      self->model = owner->model;
      self->s.skinnum = owner->s.skinnum;
      self->s.modelindex = owner->s.modelindex;
      self->s.modelindex2 = owner->s.modelindex;

      self->s.effects = 0;
      self->s.frame = 0;
      self->classname = "decoy";
      self->health = 20;
      self->max_health = 20;
	
      self->monsterinfo.scale = MODEL_SCALE;
      VectorSet (self->mins, -16, -16, -24);
      VectorSet (self->maxs, 16, 16, 32);
      self->movetype = MOVETYPE_STEP;
      self->solid = SOLID_BBOX;
      self->clipmask = MASK_PLAYERSOLID;
      self->takedamage = DAMAGE_AIM;

      self->mass = 100;
      self->pain = decoy_pain;
      self->die = decoy_die;
      self->monsterinfo.stand = decoy_stand;
      self->monsterinfo.walk = NULL;
      self->monsterinfo.run = decoy_run;
      self->monsterinfo.dodge = NULL;
      self->monsterinfo.attack = decoy_attack;
      self->monsterinfo.melee = NULL;
      self->monsterinfo.sight = decoy_sight;

      //Dont attack anything to start with
      self->monsterinfo.aiflags & AI_GOOD_GUY;

      //Set up sounds
      sound_idle =    gi.soundindex ("soldier/solidle1.wav");
      sound_sight1 =  gi.soundindex ("soldier/solsght1.wav");
      sound_sight2 =  gi.soundindex ("soldier/solsrch1.wav");
      sound_pain = gi.soundindex ("soldier/solpain1.wav");
      sound_death = gi.soundindex ("misc/keyuse.wav");
      gi.soundindex ("soldier/solatck1.wav");

      self->health = 30;
      self->gib_health = -30;

      // Face the decoy the same direction as player
      self->s.angles[PITCH] = owner->s.angles[PITCH];
      self->s.angles[YAW] = owner->s.angles[YAW];
      self->s.angles[ROLL] = owner->s.angles[ROLL];

      gi.linkentity (self);

      // First animation sequence
      self->monsterinfo.stand (self);

      //Let monster code control this decoy
      walkmonster_start (self);
  }

Explaination: The decoy entity is spawned, and the necessary attributes are assigned. Notice that the decoys are linked together. The owner->decoy variable gives the player a pointer to their decoy. The self->creator variable lets the decoy know who created it. When I originally built the decoy, I tried setting the self->owner. I found out the hard way that you aren't supposed to use that. When I used self->owner, I could walk through my decoy!

The monsterinfo variables are used to hold pointers to the different animation functions. After we point our decoy in the right direction, we link the entity, then call the walkmonster_start function. This will set up the decoy to use the monster ai code.

  // SP_Decoy - Handle DECOY command
  void SP_Decoy(edict_t *self) 
  {

      //See if we should decoy turn it on or off
      char    *string;
      int  turnon;

      string=gi.args();

      if (Q_stricmp ( string, "on") == 0) 
          turnon = true;
      else if (Q_stricmp ( string, "off") == 0) 
          turnon = false;
      else {  //toggle status
          if (self->decoy) turnon = false;
              else turnon = true;
      }


      //If they want to turn it on and it's already on, return
      if ( (turnon == true) && (self->decoy) ) return;

      //If they want to turn it off and it's already off, return
      if ( (turnon == false) && !(self->decoy) ) return;

      //Remove decoy if it exists
      if ( self->decoy ) 
      {
          G_FreeEdict(self->decoy);
          self->decoy = NULL;
          gi.cprintf (self, PRINT_HIGH, "Decoy destroyed.\n");
          return;
          }

      //Create decoy
      spawn_decoy(self);

      gi.cprintf (self, PRINT_HIGH, "Decoy created.\n");
      }
Explaination: Finally, this is the code to handle the user's command. If you enter "cmd decoy on" it will turn on the decoy. If you enter "cmd decoy off" it will remove the decoy. Typing "cmd decoy" by itself will toggle the decoy on and off.

Well, thats it. You now should have a fully functional decoy to incorporate into your game mod. You could consider it a primitive bot you can build onto.

Have some fun with the animations. Notice that the decoy_taunt function is not used. Try setting self->monsterinfo.stand to decoy_taunt instead of decoy_stand. It will continually do the taunt!

You can try this change on the Weapons Factory server at 199.204.0.51.  Visit the : The Reno Brothers Weapons Factory page for documentation on all of our server mods.


Copyright 1998 - Gregg Reno  (Headache)
[email protected]

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4