
|
Quake Style - Quake 2
Tutorials |
Defense Laser Rune:
Main credits for this one go to Yaya, whose original defense laser tutorial can
be found here at Qdevels. We only need very few done to get it rolling, some of
that are even points Yaya back then set on his to-do list. When finished,
you'll have a hand-grenade you can stick to the wall, spawns a laser beam to
the next (any distance) wall which kills everybody and lasts for some time
(naturally, you can modify lifespan, damage and other parameters in the code if
you want different settings than the ones I used).
Requirements:
The stuff we did in the previous parts of "Tech Power Up Mayhem".
This series of tutorials builds up step-by-step and you must have done any
previous pages before adding this one!
Add in the blue code
and take out the pink code. Regular yellow code is Zoid's original stuff.
Let's make the big ones first: Here's Yaya's tut again, with all the needed
modifications; put this in a new file called g_laser1.c, and make sure you
include it in the makefile:
#include "g_local.h"#include "g_laser1.h" void PlaceLaser (edict_t *ent){ edict_t *self, *grenade; vec3_t forward, wallp; trace_t tr; int laser_colour[] = { 0xf2f2f0f0, // red 0xd0d1d2d3, // green 0xf3f3f1f1, // blue 0xdcdddedf, // yellow 0xe0e1e2e3 // bitty yellow strobe }; if ((!ent->client) || (ent->health<=0)) return; // modify this if-statement and the message // if you want a different number of max defense // laser which are possible at a certain time if (ent->client->rune_count > 5) { gi.cprintf(ent, PRINT_HIGH, "No more than 5 defense lasers at one time\n"); return; } ent->client->rune_count += 1; // Setup "little look" to close wall VectorCopy(ent->s.origin,wallp); // Cast along view angle AngleVectors (ent->client->v_angle, forward, NULL, NULL); // Setup end point wallp[0] = ent->s.origin[0] + forward[0] * 50; wallp[1] = ent->s.origin[1] + forward[1] * 50; wallp[2] = ent->s.origin[2] + forward[2] * 50; // trace tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID); // Line complete ? (ie. no collision) if (tr.fraction == 1.0) { gi.cprintf (ent, PRINT_HIGH, "Too far away from wall\n"); return; } // Hit sky ? if (tr.surface) if (tr.surface->flags & SURF_SKY) return; // Ok, lets stick one on then ... gi.cprintf (ent, PRINT_HIGH, "Placed laser.\n"); // ------------- // Setup laser // ------------- self = G_Spawn(); self->movetype = MOVETYPE_NONE; self->activator = ent; self->solid = SOLID_NOT; self->s.renderfx = RF_BEAM|RF_TRANSLUCENT; self->s.modelindex = 1; self->s.sound = gi.soundindex ("world/laser.wav"); self->classname = "laser1"; self->s.frame = 2; self->owner = self; self->s.skinnum = laser_colour[((int) (random() * 1000)) 5]; self->dmg = LASER_DAMAGE-10; self->think = pre_target_laser_think; self->delay = level.time + LASER_TIME; // Set orgin of laser to point of contact with wall VectorCopy(tr.endpos, self->s.origin); // convert normal at point of contact to laser angles vectoangles(tr.plane.normal, self->s.angles); // setup laser movedir (projection of laser) G_SetMovedir (self->s.angles, self->movedir); VectorSet (self->mins, -8, -8, -8); VectorSet (self->maxs, 8, 8, 8); // link to world gi.linkentity (self); // start off ... target_laser_off (self); // ... but make automatically come on self->nextthink = level.time + 2; grenade = G_Spawn(); VectorClear (grenade->mins); VectorClear (grenade->maxs); VectorCopy (tr.endpos, grenade->s.origin); vectoangles(tr.plane.normal,grenade -> s.angles); grenade->movetype = MOVETYPE_NONE; grenade->clipmask = MASK_SHOT; grenade->solid = SOLID_NOT; grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); grenade->owner = self; grenade->nextthink = level.time + LASER_TIME; grenade->think = G_FreeEdict; gi.linkentity (grenade);} void pre_target_laser_think (edict_t *self){ target_laser_on (self); self->think = laserbarriere_think;} void laserbarriere_think (edict_t *self){ edict_t *ignore; edict_t *ent; vec3_t start; vec3_t end; trace_t tr; vec3_t point; vec3_t last_movedir; int count; // Defense Laser Rune if (strcmp(self->classname,"laser1") == 0) if (level.time > self->delay) { // bit of damage T_RadiusDamage (self, self->activator, LASER_MOUNT_DAMAGE_RADIUS, NULL, LASER_MOUNT_DAMAGE, MOD_LASER1); // BANG ! gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_EXPLOSION1); gi.WritePosition(self -> s.origin); gi.multicast (self->s.origin, MULTICAST_PVS); // bye bye laser ent = self->activator; ent->client->rune_count=ent->client->rune_count-1; G_FreeEdict (self); return; } if (self->spawnflags & 0x80000000) count = 8; else count = 4; if (self->enemy) { VectorCopy (self->movedir, last_movedir); VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); VectorSubtract (point, self->s.origin, self->movedir); VectorNormalize (self->movedir); if (!VectorCompare(self->movedir, last_movedir)) self->spawnflags |= 0x80000000; } ignore = self; VectorCopy (self->s.origin, start); VectorMA (start, 2048, self->movedir, end); while(1) { tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); if (!tr.ent) break; // hurt it if we can if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_LASER1); // if we hit something that's not a monster or player or is immune to lasers, we're done if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) { if (self->spawnflags & 0x80000000) { self->spawnflags &= ~0x80000000; gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_LASER_SPARKS); gi.WriteByte (count); gi.WritePosition (tr.endpos); gi.WriteDir (tr.plane.normal); gi.WriteByte (self->s.skinnum); gi.multicast (tr.endpos, MULTICAST_PVS); } break; } ignore = tr.ent; VectorCopy (tr.endpos, start); } VectorCopy (tr.endpos, self->s.old_origin); self->nextthink = level.time + FRAMETIME;}
There are a handful of comments; the most interesting may be that through the
if-statement re rune_count, you can control how many defense lasers are
possible at a give time; note that after the laser "dies" (see
seetings in g_laser.h), rune_count is set back by one and you can place another
one. The second new file we need is g_laser1.h - insert the following:
/*LASER DEFENSE RUNE*/ // my functions void PlaceLaser (edict_t *ent);void pre_target_laser_think (edict_t *self); void deathpak_think(edict_t *deathpak); // controlling parameters#define LASER_TIME 30#define LASER_DAMAGE 80#define LASER_MOUNT_DAMAGE 40#define LASER_MOUNT_DAMAGE_RADIUS 64 // In-built Quake2 routines void target_laser_use (edict_t *self, edict_t *other, edict_t *activator);void laserbarriere_think (edict_t *self);void target_laser_on (edict_t *self); void target_laser_off (edict_t *self); // Laser Beam Color Codes #define Laser_Red 0xf2f2f0f0 // bright red #define Laser_Green 0xd0d1d2d3 // bright green #define Laser_Blue 0xf3f3f1f1 // bright blue #define Laser_Yellow 0xdcdddedf // bright yellow #define Laser_YellowS 0xe0e1e2e3 // yellow strobe #define Laser_DkPurple 0x80818283 // dark purple #define Laser_LtBlue 0x70717273 // light blue #define Laser_Green2 0x90919293 // different green #define Laser_Purple 0xb0b1b2b3 // purple #define Laser_Red2 0x40414243 // different red #define Laser_Orange 0xe2e5e3e6 // orange #define Laser_Mix 0xd0f1d3f3 // mixture #define Laser_RedBlue 0xf2f3f0f1 // inner = red, outer = blue #define Laser_BlueRed 0xf3f2f1f0 // inner = blue, outer = red #define Laser_GreenY 0xdad0dcd2 // inner = green, outer = yellow #define Laser_YellowG 0xd0dad2dc // inner = yellow, outer = green
The parameters should be self-explanatory; toy around with 'em until you're
satisfied. As we want a specific MOD (means of death), we have to add one to
g_local.h:
#define MOD_TARGET_LASER 30#define MOD_TRIGGER_HURT 31#define MOD_HIT 32#define MOD_TARGET_BLASTER 33#define MOD_GRAPPLE 34#define MOD_LASER1 35
Then we gotta take care of the messages, which is done in p_client.c - look for
the ClientObituary() function. There we have to add (as with all deaths) three
messages. The first one will look a little differently than you're used to to
make sure everything is credited properly. In the "if
(deathmatch->value || coop->value)"-statement add the following:
case MOD_TRIGGER_HURT: message = "was in the wrong place"; break;case MOD_LASER1:
//have to prevent this from checking for "MOD_LASER1" //since that assumes that it was a self inflicted death if(strcmp(inflictor->classname, "laser1") && (attacker != inflictor->activator)) message = "got a defense laser up his butt"; break;
Second, in the "if (attacker == self)"-statement, add:
case MOD_BFG_BLAST: message = "should have used a smaller gun"; break;case MOD_LASER1:
message = "was killed by the own defense laser";break;
default: if (IsFemale(self)) message = "killed herself"; else message = "killed himself"; break;
Third, in the "if (attacker &&
attacker->client)"-statement, add this:
//ZOID case MOD_GRAPPLE: message = "was caught by"; message2 = "'s grapple"; break;//ZOIDcase MOD_LASER1:
message="got"; message2="'s defense laser up his butt";break;
} if (message) { gi.bprintf (PRINT_MEDIUM,"s s ss\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
Did we forget the item itself? Yep, so we should open g_items.c and add it at
the very end of the itemlist[]:
/* Defense Laser */ { "item_tech5", CTFPickup_Tech, NULL, CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? NULL, "items/pkup.wav", "models/ctf/regeneration/tris.md2", EF_ROTATE, NULL,/* icon */ "tech4",/* pickup */ "Defense Laser [U]",/* width */ 2, 0, NULL, IT_TECH, // 0, // NOTE: activate/deactivate this line to set up for different source bases (CTF or 3.20) NULL, 0,/* precache */ "ctf/tech4.wav" }, // end of list marker {NULL}};
Important note: Depending on which source base you use, you may need to
include the line above ("NOTE: activate/deactivate...") to make your
compilation work without warnings. If you use the CTF source as used in our
tut, you can leave it like above; if you added the CTF functionality to the
3.20 source yourself, you need to include this line in your source. (This is
the Viewable Weapons ID line!)
Note: In case you change the pickup name, you also must change every other
occurence of it (like in the Rune_Use() function).
Feel free to modify sounds and item models (see also my comment at the bottom
of Part Two).
Last step: Open g_ctf.c and replace this:
static char *tnames[] = { "item_tech1", "item_tech2", "item_tech3", "item_tech4", NULL};
with this:
static char *tnames[] = { "item_tech1", "item_tech2", "item_tech3", "item_tech4", "item_tech5", NULL};
And before you compile, you need to extend the Rune_Use() function:
void Rune_Use(edict_t *ent){ int index; gitem_t *it; char *rune_msg; // say "no rune or automatically activated rune" if there is no // special usage (=pressing a button) needed or if player // doesn't own a rune // otherwise just call the using function for that specific rune rune_msg="No rune or automatically activated rune"; it = FindItem ("Defense Laser [U]"); index = ITEM_INDEX (it); if (ent->client->pers.inventory[index]) { PlaceLaser (ent); return; } gi.centerprintf(ent, "s\n",rune_msg);}
Woops, done! Compile, try! Be sure to bind a key to "drop tech" (to
throw away a rune if you want pick up a new one) and to "userune" (to
use a rune like the one we just added).
Nick back to the tutorials page and have a look at the next tutorial in the
sequence.
--
Credits:
Tutorial by peter
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.