Quake Style - Quake 2 Tutorials
Event Scripting - Event Scripting Part 1
Setting up the structure and adding a few basic events.

I was laying in bed the other night and i had a wonderful idea. I'd been doing lots of mIRC scripting that day and i wondered how hard it would be to implement some kind of event scripting language in quake2. Over the next few parts of this tutorial i'm going to try to write a mini-scripting language using the quake2 console.

This is how it will work:

The user will have 2 .cfg files in a "ev" directory under their mod dir.
EG:

C:\quake2\mymod\ev\ev_dm.cfg
C:\quake3\mymod\ev\ev_sp.cfg

ev_dm.cfg will contain deathmatch script and ev_sp.cfg will contain singleplayer script. Adopting this for your mod you could add ev_tp.cfg for teamplay ev_lms.cfg for last man standing etc, etc.

Right first off, you have to implement Willi's stuffcmd tutorial. It's in the old tutorial section. Done that? Great. Now open up p_client.c and find the ClientBeginDeathmatch() function. Now add in this code near the end of the function:



	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);

	///Vondi Start - exec the clients deathmatch script
	stuffcmd(ent, "exec ev/ev_dm.cfg\n");
	//Vondi End	

	// make sure all view stuff is valid
	ClientEndServerFrame (ent);
}

Still in p_client.c find the ClientBegin() function. Insert this "curiously similar" code, again near the end.

	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);

	///Vondi Start - exec the clients deathmatch script
                if (!deathmatch)
	stuffcmd(ent, "exec ev/ev_dm.cfg\n");
	//Vondi End	

	// make sure all view stuff is valid
	ClientEndServerFrame (ent);
}


Now, replace your ClientObituary function in p_client.c with this:



