This tut lets your mod have
BREAKABLE GLASS! Sweet, eh? OK, this is actually fairly easy. I wanna
thank Wang for helping
me with the code and Kalifa for the glass
models. Whoops, I can't forget Mastaba for
the help on the random code. You need to grab the media pack for
this tut and optionally my sample map.
Note: Mappers need to first, make it a func_door(anything will do) then
press "n" and change the func_door to func_breakable, or optionally edit
the "entities.def" file and add in func_breakable, so you can just select
it from the normal drop down list in Q3Radiant. They also need to set the
life of the glass by pressing "n" while selecting the entity and in the
key box type in "health" and in the value box type in whatever NUMBER of
life it should have. If you dont it defaults to 5 life. Your mapper needs
to add the ifx.shader into their baseq3/scripts directory as well as their
mymod/scripts folder. You will want he/she to edit the shaderlist.txt in
their baseq3/scripts directory and add at the VERY bottom OK, first lets add our function for when Quake3 recognizes the glass. Add this to the bottom of g_misc.c: /*QUAKED func_breakable (1 0 0) (-16 -16 -16) (16 16 16) Explodes glass */ void SP_func_breakable( gentity_t *ent ) { int health; // Make it appear as the brush trap_SetBrushModel( ent, ent->model ); // Lets give it 5 health if the mapper did not set its health G_SpawnInt( "health", "0", &health ); if( health <= 0 ) health = 5; ent->health = health; // Let it take damage ent->takedamage = qtrue; // Let it know it is a breakable object ent->s.eType = ET_BREAKABLE; // If the mapper gave it a model, use it if ( ent->model2 ) { ent->s.modelindex2 = G_ModelIndex( ent->model2 ); } // Link all ^this^ info into the ent trap_LinkEntity (ent); } Below that code lets add the function that G_Damage calls to see if we can break it yet: /* ================= G_BreakGlass ================= */ void G_BreakGlass(gentity_t *ent, vec3_t point, int mod) { gentity_t *tent; vec3_t size; vec3_t center; qboolean splashdmg; // Get the center of the glass VectorSubtract(ent->r.maxs, ent->r.mins, size); VectorScale(size, 0.5, size); VectorAdd(ent->r.mins, size, center); // If the glass has no more life, BREAK IT if( ent->health <= 0 ) { G_FreeEntity( ent ); // Tell the program based on the gun if it has no splash dmg, no reason to ad ones with // splash dmg as qtrue as is that is the default switch( mod ) { case MOD_GAUNTLET: splashdmg = qfalse; break; case MOD_SHOTGUN: splashdmg = qfalse; break; case MOD_MACHINEGUN: splashdmg = qfalse; break; case MOD_RAILGUN: splashdmg = qfalse; break; case MOD_LIGHTNING: splashdmg = qfalse; break; default: splashdmg = qtrue; break; } // Call the function to show the glass shards in cgame // center can be changed to point which will spawn the // where the killing bullet hit but wont work with Splash Damage weapons // so I just use the center of the glass switch( splashdmg ){ case qtrue: tent = G_TempEntity( center, EV_BREAK_GLASS ); break; case qfalse: tent = G_TempEntity( point, EV_BREAK_GLASS ); break; } tent->s.eventParm = 0; } } Now lets let G_BreakGlass be called from g_combat.c. Open up g_local.h and find: // // g_misc.c // void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); Below it place: void G_BreakGlass( gentity_t *ent, vec3_t point, int mod ); Thats all we got to do with g_misc.c, now lets go to g_spawn.c and find this: void SP_func_timer (gentity_t *self); And below it add: void SP_func_breakable (gentity_t *ent); Now find: {"func_timer", SP_func_timer}, // rename trigger_timer? Below it place: {"func_breakable", SP_func_breakable}, Thats it with g_spawn.c. In case you want to know, that last line we added tells it to call our SP_func_breakable function when it finds a func_breakable in the map. Open up g_combat.c and find: // shootable doors / buttons don't actually have any health if ( targ->s.eType == ET_MOVER ) { if ( targ->use && targ->moverState == MOVER_POS1 ) { targ->use( targ, inflictor, attacker ); } return; } Under it add: // If we shot a breakable item subtract the damage from its health and try to break it if ( targ->s.eType == ET_BREAKABLE ) { targ->health -= damage; G_BreakGlass( targ, point, mod ); return; } OK, we are done with g_combat.c, now we gotta get the bots to shoot the glass! So open up ai_dmq3.c and find: //if it is a door if (!strcmp(classname, "func_door")) { if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { //if health the door must be shot to open if (health) return ent; } } Below it add: //if it is some glass if (!strcmp(classname, "func_breakable")) { return ent; } Now the last edit for this file, go find: else if (!strcmp(classname, "func_door")) { //shoot at the shootable door trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); modelindex = atoi(model+1); //if the model is not loaded if (!modelindex) return; VectorClear(angles); BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs); //door origin VectorAdd(mins, maxs, origin); VectorScale(origin, 0.5, origin); // VectorSubtract(origin, bs->eye, movedir); vectoangles(movedir, moveresult->ideal_viewangles); moveresult->flags |= MOVERESULT_MOVEMENTVIEW; moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; //select the machinegun and shoot trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN); if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) { trap_EA_Attack(bs->client); } return; } Agin, directly below it add: else if (!strcmp(classname, "func_breakable")) { //shoot at the shootable door trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); modelindex = atoi(model+1); //if the model is not loaded if (!modelindex) return; VectorClear(angles); BotModelMinsMaxs(modelindex, ET_BREAKABLE, mins, maxs); //door origin VectorAdd(mins, maxs, origin); VectorScale(origin, 0.5, origin); // VectorSubtract(origin, bs->eye, movedir); vectoangles(movedir, moveresult->ideal_viewangles); moveresult->flags |= MOVERESULT_MOVEMENTVIEW; moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; //select the machinegun and shoot trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN); if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) { trap_EA_Attack(bs->client); } return; } Thats all for ai_dmq3.c! What that does is first is says if there is a func_breakable, it MUST be shot to open/unblock its path/whatever. Then in BotAIBlocked it looks for func_breakable and if its there, the bot pulls out his boomstick and shoots it. Now we are nearly done with the game part of this. So lets keep on rolling. All we got left is to open up bg_public.h and add some stuff. So open it up and find: typedef enum { ET_GENERAL, ET_PLAYER, ET_ITEM, ET_MISSILE, ET_MOVER, Add below it: ET_BREAKABLE, One last thing! Find this: EV_GIB_PLAYER, // gib a previously living player And below it add: EV_BREAK_GLASS, Now, thats it for the game module! So go to cg_local.h and find: qhandle_t gibLeg; qhandle_t gibSkull; qhandle_t gibBrain; Add below it: qhandle_t glass01; qhandle_t glass02; qhandle_t glass03; Now find: void CG_GibPlayer( vec3_t playerOrigin ); And add below it: void CG_BreakGlass( vec3_t playerOrigin ); Thats it for cg_local.h, lets open up cg_main.c and register the models. Find: cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); Like always, below it add: cgs.media.glass01 = trap_R_RegisterModel( "models/breakables/glass01.md3" ); cgs.media.glass02 = trap_R_RegisterModel( "models/breakables/glass02.md3" ); cgs.media.glass03 = trap_R_RegisterModel( "models/breakables/glass03.md3" ); Go open up cg_event.c and find: case EV_GIB_PLAYER: DEBUGNAME("EV_GIB_PLAYER"); trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); CG_GibPlayer( cent->lerpOrigin ); break; Below it add: case EV_BREAK_GLASS: DEBUGNAME("EV_BREAK_GLASS"); // Change cgs.media.gibSound to whatever sound you want it to use // I think the gib sound sounds pretty good trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); CG_BreakGlass( cent->lerpOrigin ); break; Now open up cg_effects.c, we need to add the functions that produce the gibs, so at the bottom of the file add: /* ================== CG_LaunchGlass ================== */ void CG_LaunchGlass( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + 30000 + random() * 3000; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); re->hModel = hModel; le->pos.trType = TR_GRAVITY; VectorCopy( origin, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; le->bounceFactor = 0.3; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } /* =================== CG_BreakGlass Generated a bunch of glass shards launching out from the glass location =================== */ #define GLASS_VELOCITY 175 #define GLASS_JUMP 125 void CG_BreakGlass( vec3_t playerOrigin ) { vec3_t origin, velocity; int value; // How many shards to generate int count = 50; // The array of possible numbers int states[] = {1,2,3}; // Get the size of the array int numstates = sizeof(states)/sizeof(states[0]); // Countdown "count" so this will subtract 1 from the "count" // X many times. X being the "count" value while ( count-- ) { // Generate the random number every count so every shard is a // of the three. If this is placed above it only gets a random // number every time a piece of glass is broken. value = states[rand()%numstates]; VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GLASS_VELOCITY; velocity[1] = crandom()*GLASS_VELOCITY; velocity[2] = GLASS_JUMP + crandom()*GLASS_VELOCITY; switch (value) { case 1: // If our random number was 1, generate the 1st shard piece CG_LaunchGlass( origin, velocity, cgs.media.glass01 ); break; case 2: CG_LaunchGlass( origin, velocity, cgs.media.glass02 ); break; case 3: CG_LaunchGlass( origin, velocity, cgs.media.glass03 ); break; } } } Now the the VERY last step. We gotta let it know how to draw this object. In cg_ents.c, find: case ET_MOVER: CG_Mover( cent ); break; And below it place: case ET_BREAKABLE: CG_Mover( cent ); break; Yes, the ET_BREAKABLE is suppost to call CG_Mover. All that function does is tell the engine how to render it. And CG_Mover is the exact way we want to render our glass. Compile ALL 3 projects and enjoy. I spend my time programming and writing these tutorials, so please feel free to use this code in your mod as long as www.inolen.com and the author receive credit. | |
Design Copyright inolen 2000 |