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 >>