stvoy-sp-sdk/game/g_breakable.cpp

782 lines
20 KiB
C++
Raw Normal View History

2002-11-22 00:00:00 +00:00
#include "g_local.h"
#include "g_functions.h"
extern team_t TranslateTeamName( const char *name );
extern void G_SetAngles( gentity_t *ent, vec3_t angles );
//client side shortcut hacks from cg_local.h
extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
extern void CG_MiscModelExplosion( vec3_t origin, vec3_t normal );
extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale );
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
extern void SP_NPC_Reaver( gentity_t *self);
extern qboolean player_locked;
void funcBBrushDieGo (gentity_t *self)
{
vec3_t org, dir, up;
gentity_t *attacker = self->enemy;
float scale;
int numChunks;
material_t chunkType = self->material;
//So chunks don't get stuck inside me
self->s.solid = 0;
self->contents = 0;
self->clipmask = 0;
gi.linkentity(self);
VectorSet(up, 0, 0, 1);
if ( self->target && attacker != NULL )
{
G_UseTargets(self, attacker);
}
VectorSubtract(self->absmax, self->absmin, org);//size
scale = VectorLength(org)/64.0f;
numChunks = ceil(scale*2) + Q_irand(0, 3);
// Cap the scale so it doesn't get too out of control
if ( scale > 1.0f )
scale = 1.0f;
if ( scale < 0.2f )
scale = 0.2f;
VectorMA(self->absmin, 0.5, org, org);
if ( attacker != NULL && attacker->client )
{
VectorSubtract( org, attacker->currentOrigin, dir );
VectorNormalize( dir );
}
else
{
VectorCopy(up, dir);
}
if(self->splashDamage > 0 && self->splashRadius > 0)
{
G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
CG_MiscModelExplosion( org, dir );
G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
}
//FIXME: base numChunks off size?
CG_Chunks( self->s.number, org, dir, Q_flrand(400, 600), numChunks, chunkType, 0, scale );
gi.AdjustAreaPortalState( self, qtrue );
G_FreeEntity( self );
}
void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
self->takedamage = qfalse;//stop chain reaction runaway loops
G_SetEnemy(self, attacker);
if(self->delay)
{
self->e_ThinkFunc = thinkF_funcBBrushDieGo;
self->nextthink = level.time + floor(self->delay * 1000.0f);
return;
}
funcBBrushDieGo(self);
}
void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
{
G_ActivateBehavior( self, BSET_USE );
if(self->spawnflags & 64)
{//Using it doesn't break it, makes it use it's targets
if(self->target && self->target[0])
{
G_UseTargets(self, activator);
}
}
else
{
funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
}
}
void funcBBrushPain(gentity_t *self, gentity_t *attacker, int damage)
{
if(self->painDebounceTime > level.time)
{
return;
}
if(self->paintarget)
{
G_UseTargets2 (self, self->activator, self->paintarget);
}
G_ActivateBehavior( self, BSET_PAIN );
if(self->wait == -1)
{
self->e_PainFunc = painF_NULL;
return;
}
self->painDebounceTime = level.time + self->wait;
}
static void InitBBrush ( gentity_t *ent )
{
float light;
vec3_t color;
qboolean lightSet, colorSet;
VectorCopy( ent->s.origin, ent->pos1 );
gi.SetBrushModel( ent, ent->model );
ent->e_DieFunc = dieF_funcBBrushDie;
ent->svFlags |= SVF_BBRUSH;
// if the "model2" key is set, use a seperate model
// for drawing, but clip against the brushes
if ( ent->model2 )
{
ent->s.modelindex2 = G_ModelIndex( ent->model2 );
}
// if the "color" or "light" keys are set, setup constantLight
lightSet = G_SpawnFloat( "light", "100", &light );
colorSet = G_SpawnVector( "color", "1 1 1", color );
if ( lightSet || colorSet )
{
int r, g, b, i;
r = color[0] * 255;
if ( r > 255 ) {
r = 255;
}
g = color[1] * 255;
if ( g > 255 ) {
g = 255;
}
b = color[2] * 255;
if ( b > 255 ) {
b = 255;
}
i = light / 4;
if ( i > 255 ) {
i = 255;
}
ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
}
if(ent->spawnflags & 128)
{//Can be used by the player's BUTTON_USE
ent->svFlags |= SVF_PLAYER_USABLE;
}
ent->s.eType = ET_MOVER;
gi.linkentity (ent);
ent->s.pos.trType = TR_STATIONARY;
VectorCopy( ent->pos1, ent->s.pos.trBase );
}
/*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE x x x x x USE_NOT_BREAK PLAYER_USE
PLAYER_USE - Player can use it with the use button
USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
When destroyed, fired it's trigger and chunks and plays sound "noise" or sound for type if no noise specified
This will change soon, so don't count on setting the material type this way just yet
INVINCIBLE - can only be broken by being used
"targetname" entities with matching target will fire it
"paintarget" target to fire when hit (but not destroyed)
"wait" how long minimum to wait between firing paintarget each time hit
"delay" When killed or used, how long (in seconds) to wait before blowing up (none by default)
"model2" .md3 model to also draw
"target" all entities with a matching targetname will be used when this is destoryed
"health" default is 10
Damage: default is none
"splashDamage" - damage to do (will make it explode on death
"splashRadius" - radius for above damage
"team" - This cannot take damage from members of this team:
"starfleet"
"borg"
"parasite"
"scavengers"
"klingon"
"malon"
"hirogen"
"imperial"
"stasis"
"species8472"
"dreadnought"
"forge"
Don't know if these work:
"color" constantLight color
"light" constantLight radius
"material" - default is "metal" - choose from this list:
0 = MAT_METAL (default)
1 = MAT_GLASS
2 = MAT_ELECTRICAL (sparks)
3 = MAT_ORGANIC (not implemented)
4 = MAT_BORG (borg chunks)
5 = MAT_STASIS (stasis chunks)
6 = MAT_GLASS_METAL (mixed chunk type)
(there will be more eventually)
*/
void SP_func_breakable( gentity_t *self )
{
if(!(self->spawnflags & 1))
{
if(!self->health)
{
self->health = 10;
}
}
if (self->health)
{
self->takedamage = qtrue;
}
G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
self->e_UseFunc = useF_funcBBrushUse;
if(self->paintarget)
{
self->e_PainFunc = painF_funcBBrushPain;
}
if ( self->team && self->team[0] )
{
self->noDamageTeam = TranslateTeamName( self->team );
if(self->noDamageTeam == TEAM_FREE)
{
G_Error("team name %s not recognized\n", self->team);
}
}
self->team = NULL;
if (!self->model) {
G_Error("func_breakable with NULL model\n");
}
InitBBrush( self );
}
void Touch_AssimilationTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )
{
if ( !other )
{
return;
}
if( !other->client )
{
return;
}
if ( other->s.number != 0 )
{
return;
}
/*
if ( ent->svFlags & SVF_INACTIVE )
{//set by the model being broken?
return;
}
*/
if ( ent->owner->health <= 0 )
{
return;
}
other->s.eFlags |= EF_ASSIMILATED;
player_locked = qtrue;
ent->owner->enemy = other;
if ( ent->owner->s.eType != ET_THINKER )
{
ent->owner->e_clThinkFunc = clThinkF_CG_Assimilator;
ent->owner->s.eType = ET_THINKER;
//assim sound
G_Sound( ent->owner, G_SoundIndex( "sound/enemies/borg/borgass.wav" ) );
G_Sound( ent->owner, G_SoundIndex( "sound/enemies/borg/alcovein.wav" ) );
}
//assim loop sound
G_Sound( ent->owner, G_SoundIndex( "sound/enemies/borg/borgassloop.wav" ) );
if ( !Q_irand(0, 5) )
{
G_Damage( other, ent->owner, ent->owner, NULL, NULL, 1, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR, MOD_ASSIMILATE );
}
}
void SpawnAssimilationTrigger( gentity_t *ent )
{
gentity_t *other;
vec3_t mins, maxs;
// find the bounds of everything on the team
VectorCopy (ent->absmin, mins);
VectorCopy (ent->absmax, maxs);
mins[2] -= 64;
// create a trigger with this size
other = G_Spawn ();
VectorCopy (mins, other->mins);
VectorCopy (maxs, other->maxs);
other->owner = ent;
other->contents = CONTENTS_TRIGGER;
other->e_TouchFunc = touchF_Touch_AssimilationTrigger;
gi.linkentity( other );
}
#define CHUNK_SCALE_FACTOR 0.3f // This was arrived at by trial and error
void misc_model_breakable_pain ( gentity_t *self, gentity_t *other, int damage )
{
vec3_t dir, dis;
int numChunks;
float damg = 0.25, //damg is a percentage of max_health
scale;
//FIXME: debounce time?
//Throw off some chunks
AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
VectorNormalize( dir );
if( self->max_health )
{
damg = (float)damage * 0.15 / (float)self->max_health;
}
numChunks = random() * 8 + 8;
// Scale the chunks, a function of damage done, size of object, num of chunks, etc.
VectorSubtract( self->mins, self->maxs, dis );
scale = ( VectorNormalize( dis ) * damg ) / numChunks * CHUNK_SCALE_FACTOR;
if ( scale > 0.4f ) {
scale = 0.4f;
}
if ( scale < 0.1f ) {
scale = 0.1f;
}
if(self->health <= 0)
{//dead, don't react to pain
CG_Chunks( self->s.number, self->currentOrigin, dir, 300, numChunks, self->material, self->s.modelindex3, scale );
}
else
{//still alive, react to the pain
// Glass pain shouldn't throw chunks...
if ( self->material != MAT_GLASS && self->material != MAT_GLASS_METAL )
CG_Chunks( self->s.number, self->currentOrigin, dir, 300, numChunks, self->material, 0, scale );
if ( self->paintarget )
{
G_UseTargets2 (self, self->activator, self->paintarget);
}
//Don't do script if dead
G_ActivateBehavior( self, BSET_PAIN );
}
}
void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
{
vec3_t dir, up;
//NOTE: Stop any scripts that are currently running (FLUSH)... ?
//Turn off animation
self->s.frame = self->startFrame = self->endFrame = 0;
self->svFlags &= ~SVF_ANIMATING;
self->health = 0;
//Throw some chunks
misc_model_breakable_pain ( self, inflictor, damage );
self->e_PainFunc = painF_NULL;
self->e_DieFunc = dieF_NULL;
// self->e_UseFunc = useF_NULL;
self->takedamage = qfalse;
if ( !(self->spawnflags & 4) )
{//We don't want to stay solid
self->s.solid = 0;
self->contents = 0;
self->clipmask = 0;
gi.linkentity(self);
}
VectorSet(up, 0, 0, 1);
if(self->target)
{
G_UseTargets(self, attacker);
}
//VectorSubtract(self->absmax, self->absmin, org);//size
//scale = VectorLength(org)/64.0f;
//VectorMA(self->absmin, 0.5, org, org);
if(inflictor->client)
{
VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
VectorNormalize( dir );
}
else
{
VectorCopy(up, dir);
}
if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
{
// Ok, we are allowed to explode, so do it now!
if(self->splashDamage > 0 && self->splashRadius > 0)
{
//FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's
// a bit better?
G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
CG_SurfaceExplosion(self->currentOrigin, up, 20.0f, 12.0f, ((self->spawnflags&16)==qfalse) );
G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
}
else
{
// This is the default explosion
CG_MiscModelExplosion( self->currentOrigin, dir );
G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
}
}
if(self->s.modelindex2 != -1 && !(self->spawnflags & 8))
{//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist
self->svFlags |= SVF_BROKEN;
self->s.modelindex = self->s.modelindex2;
if ( self->behaviorSet[BSET_DEATH] )
{
G_ActivateBehavior( self, BSET_DEATH );
}
}
else
{
G_FreeEntity( self );
}
}
//------------------------------------------------------------
void reaver_vat_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
self->s.eFlags &= ~EF_NODRAW;
self->contents = CONTENTS_SOLID;
gi.linkentity( self );
}
//------------------------------------------------------------
void reaver_vat_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
{
vec3_t dir;
//Turn off animation
self->s.frame = self->startFrame = self->endFrame = 0;
self->svFlags &= ~SVF_ANIMATING;
self->health = 0;
self->takedamage = qfalse;
self->contents = 0;
//Throw some chunks
misc_model_breakable_pain ( self, inflictor, damage );
self->e_PainFunc = painF_NULL;
self->e_DieFunc = dieF_NULL;
if ( self->target )
G_UseTargets( self, attacker );
self->svFlags |= SVF_BROKEN;
self->s.modelindex = self->s.modelindex2;
if ( self->behaviorSet[BSET_DEATH] )
G_ActivateBehavior( self, BSET_DEATH );
// Add in the main effects
if ( inflictor->client )
{
VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
VectorNormalize( dir );
}
else
{
VectorSet( dir, 0, 0, 1 ); // up
}
CG_Chunks( self->s.number, self->currentOrigin, dir, 400, 8, MAT_GLASS, 0, 1.0f );
CG_MiscModelExplosion( self->currentOrigin, dir );
G_AddEvent( self, EV_FX_MAGIC_SMOKE, 0 );
gentity_t *ent = G_Spawn();
VectorCopy( self->currentOrigin, ent->s.origin );
VectorCopy( self->s.angles, ent->s.angles );
ent->spawnflags |= 32;
ent->enemy = attacker;
ent->behaviorSet[BSET_SPAWN] = "BS_RUN_AND_SHOOT";
ent->NPC_targetname = self->NPC_targetname;
SP_NPC_Reaver( ent );
}
void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator)
{
if ( self->health <= 0 && self->max_health > 0)
{//used while broken fired target3
G_UseTargets2( self, activator, self->target3 );
return;
}
G_ActivateBehavior( self, BSET_USE );
//Don't explode if they've requested it to not
if ( self->spawnflags & 64 )
{//Usemodels toggling
if ( self->spawnflags & 32 )
{
if( self->s.modelindex == self->sound1to2 )
{
self->s.modelindex = self->sound2to1;
}
else
{
self->s.modelindex = self->sound1to2;
}
}
return;
}
misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN );
}
#define MDL_OTHER 0
#define MDL_ARMOR_HEALTH 1
#define MDL_AMMO 2
void misc_model_breakable_init( gentity_t *ent )
{
int type;
// FIXME : these should be using the proper spawn functions
if (!Q_stricmp(ent->model,"models/mapobjects/stasis/plugin2.md3"))
{
type = MDL_ARMOR_HEALTH;
}
else if (!Q_stricmp(ent->model,"models/mapobjects/stasis/plugin.md3"))
{
type = MDL_AMMO;
}
else if (!Q_stricmp(ent->model,"models/mapobjects/borg/plugin2.md3"))
{
type = MDL_ARMOR_HEALTH;
}
else if (!Q_stricmp(ent->model,"models/mapobjects/borg/plugin.md3"))
{
type = MDL_AMMO;
}
else
{
type = MDL_OTHER;
}
//Main model
ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
if ( ent->spawnflags & 1 )
{//Blocks movement
ent->contents = CONTENTS_BODY;//Was CONTENTS_SOLID, but only architecture should be this
}
else if ( ent->health )
{//Can only be shot
ent->contents = CONTENTS_SHOTCLIP;
}
if (type == MDL_OTHER)
{
ent->e_UseFunc = useF_misc_model_use;
}
else if ( type == MDL_ARMOR_HEALTH )
{
G_SoundIndex("sound/player/suithealth.wav");
ent->e_UseFunc = useF_health_use;
if (!ent->count)
{
ent->count = 100;
}
ent->health = 60;
}
else if ( type == MDL_AMMO )
{
G_SoundIndex("sound/player/suitenergy.wav");
ent->e_UseFunc = useF_ammo_use;
if (!ent->count)
{
ent->count = 100;
}
ent->health = 60;
}
if ( ent->health )
{
G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
ent->max_health = ent->health;
ent->takedamage = qtrue;
ent->e_PainFunc = painF_misc_model_breakable_pain;
ent->e_DieFunc = dieF_misc_model_breakable_die;
}
}
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
AUTOANIMATE - Will cycle it's anim
DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
PLAYER_USE - Player can use it with the use button
NO_EXPLOSION - By default, will explode when it dies...this is your override.
"model" arbitrary .md3 file to display
"health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
"targetname" when used, dies and displays damagemodel, if any (if not, removes itself)
"target" What to use when it dies
"target2" What to use when it's repaired
"target3" What to use when it's used while it's broken
"paintarget" target to fire when hit (but not destroyed)
"count" the amount of armor/health/ammo given (default 50)
Damage: default is none
"splashDamage" - damage to do (will make it explode on death)
"splashRadius" - radius for above damage
"team" - This cannot take damage from members of this team:
"starfleet"
"borg"
"parasite"
"scavengers"
"klingon"
"malon"
"hirogen"
"imperial"
"stasis"
"species8472"
"dreadnought"
"forge"
"material" - default is "metal" - choose from this list:
0 = MAT_METAL (default)
1 = MAT_GLASS
2 = MAT_ELECTRICAL (sparks)
3 = MAT_ORGANIC (not implemented)
4 = MAT_BORG (borg chunks)
5 = MAT_STASIS (stasis chunks)
6 = MAT_GLASS_METAL (mixed chunk type)
FIXME/TODO:
set size better?
multiple damage models?
multiple chunk models?
don't throw chunks on pain, or throw level 1 chunks only on pains?
custom explosion effect/sound?
*/
void SP_misc_model_breakable( gentity_t *ent )
{
char damageModel[MAX_QPATH];
char chunkModel[MAX_QPATH];
char useModel[MAX_QPATH];
int len;
misc_model_breakable_init( ent );
len = strlen( ent->model ) - 4;
strncpy( damageModel, ent->model, len );
damageModel[len] = 0; //chop extension
strncpy( chunkModel, damageModel, sizeof(chunkModel));
strncpy( useModel, damageModel, sizeof(useModel));
if (ent->takedamage) {
//Dead/damaged model
if( !(ent->spawnflags & 8) ) { //no dmodel
strcat( damageModel, "_d1.md3" );
ent->s.modelindex2 = G_ModelIndex( damageModel );
}
//Chunk model
strcat( chunkModel, "_c1.md3" );
ent->s.modelindex3 = G_ModelIndex( chunkModel );
}
//Use model
if( ent->spawnflags & 32 ) { //has umodel
strcat( useModel, "_u1.md3" );
ent->sound1to2 = G_ModelIndex( useModel );
}
if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] )
{
VectorSet (ent->mins, -16, -16, -16);
}
if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] )
{
VectorSet (ent->maxs, 16, 16, 16);
}
if ( ent->spawnflags & 2 )
{
ent->s.eFlags |= EF_ANIM_ALLFAST;
}
G_SetOrigin( ent, ent->s.origin );
G_SetAngles( ent, ent->s.angles );
gi.linkentity (ent);
if ( ent->spawnflags & 128 )
{//Can be used by the player's BUTTON_USE
ent->svFlags |= SVF_PLAYER_USABLE;
}
if ( ent->team && ent->team[0] )
{
ent->noDamageTeam = TranslateTeamName( ent->team );
if ( ent->noDamageTeam == TEAM_FREE )
{
G_Error("team name %s not recognized\n", ent->team);
}
}
ent->team = NULL;
/*
//looks like crap and would be unfair
if ( Q_stricmp( "models/mapobjects/borg/blite.md3", ent->model ) == 0 )
{
SpawnAssimilationTrigger( ent );
}
*/
}