Skip to content

Commit b5eb7c8

Browse files
committed
Consolidate breakable logic and add staged damage support for func_breakable
Refactor breakable handling and introduce configurable staged damage behavior for func_breakable. - Consolidate duplicated material break code (glass/wood/metal) into a shared helper. - Add support for secondary target triggering (target2) and wire it into breakable execution flow. - Extend func_breakable with optional damage filtering via breakdamagefilter. - Add Phase-A staged destruction support with breakstages and breakstageeffects. - Emit lightweight stage feedback effects when damage crosses configured thresholds. - Add validation/warnings for unsupported stage/filter flag combinations. - Update bot activation checks to include func_breakable parity with existing breakable entities. - Document new mapper-facing keys in entities.def. This change keeps final-break behavior intact while improving maintainability and allowing richer mapper-controlled breakable interactions.
1 parent 2a8dda8 commit b5eb7c8

File tree

12 files changed

+244
-173
lines changed

12 files changed

+244
-173
lines changed

engine/code/client/cl_main.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3582,8 +3582,8 @@ void CL_Init( void ) {
35823582

35833583
rconAddress = Cvar_Get ("rconAddress", "", 0);
35843584

3585-
cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE);
3586-
cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE);
3585+
cl_yawspeed = Cvar_Get ("cl_yawspeed", "110", CVAR_ARCHIVE);
3586+
cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "110", CVAR_ARCHIVE);
35873587
cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);
35883588

35893589
cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE );
@@ -3634,10 +3634,10 @@ void CL_Init( void ) {
36343634
m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE);
36353635
#endif
36363636

3637-
j_pitch = Cvar_Get ("j_pitch", "0.022", CVAR_ARCHIVE);
3638-
j_yaw = Cvar_Get ("j_yaw", "-0.022", CVAR_ARCHIVE);
3639-
j_forward = Cvar_Get ("j_forward", "-0.25", CVAR_ARCHIVE);
3640-
j_side = Cvar_Get ("j_side", "0.25", CVAR_ARCHIVE);
3637+
j_pitch = Cvar_Get ("j_pitch", "0.017", CVAR_ARCHIVE);
3638+
j_yaw = Cvar_Get ("j_yaw", "-0.017", CVAR_ARCHIVE);
3639+
j_forward = Cvar_Get ("j_forward", "-0.20", CVAR_ARCHIVE);
3640+
j_side = Cvar_Get ("j_side", "0.20", CVAR_ARCHIVE);
36413641
j_up = Cvar_Get ("j_up", "0", CVAR_ARCHIVE);
36423642

36433643
j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE);

