Post tutorial RSS Doom Source Code Tutorial 8

Modding The Monsters In Doom Part 1 - The Zombieman. Today we turn the Zombieman into a sharpshooting marksman.

Posted by on - Basic Client Side Coding

Doom Source Code tutorial 8
Game: Doom or Doom2
Level: Basic.
Objective: Understand the basics of monsters' states, attributes and characteristics.
Resources required: VC++ 2008 Express Edition; source code for Doom (tested with Doomsday ver 1.9.0-beta6.9 and chocolate doom).
Study Your Enemy, A Prelude:
For the next few tutorials we will analyze in sufficient details how some of the monsters in doom behave in the game. This should give us enough information to start making our own monster mods. I should point out right from the beginning that most of these entities share the same structures in terms of attributes, States,and characteristics as we'll soon see. So what applies to one monster in terms of behavior or the way he reacts ,moves, shoots or dies should apply in most parts to other monsters especially if they are from the same family (zombies & sergeants; or imps & barons etc). This makes life easier for us, as what we learn about one monster in the way he does his things can be utilized to do similar things with his kinfolks. This simply means I will only give one example each in every tutorial; it is up to the reader to devise his own ideas for the others based on the information given in the article. For example in this tutorial we will make the zombie a sniper soldier; you can certainly do the same thing using almost the same code with the sergeant and the heavy weapons dude (Chain gunner) and vice versa. Let's start:

Introduction:
Having powered up our weapons in previous articles, today we‘ll try to be fair and restore the balance to the game by giving the monsters a fighting chance against the player. First we will try our luck with the humble zombie man. How about turning him into a real marksman with deadly aim, we'll give him a sniper rifle capable of killing any player from a couple of miles away. We will also raise his hit points making him harder to kill (or infighting will be a real quick bloody massacre). We shall increase his running speed too just to make him chase you straight to hell; a real nightmare mode.

Procedure:

1. First of all we need to locate were these values can be hacked .We shall take a close look at the attributes and characteristics of the zombie man aka the "possessed". For jdoom, open up your friendly neighborhood "objects.ded" file and go straight to the "Thing" entry with the ID = POSSESSED and have a look there, you should see the structure shown below:
Thing {
ID = "POSSESSED";
Name = "Zombieman";
DoomEd number = 3004;
Spawn state = "POSS_STND";
See state = "POSS_RUN1";
Pain state = "POSS_PAIN";
Melee state = "NULL";
Missile state = "POSS_ATK1";
Death state = "POSS_DIE1";
Xdeath state = "POSS_XDIE1";
Raise state = "POSS_RAISE1";
See sound = "posit1";
Attack sound = "pistol";
Pain sound = "popain";
Death sound = "podth1";
Active sound = "posact";
Reaction time = 8;
Pain chance = 200;
Spawn health = 20;
Speed = 8;
Radius = 20;
Height = 56;
Mass = 100;
Flags = solid | shootable | countkill;
Flags2 = passmobj | telestomp;
}

2. The values we are interested in changing in order to create this mode are those for the spawn health and the speed. They are shown in red above. We will make our zombie 10 times tougher (increase the health value to 200 ) and let him run twice as fast ( change the speed to 16). If you are modding for Chocolate doom, you should find the above stuff in the source file info.c
3. We want to do this from the source code, so let's examine the entries in the relevant type definition structure. It is important at this stage to study this block of code as it applies to every monster in Doom. You should find this in the "info.h" file for Chocolate Doom as shown below:

typedef struct
{
int doomednum;
int spawnstate;
int spawnhealth;
int seestate;
int seesound;
int reactiontime;
int attacksound;
int painstate;
int painchance;
int painsound;
int meleestate;
int missilestate;
int deathstate;
int xdeathstate;
int deathsound;
int speed;
int radius;
int height;
int mass;
int damage;
int activesound;
int flags;
int raisestate;
} mobjinfo_t;

And for Doomsday (jDoom), also in info.h:
typedef struct {
int doomEdNum;
int spawnHealth;
float speed;
float radius;
float height;
int mass;
int damage;
int flags;
int flags2;
int flags3;
int reactionTime;
int painChance;
int states[NUM_STATE_NAMES];
int painSound;
int deathSound;
int activeSound;
int attackSound;
int seeSound;
int misc[NUM_MOBJ_MISC];
} mobjinfo_t;

4. I am showing both files here to emphasize the fact that every port for Doom has its own ways of doing things, for example in Chocolate doom it says "spawnhealth" while in doomsday it is "spawnHealth " . Further speed is declared as integer in Choc doom while it is float in jdoom . So if your mod does not work the first time, then you'd better examine the entries in files like these and check the names and spelling. In addition C and C++ are case sensitive. Mind your spelling as for jdoom we need to use the game import (gi) definition MOBJINFO and not the mobjinfo (as used in chocolate doom) to access and change the data in these structures as we'll see soon. Enough confusion for today!
5. As for arming our zombie with a sniper's rifle, we need first to study his attack States, there are only 3, find them inside the same file "info.h". Note the similarity with those used for the weapons we did in previous articles, because the structure code is the same one. Here it is just to remind you:This is for Doomsday:

typedef struct state_s
{
int sprite;
int frame;
int tics;
acfnptr_t action;
int nextState;
int misc[NUM_STATE_MISC];
int flags;
} state_t;
acfnptr_t means action function pointer type .
And this one belongs to chocolate doom:

typedef struct
{
spritenum_t sprite;
int frame;
int tics;
actionf_t action;
statenum_t nextstate;
int misc1;
int misc2;
} state_t;

