The complete megahit game that set the world afire. Plus All-New Episode IV: Thy Flesh Consumed.The demons came and the marines died. Except one. You are the last defense against these hell-spawned hordes. Prepare for the most intense mutant-laden, blood-splattered action ever! The texture-mapped virtual world is so real, you don't just play DOOM - you live it.The Ultimate DOOM takes you beyond anything you've ever experienced. First, you get all three original episodes - that's 27 levels of awesome, explosive excitement. Then it really blows you away with an all-new episode: Thy Flesh Consumed. Now you're dead meat. Just when you think you're getting pretty good at DOOM, you get hit with Perfect Hatred, Sever the Wicked and seven other expert levels never seen before! They're so incredibly tough, the first 27 levels will seem like a walk in the park!

Post tutorial Report RSS Doom Source Code Tutorial 9

Modding The Monsters In Doom Part 2 - The Sergeant. In this tutorial we arm the sergeant with a machine gun.

Posted by on - Basic Client Side Coding

Doom Source Code tutorial 9
Game: Doom or Doom2
Level: Basic.
Objective: Understand the basics of the sergeant's' states, attacks and characteristics.
Resources required: VC++ 2008 Express Edition; source code for Doom (tested with Doomsday ver 1.9.0-beta6.9 and chocolate doom).
Introduction:Today we are going to turn our shot guy into a machine gun guy. The mod itself is simple and just requires us to add a couple of functions to make him shoot faster; just remember what we did with the shotgun for the player in tutorial 5.
Procedure:
1. The best way to start this is to examine the attack states of the monster. Let's take a look at the first one. Here it is:

State {
ID = "SPOS_ATK1";
Sprite = "SPOS";
Frame = 4;
Tics = 10;
Action = "A_FaceTarget";
Next state = "SPOS_ATK2";
}

You can see it is similar to that of the zombie man but of course displaying a different sprite. Let's now look at the second one:

State {
ID = "SPOS_ATK2";
Sprite = "SPOS";
Frame = 5;
Flags = fullbright;
Tics = 10;
Action = "A_SPosAttack";
Next state = "SPOS_ATK3";
}

2. This is also looks similar. We need now to examine the action function that does the actual firing, A_SPosAttack , you'll find this one in p_enemy.c , the following is for Chocolate Doom; the one in jdoom should be identical. I have added comments to explain what each line does :

void A_SPosAttack (mobj_t* actor)
{
int i;
int angle;
int bangle;
int damage;
int slope;
// if there is no target ,then just return to the calling state
if (!actor->target)
return;
// start playing the firing sound for the shotgun
S_StartSound (actor, sfx_shotgn);
// make the sergeant face the target and set the "actor->angle"
A_FaceTarget (actor);
bangle = actor->angle;
// set aim in the vertical direction
slope = P_AimLineAttack (actor, bangle, MISSILERANGE);
// firing loop. It shoots 3 bullets at slightly different angle
//each time plus random damage, see shotgun tutorial.
for (i=0 ; i<3 ; i++) //better remove loop,one shot is enuf
{
angle = bangle + ((P_Random()-P_Random())<<20);
damage = ((P_Random()%5)+1)*3;
P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}
}

3. So the sergeant shoots 3 bullets in a slightly different direction each time and with a varying damage rate. This works fine for the shotgun but now he is supposed to be carrying an automatic weapon, so we need to get rid of the loop and reduce the shots to one but we'll keep the random damage . Our final function should look like this:

void A_SPosAttack (mobj_t* actor)
{
int i;
int angle;
int bangle;
int damage;
int slope;
if (!actor->target)
return;
S_StartSound (actor, sfx_shotgn);
A_FaceTarget (actor);
bangle = actor->angle;
slope = P_AimLineAttack (actor, bangle, MISSILERANGE);
damage = ((P_Random()%5)+1)*3;
P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}

4. Now our sergeant has finished firing his single shot ,and control is handed over to the next state SPOS_ATK3 :

