Tutorial 10 - Neue Waffen

von AssKicka


In diesem Tutorial lernst du, wie man eine neue Waffe inklusive Modell, Shader, Skin und Verhalten in Q3A integriert. Wir benutzen dazu den Flammenwerfer der Modifikation Solidground 2.0. Dies ist ein sehr langes Tutorial, das viele Änderungen und Zusätze in cgame, ui und game benötigt, also hole dir erstmal eine Cola und dann fang an zu lesen...

1. Waffen- und Munitionsdefinitionen hinzufügen

Zuerst werden wir den Flammenwerfer und die daraus resultierende Todesart MOD (means of death) in die entsprechenden Aufzählungstypen einfügen. Öffne bg_public.h und ergänze die folgende Zeile in weapon_t (Z 295):


	WP_GRAPPLING_HOOK,
#ifdef MISSIONPACK
	WP_NAILGUN,
	WP_PROX_LAUNCHER,
	WP_CHAINGUN,
#endif	
	WP_FLAME_THROWER,
	WP_NUM_WEAPONS
} weapon_t;

Neben den einzelnen Waffen enthält der Aufzählungstyp weapon_t als ersten Wert WEAPON_NONE und als letzten Wert WP_NUM_WEAPONS. Aus diesen Werten werden an anderer Stelle im Code die Anzahl der Elemente von weapon_t (also der Waffentypen) bestimmt, ebenso der Wertebereich für Variablen dieses Typs. Das funktioniert aber nur, wenn sich alle Waffen zwischen diesen beiden Werten befinden.

Weiter unten fügen wir dem Typ meansOfDeath_t folgende Zeile hinzu um später die Todesart mit einer entsprechenden Textmeldung auswerten zu können (Z 570).

 
	MOD_GRAPPLE ,	//  <- komma
	MOD_FLAME_THROWER
} meansOfDeath_t;

Nun öffne die Datei g_combat.c. Zum Loggen der Ereignisse (später zu besichtigen in games.log) müssen wir in char *modNames[] folgendes hinzufügen (Z 290):


	"MOD_GRAPPLE" ,	// <- komma
	"MOD_FLAME_THROWER"
};

Die letzten beiden Ergänzungen müssen an identischen Positionen vorgenommen werden, da der Aufzählungstyp benutzt wird, um den Index im char* Feld zu ermitteln.

Nun müssen wir die Indizes fürs Inventory und das Modell einfügen. Öffne inv.h und füge die folgende beiden Zielen hinzu(Z 55):


#define INVENTORY_FLAMETHROWER		50

und (Z 130):


#define MODELINDEX_FLAMETHROWER		52

Falls diese Werte schon vergeben sind, benutze den nächstmöglich grösseren.

Als nächstes fügen wir die Definitionen für die neue Waffe und ihre Munition ein. Öffne bg_misc.c und füge am Ende von bg_itemlist[] diese Zeilen ein: (Z 900)

		  
#endif

/*QUAKED weapon_flamethrower (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
	{
		"weapon_flamethrower",
		"sound/misc/w_pkup.wav",
		{ "models/weapons2/flamethrower/flamethrower.md3",
		0, 0, 0},
/* icon */		"icons/iconw_flame",
/* pickup */	"flame thrower",
		20,
		IT_WEAPON,
		WP_FLAME_THROWER,
/* precache */ "",
/* sounds */ ""
	},
	
	// end of list marker
	{NULL}
};

Was hat das alles zu bedeuten? Mit diesen Werten wird eine Variable des Typs gitem_t gefüttert.

1) Die erste Zeile muss so aussehen und so stehen bleiben, auch wenn es nur ein Kommentar ist. Der Quake3 Leveleditor benutzt diese Information, wenn man die Waffe in eine Map als einsammelbares Item setzt.

2) weapon_flamethrower ist der Name unserer Waffe

3) "sound/misc...." ist der Sound, der beim Einsammeln der Waffe abgespielt wird

