30. September 2022

Quake Downloads

Your ressource for your gaming needs…

Mage

Created By:Kryten
eMail:kryten@inside3d.com
Difficulty Scale:Normal

In this tutorial I will show you how to add a new monster. The monster is a Spell Master, or Mage. I created the Mage while working on Flat Earth. Sadly the code to Flat Earth was lost and I had to decompile it, with much work, to bring you this tutorial.

The Mage has two attacks; one he will push you back with great thrust causing some damage to you, for his other attack he will conjure up a ball of lava and launch it at you. The Spell Master hovers over the ground and will just float to wherever he wants to go. Because of this the same animations are use for walking, running, and standing.

The first step is to downlosd the Spell Master Model.

To get started create a new file and call it spelmast.qc. And place it at the bottom of the list of files to compile in the progs.src. In the spelmast.qc start by adding spell_ai:

void (float dist) spell_ai =
{
	if (self.style == 1)
		ai_stand ();
	else
	{
		if (self.style == 2)
			ai_walk (dist);
		else
		{
			if (self.style == 3)
			{
				ai_run (dist * 2);
				ai_face ();
			}
		}
	}
};

Because the same animations are used for walking, running, and standing this function determine what the Mage is actually doing so it call the correct ai_ functions. Add this next:

void () spell_walk1 = [0, spell_walk2]
{
	self.effects = 0;
	if (random () < 0.2)
		sound (self, CHAN_VOICE, "boss2/idle.wav", 1, ATTN_IDLE);
	spell_ai (6);
};
void () spell_walk2 = [1, spell_walk3] {spell_ai (4);};
void () spell_walk3 = [2, spell_walk4] {spell_ai (3);};
void () spell_walk4 = [3, spell_walk5] {spell_ai (5);};
void () spell_walk5 = [4, spell_walk6] {spell_ai (6);};
void () spell_walk6 = [5, spell_walk7] {spell_ai (4);};
void () spell_walk7 = [6, 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 ();
};

Thats the frames and the functions that set what mode the Mage is in. Continue now by adding the attacking functions:

void () Spell_launch_lava;
void () Spell_push;

void () spell_attacka1 = [7, spell_attacka2] {ai_face ();};
void () spell_attacka2 = [8, spell_attacka3] {ai_face ();};
void () spell_attacka3 = [9, spell_attacka4] {ai_face ();};
void () spell_attacka4 = [10, spell_attacka5] {ai_face ();};
void () spell_attacka5 = [11, spell_attacka6] {ai_face ();};
void () spell_attacka6 = [12, spell_attacka7] {ai_face ();};
void () spell_attacka7 = [13, spell_attacka8] {ai_face ();};
void () spell_attacka8 = [14, spell_attacka9] {ai_face ();};
void () spell_attacka9 = [15, spell_attacka10] {ai_face ();};
void () spell_attacka10 = [16, spell_attacka11] {self.effects = EF_DIMLIGHT;ai_face ();};
void () spell_attacka11 = [17, spell_attacka12] {ai_face ();};
void () spell_attacka12 = [18, spell_attacka13] {ai_face ();};
void () spell_attacka13 = [19, spell_attacka14] {ai_face ();};
void () spell_attacka14 = [20, spell_attacka15] {Spell_launch_lava ();ai_face ();};
void () spell_attacka15 = [21, spell_run] {ai_face ();};

void () spell_attackb1 = [22, spell_attackb2] {ai_face ();};
void () spell_attackb2 = [23, spell_attackb3] {ai_face ();};
void () spell_attackb3 = [24, spell_attackb4] {ai_face ();};
void () spell_attackb4 = [25, spell_attackb5] {ai_face ();};
void () spell_attackb5 = [26, spell_attackb6] {ai_face ();Spell_push ();};
void () spell_attackb6 = [27, spell_attackb7] {ai_face ();};
void () spell_attackb7 = [28, spell_attackb8] {ai_face ();};
void () spell_attackb8 = [29, spell_attackb9] {ai_face ();};
void () spell_attackb9 = [30, spell_run] {ai_face ();};

void () spell_attack =
{
	local vector delta;

	delta = (self.enemy.origin - self.origin);
	if (vlen (delta) <= 90)
		spell_attackb1 ();
	else
	{
		if ((vlen (delta) <= 300) && (random () < 0.5))
			spell_attackb1 ();
		else
			spell_attacka1 ();
	}
};

void () Spell_MissileTouch =
{
	local float damg;

	if (other == self.owner)
		return;
	if (pointcontents (self.origin) == CONTENT_SKY)
	{
		remove (self);
		return;
	}
	damg = (50+ (random () * 20));
	if (other.health)
		T_Damage (other,self,self.owner,damg);

	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 =
{
	local vector vec;

	sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM);
	vec = (self.enemy.origin - self.origin);
	vec = normalize (vec);

	newmis = spawn ();
	newmis.owner = self;

	newmis.solid = SOLID_BBOX;
	newmis.movetype = MOVETYPE_FLYMISSILE;
	setmodel (newmis, "progs/lavaball.mdl");
	setsize (newmis, '0 0 0', '0 0 0');	
	newmis.origin = self.origin + '0 0 16';
	newmis.velocity = vec * 600;
	newmis.avelocity = '500 500 500';
	newmis.touch = Spell_MissileTouch;
	self.effects = 0;
};