engine/code/game/ai_dmq3.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4295,10 +4295,10 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ
42954295
return 0;
42964296
}
42974297
//if it is a door
4298-
//Check if the bot steps over some breakable glass , possible use your weapon on it
4299-
if (!strcmp(classname, "func_breakglass")) {
4300-
return ent;
4301-
}
4298+
//Check if the bot steps over breakable geometry, possible use your weapon on it
4299+
if ( !strcmp( classname, "func_breakglass" ) || !strcmp( classname, "func_breakable" ) ) {
4300+
return ent;
4301+
}
43024302
if (!strcmp(classname, "func_door")) {
43034303
if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
43044304
//if the door has health then the door must be shot to open

engine/code/game/g_combat.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,47 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
950950
// shootable doors / buttons don't actually have any health
951951
if ( targ->s.eType == ET_MOVER ) {
952952
if ( !Q_stricmp( targ->classname, "func_breakable" ) ) {
953+
int oldHealth;
954+
int oldStage;
955+
int newStage;
956+
957+
qboolean allowDamage = qtrue;
958+
959+
if ( targ->breakableDamageFilter ) {
960+
allowDamage = qfalse;
961+
962+
if ( (targ->breakableDamageFilter & 1) && attacker && attacker->client ) {
963+
allowDamage = qtrue;
964+
}
965+
966+
if ( (targ->breakableDamageFilter & 2) &&
967+
( mod == MOD_ROCKET_SPLASH || mod == MOD_GRENADE_SPLASH || mod == MOD_PLASMA_SPLASH ||
968+
mod == MOD_BFG_SPLASH || mod == MOD_MINE_SPLASH || mod == MOD_BREAKABLE_SPLASH ) ) {
969+
allowDamage = qtrue;
970+
}
971+
972+
if ( (targ->breakableDamageFilter & 4) &&
973+
( mod == MOD_VEHICLE_COLLISION || mod == MOD_WORLD_COLLISION || mod == MOD_HIGH_FORCES ) ) {
974+
allowDamage = qtrue;
975+
}
976+
}
977+
978+
if ( !allowDamage ) {
979+
return;
980+
}
981+
982+
oldHealth = targ->health;
953983
targ->health -= damage;
984+
985+
if ( targ->breakableStages > 0 && targ->breakableMaxHealth > 0 && oldHealth > 0 && targ->health > 0 ) {
986+
oldStage = ( oldHealth * targ->breakableStages ) / targ->breakableMaxHealth;
987+
newStage = ( targ->health * targ->breakableStages ) / targ->breakableMaxHealth;
988+
989+
if ( newStage < oldStage ) {
990+
Breakable_EmitStageEffects( targ );
991+
}
992+
}
993+
954994
if (targ->health <= 0)
955995
Break_Breakable (targ, attacker);
956996
} else if ( targ->use && (targ->moverState == MOVER_POS1

engine/code/game/g_local.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ struct gentity_s {
141141
int timestamp; // body queue sinking, etc
142142

143143
char *target;
144+
char *target2;
144145
char *targetname;
145146
char *team;
146147
char *targetShaderName;
@@ -168,6 +169,10 @@ struct gentity_s {
168169
qboolean takedamage;
169170

170171
int damage;
172+
int breakableDamageFilter; // func_breakable damage filter bitmask (0 = allow all)
173+
int breakableMaxHealth; // func_breakable baseline health for stage calculations
174+
int breakableStages; // number of pre-break stages to emit (0 disables staged destruction)
175+
int breakableStageEffects; // staged effect bitmask (1=debris pulse, 2=explosion pulse)
171176
int splashDamage; // quad will increase this without increasing radius
172177
int splashRadius;
173178
int methodOfDeath;
@@ -641,6 +646,7 @@ void G_KillBox (gentity_t *ent);
641646
gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match);
642647
gentity_t *G_PickTarget (char *targetname);
643648
void G_UseTargets (gentity_t *ent, gentity_t *activator);
649+
void G_UseTargets2 (gentity_t *ent, gentity_t *activator);
644650
void G_SetMovedir ( vec3_t angles, vec3_t movedir);
645651

646652
void G_InitGentity( gentity_t *e );
@@ -730,6 +736,7 @@ gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t aimdir );
730736
void G_RunMover( gentity_t *ent );
731737
void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace );
732738
void Break_Breakable(gentity_t *ent, gentity_t *other);
739+
void Breakable_EmitStageEffects( gentity_t *ent );
733740

734741
//
735742
// g_trigger.c

engine/code/game/g_misc.c

Lines changed: 45 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -571,49 +571,55 @@ void DropPortalSource( gentity_t *player ) {
571571
trap_LinkEntity (ent);
572572
}
573573

574+
/*
575+
=================
576+
Break_MaterialBrush
577+
578+
Shared helper for breakglass/breakwood/breakmetal brush entities.
579+
=================
580+
*/
581+
static void Break_MaterialBrush( gentity_t *ent, vec3_t point, int mod, int breakEvent ) {
582+
gentity_t *tent;
583+
vec3_t size;
584+
vec3_t center;
585+
qboolean splashdmg;
586+
587+
// Get the center of the brush
588+
VectorSubtract( ent->r.maxs, ent->r.mins, size );
589+
VectorScale( size, 0.5, size );
590+
VectorAdd( ent->r.mins, size, center );
591+
592+
if ( ent->health > 0 ) {
593+
return;
594+
}
595+
596+
G_FreeEntity( ent );
597+
598+
switch ( mod ) {
599+
case MOD_GAUNTLET:
600+
splashdmg = qfalse;
601+
break;
602+
default:
603+
splashdmg = qtrue;
604+
break;
605+
}
606+
607+
// Emit break event for cgame
608+
if ( splashdmg ) {
609+
tent = G_TempEntity( center, breakEvent );
610+
} else {
611+
tent = G_TempEntity( point, breakEvent );
612+
}
613+
tent->s.eventParm = 0;
614+
}
615+
574616
/*
575617
=================
576618
G_BreakGlass
577619
=================
578620
*/
579621
void G_BreakGlass(gentity_t *ent, vec3_t point, int mod) {
580-
gentity_t *tent;
581-
vec3_t size;
582-
vec3_t center;
583-
qboolean splashdmg;
584-
585-
// Lanz: Cleaned up compiler errors.
586-
587-
// Get the center of the glass
588-
VectorSubtract(ent->r.maxs, ent->r.mins, size);
589-
VectorScale(size, 0.5, size);
590-
VectorAdd(ent->r.mins, size, center);
591-
592-
// if the health of our glass brush is at 0 we'll break it
593-
if( ent->health <= 0 ) {
594-
G_FreeEntity( ent );
595-
596-
switch( mod ) {
597-
case MOD_GAUNTLET:
598-
splashdmg = qfalse;
599-
break;
600-
default:
601-
splashdmg = qtrue;
602-
break;
603-
}
604-
605-
// Now we call our EVs(Events) so that cgame can work on it
606-
switch( splashdmg ){
607-
case qtrue:
608-
tent = G_TempEntity( center, EV_BREAK_GLASS );
609-
tent->s.eventParm = 0;
610-
break;
611-
case qfalse:
612-
tent = G_TempEntity( point, EV_BREAK_GLASS );
613-
tent->s.eventParm = 0;
614-
break;
615-
}
616-
}
622+
Break_MaterialBrush( ent, point, mod, EV_BREAK_GLASS );
617623
}
618624
/*QUAKED func_breakwood (1 0 0) (-16 -16 -16) (16 16 16)
619625
we have breakable wood
@@ -648,43 +654,7 @@ void DropPortalSource( gentity_t *player ) {
648654
=================
649655
*/
650656
void G_BREAKWOOD(gentity_t *ent, vec3_t point, int mod) {
651-
gentity_t *tent;
652-
vec3_t size;
653-
vec3_t center;
654-
qboolean splashdmg;
655-
656-
// Lanz: cleaned up compiler errors.
657-
658-
// Get the center of the BOX
659-
VectorSubtract(ent->r.maxs, ent->r.mins, size);
660-
VectorScale(size, 0.5, size);
661-
VectorAdd(ent->r.mins, size, center);
662-
663-
// if the health of our wood brush is at 0 we'll move it
664-
if( ent->health <= 0 ) {
665-
G_FreeEntity( ent );
666-
667-
switch( mod ) {
668-
case MOD_GAUNTLET:
669-
splashdmg = qfalse;
670-
break;
671-
default:
672-
splashdmg = qtrue;
673-
break;
674-
}
675-
676-
// Now we call our EVs(Events) so that cgame can work on it
677-
switch( splashdmg ){
678-
case qtrue:
679-
tent = G_TempEntity( center, EV_BREAKWOOD );
680-
tent->s.eventParm = 0;
681-
break;
682-
case qfalse:
683-
tent = G_TempEntity( point, EV_BREAKWOOD );
684-
tent->s.eventParm = 0;
685-
break;
686-
}
687-
}
657+
Break_MaterialBrush( ent, point, mod, EV_BREAKWOOD );
688658
}
689659

690660
/*QUAKED func_breakmetal (1 0 0) (-16 -16 -16) (16 16 16)
@@ -720,41 +690,5 @@ void G_BREAKWOOD(gentity_t *ent, vec3_t point, int mod) {
720690
=================
721691
*/
722692
void G_BREAKMETAL(gentity_t *ent, vec3_t point, int mod) {
723-
gentity_t *tent;
724-
vec3_t size;
725-
vec3_t center;
726-
qboolean splashdmg;
727-
728-
// Lanz: Cleaned up compiler errors.
729-
730-
// Get the center of the metal
731-
VectorSubtract(ent->r.maxs, ent->r.mins, size);
732-
VectorScale(size, 0.5, size);
733-
VectorAdd(ent->r.mins, size, center);
734-
735-
// if the health of our metal brush is at 0 we'll break it
736-
if( ent->health <= 0 ) {
737-
G_FreeEntity( ent );
738-
739-
switch( mod ) {
740-
case MOD_GAUNTLET:
741-
splashdmg = qfalse;
742-
break;
743-
default:
744-
splashdmg = qtrue;
745-
break;
746-
}
747-
748-
// Now we call our EVs(Events) so that cgame can work on it
749-
switch( splashdmg ){
750-
case qtrue:
751-
tent = G_TempEntity( center, EV_BREAKMETAL );
752-
tent->s.eventParm = 0;
753-
break;
754-
case qfalse:
755-
tent = G_TempEntity( point, EV_BREAKMETAL );
756-
tent->s.eventParm = 0;
757-
break;
758-
}
759-
}
693+
Break_MaterialBrush( ent, point, mod, EV_BREAKMETAL );
760694
}

0 commit comments

Comments
 (0)