4) "models/weapons2..." ist das 3D-Modell, das als Waffe benutzt werden wird. Wir nehmen zu diesem Zweck den Flammenwerfer der Mod Solidground 2.0.

5) "icons/iconw...." ist das Icon, das bei der Auswahl der Waffe als aktuelle Waffe angezeigt wird

6) "/* pickup */...." ist der Name, der am Bildschirm beim Einsammeln der Waffe erscheint

7) "20,..." ist die Menge an Munition, die man beim Aufsammeln erhält

8) "IT_WEAPON..." identifiziert das Item als Waffe

9) "WP_FLAME_THROWER..." Details zum Itemtyp, also: eine Waffe vom Typ Flamenwerfer

Als nächstes werden wir die Munition hinzufügen, die vom Flammenwerfer benutzt wird. Wir verwenden dafür einfach das Model der BFG Munition. Füge folgende Zeilen direkt unter obigem Code ein.

		  
/*QUAKED ammo_flame (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
	{
		"ammo_flame",
		"sound/misc/am_pkup.wav",
		{ "models/powerups/ammo/bfgam.md3", 
		0, 0, 0},
/* icon */" icons/icona_bfg",
/* pickup */ "Flame Ammo",
		50,
		IT_AMMO,
WP_FLAME_THROWER,
/* precache */ "",
/* sounds */ ""
	},

	// end of list marker
	{NULL}
};

Die Bedeutung des Codes ist nahezu analog zu dem Obigen. Das einzig interessante ist, dass "WP_FLAME_THROWER..." hier angibt, für welche Waffe die Munition ist.

2. Die Feuerrate einstellen

Da unsere Waffe ein Flammenwerfer ist, wollen wir, dass er eine sehr hohe Feuerrate hat. Um dies zu bewerkstelligen fügen wir in der Funktion PM_Weapon in bg_pmove.c folgendes hinzu (Z 1660):


	case WP_GRAPPLING_HOOK:
		addTime = 400;
		break;
	case WP_FLAME_THROWER:
		addTime = 40;
		break; 
#ifdef MISSIONPACK

Je niedriger der Wert von addtime ist, umso höher ist die Feurrate!

3. Den Spieler mit Flammenwerfer spawnen

Wir müssen den Flammenwerfer als Standardwaffe für jeden respawnenden Spieler einstellen, da es momentan keine Maps gibt, in denen unsere neue Waffe geladen wird. Öffne die Datei g_client.c und ergänze in der Funktion ClientSpawn (Z 1170):


	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
	client->ps.ammo[WP_GAUNTLET] = -1;
	client->ps.ammo[WP_GRAPPLING_HOOK] = -1;

	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_FLAME_THROWER );
	client->ps.ammo[WP_FLAME_THROWER] = 999;

Mit der ersten Zeile haben wir das Bit für WP_FLAME_THROWER im Inventory des Spielers gesetzt, in der zweiten haben wir die Munition auf 999 gesetzt, da es momentan auch noch keine Maps mit unserer Ammo gibt. Wobei wir aber schon das Item "flame_ammo" definiert haben...

Zudem müssen wir registrieren, mit welchen Items der Spieler startet. Öffne g_items.c und schreibe in die Funktion ClearRegisteredItems folgendes (Z 785):


	// players always start with the base weapon
	RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
	RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
	RegisterItem( BG_FindItemForWeapon( WP_FLAME_THROWER) );
#ifdef MISSIONPACK
	if( g_gametype.integer == GT_HARVESTER ) {

4. Den Flammenwerfer nicht ablegen

Wir wollen verhindern, dass der Flammenwerfer abgelegt wird, wenn der Spieler gefraggt wird. Das ist nicht zwingend notwendig, aber es bietet sich zu Lehrzwecken gut an :o). Öffne die Datei g_combat.c und ändere in TossClientItems folgenden Code (Z 70):


	if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK || weapon == WP_FLAME_THROWER) {
		if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
			weapon = self->client->pers.cmd.weapon;
		}

Wenn ein Spieler gefraggt wird, so bleibt normalerweise diejenige Waffe liegen die er gerade ausgewählt hat. Wird er aber während des Waffenwechsels gefraggt, so wird unterschieden: ist die Waffe von der er wechselt die Machinegun oder Grappling Hook ( oder Flammenwerfer) so wird die Waffe aktiviert, zu der der Spieler wechseln wollte. Andernfalls wird die Waffe aktiviert, von der der Spieler wechseln wollte.
Direkt darunter folgt der Code zum ablegen der Waffe, ergänze folgendes:


if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK && 
		 weapon != WP_FLAME_THROWER &&	self->client->ps.ammo[ weapon ] ) {
		// find the item type for this weapon
		item = BG_FindItemForWeapon( weapon );

		// spawn the item
		Drop_Item( self, item, 0 );
	}

Hier verhindern wir das Waffen vom Flammenwerfer-Typ abgelegt werden, genau wie Waffen ohne Munition + MG + Hook + Pummel.

5. Die Waffe abfeuern

Jetzt müssen wir noch Projektile erzeugen und ihnen eine think-Funktionen spendieren. In der Datei g_weapon.c fügen wir der Funktion FireWeapon folgendes hinzu (Z 860):


	case WP_GRAPPLING_HOOK:
		Weapon_GrapplingHook_Fire( ent );
		break;
	case WP_FLAME_THROWER :
		Weapon_fire_flame( ent );
		break;
#ifdef MISSIONPACK
	case WP_NAILGUN:

Dies wird die Funktion "Weapon_fire_flame" aufrufen (die wir gleich noch definieren werden), sobald der Spieler den Flammenwerfer abfeuert.
Wir bleiben in g_weapons.c und fügen direkt unter Weapon_LightningFire(), aber noch vor den Q3TA Waffen die Funktion Weapon_fire_flame ein (Z 670):

 /*
            =======================================================================
            FLAME_THROWER
            =======================================================================
            */
            void Weapon_fire_flame (gentity_t *ent ) {
            gentity_t *m;
            
            m = fire_flame(ent, muzzle, forward);
            m->damage *= s_quadFactor;
            m->splashDamage *= s_quadFactor;
            }

#ifdef MISSIONPACK
/*
======================================================================

NAILGUN

Das mag erst ein wenig verwirrend aussehen, ist aber in Wirklichkeit sehr einfach. Zuerst erschaffen wir einen Zeiger auf ein Entity namens "m" vom Typ gentity_t. Dann rufen wir die Funktion "fire_flame" auf und übergeben dieser das Entity des Funktionsaufrufers, den Startpunkt (muzzle) und den Richtungsvektor (forward).

Die Funktion "fire_flame" wird ein neues Entity erschaffen (bisher haben wir ja nur den Zeiger dafür angelegt) und all die Informationen über das neue Entity in m speichern. Danach multiplizieren wir den Schaden und Splashdamage noch mit dem Quadfaktor (1 falls inaktiv, 3 falls aktiv). Damit damage / splashdamage mit etwas multipliziert werden können, müssen natürlich erst einaml einen Wert bekommen. Genau dafür ist die Funktion fire_flame zuständig. Öffne g_missile.c und füge folgendes zwischen fire_grapple und den Q3TA Funktionen ein (Z 655):


/*
=================
fire_flame
=================
*/
gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t dir) {
	gentity_t *bolt;

	VectorNormalize (dir);

	bolt = G_Spawn();
	bolt->classname = "flame";
	bolt->nextthink = level.time + 1500;
	bolt->think = G_ExplodeMissile;
	bolt->s.eType = ET_MISSILE;
	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	bolt->s.weapon = WP_FLAME_THROWER;
	bolt->r.ownerNum = self->s.number;
	bolt->parent = self;
	bolt->damage = 30;
	bolt->splashDamage = 25;
	bolt->splashRadius = 45;
	bolt->methodOfDeath = MOD_FLAME_THROWER;
	bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH;
	bolt->clipmask = MASK_SHOT;

	bolt->s.pos.trType = TR_LINEAR;
	bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
	VectorCopy( start, bolt->s.pos.trBase );
	VectorScale( dir, 300, bolt->s.pos.trDelta );
	SnapVector( bolt->s.pos.trDelta ); // Netzbandbreite einsparen

	VectorCopy (start, bolt->r.currentOrigin);

return bolt;
}

#ifdef MISSIONPACK
/*
=================
fire_nail
=================
*/

Ok, das sieht erstmal fies aus. Gehen wir die Funktion im einzelnen durch. (Ich werde nur die Dinge erklären, die nicht selbsterklärend sind :o)).
Zu beginn erzeugen wir einen Zeiger auf ein neues Entity, genannt "bolt". Diesen Zeiger werden wir an die Weapon_fire_flame() Funktion zurückgeben.Wir erzeugen das Entity durch G_Spawn(). Das Entity bekommt den Namen "flame".

Eine Zeile darauf setzen wir die Zeit bis zum nächsten "Nachdenken" (think) auf 1,5 Sekunden. Das bedeutet, dass nach 1,5 Sekunden die eine Zeile darunter zugewiesene Think Funktion G_ExplodeMissile aufrufen wird. G_ExplodeMissile wird eine Explosion erzeugen, wenn innerhalb von 1,5 Sekunden nichts getroffen wurde.

Nun setzen wir den Entity Typ auf ET_MISSILE, wodurch in jedem Frame die Funktion G_RunMissile aufgerufen wird. Das passiert übrigens von der Funktion G_RunFrame aus in g_main.c, die Entities auf die Eigenschaft ET_MISSILE überprüft und daraufhin G_RunMissile aufruft.

Jetzt setzen wir das Entity Flag für den Server. Wenn man ein ET_MISSILE Entity benutzt, muss man unbedingt SVF_USE_CURRENT_ORIGIN und nicht entity->s.origin benutzen!

Der Eigentümer (owner) des Entities wird gesetzt.

Einige Zeilen später setzen wir die "clipmask" auf MASK_SHOT. Das bedeutet, dass dieses Entity bei CONTENTS_SOLID, CONTENTS_BODY und CONTENTS_CORPSE aufgehalten wird.

Durch "bolt->s.pos.trType = TR_LINEAR;" wird das Bewegungsmuster auf linear gestellt. Dies bedeutet, dass das Entity nicht von der Schwerkraft beeinflusst wird.

Nun müssen wir den Prototyp dieser Funktion in g_local.h eintragen. Darin gibt es vorgegebene Bereiche für die jeweiligen .c Dateien (Z 500).


gentity_t *fire_blaster (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t aimdir);
gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_bfg (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_grapple (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t aimdir);
#ifdef MISSIONPACK
gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up );
gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t aimdir );
#endif

Alle serverseitigen Funktionen für den Flammenwerfer sind nun implementiert.

6. Effekte und Graphik

Als erstes werden wir unsere neuen Shaderdefinitionen für die Flammen einbauen. Öffne cg_local.h und füge zu den vielen qhandle's in cgMedia_t deine eigenen (Z 725):


	qhandle_t	plasmaBallShader;
	qhandle_t	waterBubbleShader;
	qhandle_t	bloodTrailShader;
	qhandle_t 	flameBallShader;
	qhandle_t 	flameExplosionShader;
#ifdef MISSIONPACK
	qhandle_t	nailPuffShader;
	qhandle_t	blueProxMine;
#endif

Öffne cg_main.c und füge in CG_RegisterGraphics folgendes ein (Z 845):


	cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" );
	cgs.media.flameBallShader = trap_R_RegisterShader( "sprites/flameball" );
	cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" );
	cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" );
	cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" );

Wir haben also zwei neue Shader erzeugt ("flameballShader" und "flameExplosionShader"). Die tatsächliche Shaderfunktion wird in einer .shader Datei gespeichert (zu finden in tut10.pk3 -> solidground.shader).

Jetzt fügen wir die Waffeninformationen für den Flammenwerfer hinzu, wie Sounds, Shader für Explosionen usw. Öffne cg_weapon.c und füge in die Funktion CG_RegisterWeapon an passender Stelle folgendes ein (Z 690):


	case WP_FLAME_THROWER:
		weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse );
		MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1 );
		weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse );
		cgs.media.flameExplosionShader = trap_R_RegisterShader( "rocketExplosion" );
		break;

Dieser Abschnitt legt in Q3A fest, welche Sounds und Shader benutzt werden, und wo diese zu finden sind. Wir haben für den Flammenwerfer den Sound der PlasmaGun benutzt, und den Shader für Raketenexplosionen (rocketExplosion).

In der gleichen Datei fügen wir in der Funktion CG_MissileHitWall das Fettgedruckte ein (Z 1865):


	case WP_SHOTGUN:
		mod = cgs.media.bulletFlashModel;
		shader = cgs.media.bulletExplosionShader;
		mark = cgs.media.bulletMarkShader;
		sfx = 0;
		radius = 4;
		break;
	case WP_FLAME_THROWER:
		mod = cgs.media.dishFlashModel;
		shader = cgs.media.flameExplosionShader;
		sfx = cgs.media.sfx_plasmaexp;
		mark = cgs.media.burnMarkShader;
		radius = 16;
		break;

Hier legen wir fest, was geschieht, wenn unser Flammenprojektil auf etwas Solides trifft. Was bedeutet das alles?

1) mod: Dies ist das Modell, auf das wir unseren Shader "flameExplosionShader" anwenden
2) shader: Das ist der Shader, den wir auf das Modell anwenden
3) sfx: Dies ist der Sound, der bei der Explosion abgespielt wird
4) mark: Der Shader für die "Marks on Wall", also die Schußspuren
5) radius: Der Radius der Explosion

Nun müssen wir die Grafik für die Flammenprojektile hinzufügen (also für das Flammen-Entity, das wir zuvor gespawnt haben). Öffne cg_ents.c und füge den folgenden Code in CG_Missile ein (Z 450):


if ( cent->currentState.weapon == WP_PLASMAGUN ) {
		ent.reType = RT_SPRITE;
		ent.radius = 16;
		ent.rotation = 0;
		ent.customShader = cgs.media.plasmaBallShader;
		trap_R_AddRefEntityToScene( &ent );
		return;
	}
	if (cent->currentState.weapon == WP_FLAME_THROWER ) {
		ent.reType = RT_SPRITE;
		ent.radius = 32;
		ent.rotation = 0;
		ent.customShader = cgs.media.flameBallShader;
		trap_R_AddRefEntityToScene( &ent );
		return;
	}

Hier haben wir angegeben, dass unser Flammenprojektil (Entity) den "flameBallShader" benutzen soll. Den Entitytypen haben wir als Sprite definiert, der Radius des Sprites ist 32 und die Rotation des Entities wird (hier) auf 0 gesetzt. Die Rotation des Projektils wird schon vom "flameBallShader" durchgeführt, indem die Textur "flameball.tga" gedreht wird, die wir zuvor erzeugt haben.

Jetzt fügen wir unsere MOD (Means Of Death) Nachricht hinzu, die erscheinen soll, wenn ein Spieler von einem Flammenwerfer getötet wird. Öffne cg_event.c und füge in CG_Obituary folgenden Code hinzu(Z 275):

  
		case MOD_BFG_SPLASH:
			message = "was blasted by";
			message2 = "'s BFG";
			break;
		case MOD_FLAME_THROWER:
			message = "was fried by";
			break;
#ifdef MISSIONPACK
		case MOD_NAIL:
			message = "was nailed by";
			break;

7. Eintrag ins Menü