State {
ID = " SPOS_ATK3";
Sprite = "SPOS";
Frame = 4;
Tics = 10;
Next state = "SPOS_RUN1";
}

5. This will simply finish the attack state with a bright flash and place the monster back into the normal chase/run state "SPOS_RUN1" . What we need to do in order to make our mod is to persuade this guy to continue shooting by creating a loop. We can do that by forcing the next state to be SPOS_ATK2 instead of SPOS_RUN1 as shown below for jdoom:

State {
ID = " SPOS_ATK3";
Sprite = "SPOS";
Frame = 4;
Tics = 10;
Next state = " SPOS_ATK2";
}

6. Do this in C code for jdoom in g_game.c and paste the folowing in the usual place (see tut. 8):STATES[S_SPOS_ATK3].nextState = S_SPOS_ATK2; //loop back to attack2 state
7. This should provide us with a continuous loop that makes the sergeant fire continuously giving us the required rapid fire. For Chocolate doom,the structure is different as no external defs files are used. Edit info.c as shown next; but first you need to do a search for this code block and then, change the code shown in red ,

from this:
{SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
{SPR_SPOS,32773,10,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
{SPR_SPOS,4,10,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_ATK3

to this:
{SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
{SPR_SPOS,32773,10,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
{SPR_SPOS,4,10,{NULL}, S_SPOS_ATK2,0,0}, // S_SPOS_ATK3

8. Now compile your mod and try it out. You'll notice that your sergeants are firing non-stop even if you get out of their sight or get killed. That's not exactly what we want. We need to put some conditions in place in order to control this situation .We want these monsters to use their weapons but subject to the following criteria:
1.We want the firing to stop if there is no target, just to be on the safe side.
2.We want the monsters to stop shooting when you are out of sight or hide somewhere.
3.We certainly need them to cease firing when you are dead.
For the first condition, a "(!actor->target)" will do the job. For the second one we can use the built-in function "P_CheckSight (actor, actor->target)" to check for a clear line of sight between player and monster; but with an inversion as we'll be using an OR operator and the third one is easy; we can use this statement: "actor->target->health <= 0" ,i.e. the enemy (you) is dead.
9. Remember how the chaingun worked in tutorial 3 and how we later modded the shotgun into a machine gun in tutorial 5, we used a function to refire the gun, we need to do the same thing here subject to the above conditions as in this case a dumb monster is supposed to be firing the weapon and not a human player. Therefore we need to add a new action function to the game; we shall call it A_SPosRefire ; here is the one we can use for chocolate doom. Define it as follows in p_enemy.c just before the one for A_SPosAttack :

void A_SPosRefire (mobj_t* actor)
{
A_FaceTarget (actor);
if (!actor->target
|| actor->target->health <= 0 // use logical OR
|| !P_CheckSight (actor, actor->target) )
{
P_SetMobjState (actor, actor->info->seestate);
}
}

10. In the last line above pay attention to the function P_SetMobjState , this stands for set map object state. It is a very useful one for modding as we can utilize it to change the states of an entity. This time we are setting the state of the sergeant to the "see state" which is already pre-defined as "S_SPOS_RUN1" (run or chase) in the shotguy structure; below is a portion of this for chocolate doom copied from info.c:

// MT_SHOTGUY
9, // doomednum
S_SPOS_STND, // spawnstate
70, // spawnhealth
S_SPOS_RUN1, // seestate
sfx_posit2, // seesound

11. But wait for it, jDoom uses different functions and it says Change State and Get State, here it is:

P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));

The end result is the same, it will put your shotguy in the see state which is also pre-defined here as the SPOS_RUN1 as we can see in this segment from "objects.ded" of doomsday:

Thing {
ID = "SHOTGUY";
Name = "Shotgun Guy";
DoomEd number = 9;
Spawn state = "SPOS_STND";
See state = "SPOS_RUN1";
Pain state = "SPOS_PAIN";

12. Here is the complete code block for the action function definition we use in jdoom, notice the difference in the naming convention using C_DECL :

void C_DECL A_SPosRefire(mobj_t* actor)
{
A_FaceTarget(actor);
if (!actor->target || actor->target->health <= 0 || // use logical OR
!P_CheckSight(actor, actor->target))
{
P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
}
}

13. Before we can use the above function in jdoom we must first declare it in the header file acfnlink.h (find it under "Header files" in jdoom solution explorer section) as shown below .I declared A_SPosRefire() right at the bottom of the list ; do not forget the brackets ().

void C_DECL A_XScream();
void C_DECL A_SPosRefire(); //added new function
#endif

14. And for chocolate doom I placed the declaration as shown below, somewhere at the top of the info.c file:

void A_FaceTarget();
void A_PosAttack();
void A_Scream();
void A_SPosAttack();
void A_SPosRefire (); //added new function
void A_VileChase();
void A_VileStart();

15. Next important step is to add a pointer to this action function in the relevant state . For jdoom add the following line to g_game.c ; a good location for it is right after the line in 6 above:

STATES[S_SPOS_ATK3].action = A_SPosRefire ;

16. And for chocolate doom insert it in the structure in info.c in place of "NULL" as shown below in red:
{SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
{SPR_SPOS,32773,10,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
{SPR_SPOS,4,10,{A_SPosRefire},S_SPOS_ATK2,0,0}, // S_SPOS_ATK3
{SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN

17. We also need to increase the speed of firing to get real machine gun action ; so reduce the tic rate to a lower value, I found that setting this to 4 tics for both firing frames was best on my computer, try it and see if you are happy with this number ,but don't set it too low. The finished code for the above,with all the changes in red should look like this:

{SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
{SPR_SPOS,32773,4,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
{SPR_SPOS,4,4,{A_SPosRefire},S_SPOS_ATK2,0,0}, // S_SPOS_ATK3
{SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN

18. For jdoom in g_game.c , we access each member of the individual states using the dot operator as shown below, unless you still prefer to text edit the defs files directly:

//place this block just before this comment in g_game.c: " // <-- KLUDGE "
STATES[S_SPOS_ATK3].action = A_SPosRefire ; //added new action function
STATES[S_SPOS_ATK3].nextState = S_SPOS_ATK2; //loop back to attack2 state
STATES[S_SPOS_ATK2].tics = 4; // reduced tics for fast refire
STATES[S_SPOS_ATK3].tics = 4;

19. Now we should be ready and set to go. Compile your mod and play a level, you can see a more satisfying result. As soon as the sergeants start shooting at you get out of the way,they should immediately cease firing and start looking for you. That's more like it. If you are testing your mod with iddqd, get back to normal and let a sergeant or two kill you; once you are dead he or they should immediately declare a cease fire! Much better!
20. We can do some fine tuning to this mod with one more optional refinement. We can make the sergeant move around while firing instead of just standing in one place . Do this by adding a random control function to set the monster in the see state every time the random number generated falls below say 40. Here is the complete final code for doomsday:

void C_DECL A_SPosRefire(mobj_t* actor)
{
if(P_Random() < 40)
P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
A_FaceTarget(actor);
if(!actor->target || actor->target->health <= 0 ||
!P_CheckSight(actor, actor->target))
{
P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
}
}

21. And if chocolate doom is your favorite flavor of the game:

void A_SPosRefire (mobj_t* actor)
{
if (P_Random () < 40)
P_SetMobjState (actor, actor->info->seestate);
A_FaceTarget (actor);
if (!actor->target
|| actor->target->health <= 0
|| !P_CheckSight (actor, actor->target) )
{
P_SetMobjState (actor, actor->info->seestate);
}
}

22. Copy and paste the above code, compile and try. Now your shot guy should every now and then breaks from firing and does some chasing. More realistic. Of course you can re-adjust those figures and other attributes and properties like health and speed to satisfy what you are trying to do in your map. Do it in the same way we did for the zombie in tutorial 8. There are other things we can do to this monster but we'll leave it for the future.
That's it for this article. Hope you join us next time.
Adam.
Return to tutorials page.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: