In this tutorial we will add a new monster to Quake! Our new monster will 2 attackes, for the first attack our monsters will launch a lavaball, the second attack will throw the player back.

First download the new model and test map from here. (here should link to the model/map)

To get started make a new file named spellmas.qc, that file will contain the code for the Spell Master. Add this to the top of the spellmas.qc:

/*
==============================================================================

Spell Master
code by: Philip Martin  aka: Kryten           Monster Skin by: Malengine
==============================================================================
*/

Thats just a comment that identifies what this file is and who made the code/model skin (the model was made by id software but never made it into Quake). Now whe will declair the frames for the model:

//7 walk frames!
$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7
//15 frames for lava attack
$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8
$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15
//9 frames for push attack
$frame attackb1 attackb2 attackb3 attackb4 attackb5 attackb6 attackb7 attackb8
$frame attackb9
//4 pain frames
$frame pain1 pain2 pain3 pain4
//and 16 death frames
$frame die1 die2 die3 die4 die5 die6 die7 die8 die9 die10 die11 die12 die13
$frame die14 die15 die16

We do this so we can just call for a frame name and not have to know the number of that frame. Now I made the Spell master use all the walk frames for standing, running, and walking. The way I did this is a little odd, but very simple. Here is the standing/walking/running code:

void(float dist) spell_ai =
{
//this handles all the standing/walking and running
//this will alow us to use the same functions for standing/walking and running
	if (self.style == 1) //standing
		ai_stand();
	else if (self.style == 2) //walking
		ai_walk(dist);
	else if (self.style == 3) //running
		{ ai_run(dist*2); ai_face(); } //always face enemy when running
};

void() spell_walk1 =[ $walk1, spell_walk2 ] {self.effects = 0; //just in case
if (random() < 0.2)
	sound (self, CHAN_VOICE, "boss2/idle.wav", 1, ATTN_IDLE);
spell_ai(6);};
void() spell_walk2 =[ $walk2, spell_walk3 ] {spell_ai(4);};
void() spell_walk3 =[ $walk3, spell_walk4 ] {spell_ai(3);};
void() spell_walk4 =[ $walk4, spell_walk5 ] {spell_ai(5);};
void() spell_walk5 =[ $walk5, spell_walk6 ] {spell_ai(6);};
void() spell_walk6 =[ $walk6, spell_walk7 ] {spell_ai(4);};
void() spell_walk7 =[ $walk7, spell_walk1 ] {spell_ai(3);};

void() spell_stand = { self.style = 1; spell_walk1(); };
void() spell_walk  = { self.style = 2; spell_walk1(); };
void() spell_run   = { self.style = 3; spell_walk1(); };


When the Spell Master launches the lavaball he will glow for a while. If the player attackes him or movse away from him thenhe might stop the attack to go into pain or to hunt the player. By putting the self.effects there we stop him from glowing just in case this happens. Now here is the attack code:

void () Spell_launch_lava;
void () Spell_push;

void() spell_attacka1 =[ $attacka1,  spell_attacka2  ] {ai_face();};
void() spell_attacka2 =[ $attacka2,  spell_attacka3  ] {ai_face();};
void() spell_attacka3 =[ $attacka3,  spell_attacka4  ] {ai_face();};
void() spell_attacka4 =[ $attacka4,  spell_attacka5  ] {ai_face();};
void() spell_attacka5 =[ $attacka5,  spell_attacka6  ] {ai_face();};
void() spell_attacka6 =[ $attacka6,  spell_attacka7  ] {ai_face();};
void() spell_attacka7 =[ $attacka7,  spell_attacka8  ] {ai_face();};
void() spell_attacka8 =[ $attacka8,  spell_attacka9  ] {ai_face();};
void() spell_attacka9 =[ $attacka9,  spell_attacka10 ] {ai_face();};
void() spell_attacka10 =[ $attacka10, spell_attacka11 ] {self.effects = EF_DIMLIGHT;ai_face();};
void() spell_attacka11 =[ $attacka11, spell_attacka12 ] {ai_face();};
void() spell_attacka12 =[ $attacka12, spell_attacka13 ] {ai_face();};
void() spell_attacka13 =[ $attacka13, spell_attacka14 ] {ai_face();};
void() spell_attacka14 =[ $attacka14, spell_attacka15 ] {Spell_launch_lava();ai_face();};
void() spell_attacka15 =[ $attacka15, spell_run      ] {ai_face();};

void() spell_attackb1 =[ $attackb1, spell_attackb2 ] {ai_face();};
void() spell_attackb2 =[ $attackb2, spell_attackb3 ] {ai_face();};
void() spell_attackb3 =[ $attackb3, spell_attackb4 ] {ai_face();};
void() spell_attackb4 =[ $attackb4, spell_attackb5 ] {ai_face();};
void() spell_attackb5 =[ $attackb5, spell_attackb6 ] {ai_face(); Spell_push(); };
void() spell_attackb6 =[ $attackb6, spell_attackb7 ] {ai_face();};
void() spell_attackb7 =[ $attackb7, spell_attackb8 ] {ai_face();};
void() spell_attackb8 =[ $attackb8, spell_attackb9 ] {ai_face();};
void() spell_attackb9 =[ $attackb9, spell_run      ] {ai_face();};

void() spell_attack =
{ //this will pick from one of 2 attacks
	local vector	delta;
	delta = self.enemy.origin - self.origin;

	if (vlen (delta) <= 90)
		spell_attackb1(); //used to push players away: always push if players are close!
	else if ((vlen (delta) <= 300) && (random() < 0.5))
		spell_attackb1(); //used to push players away
	else
		spell_attacka1(); //easy to dodge in most cases but can be deadly if you don't!
};

The spell_attack will pick what attack to use, if the player is too close the Spell Master will always push the player away, the rest of the time it is randomly chosen if the player is within a certen range. Now we will add the lavaball code and the push code:

void() Spell_MissileTouch =
{
	local float	damg;

	if (other == self.owner)
		return;		// don't explode on owner
	if (pointcontents(self.origin) == CONTENT_SKY)
		{ remove(self); return; }

	damg = 50 + random()*20; //up to 70 damage!

	if (other.health)
		T_Damage (other, self, self.owner, damg);

// don't do radius damage to the other, because all the damage was done in the impact
	T_RadiusDamage (self, self.owner, 120, other);
	self.origin = self.origin - 8*normalize(self.velocity);
	WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
	WriteByte (MSG_BROADCAST, TE_EXPLOSION);
	WriteCoord (MSG_BROADCAST, self.origin_x);
	WriteCoord (MSG_BROADCAST, self.origin_y);
	WriteCoord (MSG_BROADCAST, self.origin_z);
	BecomeExplosion ();
};

void () Spell_launch_lava =
{
	sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM);

	local	vector	vec;
	vec = self.enemy.origin - self.origin;
	vec = normalize(vec);

	newmis = spawn ();
	newmis.owner = self;
	newmis.movetype = MOVETYPE_FLYMISSILE; //like a rocket
	newmis.solid = SOLID_BBOX;
	newmis.classname = "spell_lava";

	newmis.avelocity = '500 500 500';
	newmis.velocity = vec * 600;
	newmis.angles = vectoangles(newmis.velocity);

	newmis.touch = Spell_MissileTouch; //if we hit something go boom!
	newmis.nextthink = time + 5;
	newmis.think = SUB_Remove;

	setmodel (newmis, "progs/lavaball.mdl");
	setsize (newmis, '0 0 0', '0 0 0');		
	setorigin (newmis, self.origin + v_forward*16 + '0 0 16');

	self.effects = 0;
};
void () Spell_push =
{
	local vector	delta;
	local float 	ldmg;

	if (self.enemy.classname != "player") //only push players
		return;

	delta = self.enemy.origin - self.origin;

	ldmg = random() * 25; //up to 25
	T_Damage (self.enemy, self, self, ldmg); //hurt the player
	sound (self, CHAN_WEAPON, "wizard/wsight.wav", 1, ATTN_NORM);

//take them off the ground so we can push them
	if (self.enemy.flags & FL_ONGROUND)
		self.enemy.flags = self.enemy.flags - FL_ONGROUND;
//and push them!
	self.enemy.velocity = delta * 10; //up to 3000!
	self.enemy.velocity_z = 100;
};