void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
{
	int			mod;
	char		*message;
	char		*message2;
	qboolean	ff;

	if (coop->value && attacker->client)
		meansOfDeath |= MOD_FRIENDLY_FIRE;

	if (deathmatch->value || coop->value)
	{
		ff = meansOfDeath & MOD_FRIENDLY_FIRE;
		mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
		message = NULL;
		message2 = "";

		switch (mod)
		{
		case MOD_SUICIDE:
			message = "suicides";
			stuffcmd(attacker, "EV_death_suicide\n"); //Vondi	
			break;
		case MOD_FALLING:
			message = "cratered";
			stuffcmd(attacker, "EV_death_cratered\n"); //Vondi	
			break;
		case MOD_CRUSH:
			message = "was squished";
			stuffcmd(attacker, "EV_death_crushed\n"); //Vondi	
			break;
		case MOD_WATER:
			message = "sank like a rock";
			stuffcmd(attacker, "EV_death_drown\n"); //Vondi	
			break;
		case MOD_SLIME:
			message = "melted";
			stuffcmd(attacker, "EV_death_slime\n"); //Vondi	
			break;
		case MOD_LAVA:
			message = "does a back flip into the lava";
			stuffcmd(attacker, "EV_death_lava\n"); //Vondi	
			break;
		case MOD_EXPLOSIVE:
		case MOD_BARREL:
			message = "blew up";
			stuffcmd(attacker, "EV_death_explosion\n"); //Vondi	
			break;
		case MOD_EXIT:
			message = "found a way out";
			break;
		case MOD_TARGET_LASER:
			message = "saw the light";
			stuffcmd(attacker, "EV_death_laser\n"); //Vondi	
			break;
		case MOD_TARGET_BLASTER:
			message = "got blasted";
			stuffcmd(attacker, "EV_death_blaster\n"); //Vondi	
			break;
		case MOD_BOMB:
		case MOD_SPLASH:
		case MOD_TRIGGER_HURT:
			message = "was in the wrong place";
			stuffcmd(attacker, "EV_death_explosion\n"); //Vondi	
			break;
		}
		if (attacker == self)
		{
			switch (mod)
			{
			case MOD_HELD_GRENADE:
				message = "tried to put the pin back in";
			stuffcmd(attacker, "EV_suicide_heldgrenade\n"); //Vondi	
				break;
			case MOD_HG_SPLASH:
			case MOD_G_SPLASH:
				if (IsNeutral(self))
					message = "tripped on its own grenade";
				else if (IsFemale(self))
					message = "tripped on her own grenade";
				else
					message = "tripped on his own grenade";
			stuffcmd(attacker, "EV_suicide_grenade\n"); //Vondi	
				break;
			case MOD_R_SPLASH:
				if (IsNeutral(self))
					message = "blew itself up";
				else if (IsFemale(self))
					message = "blew herself up";
				else
					message = "blew himself up";
			stuffcmd(attacker, "EV_suicide_rocketsplash\n"); //Vondi	
				break;
			case MOD_BFG_BLAST:
				message = "should have used a smaller gun";
				break;
			default:
				if (IsNeutral(self))
					message = "killed itself";
				else if (IsFemale(self))
					message = "killed herself";
				else
					message = "killed himself";
			stuffcmd(attacker, "EV_suicide_bfg\n"); //Vondi	
				break;
			}
		}
		if (message)
		{
			gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
			if (deathmatch->value)
				self->client->resp.score--;
			self->enemy = NULL;
			return;
		}

		self->enemy = attacker;
		if (attacker && attacker->client)
		{
			switch (mod)
			{
			case MOD_BLASTER:
				message = "was blasted by";
			stuffcmd(self, "EV_killed_blaster\n"); //Vondi	
				break;
			case MOD_SHOTGUN:
				message = "was gunned down by";
			stuffcmd(self, "EV_killed_shotgun\n"); //Vondi	
				break;
			case MOD_SSHOTGUN:
				message = "was blown away by";
				message2 = "'s super shotgun";
			stuffcmd(self, "EV_killed_sshotgun\n"); //Vondi	
				break;
			case MOD_MACHINEGUN:
				message = "was machinegunned by";
			stuffcmd(self, "EV_killed_machinegun\n"); //Vondi	
				break;
			case MOD_CHAINGUN:
				message = "was cut in half by";
				message2 = "'s chaingun";
			stuffcmd(self, "EV_killed_chaingun\n"); //Vondi	
				break;
			case MOD_GRENADE:
				message = "was popped by";
				message2 = "'s grenade";
			stuffcmd(self, "EV_killed_grenade\n"); //Vondi	
				break;
			case MOD_G_SPLASH:
				message = "was shredded by";
				message2 = "'s shrapnel";
			stuffcmd(self, "EV_killed_grenade_splash\n"); //Vondi	
				break;
			case MOD_ROCKET:
				message = "ate";
				message2 = "'s rocket";
			stuffcmd(self, "EV_killed_rocket\n"); //Vondi	
				break;
			case MOD_R_SPLASH:
				message = "almost dodged";
				message2 = "'s rocket";
			stuffcmd(self, "EV_killed_rocket_splash\n"); //Vondi	
				break;
			case MOD_HYPERBLASTER:
				message = "was melted by";
				message2 = "'s hyperblaster";
			stuffcmd(self, "EV_killed_hyperblaster\n"); //Vondi	
				break;
			case MOD_RAILGUN:
				message = "was railed by";
			stuffcmd(self, "EV_killed_railgun\n"); //Vondi	
				break;
			case MOD_BFG_LASER:
				message = "saw the pretty lights from";
				message2 = "'s BFG";
			stuffcmd(self, "EV_killed_bfglaser\n"); //Vondi	
				break;
			case MOD_BFG_BLAST:
				message = "was disintegrated by";
				message2 = "'s BFG blast";
			stuffcmd(self, "EV_killed_bfg\n"); //Vondi	
				break;
			case MOD_BFG_EFFECT:
				message = "couldn't hide from";
				message2 = "'s BFG";
			stuffcmd(self, "EV_killed_bfg\n"); //Vondi	
				break;
			case MOD_HANDGRENADE:
				message = "caught";
				message2 = "'s handgrenade";
			stuffcmd(self, "EV_killed_hgrenade\n"); //Vondi	
				break;
			case MOD_HG_SPLASH:
				message = "didn't see";
				message2 = "'s handgrenade";
			stuffcmd(self, "EV_killed_hgrenade_splash\n"); //Vondi	
				break;
			case MOD_HELD_GRENADE:
				message = "feels";
				message2 = "'s pain";
			stuffcmd(self, "EV_killed_held_grenade\n"); //Vondi	
				break;
			case MOD_TELEFRAG:
				message = "tried to invade";
				message2 = "'s personal space";
			stuffcmd(self, "EV_killed_telefrag\n"); //Vondi	
				break;
			}
			if (message)
			{
				gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
				if (deathmatch->value)
				{
					if (ff)
						attacker->client->resp.score--;
					else
						attacker->client->resp.score++;
				}
				return;
			}
		}
	}

	gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
	if (deathmatch->value)
		self->client->resp.score--;
}



Woah, thats a lot of code. What does it do? Well, the death message code seemed a good palce to put this since the attacker/victim stuff is all their ready to be used and im lazy. It's fairly self explanatory, read through it. (It's the only way your going to learn, *WHIP*)

now create a "EV" dir in your mods directory. In that dir place ev_dm.cfg and ev_sp.cfg. To bind some commands to an event do this:

alias EV_killed_bfg "say wow, i died;echo I was killed by a bfg"

Yes, they're just alias but you'd be surprised how much fun you can have with this scripting. There are only two drawbacks.

1. Abuse, spammers can have a field day with this. (I'll be dealing with it later)
2. Memory, if you use *ALL* of those defined events your going to use a hell of a lot of memory and may crash quake2. I didn't do it during testing but im sure its possible. (I'll attempt to fix this later)

-NEXT ISNTALLMENT-

You can expect:

1. More events (item pickups etc)
2. CVAR's to control the language from the server, server admins can allow/disallow entire blocks of events or individual ones. Oh the power.

As always if you find anything screwed up in this tutorial then _PLEASE_ email so i can correct it.


-- Credits:
   Tutorial by Vondi
   Return to QS Tutorials

-- Important:
   If you do use something from QuakeStyle in your mod, please give us credit.
   Our code is copyrighted, but we give permission to everyone to use it in any way they see fit, as long as we are recognized.