|
TUTORIAL 13 - Lightning Discharge
von The SARACEN
Hallo Leute, eigentlich begleitet euch SumFaka durch dieses
Tutorial aber der ganze Code ist von The SARACEN. Das hier ist wirklich
interessanter Stoff... Im Wesentlichen fügen wir dem Spiel ein neues 'event' hinzu.
Das erfordert Änderungen sowohl in der game dll/qvm (damit der Effekt ausgelöst wird)
als auch in der cgame dll/qvm (damit der Client den Effekt sehen kann). Das wird ein
Spaß (es sei denn du bist ein totaler 'monga')
Aktualisiert für den 1.27g source code. Die Funktion CG_SmokePuff()
erhält jetzt ein extra Argument für das verblassen der Grafik.
1. Definiere Lightning Discharge 'Event' und 'MOD'
Zuerst geh in das game Projekt und öffne bg_public.h. In
entity_event_t werden wir ein neues 'event' einfügen, direkt unter den anderen
Waffen events (rail trails, shotty sprays, bullet marks etc).
Wenn dich die C Syntax verwirrt, verwenden wir einzigartige Konstanten
für den entity_event_t Datentyp. Guck dir einmal ein paar von den
anderen event Typen an... nicht alle sind 'sichtbare Dinge' (z.B. EV_NOAMMO) aber sie
alle haben etwas gemeinsam - wenn diese events geschehen, muss der Client darüber
informiert werden.
EV_MISSILE_HIT,
EV_MISSILE_MISS,
EV_MISSILE_MISS_METAL,
EV_RAILTRAIL,
EV_SHOTGUN,
EV_BULLET, // otherEntity is the shooter
EV_LIGHTNING_DISCHARGE, // The SARACEN's Lightning Discharge
EV_PAIN,
EV_DEATH1,
EV_DEATH2,
EV_DEATH3,
EV_OBITUARY,
Auf die selbe Weise werden wir ein neues meansOfDeath_t einfügen.
Das meansOfDeath_t oder MOD wird genutzt, wenn jemand stirbt, um die passende
Todes-Nachricht auszuwählen (erinnert ihr euch an ClientObituary von Quake und Quake2 ?
Dies ist etwas ähnliches...).
// means of death
typedef enum {
MOD_UNKNOWN,
MOD_SHOTGUN,
MOD_GAUNTLET,
MOD_MACHINEGUN,
MOD_GRENADE,
MOD_GRENADE_SPLASH,
MOD_ROCKET,
MOD_ROCKET_SPLASH,
MOD_PLASMA,
MOD_PLASMA_SPLASH,
MOD_RAILGUN,
MOD_LIGHTNING,
MOD_LIGHTNING_DISCHARGE, // The SARACEN's Lightning Discharge
MOD_BFG,
MOD_BFG_SPLASH,
MOD_WATER,
MOD_SLIME,
MOD_LAVA,
MOD_CRUSH,
MOD_TELEFRAG,
MOD_FALLING,
MOD_SUICIDE,
MOD_TARGET_LASER,
MOD_TRIGGER_HURT,
#ifdef MISSIONPACK
MOD_NAIL,
MOD_CHAINGUN,
MOD_PROXIMITY_MINE,
MOD_KAMIKAZE,
MOD_JUICED,
#endif
MOD_GRAPPLE
} meansOfDeath_t;
Jetzt öffne g_combat.c und füge einen String wie unten aufgeführt ein.
(Warum haben wir diese Änderungen sowie die Oben gemacht ? Ganz einfach,
die EV_MOD's sind konstante Werte und da sie Keine Strings sind,
können sie nicht in den Spiele Messages genutzt werden.
Wir definieren hier einige nützliche Error Strings zu diesem Zweck).
"MOD_RAILGUN",
"MOD_LIGHTNING",
"MOD_LIGHTNING_DISCHARGE", // The SARACEN's Lightning Discharge
"MOD_BFG",
"MOD_BFG_SPLASH",
2. Implementiere eine 'Wasser Radius Schaden' Funktion
Immernoch in game und g_combat.c, fügen wir unter der Funktion G_RadiusDamage folgende Funktion ein :
/*
============
G_WaterRadiusDamage for The SARACEN's Lightning Discharge
============
*/
qboolean G_WaterRadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius,
gentity_t *ignore, int mod)
{
float points, dist;
gentity_t *ent;
int entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t mins, maxs;
vec3_t v;
vec3_t dir;
int i, e;
qboolean hitClient = qfalse;
if (!(trap_PointContents (origin, -1) & MASK_WATER)) return qfalse;
// if we're not underwater, forget it!
if (radius < 1) radius = 1;
for (i = 0 ; i < 3 ; i++)
{
mins[i] = origin[i] - radius;
maxs[i] = origin[i] + radius;
}
numListedEntities = trap_EntitiesInBox (mins, maxs, entityList, MAX_GENTITIES);
for (e = 0 ; e < numListedEntities ; e++)
{
ent = &g_entities[entityList[e]];
if (ent == ignore) continue;
if (!ent->takedamage) continue;
// find the distance from the edge of the bounding box
for (i = 0 ; i < 3 ; i++)
{
if (origin[i] < ent->r.absmin[i]) v[i] = ent->r.absmin[i] - origin[i];
else if (origin[i] > ent->r.absmax[i]) v[i] = origin[i] - ent->r.absmax[i];
else v[i] = 0;
}
dist = VectorLength(v);
if (dist >= radius) continue;
points = damage * (1.0 - dist / radius);
if (CanDamage (ent, origin) && ent->waterlevel) // must be in the water, somehow!
{
if (LogAccuracyHit (ent, attacker)) hitClient = qtrue;
VectorSubtract (ent->r.currentOrigin, origin, dir);
// push the center of mass higher than the origin so players
// get knocked into the air more
dir[2] += 24;
G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
}
}
return hitClient;
}
Dazu gehört jetzt natürlich auch noch der Funktions-Prototype in g_local.h unter dem von G_RadiusDamage:
qboolean G_RadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod);
qboolean G_WaterRadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod);
3. Lightning Gun Fire Funktion modifizieren
Öffne g_weapon.c und such nach Weapon_LightningFire. Wir müssen die Waffe so verändern,
dass sie G_WaterRadiusDamage aufruft, wenn sie unter Wasser abgefeuert wurde.
/*
======================================================================
LIGHTNING GUN
======================================================================
*/
void Weapon_LightningFire( gentity_t *ent ) {
trace_t tr;
vec3_t end;
#ifdef MISSIONPACK
vec3_t impactpoint, bouncedir;
#endif
gentity_t *traceEnt, *tent;
int damage, i, passent;
damage = 8 * s_quadFactor;
passent = ent->s.number;
for (i = 0; i < 10; i++) {
VectorMA( muzzle, LIGHTNING_RANGE, forward, end );
// The SARACEN's Lightning Discharge - START
if (trap_PointContents (muzzle, -1) & MASK_WATER)
{
int zaps;
gentity_t *tent;
zaps = ent->client->ps.ammo[WP_LIGHTNING]; // determines size/power of discharge
if (!zaps) return; // prevents any subsequent frames causing second discharge + error
zaps++; // pmove does an ammo[gun]--, so we must compensate
SnapVectorTowards (muzzle, ent->s.origin); // save bandwidth
tent = G_TempEntity (muzzle, EV_LIGHTNING_DISCHARGE);
tent->s.eventParm = zaps; // duration / size of explosion graphic
ent->client->ps.ammo[WP_LIGHTNING] = 0; // drain ent's lightning count
if (G_WaterRadiusDamage (muzzle, ent, damage * zaps,
(damage * zaps) + 16, NULL, MOD_LIGHTNING_DISCHARGE))
g_entities[ent->r.ownerNum].client->accuracy_hits++;
return;
}
// The SARACEN's Lightning Discharge - END
trap_Trace( &tr, muzzle, NULL, NULL, end, passent, MASK_SHOT );
#ifdef MISSIONPACK
// if not the first trace (the lightning bounced of an invulnerability sphere)
if (i) {
// add bounced off lightning bolt temp entity
// the first lightning bolt is a cgame only visual
//
tent = G_TempEntity( muzzle, EV_LIGHTNINGBOLT );
VectorCopy( tr.endpos, end );
SnapVector( end );
VectorCopy( end, tent->s.origin2 );
}
#endif
if ( tr.entityNum == ENTITYNUM_NONE ) {
return;
}
traceEnt = &g_entities[ tr.entityNum ];
if ( traceEnt->takedamage) {
#ifdef MISSIONPACK
if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
VectorCopy( impactpoint, muzzle );
VectorSubtract( end, impactpoint, forward );
VectorNormalize(forward);
// the player can hit him/herself with the bounced lightning
passent = ENTITYNUM_NONE;
}
else {
VectorCopy( tr.endpos, muzzle );
passent = traceEnt->s.number;
}
continue;
}
else {
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_LIGHTNING);
}
#else
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_LIGHTNING);
#endif
}
if ( traceEnt->takedamage && traceEnt->client ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
tent->s.otherEntityNum = traceEnt->s.number;
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = ent->s.weapon;
if( LogAccuracyHit( traceEnt, ent ) ) {
ent->client->accuracy_hits++;
}
} else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
break;
}
}
Die Kleinigkeit, die The SARACEN hier hinzugefügt hat, ist ziemlich direkt -
Zuerst nutze die trap_PointContents funktion, um zu testen, ob die
Waffe unter Wasser abgefeuert wurde. Ein temporäres Entity EV_LIGHTNING_DISCHARGE wird dann erstellt und automatisch zu den
Clients übertragen (damit sie es sehen können). Dann erzeugen wir ein G_WaterRadiusDamage proportional zu der restlichen Munition, die
der Spieler noch hat (zaps). Jeder in diesem Bereich sollte gut
durchgebraten werden.
4. Führen Sie die Todesanzeigen ein
Ok, geh in das cgame Projekt und öffne cg_local.h. Zuerst füge folgenden
Funktions-Prototypen zu den anderen Prototypen unter // cg_effects.c hinzu:
localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
qhandle_t hModel, qhandle_t shader, int msec,
qboolean isSprite );
void CG_Lightning_Discharge (vec3_t origin, int msec); // The SARACEN's Lightning Discharge
Jetzt öffne cg_event.c und gehe in CG_Obituary. Wir müssen
hier einige Todes-Nachrichten für unseren neuen meansOfDeath hinzufügen:
case MOD_PLASMA_SPLASH:
if ( gender == GENDER_FEMALE )
message = "melted herself";
else if ( gender == GENDER_NEUTER )
message = "melted itself";
else
message = "melted himself";
break;
// The SARACEN's Lightning Discharge - START
case MOD_LIGHTNING_DISCHARGE:
if (gender == GENDER_FEMALE)
message = "discharged herself";
else if (gender == GENDER_NEUTER)
message = "discharged itself";
else
message = "discharged himself";
break;
// The SARACEN's Lightning Discharge - END
case MOD_BFG_SPLASH:
message = "should have used a smaller gun";
break;
default:
if ( gender == GENDER_FEMALE )
message = "killed herself";
else if ( gender == GENDER_NEUTER )
message = "killed itself";
else
message = "killed himself";
break;
Und jetzt etwas weiter unten hatte The SARACEN nochmehr spass mit den TodesNachrichten:
case MOD_RAILGUN:
message = "was railed by";
break;
// The SARACEN's Lightning Discharge - START
/* // original obituary
case MOD_LIGHTNING:
message = "was electrocuted by";
break;
*/ // Classic Quake style obituary - the original and the best!!!
case MOD_LIGHTNING:
message = "was shafted by";
break;
case MOD_LIGHTNING_DISCHARGE:
message = "was discharged by";
break;
// The SARACEN's Lightning Discharge - END
case MOD_BFG:
case MOD_BFG_SPLASH:
message = "was blasted by";
message2 = "'s BFG";
break;
5. Einen 'Event Hook' hinzufügen.
Jetzt müssen wir noch in CG_EntityEvent einstellen, welche Funktion aufgerufen wird,
wenn ein bestimmter Fall eintritt (unsere lightning discharge!)
case EV_SHOTGUN:
DEBUGNAME("EV_SHOTGUN");
CG_ShotgunFire( es );
break;
// The SARACEN's Lightning Discharge - START
case EV_LIGHTNING_DISCHARGE:
DEBUGNAME("EV_LIGHTNING_DISCHARGE");
CG_Lightning_Discharge (position, es->eventParm); // eventParm is duration/size
break;
// The SARACEN's Lightning Discharge - END
case EV_GENERAL_SOUND:
DEBUGNAME("EV_GENERAL_SOUND");
if ( cgs.gameSounds[ es->eventParm ] ) {
trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] );
} else {
s = CG_ConfigString( CS_SOUNDS + es->eventParm );
trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) );
}
break;
6. Verhindern, dass der Client den Shaft zeichnet.
Ok jetzt müssen wir den Client daran hindern, dass er den Shaft zeichnet,
wenn die Waffe unter Wasser agbefeuert wurde. (Erinnert ihr euch ? Vorhin haben
wir das Schussverhalten im game Projekt geändert und alle Sichtbarmachungen
erfolgen im cgame Projekt, mit dem wir gerade arbeiten). Dazu verändern wir
CG_LightningBolt in cg_weapons.c:
/*
===============
CG_LightningBolt
Origin will be the exact tag point, which is slightly
different than the muzzle point used for determining hits.
The cent should be the non-predicted cent if it is from the player,
so the endpoint will reflect the simulated strike (lagging the predicted
angle)
===============
*/
static void CG_LightningBolt( centity_t *cent, vec3_t origin ) {
trace_t trace;
refEntity_t beam;
vec3_t forward;
vec3_t muzzlePoint, endPoint;
if (cent->currentState.weapon != WP_LIGHTNING) {
return;
}
memset( &beam, 0, sizeof( beam ) );
// CPMA "true" lightning
if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) {
vec3_t angle;
int i;
for (i = 0; i < 3; i++) {
float a = cent->lerpAngles[i] - cg.refdefViewAngles[i];
if (a > 180) {
a -= 360;
}
if (a < -180) {
a += 360;
}
angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value);
if (angle[i] < 0) {
angle[i] += 360;
}
if (angle[i] > 360) {
angle[i] -= 360;
}
}
AngleVectors(angle, forward, NULL, NULL );
VectorCopy(cent->lerpOrigin, muzzlePoint );
// VectorCopy(cg.refdef.vieworg, muzzlePoint );
} else {
// !CPMA
AngleVectors( cent->lerpAngles, forward, NULL, NULL );
VectorCopy(cent->lerpOrigin, muzzlePoint );
}
// FIXME: crouch
muzzlePoint[2] += DEFAULT_VIEWHEIGHT;
VectorMA( muzzlePoint, 14, forward, muzzlePoint );
// The SARACEN's Lightning Discharge
if (trap_CM_PointContents (muzzlePoint, 0) & MASK_WATER) return;
// project forward by the lightning range
VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint );
// see if it hit a wall
CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint,
cent->currentState.number, MASK_SHOT );
// this is the endpoint
VectorCopy( trace.endpos, beam.oldorigin );
// use the provided origin, even though it may be slightly
// different than the muzzle origin
VectorCopy( origin, beam.origin );
beam.reType = RT_LIGHTNING;
beam.customShader = cgs.media.lightningShader;
trap_R_AddRefEntityToScene( &beam );
// add the impact flare if it hit something
if ( trace.fraction < 1.0 ) {
vec3_t angles;
vec3_t dir;
VectorSubtract( beam.oldorigin, beam.origin, dir );
VectorNormalize( dir );
memset( &beam, 0, sizeof( beam ) );
beam.hModel = cgs.media.lightningExplosionModel;
VectorMA( trace.endpos, -16, dir, beam.origin );
// make a random orientation
angles[0] = rand() % 360;
angles[1] = rand() % 360;
angles[2] = rand() % 360;
AnglesToAxis( angles, beam.axis );
trap_R_AddRefEntityToScene( &beam );
}
}
7. Den Effekt zeichnen.
Öffne cg_effect.c und füge folgende Funktion direkt unter CG_SpawnEffect ein:
/*
====================
CG_Lightning_Discharge by The SARACEN
====================
*/
void CG_Lightning_Discharge (vec3_t origin, int msec)
{
localEntity_t *le;
if (msec <= 0) CG_Error ("CG_Lightning_Discharge: msec = %i", msec);
le = CG_SmokePuff ( origin, // where
vec3_origin, // where to
((48 + (msec * 10)) / 16), // radius
1, 1, 1, 1, // RGBA color shift
300 + msec, // duration
cg.time, // start when?
0, // fade in time
0, // flags (?)
trap_R_RegisterShader ("models/weaphits/electric.tga"));
le->leType = LE_SCALE_FADE;
}
Was tut diese Funktion ? The SARACEN hat eine große Rauchwolke erzeugt, die eine definierte
Anzahl von Millisekunden anhält (Proportional zur Stärke der Entladung - In anderen Worten
Die Menge der Munition, die entladen wurde).
Das war's. Noch einmal Danke an The SARACEN für diesen genialen Code und ich hoffe, dass
meine (me==SumFuka) Erklärungen ihm gerecht wurden. Jetzt geht spielen, wer den Red Armor
bekommt in dem Level mit dem Red Armor auf dem Grund des Pools.
<< Tutorial 12 | | Tutorial 14 >>
|