Zuletzt werden wir das Kommando "weapon 11" hinzufügen, damit wir diesem eine Taste zuweisen können. Öffne ui_controls2.c (im q3_ui Modul !) und schreibe zwischen die bereits vorhandenen #define-Anweisungen (Z 85):


#define ID_WEAPON8		24	
#define ID_WEAPON9		25
#define ID_WEAPON11	26	
#define ID_ATTACK		27
#define ID_WEAPPREV	28

Alle ID_Werte nach ID_WEAPON11 müssen um eins erhöht werden.
Etwas weiter unten (Z 130):


#define ANIM_DIE				24
#define ANIM_CHAT			25
#define ANIM_WEAPON11	26

Einige Zeilen tiefer, in controls_t, kommt diese Zeile dazu (Z 150):


	menutext_s			weapons;
	menutext_s			misc;

	menuaction_s		flamethrower;
	menuaction_s		walkforward;
	menuaction_s		backpedal;

In der g_bindings Struktur weiter unten füge folgendes ein (Z 245):


	{"messagemode4","chat - attacker",ID_CHAT4,ANIM_CHAT,-1,-1,-1,-1},
	{"weapon 11","flame thrower",ID_WEAPON11,ANIM_WEAPON11,'f',-1,-1, -1},
	{(char*)NULL,(char*)NULL,0,0,-1,-1,-1,-1},
};

Ein paar Zeilen tiefer in g_weapons_controls (Z 295):


	(menucommon_s *)&s_controls.plasma,           
	(menucommon_s *)&s_controls.bfg,
	(menucommon_s *)&s_controls.flamethrower,
	NULL,
};

In der Funktion Controls_UpdateModel (Z 505):


	case ANIM_WEAPON10:
		s_controls.playerWeapon = WP_GRAPPLING_HOOK;
		break;

	case ANIM_WEAPON11:
		s_controls.playerWeapon = WP_FLAME_THROWER;
		break;

	case ANIM_ATTACK:
		s_controls.playerTorso = TORSO_ATTACK;
		break;

Gleich haben wir es geschafft... in die Funktion Controls_MenuInit kommt folgendes hinein (Z 1385):

		  
	s_controls.bfg.generic.ownerdraw = Controls_DrawKeyBinding;
	s_controls.bfg.generic.id        = ID_WEAPON9;

	s_controls.flamethrower.generic.type = MTYPE_ACTION; 
	s_controls.flamethrower.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_GRAYED|QMF_HIDDEN; 
	s_controls.flamethrower.generic.callback = Controls_ActionEvent; 
	s_controls.flamethrower.generic.ownerdraw = Controls_DrawKeyBinding; 
	s_controls.flamethrower.generic.id = ID_WEAPON11;

etwas weiter unten (Z 1605):


	Menu_AddItem( &s_controls.menu, &s_controls.rocketlauncher );
	Menu_AddItem( &s_controls.menu, &s_controls.lightning );
	Menu_AddItem( &s_controls.menu, &s_controls.railgun );
	Menu_AddItem( &s_controls.menu, &s_controls.plasma );
	Menu_AddItem( &s_controls.menu, &s_controls.bfg );
	Menu_AddItem( &s_controls.menu, &s_controls.flamethrower );

Die allerletzte Änderung! Wir benötigen hierzu in der Datei ui_players.c die Funktion UI_PlayerInfo_SetWeapon (Z 125):


	case WP_GRAPPLING_HOOK:
		MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
		break;

	case WP_FLAME_THROWER:
		MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
		break;

	default:
		MAKERGB( pi->flashDlightColor, 1, 1, 1 );
		break;

Wow, wir haben es geschafft! Nun müssen alle drei Teile kompiliert werden, und der Flammenwerfer sollte funktionieren. Dies war ein sehr langes Tutorial mit relativ hohem Niveau. Allerdings kann man jetzt basierend auf diesem seine eigenen Waffen nach belieben einbauen, was den eigenen Mod schon mal von vielen anderen abheben sollte.

<< Tutorial 9 | | Tutorial 11 >>