void () Spell_push =
{
	local vector delta;
	local float ldmg;

	if (self.enemy.classname != "player")
		return;
	delta = (self.enemy.origin - self.origin);
	ldmg = (random () * 25);
	T_Damage (self.enemy, self, self, ldmg);
	sound (self, CHAN_WEAPON, "wizard/wsight.wav", 1, ATTN_NORM);
	self.enemy.flags = self.enemy.flags - (self.enemy.flags & FL_ONGROUND);
	self.enemy.velocity = (delta * 10);
	self.enemy.velocity_z = 100;
};

The spell_attack determines what attack to actually use. Take some time and study these functions. The Spell_Push will only affect players, if I remember correctly from when I originally created this, pushing monsters will cause some bug. Next is pain, death, and spawning:

void () spell_pain1 = [31, spell_pain2] {};
void () spell_pain2 = [32, spell_pain3] {};
void () spell_pain3 = [33, spell_pain4] {};
void () spell_pain4 = [34, spell_run] {};
void () spell_pain =
{
	if (self.pain_finished > time)
		return;
	sound (self,2,"wizard/wdeath.wav",1,1);
	spell_pain1 ();
	self.pain_finished = (time + 3);
};

void () spell_death1 = [35, spell_death2] {};
void () spell_death2 = [36, spell_death3] {};
void () spell_death3 = [37, spell_death4] {};
void () spell_death4 = [38, spell_death5] {};
void () spell_death5 = [39, spell_death6] {};
void () spell_death6 = [40, spell_death7] {};
void () spell_death7 = [41, spell_death8] {};
void () spell_death8 = [42, spell_death9] {};
void () spell_death9 = [43, spell_death10] {};
void () spell_death10 = [44, spell_death11] {};
void () spell_death11 = [45, spell_death12] {};
void () spell_death12 = [46, spell_death13] {};
void () spell_death13 = [47, spell_death14] {};
void () spell_death14 = [48, spell_death15] {};
void () spell_death15 = [49, spell_death16] {};
void () spell_death16 = [50, spell_death16] {};

void () spell_die =
{
	self.effects = 0;
	if (self.health < -40)
	{
		sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM);
		ThrowHead ("progs/h_shal.mdl", self.health);
		ThrowGib ("progs/gib1.mdl", self.health);
		ThrowGib ("progs/gib2.mdl", self.health);
		ThrowGib ("progs/gib3.mdl", self.health);
		return;
	}
	sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM);
	spell_death1 ();
	self.solid = SOLID_NOT;
};

void () monster_spellmaster =
{
	if (deathmatch)
	{
		remove (self);
		return ;
	}
	precache_model2 ("progs/spellmas.mdl");
	precache_model2 ("progs/h_shal.mdl");
	precache_sound ("wizard/wdeath.wav");
	precache_sound ("wizard/wsight.wav");
	precache_sound2 ("blob/death1.wav");
	precache_sound2 ("boss2/idle.wav");
	precache_sound2 ("shalrath/attack2.wav");
	precache_sound2 ("shalrath/sight.wav");

	self.solid = SOLID_SLIDEBOX;
	self.movetype = MOVETYPE_STEP;
	setmodel (self, "progs/spellmas.mdl");
	setsize (self,'-32 -32 -24','32 32 64');
	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;
	walkmonster_start ();
};

Thats it for the spelmast.qc. Close that file now, and compile. You should not get any errors, if you do just check over the steps and try again.

Ok, so you have your new monster, and you enjoy playing it right? Oh wait, you need to actually spawn it in a map. I think it replaces the Shalrath very well so you can make it replace the shalrath about 75% of the time. Open the shalrath.qc and go down to the monster_shalrath function. Add this to the top of that function:

	if (random() <= 0.75) // 75%
	{
		self.classname = "monster_spellmaster";
		monster_spellmaster();
		return;
	}

If you compile now you might get an error, you need to add the prototype for the monster_spellmaster function, add it just above the monster_shalrath function:

void () monster_spellmaster;

Compile and, oh wait you need to take care of the death message, and the “wake-up” sound. For the death message goto the ClientObituary function at the bottom of the clint.qc and add this after other lines like it:

	if (attacker.classname == "monster_spellmaster")
		bprint (" was killed by a Mage\n"); // come up with your own

For the “wake-up” sound find the SightSound function in the ai.qc. Add this to the end:

	else if (self.classname == "monster_spellmaster")
		sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM);

Now compile and you’re done.