Now for the Zombie man states, this is in objects.ded for jdoom:
State {
ID = "POSS_ATK1";
Sprite = "POSS";
Frame = 4;
Tics = 10;
Action = "A_FaceTarget";
Next state = "POSS_ATK2";
}

State {
ID = "POSS_ATK2";
Sprite = "POSS";
Frame = 5;
Flags = fullbright;
Tics = 8;
Action = "A_PosAttack";
Next state = "POSS_ATK3";
}

State {
ID = "POSS_ATK3";
Sprite = "POSS";
Frame = 4;
Tics = 8;
Next state = "POSS_RUN1";
}

and in a different format for chocolate doom; attack states are shown in red, comments in green:

{SPR_POSS,3,4,{A_Chase},S_POSS_RUN1,0,0}, // S_POSS_RUN8
{SPR_POSS,4,10,{A_FaceTarget},S_POSS_ATK2,0,0}, // S_POSS_ATK1
{SPR_POSS,5,8,{A_PosAttack},S_POSS_ATK3,0,0}, // S_POSS_ATK2
{SPR_POSS,4,8,{NULL},S_POSS_RUN1,0,0}, // S_POSS_ATK3
{SPR_POSS,6,3,{NULL},S_POSS_PAIN2,0,0}, // S_POSS_PAIN

6. The first state plays the attack frame (#4) for a period of 10 tics. You can also see that it calls for the execution of the action function that makes the zombie face his enemy. This is done by calculating the angle between the monster (actor) and its intended target (you, the player or other monsters, if infighting is set) using simple co-ordinate geometry and trigonometry in 2D. Have a look below, I‘ve added some comment lines to explain what each statement is doing. Here is the code for chocolate doom, the one for jdoom is very similar:

void A_FaceTarget (mobj_t* actor)
{
//if there is no target,then just return
if (!actor->target)
return;
// flag for monsters laying in ambush positions
actor->flags &= ~MF_AMBUSH;
//calculate view angle between monster and player
actor->angle = R_PointToAngle2 (actor->x,
actor->y,
actor->target->x,
actor->target->y);
//check if the target is a shadow demon (spectre)
// or if the player is using invisibility power-up,
//if so then mess-up the angle of aim for zombie
if (actor->target->flags & MF_SHADOW)
actor->angle += (P_Random()-P_Random())<<21;
}

7. The second attack state calls the firing function A_PosAttack and simultaneously sets the flag for the next state to flood the locality with a bright gun flash (jdoom). This is the action function we need to examine in order to make our sniper rifle. You should find it in the file p_enemy.c. Again, bear in mind that "actor" here means the monster (in this case zombie) and that "target" is you. I am only showing below the definition used in jdoom; the other port is almost identical. Just study the comments and changes shown in red:

void C_DECL A_PosAttack(mobj_t *actor)
{
int damage;
angle_t angle;
float slope;
//if target does not exist for zombie then simply return
if (!actor->target)
return;
// call this function to set the view angle "actor->angle" (for aiming)
A_FaceTarget(actor);
angle = actor->angle;
//set vertical aim for zombie
slope = P_AimLineAttack(actor, angle, MISSILERANGE);
//play pistol firing sound
S_StartSound(SFX_PISTOL, actor);
//the next line messes up the zombie's aim,this the one we need to remove
angle += (P_Random() - P_Random()) << 20;
//sets the rate of damage with a random factor,we'll mod this one too
damage = ((P_Random() % 5) + 1) * 3;
//this function does the actual firing.
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}

8. Finally the last state displays the closing attack state and returns the action back to the RUN1 state to continue the chase!
9. Let's create our mod now. We shall start with the weapon. Like we said we need to improve the zombies very poor aim,so we must completely remove the random factor in the firing function, simply delete or comment out the line responsible for this,it is shown in red in the above code block.
10. Next you want your sniper to have a powerful weapon not a pea shooter, so increase the damege rate 10 times. We should keep the random function intact, just in case he decides to shoot you in the ear ,after all he is still a zombie!
11. Your final code for chocolate doom should look something like the following; jdoom uses C_DECL A_PosAttack instead , otherwise it is almost the same:

void A_PosAttack (mobj_t* actor)
{
int angle;
int damage;
int slope;
if (!actor->target)
return;
A_FaceTarget (actor);
angle = actor->angle;
slope = P_AimLineAttack (actor, angle, MISSILERANGE);
S_StartSound (actor, sfx_pistol);
// delete or comment out next line for max accuracy
//angle += (P_Random()-P_Random())<<20;
//next multiply damage by a factor of 10
damage = 10 * ((P_Random()%5)+1)*3;
P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}

12. Next, we want to increase the spawn health of our zombie to 200, this is very easy to do. Remember the work we did in our previous tutorial (tut 7), we just need to use the dot operator to access the value in the zombie's structures once loaded into memory . For Chocolate doom, do it this way using the map object information type definition (oh, hell... quite a mouthful) or mobjinfo as shown below:

mobjinfo[MT_POSSESSED].spawnhealth = 200; //u can also do this in info.c

and for jDoom:

MOBJINFO[MT_POSSESSED].spawnHealth = 200;

13. Same way for the speed:

mobjinfo[MT_POSSESSED].speed = 16; //choc doom ,u can also do this in info.c

MOBJINFO[MT_POSSESSED].speed = 16; //jDoom

14. Add the above 2 changes next to the locations we used for the chain gun in tutorial 7 (in g_game.c file). Save your work, build your chocolate-doom.exe or jdoom.dll or whatever port you are happy with and try out your mod. Careful now, there are berserk sniping zombies on steroids lurking on your map; better take cover or you'll be dead before you see one! See you in the next article.
Stay safe while playing.
Adam.

Return To Tutorials Page.

Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.