Now the pain and death:

void() spell_pain1 =[ $pain1, spell_pain2 ] {};
void() spell_pain2 =[ $pain2, spell_pain3 ] {};
void() spell_pain3 =[ $pain3, spell_pain4 ] {};
void() spell_pain4 =[ $pain4, spell_run   ] {};

void() spell_pain =
{
	if (self.pain_finished > time)
		return;

	sound (self, CHAN_VOICE, "wizard/wdeath.wav", 1, ATTN_NORM);

	spell_pain1();
	self.pain_finished = time + 3;
};

void() spell_death1 =[ $die1, spell_death2 ] {};
void() spell_death2 =[ $die2, spell_death3 ] {};
void() spell_death3 =[ $die3, spell_death4 ] {};
void() spell_death4 =[ $die4, spell_death5 ] {};
void() spell_death5 =[ $die5, spell_death6 ] {};
void() spell_death6 =[ $die6, spell_death7 ] {};
void() spell_death7 =[ $die7, spell_death8 ] {};
void() spell_death8 =[ $die8, spell_death9 ] {};
void() spell_death9 =[ $die9, spell_death10 ] {};
void() spell_death10 =[ $die10, spell_death11 ] {};
void() spell_death11 =[ $die11, spell_death12 ] {};
void() spell_death12 =[ $die12, spell_death13 ] {};
void() spell_death13 =[ $die13, spell_death14 ] {};
void() spell_death14 =[ $die14, spell_death15 ] {};
void() spell_death15 =[ $die15, spell_death16 ] {};
void() spell_death16 =[ $die16, spell_death16 ] {};

void() spell_die =
{
	self.effects = 0; //just in case he was in the middle of an attack when he was killed

	sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM);

	spell_death1();
	self.solid = SOLID_NOT;
};

Now you have all the code needed to make the Spell Master attack, stand, walk, run, go through pain, and die. Now all you need is the code to spawn the Spell Master, and here it is:


/*monster_spellmaster (1 0 0) (-32 -32 -24) (32 32 48) Ambush
*/

void() monster_spellmaster =
{
	if (deathmatch)
	{
		remove(self);
		return;
	}

	precache_model2 ("progs/spellmas.mdl");
	precache_model2 ("progs/h_shal.mdl");

	precache_sound ("wizard/wdeath.wav");	//pain sound
	precache_sound ("wizard/wsight.wav");	//push attack
	precache_sound2 ("blob/death1.wav");	//death sound
	precache_sound2 ("boss2/idle.wav");		//idle sounds
	precache_sound2 ("shalrath/attack2.wav");	//lavaball attack
	precache_sound2 ("shalrath/sight.wav");	//sight sound


	self.solid = SOLID_SLIDEBOX;
	self.movetype = MOVETYPE_STEP;

	setmodel (self, "progs/spellmas.mdl");
	setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX);
	self.health = 500;

	self.th_stand = spell_stand;
	self.th_walk = spell_walk;
	self.th_run = spell_run;
	self.th_die = spell_die;
	self.th_pain = spell_pain;
	self.th_missile = spell_attack;

	self.think = walkmonster_start;
	self.nextthink = time + 0.1 + random ()*0.1;	
};

Now open the progs.src and go down to this line:
oldone.qc

and add this line after that:
spellmas.qc

Thats it! You now have a new monster!


If you want to see the spell master on more then just the test map you can make it replace another monster most of the time or all of the time. To make it replace the Shalrath about half the time then open the shalrath.qc and go down to:

/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush
*/
void() monster_shalrath =
{
	if (deathmatch)
	{
		remove(self);
		return;
	}

Before that add this line:
void() monster_spellmaster;

and After that add these lines:

	if (random() > 0.5)
	{
		monster_spellmaster();
		return;
	}

There you go, the spell master will replace the shalrath about half the time!