Files
quakemapping/mod_ad_dev/my_progs/breakable.qc
2019-12-30 22:24:44 +01:00

1421 lines
61 KiB
Plaintext

/*======================================================================
Breakable functions
======================================================================*/
float BREAK_STARTOFF = 1; // Will wait for trigger to spawn
float BREAK_NOSHOOT = 2; // Cannot be damaged/shot, trigger only
float BREAK_EXPLOSION = 4; // Spawn sprite/particle explosion
float BREAK_SILENT = 8; // No initial break sound
float BREAK_DAMAGE = 16; // Rubble does damage on touch
float BREAK_NOMONSTER = 32; // No damage from monsters
float BREAK_NOSOUND = 64; // No impact sound for rubble
float BREAK_NOROTATE = 128; // No Y rotation for rubble
float BREAK_MOVEDIR = 4096; // Set by entity, movedir/angles active
float MONTRIG_NODELAY = 1; // No delay between monster and breakable trigger
float MONTRIG_WAKEUPANIM = 2; // Monster trigger does special wakeup animation
float BREAKWALL_START_ON = 1; // Switchable bmodel for breakable setups
float BREAKWALL_SOLID = 2; // Solid player collision when active
float BREAKWALL_FADEOUT = 4; // Will fade out after a certain amount of time
float BTYPE_ROCK = 1; // Default rock/brick
float BTYPE_WOOD = 2;
float BTYPE_GLASS = 3;
float BTYPE_METAL = 4;
float BTYPE_BRICK = 5;
float BTYPE_CERAMIC = 6;
float BTYPE_MAX = 6;
float BTYPE_CUSTOM = 10;
float BMODTYPE_SELF = 1; // Single self models
float BMODTYPE_CUSTOM = 5; // Custom models
float BMODTYPE_ROCK1 = 10; // ID rock4_1 (dark large bricks)
float BMODTYPE_ROCK2 = 11; // ID rock4_2 (light stone)
float BMODTYPE_ROCK3 = 12; // ID rock3_8 (light vertical)
float BMODTYPE_ROCK4 = 13; // ID city5_3 (white plaster)
float BMODTYPE_ROCK5 = 14; // ID stone1_7b (dark blue ver)
float BMODTYPE_ROCK6 = 15; // ID cliff4 (black rock)
float BMODTYPE_WOOD1 = 20; // ID dung01_3 (dark)
float BMODTYPE_WOOD2 = 21; // ID dung01_2 (light)
float BMODTYPE_WOOD3 = 22; // ID wizwood1_7 (mouldy)
float BMODTYPE_GLASS1 = 30; // ID window1_2 (blue sqr)
float BMODTYPE_GLASS2 = 31; // ID window01_4 (red stain)
float BMODTYPE_GLASS3 = 32; // ID window02_1 (yellow stain)
float BMODTYPE_GLASS4 = 33; // ID window01_3 (purple stain)
float BMODTYPE_METAL1 = 40; // ID metal1_2 (brown generic)
float BMODTYPE_METAL2 = 41; // ID metal4_5 (metal4_4 generic)
float BMODTYPE_METAL3 = 42; // ID metal4_7 (rivet metal panels)
float BMODTYPE_METAL4 = 43; // ID cop1_1 (green generic)
float BMODTYPE_METAL5 = 44; // ID metal2_8 (blue generic)
float BMODTYPE_BRICK1 = 50; // ID wbrick1_5 (large brown)
float BMODTYPE_BRICK2 = 51; // ID city2_3 (small sewer green)
float BMODTYPE_BRICK3 = 52; // ID city6_8 (small drywall greyish)
float BMODTYPE_BRICK4 = 53; // ID wiz1_4 (large white)
float BMODTYPE_BRICK5 = 54; // ID city2_1 (small red brick)
float BMODTYPE_BRICK6 = 55; // ID city1_6 (small brown brick)
float BMODTYPE_BRICK7 = 56; // ID city4_5 (small blue brick)
float BMODTYPE_CERAMIC1 = 60; // Blank atm
//----------------------------------------------------------------------
// Breakable objects explosion/impact sounds
string SOUND_BRK_ROCK = "break/rock_impact.wav";
string SOUND_BRK_WOOD = "break/wood_impact.wav";
string SOUND_BRK_GLASS = "break/glass_impact.wav";
string SOUND_BRK_METAL = "break/metal_impact.wav";
string SOUND_BRK_CERAMIC = "break/ceramic_impact.wav";
string SOUND_IMP_ROCK1 = "break/rock_i1.wav";
string SOUND_IMP_ROCK2 = "break/rock_i2.wav";
string SOUND_IMP_ROCK3 = "break/rock_i3.wav";
string SOUND_IMP_ROCK4 = "break/rock_i4.wav";
string SOUND_IMP_WOOD1 = "break/wood_i1.wav";
string SOUND_IMP_WOOD2 = "break/wood_i2.wav";
string SOUND_IMP_WOOD3 = "break/wood_i3.wav";
string SOUND_IMP_WOOD4 = "break/wood_i4.wav";
string SOUND_IMP_GLASS1 = "break/glass_i1.wav";
string SOUND_IMP_GLASS2 = "break/glass_i2.wav";
string SOUND_IMP_GLASS3 = "break/glass_i3.wav";
string SOUND_IMP_GLASS4 = "break/glass_i4.wav";
string SOUND_IMP_METAL1 = "break/metal_i1.wav";
string SOUND_IMP_METAL2 = "break/metal_i2.wav";
string SOUND_IMP_METAL3 = "break/metal_i3.wav";
string SOUND_IMP_METAL4 = "break/metal_i4.wav";
string SOUND_IMP_CERAMIC1 = "break/ceramic_i1.wav";
string SOUND_IMP_CERAMIC2 = "break/ceramic_i2.wav";
string SOUND_IMP_CERAMIC3 = "break/ceramic_i3.wav";
string SOUND_IMP_CERAMIC4 = "break/ceramic_i4.wav";
//----------------------------------------------------------------------
// Breakable objects explosion/impact models
// ID rock4_1, rock4_2, rock3_8, city5_3, stone1_7b, cliff4
string MODEL_BRK_ROCK1A = "maps/ad_brk/rock01.bsp";
string MODEL_BRK_ROCK1B = "maps/ad_brk/rock02.bsp";
string MODEL_BRK_ROCK1C = "maps/ad_brk/rock03.bsp";
string MODEL_BRK_ROCK1D = "maps/ad_brk/rock04.bsp";
string MODEL_BRK_ROCK2A = "maps/ad_brk/rock05.bsp";
string MODEL_BRK_ROCK2B = "maps/ad_brk/rock06.bsp";
string MODEL_BRK_ROCK2C = "maps/ad_brk/rock07.bsp";
string MODEL_BRK_ROCK2D = "maps/ad_brk/rock08.bsp";
string MODEL_BRK_ROCK3A = "maps/ad_brk/rock09.bsp";
string MODEL_BRK_ROCK3B = "maps/ad_brk/rock10.bsp";
string MODEL_BRK_ROCK3C = "maps/ad_brk/rock11.bsp";
string MODEL_BRK_ROCK3D = "maps/ad_brk/rock12.bsp";
string MODEL_BRK_ROCK4A = "maps/ad_brk/rock13.bsp";
string MODEL_BRK_ROCK4B = "maps/ad_brk/rock14.bsp";
string MODEL_BRK_ROCK4C = "maps/ad_brk/rock15.bsp";
string MODEL_BRK_ROCK4D = "maps/ad_brk/rock16.bsp";
string MODEL_BRK_ROCK5A = "maps/ad_brk/rock17.bsp";
string MODEL_BRK_ROCK5B = "maps/ad_brk/rock18.bsp";
string MODEL_BRK_ROCK5C = "maps/ad_brk/rock19.bsp";
string MODEL_BRK_ROCK5D = "maps/ad_brk/rock20.bsp";
string MODEL_BRK_ROCK6A = "maps/ad_brk/rock21.bsp";
string MODEL_BRK_ROCK6B = "maps/ad_brk/rock22.bsp";
string MODEL_BRK_ROCK6C = "maps/ad_brk/rock23.bsp";
string MODEL_BRK_ROCK6D = "maps/ad_brk/rock24.bsp";
// ID dung01_3 (dark), dung01_2 (light), wizwood1_7 (mouldy)
string MODEL_BRK_WOOD1A = "maps/ad_brk/wood01.bsp";
string MODEL_BRK_WOOD1B = "maps/ad_brk/wood02.bsp";
string MODEL_BRK_WOOD1C = "maps/ad_brk/wood03.bsp";
string MODEL_BRK_WOOD1D = "maps/ad_brk/wood04.bsp";
string MODEL_BRK_WOOD2A = "maps/ad_brk/wood05.bsp";
string MODEL_BRK_WOOD2B = "maps/ad_brk/wood06.bsp";
string MODEL_BRK_WOOD2C = "maps/ad_brk/wood07.bsp";
string MODEL_BRK_WOOD2D = "maps/ad_brk/wood08.bsp";
string MODEL_BRK_WOOD3A = "maps/ad_brk/wood09.bsp";
string MODEL_BRK_WOOD3B = "maps/ad_brk/wood10.bsp";
string MODEL_BRK_WOOD3C = "maps/ad_brk/wood11.bsp";
string MODEL_BRK_WOOD3D = "maps/ad_brk/wood12.bsp";
// ID window1_2 (blue sqr), window01_4 (red stain),
// window02_1 (yellow stain), window01_3 (purple dragon)
string MODEL_BRK_GLASS1A = "maps/ad_brk/glass01.bsp";
string MODEL_BRK_GLASS1B = "maps/ad_brk/glass02.bsp";
string MODEL_BRK_GLASS1C = "maps/ad_brk/glass03.bsp";
string MODEL_BRK_GLASS1D = "maps/ad_brk/glass04.bsp";
string MODEL_BRK_GLASS2A = "maps/ad_brk/glass05.bsp";
string MODEL_BRK_GLASS2B = "maps/ad_brk/glass06.bsp";
string MODEL_BRK_GLASS2C = "maps/ad_brk/glass07.bsp";
string MODEL_BRK_GLASS2D = "maps/ad_brk/glass08.bsp";
string MODEL_BRK_GLASS3A = "maps/ad_brk/glass09.bsp";
string MODEL_BRK_GLASS3B = "maps/ad_brk/glass10.bsp";
string MODEL_BRK_GLASS3C = "maps/ad_brk/glass11.bsp";
string MODEL_BRK_GLASS3D = "maps/ad_brk/glass12.bsp";
string MODEL_BRK_GLASS4A = "maps/ad_brk/glass13.bsp";
string MODEL_BRK_GLASS4B = "maps/ad_brk/glass14.bsp";
string MODEL_BRK_GLASS4C = "maps/ad_brk/glass15.bsp";
string MODEL_BRK_GLASS4D = "maps/ad_brk/glass16.bsp";
// ID metal1_2 (brown generic), metal4_5 (metal4_4 generic),
// metal4_7 (rivet metal panels), cop1_1 (green generic)
string MODEL_BRK_METAL1A = "maps/ad_brk/metal01.bsp";
string MODEL_BRK_METAL1B = "maps/ad_brk/metal02.bsp";
string MODEL_BRK_METAL1C = "maps/ad_brk/metal03.bsp";
string MODEL_BRK_METAL1D = "maps/ad_brk/metal04.bsp";
string MODEL_BRK_METAL2A = "maps/ad_brk/metal05.bsp";
string MODEL_BRK_METAL2B = "maps/ad_brk/metal06.bsp";
string MODEL_BRK_METAL2C = "maps/ad_brk/metal07.bsp";
string MODEL_BRK_METAL2D = "maps/ad_brk/metal08.bsp";
string MODEL_BRK_METAL3A = "maps/ad_brk/metal09.bsp";
string MODEL_BRK_METAL3B = "maps/ad_brk/metal10.bsp";
string MODEL_BRK_METAL3C = "maps/ad_brk/metal11.bsp";
string MODEL_BRK_METAL3D = "maps/ad_brk/metal12.bsp";
string MODEL_BRK_METAL4A = "maps/ad_brk/metal13.bsp";
string MODEL_BRK_METAL4B = "maps/ad_brk/metal14.bsp";
string MODEL_BRK_METAL4C = "maps/ad_brk/metal15.bsp";
string MODEL_BRK_METAL4D = "maps/ad_brk/metal16.bsp";
string MODEL_BRK_METAL5A = "maps/ad_brk/metal17.bsp";
string MODEL_BRK_METAL5B = "maps/ad_brk/metal18.bsp";
string MODEL_BRK_METAL5C = "maps/ad_brk/metal19.bsp";
string MODEL_BRK_METAL5D = "maps/ad_brk/metal20.bsp";
// ID wbrick1_5 (large brown), city2_3 (small sewer green),
// city6_8 (small drywall greyish), wiz1_4 (large white)
// city2_1 (small red brick), city1_6 (small brown)
string MODEL_BRK_BRICK1A = "maps/ad_brk/brick01.bsp";
string MODEL_BRK_BRICK1B = "maps/ad_brk/brick02.bsp";
string MODEL_BRK_BRICK1C = "maps/ad_brk/brick03.bsp";
string MODEL_BRK_BRICK1D = "maps/ad_brk/brick04.bsp";
string MODEL_BRK_BRICK2A = "maps/ad_brk/brick05.bsp";
string MODEL_BRK_BRICK2B = "maps/ad_brk/brick06.bsp";
string MODEL_BRK_BRICK2C = "maps/ad_brk/brick07.bsp";
string MODEL_BRK_BRICK2D = "maps/ad_brk/brick08.bsp";
string MODEL_BRK_BRICK3A = "maps/ad_brk/brick09.bsp";
string MODEL_BRK_BRICK3B = "maps/ad_brk/brick10.bsp";
string MODEL_BRK_BRICK3C = "maps/ad_brk/brick11.bsp";
string MODEL_BRK_BRICK3D = "maps/ad_brk/brick12.bsp";
string MODEL_BRK_BRICK4A = "maps/ad_brk/brick13.bsp";
string MODEL_BRK_BRICK4B = "maps/ad_brk/brick14.bsp";
string MODEL_BRK_BRICK4C = "maps/ad_brk/brick15.bsp";
string MODEL_BRK_BRICK4D = "maps/ad_brk/brick16.bsp";
string MODEL_BRK_BRICK5A = "maps/ad_brk/brick17.bsp";
string MODEL_BRK_BRICK5B = "maps/ad_brk/brick18.bsp";
string MODEL_BRK_BRICK5C = "maps/ad_brk/brick19.bsp";
string MODEL_BRK_BRICK5D = "maps/ad_brk/brick20.bsp";
string MODEL_BRK_BRICK6A = "maps/ad_brk/brick21.bsp";
string MODEL_BRK_BRICK6B = "maps/ad_brk/brick22.bsp";
string MODEL_BRK_BRICK6C = "maps/ad_brk/brick23.bsp";
string MODEL_BRK_BRICK6D = "maps/ad_brk/brick24.bsp";
string MODEL_BRK_BRICK7A = "maps/ad_brk/brick25.bsp";
string MODEL_BRK_BRICK7B = "maps/ad_brk/brick26.bsp";
string MODEL_BRK_BRICK7C = "maps/ad_brk/brick27.bsp";
string MODEL_BRK_BRICK7D = "maps/ad_brk/brick28.bsp";
//----------------------------------------------------------------------
// Axe impact/swipe/miss sounds
string SOUND_AXE_SWIPE1 = "weapons/axe_swoosh1.wav"; // Fast swipe
string SOUND_AXE_SWIPE2 = "weapons/axe_swoosh2.wav"; // Faster swipe
string SOUND_AXE_WOOD = "weapons/axe_wood.wav"; // wood impact
string SOUND_AXE_GLASS = "weapons/axe_glass.wav"; // glass impact
string SOUND_AXE_METAL = "weapons/axe_metal.wav"; // metal impact
string SOUND_AXE_CERAMIC = "weapons/axe_ceramic.wav"; // ceramic impact
string SOUND_AXE_PLAYER = "player/axhit1.wav"; // ax hit meat (another player)
string SOUND_AXE_STONE = "player/axhit2.wav"; // stone impact
//=============================================================================
/*QUAKED func_breakable (0 .5 .8) ? STARTOFF NODAMAGE EXPLOSION SILENT DAMAGE NOMOSTER NOSOUND NOROTATE Not_Easy Not_Normal Not_Hard Not_DM
Spawn breakable objects from a bmodel
-------- KEYS --------
target : targets to fire when breakable is dead/used (only used once)
target2 : Additional trigger function (need target to be defined as well)
style : pre-defined sound/model types - 1=rock, 2=wood, 3=glass, 4=metal, 5=brick, 6=ceramic, 10=custom
brksound : Initial breaking sound type (override style default)
brkimpsound : Impact sound type (override style default)
brkobjects : Breakable object model type (10-15=rocks, 20-22=woods, 30-32=glass, 40-42=metals, 50-54=brick, 60=ceramic)
noise : Initial breaking sound (unique sound file)
noise1 : Custom Rubble Impact sounds (unique sound files, must have 1 defined)
noise2 : Custom Rubble Impact sound 2
noise3 : Custom Rubble Impact sound 3
noise4 : Custom Rubble Impact sound 4
brkobj1 : Custom Rubble bmodel objects (unique models, must have 1 defined)
brkobj2 : Custom Rubble bmodel objects 2
brkobj3 : Custom Rubble bmodel objects 3
brkobj4 : Custom Rubble bmodel objects 4
health : amount of damage to take before breaking (def 1)
count : minimum quantity to spawn (def 4)
cnt : random quantity to spawn (def 4) =-1 no random qty
dmg : explosive radius damage (emits from center of func object)
pos1 : x=start particle colour, y=random range, z=quantity
brkvelbase : Base amount for velocity of broken parts (def "50 50 100")
brkveladd : Random additions for velocity of broken parts (def "100 100 150")
brkavel : Amount of breaking object angle velocity (def 200)
brkfade : Fade time before rubble fades away (def 4+random()x4)
brkmondmg : Damage multiplier for monster damage against breakable
angles : direction to throw rubble (override default = impact direction)
brkgravity : Change the gravity for rubble, useful for underwater (Value = 0-1)
brktrigjump : Trigger if damaged by jumping monster attack
brktrigmissile : Trigger if damaged by rocket/grenade/shalball/radiusdmg
brktrignoplayer: No player/clients can damage this breakable
brkdelaydamage : Time pause before enabling damage on this breakable
-------- SPAWNFLAGS --------
STARTOFF : Will wait for trigger to spawn
NODAMAGE : Cannot be damaged or shot, trigger only
EXPLOSION : trigger sprite/particle explosion
SILENT : No initial breakage sound
DAMAGE : Spawning rubble can damage (def = 2, use dmg key for touch damage)
NOMONSTER : Monsters cannot damage this breakable and/or spawning rubble will not damage monsters
NOSOUND : Spawning rubble has no impact sounds
NOROTATE : Spawning rubble has No Y rotation
-------- NOTES --------
Spawn breakable ojects from a bmodel
*/
//=============================================================================
/*QUAKED func_breakable_spawner (0.5 .5 .8) (-8 -8 -8) (8 8 8) x x EXPLOSION SILENT DAMAGE NOMOSTER NOSOUND NOROTATE Not_Easy Not_Normal Not_Hard Not_DM
Spawn breakable objects from a single point
-------- KEYS --------
target : targets to fire when breakable is dead/used (only used once)
target2 : Additional trigger function (need target to be defined as well)
style : pre-defined sound/model types - 1=rock, 2=wood, 3=glass, 4=metal, 5=brick, 6=ceramic, 10=custom
brksound : Initial breaking sound type (override style default)
brkimpsound : Impact sound type (override style default)
brkobjects : Breakable object model type (10-15=rocks, 20-22=woods, 30-32=glass, 40-42=metals, 50-54=brick, 60=ceramic)
noise : Initial breaking sound (unique sound file)
noise1 : Custom Rubble Impact sounds (unique sound files, must have 1 defined)
noise2 : Custom Rubble Impact sound 2
noise3 : Custom Rubble Impact sound 3
noise4 : Custom Rubble Impact sound 4
brkobj1 : Custom Rubble bmodel objects (unique models, must have 1 defined)
brkobj2 : Custom Rubble bmodel objects 2
brkobj3 : Custom Rubble bmodel objects 3
brkobj4 : Custom Rubble bmodel objects 4
health : amount of damage to take before breaking (def 1)
count : minimum quantity to spawn (def 4)
cnt : random quantity to spawn (def 4) =-1 no random qty
dmg : explosive radius damage (emits from center of func object)
pos1 : x=start particle colour, y=random range, z=quantity
brkvelbase : Base amount for velocity of broken parts (def "50 50 100")
brkveladd : Random additions for velocity of broken parts (def "100 100 150")
brkavel : Amount of breaking object angle velocity (def 200)
brkfade : Fade time before rubble fades away (def 4+random()x4)
angles : direction to throw rubble (override default = impact direction)
brktrigjump : Trigger if damaged by jumping monster attack
brktrigmissile : Trigger if damaged by rocket/grenade/shalball/radiusdmg
brkgravity : Change the gravity for rubble, useful for underwater (Value = 0-1)
brkvol : Spawning volume vector for breakable point entity
-------- SPAWNFLAGS --------
EXPLOSION : trigger sprite/particle explosion
SILENT : No initial breakage sound
DAMAGE : Spawning rubble can damage (def = 2, use dmg key for touch damage)
NOMONSTER : Spawning rubble will not damage monsters
NOSOUND : Spawning rubble has no impact sounds
NOROTATE : Spawning rubble has No Y rotation
-------- NOTES --------
Spawn breakable ojects from a single point
*/
//----------------------------------------------------------------------
void() breakable_remove =
{
self.think = model_fade;
self.nextthink = time + 0.1;
self.ltime = self.nextthink;
};
//----------------------------------------------------------------------
void() breakable_checkfloor =
{
// Is it time for the breakable to fade away?
if (self.pain_finished > time) {
// Check floor below breakable (global function)
// Origin at base of model + 16 (typical step height)
ent_floorcheck(self, FLOOR_TRACE_BREAK);
// Keep checking
self.think = breakable_checkfloor;
self.nextthink = time + 0.1;
}
else breakable_remove();
};
//----------------------------------------------------------------------
void() breakable_particle =
{
// Are particles disabled? has the model touched something?
if (self.wait) {
// Is there any time left to setup floor checks
if (self.pain_finished > time + 0.1) {
self.think = breakable_checkfloor;
}
else self.think = breakable_remove;
self.nextthink = time + 0.1;
}
else {
// Add a bit of randomness to the particles generated
if (random() < 0.5) self.nextthink = time + 0.01;
else self.nextthink = time + 0.02;
particle (self.origin, self.oldorigin, rint(self.pos1_x + random()*self.pos1_y), self.pos1_z);
// Check for removal timer
if (self.pain_finished < time) self.think = breakable_remove;
}
};
//----------------------------------------------------------------------
void() breakable_touch =
{
if (self.touchedvoid) { entity_remove(self,0.1); return; }
if (check_skycontent(self.origin)) {entity_remove(self, 0.1); return;}
if (other.classtype == CT_FUNCBREAKOBJ) return; // Ignore other breakables
if (other.solid == SOLID_TRIGGER) return; // Ignore trigger fields
self.wait = TRUE; // No more particles
if (self.count < 1) {
self.touch = SUB_Null; // no more touching
self.solid = SOLID_NOT; // Turn off world interaction
self.avelocity = '0 0 0'; // Stop any velocity rotation
}
else {
// Does the spawning rubble hurt?
if (self.owner.spawnflags & BREAK_DAMAGE) {
if (other.takedamage) {
// Spawnflag BREAK_NOMONSTER = 32; (No damage to monsters from rubble)
// Was originally about monsters being immune to spawning rubble
// This has changed over time to mean NO monster damage to breakable
// The original intention is still active and really meant for
// breakable spawners which don't have a bmodel component.
// The ability for breakable rubble to injure monsters is not a major
// feature and the no monster damage is more important.
//
if ( other.flags & FL_MONSTER && self.owner.spawnflags & BREAK_NOMONSTER ) {}
else T_Damage(other, self, self, self.owner.dmg, DAMARMOR);
}
}
self.count = self.count - 1;
if (!(self.owner.spawnflags & BREAK_NOSOUND)) {
// Randomize impact sound
self.lip = random()*self.owner.brkimpqty;
if (self.lip < 1) sound(self, CHAN_VOICE, self.owner.noise1, 1, ATTN_BREAK);
else if (self.lip < 2) sound(self, CHAN_VOICE, self.owner.noise2, 1, ATTN_BREAK);
else if (self.lip < 3) sound(self, CHAN_VOICE, self.owner.noise3, 1, ATTN_BREAK);
else sound(self, CHAN_VOICE, self.owner.noise4, 1, ATTN_BREAK);
}
}
};
//----------------------------------------------------------------------
// Can't have multiple radius explosion on same frame
// Always delay them to prevent endless loops
//----------------------------------------------------------------------
void() funcbreakable_delayexplode =
{
T_RadiusDamage (self.attachment, self.attachment, self.dmg, self.attachment, DAMAGEALL);
};
//======================================================================
// Main function for generating rubble
//======================================================================
void() funcbreakable_use =
{
local vector dirvec, rorg, gvel, dirorg;
local float content_loop, content_flag;
if (self.waitmin == TRUE) return; // Trigger once block
if (self.waitmin2 > time) return; // Spawn damage block
//----------------------------------------------------------------------
// Play initial breaking sound (exception - silent spawnflag)
if (!(self.spawnflags & BREAK_SILENT))
sound (self.attachment, CHAN_BODY, self.noise, 1, ATTN_NORM);
//----------------------------------------------------------------------
// Hide breakable func model (ignore if point entity)
if (self.classtype == CT_FUNCBREAK) {
self.waitmin = TRUE; // Only fire breakable ONCE
self.use = SUB_Null; // No more triggers
self.takedamage = DAMAGE_NO; // No more pain/damage
self.model = ""; // hide model
self.modelindex = 0; // Make sure no model
self.solid = SOLID_NOT; // no world interaction
self.movetype = MOVETYPE_NONE;
// Work out bottom corner of min/max bounding box
self.oldorigin = self.attachment.origin - (self.size*0.5);
}
else if (self.classtype == CT_FUNCBREAKMDL) {
self.waitmin = TRUE; // Only fire breakable ONCE
self.use = SUB_Null; // No more triggers
self.takedamage = DAMAGE_NO; // No more pain/damage
self.modelindex = 0; // Make sure no model
self.model = ""; // hide model
self.solid = SOLID_NOT; // no world interaction
self.movetype = MOVETYPE_NONE;
}
// Breakable point entities just keep on spawning rubble
else self.oldorigin = self.origin;
//----------------------------------------------------------------------
// Fire all targets (usually the target is the broken remains)
// Only fire this target once and check based on target field only
// Targets are fired before rubble so pointcontent check can
// take into account any ruined sections and not spawn inside brushwork
if (self.target != "") {
// Check death entity (set in Killed in ai_combat.qc)
if (self.activate.flags & FL_CLIENT) activator = self.activate;
SUB_UseTargets();
}
// Only fire targets once, remove any further triggering
clear_trigstrs(self);
//----------------------------------------------------------------------
// By default all breakables use direction force for rubble
// If angles (movedir) set on breakable use that instead
// Work out direction of impact (using activate passed from combat.qc or trigger.qc)
// All bmodels are labelled with .bsporigin flag (origin = 0,0,0)
// attachment points to self if funcbreakable_wall active
if (self.spawnflags & BREAK_MOVEDIR) dirvec = self.movedir;
else if (!self.activate) dirvec = '0 0 1';
else {
if (self.activate.bsporigin) dirorg = bmodel_origin(self.activate);
else dirorg = self.activate.origin;
dirvec = vectoangles(self.attachment.origin - dirorg);
}
//----------------------------------------------------------------------
// Main rubble loop
while(self.count > 0 && self.brkobjqty > 0) {
//----------------------------------------------------------------------
// Work out position inside bounding box of breakable bmodel
// Just in case no space to spawn anywhere, dont do an infinite loop
content_loop = 4;
while (content_loop > 0) {
// Setup broken object inside area (min/max) of parent breakable object
if (self.classtype == CT_FUNCBREAK) {
rorg_x = self.oldorigin_x + random()*self.size_x;
rorg_y = self.oldorigin_y + random()*self.size_y;
rorg_z = self.oldorigin_z + random()*self.size_z;
}
else {
// trigger version is a single origin point with slight wobble
rorg_x = self.origin_x + crandom()*self.brkvol_x;
rorg_y = self.origin_y + crandom()*self.brkvol_y;
rorg_z = self.origin_z + crandom()*self.brkvol_z;
}
// Check point content and keep checking (limited loop cycles)
content_flag = pointcontents(rorg);
if (content_flag != CONTENT_SOLID && content_flag != CONTENT_SKY) content_loop = -1;
else content_loop = content_loop - 1;
}
//----------------------------------------------------------------------
// Was there any space to spawn rubble?
if (content_loop < 0) {
// Create new piece of rubble
newmis = spawn();
newmis.classname = "rubble";
newmis.classtype = CT_FUNCBREAKOBJ;
newmis.classgroup = CG_TEMPENT;
newmis.owner = self;
// Setup broken model/bsp (random selection if possible)
self.lip = random()*self.brkobjqty;
if (self.lip < 1) setmodel (newmis, self.brkobj1);
else if (self.lip < 2) setmodel (newmis, self.brkobj2);
else if (self.lip < 3) setmodel (newmis, self.brkobj3);
else setmodel (newmis, self.brkobj4);
// Setup origin based on previous content checks
setorigin (newmis, rorg);
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
//----------------------------------------------------------------------
// Setup movement and spin parameters
newmis.movetype = MOVETYPE_BOUNCE;
newmis.solid = SOLID_BBOX;
if (!(self.spawnflags & BREAK_NOROTATE)) newmis.angles_y = random() * 360;
newmis.avelocity = vecrand(0,self.brkavel,TRUE);
//----------------------------------------------------------------------
if (self.spawnflags & BREAK_MOVEDIR && self.movedir_y < 0) {
// Up/down direction with slight X/Y wobble
newmis.velocity_x = crandom() * (self.brkveladd_x/2);
newmis.velocity_y = crandom() * (self.brkveladd_y/2);
if (self.movedir_y == -2) newmis.velocity_z = 0 - random()*50;
else newmis.velocity_z = self.brkvelbase_z + (random()*self.brkveladd_z);
}
else {
// Directional velocity based on angle_y or activator entity
makevectors(dirvec);
gvel = v_forward * (self.brkvelbase_x + random() * self.brkveladd_x);
gvel = gvel + v_right * (crandom() * self.brkveladd_y);
gvel = gvel + v_up * (self.brkvelbase_z + random() * self.brkveladd_z);
newmis.velocity = gvel;
}
//----------------------------------------------------------------------
// How many bounce sounds - randomly pick 1-2
if (random() < 0.2) newmis.count = 2;
else newmis.count = 1;
//----------------------------------------------------------------------
// Touch and eventual fade functions
newmis.touch = breakable_touch;
newmis.pain_finished = time + self.brkfade + random()*self.brkfade;
newmis.nextthink = time + 0.01;
newmis.think = breakable_particle;
//----------------------------------------------------------------------
// Setup particle colour and particle dirction (forward)
newmis.pos1 = self.pos1;
gvel = vectoangles(newmis.velocity);
makevectors(gvel);
newmis.oldorigin = v_forward;
if (self.brkgravity) newmis.gravity = self.brkgravity;
}
//----------------------------------------------------------------------
// Keep on spawning rubble!
self.count = self.count - 1;
}
//----------------------------------------------------------------------
// breakable trigger entities can spawn debris multiple times
if (self.wait != -1) {
self.count = self.height;
}
else {
// Don't need to be active in the world anymore
setsize(self, VEC_ORIGIN, VEC_ORIGIN);
// Produce explosion sprite/particle effect using attachment entity
if (self.spawnflags & BREAK_EXPLOSION) {
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_EXPLOSION);
WriteCoord (MSG_BROADCAST, self.attachment.origin_x);
WriteCoord (MSG_BROADCAST, self.attachment.origin_y);
WriteCoord (MSG_BROADCAST, self.attachment.origin_z);
SpawnExplosion(EXPLODE_SMALL, self.attachment.origin, SOUND_REXP3);
// Do no remove breakable, the rumble is still using
// the noise keys for impact sounds
entity_hide(self);
}
//----------------------------------------------------------------------
// create any explosive damage (do this last!)
// setup the radius explosion as a delay to prevent endless loops
if (self.dmg) {
self.think = funcbreakable_delayexplode;
self.nextthink = time + 0.05;
}
}
};
//----------------------------------------------------------------------
void() funcbreakable_touch =
{
// This only works for monster and special jumping break impacts
if (!(other.flags & FL_MONSTER)) return;
if (other.flags & FL_ONGROUND) return;
if (!self.brktrigjump) return;
self.touch = SUB_Null;
self.use();
};
//----------------------------------------------------------------------
void(entity inflictor, entity attacker, float damage) funcbreakable_pain =
{
local float loop_count;
local vector vel;
// As always there are exceptions
// Grenades bounce and don't impact breakables, cannot tell impact point
// If inflictor the same as attacker then particle impact done already
if (inflictor.classtype == CT_PROJ_GL) return;
if (inflictor == attacker) return;
// Check for player exception on damage to breakable
if (inflictor.flags & FL_CLIENT && self.brktrignoplayer) return;
if (attacker.flags & FL_CLIENT && self.brktrignoplayer) return;
// Something is trying to wear down the breakable with damage
// work out facing angle and project particles upward
makevectors(inflictor.angles);
vel = -v_up*2;
while(loop_count < 4) {
particle (inflictor.origin, vel*0.1, self.bleedcolour + rint(random()*7), damage);
loop_count = loop_count + 1;
}
};
//----------------------------------------------------------------------
void() funcbreakable_death =
{
if (self.spawnflags & BREAK_NOSHOOT) return;
// Check for player exception on damage to breakable
else if (self.activate.flags & FL_CLIENT && self.brktrignoplayer) return;
else funcbreakable_use();
};
//======================================================================
// Setup all model/sound pre-cache
//----------------------------------------------------------------------
void() breakable_cache =
{
//----------------------------------------------------------------------
// If a breakable style key setup, update all undefined sounds/model keys
if (self.style > 0 && self.style < BTYPE_CUSTOM) {
if (!self.brksound) self.brksound = self.style;
if (!self.brkimpsound) self.brkimpsound = self.style;
if (!self.brkobjects) self.brkobjects = self.style*10;
}
else return;
//----------------------------------------------------------------------
// make sure initial break sound is within range types (def=rock)
if (self.brksound < BTYPE_ROCK || self.brksound > BTYPE_CUSTOM)
self.brksound = BTYPE_ROCK;
if (self.brksound == BTYPE_ROCK || self.brksound == BTYPE_BRICK) {
self.noise = SOUND_BRK_ROCK;
// Heavy rocks don't move around too much
if (!self.brkvelbase) self.brkvelbase = '0 0 50';
if (!self.brkveladd) self.brkveladd = '100 100 100';
}
else if (self.brksound == BTYPE_WOOD) {
self.noise = SOUND_BRK_WOOD;
if (!self.brkvelbase) self.brkvelbase = '50 50 50';
if (!self.brkveladd) self.brkveladd = '100 100 50';
}
else if (self.brksound == BTYPE_GLASS) {
self.noise = SOUND_BRK_GLASS;
if (!self.brkvelbase) self.brkvelbase = '50 50 50';
if (!self.brkveladd) self.brkveladd = '100 100 150';
}
else if (self.brksound == BTYPE_METAL) {
self.noise = SOUND_BRK_METAL;
if (!self.brkvelbase) self.brkvelbase = '50 50 100';
if (!self.brkveladd) self.brkveladd = '100 100 150';
}
else if (self.brksound == BTYPE_CERAMIC) {
self.noise = SOUND_BRK_CERAMIC;
if (!self.brkvelbase) self.brkvelbase = '50 50 50';
if (!self.brkveladd) self.brkveladd = '100 100 150';
}
else if (self.noise == "") {
self.noise = SOUND_EMPTY;
if (!self.brkvelbase) self.brkvelbase = '50 50 50';
if (!self.brkveladd) self.brkveladd = '100 100 150';
}
precache_sound(self.noise);
//----------------------------------------------------------------------
// make sure impack sounds are within range types (def=rock)
if (self.brkimpsound < BTYPE_ROCK || self.brkimpsound > BTYPE_CUSTOM)
self.brkimpsound = BTYPE_ROCK;
// All the impact sounds come in sets of 4 for random variety
self.brkimpqty = 4;
if (self.brkimpsound == BTYPE_ROCK || self.brkimpsound == BTYPE_BRICK) {
if (self.noise1 == "") self.noise1 = SOUND_IMP_ROCK1;
if (self.noise2 == "") self.noise2 = SOUND_IMP_ROCK2;
if (self.noise3 == "") self.noise3 = SOUND_IMP_ROCK3;
if (self.noise4 == "") self.noise4 = SOUND_IMP_ROCK4;
}
else if (self.brkimpsound == BTYPE_WOOD) {
if (self.noise1 == "") self.noise1 = SOUND_IMP_WOOD1;
if (self.noise2 == "") self.noise2 = SOUND_IMP_WOOD2;
if (self.noise3 == "") self.noise3 = SOUND_IMP_WOOD3;
if (self.noise4 == "") self.noise4 = SOUND_IMP_WOOD4;
}
else if (self.brkimpsound == BTYPE_GLASS) {
if (self.noise1 == "") self.noise1 = SOUND_IMP_GLASS1;
if (self.noise2 == "") self.noise2 = SOUND_IMP_GLASS2;
if (self.noise3 == "") self.noise3 = SOUND_IMP_GLASS3;
if (self.noise4 == "") self.noise4 = SOUND_IMP_GLASS4;
}
else if (self.brkimpsound == BTYPE_METAL) {
if (self.noise1 == "") self.noise1 = SOUND_IMP_METAL1;
if (self.noise2 == "") self.noise2 = SOUND_IMP_METAL2;
if (self.noise3 == "") self.noise3 = SOUND_IMP_METAL3;
if (self.noise4 == "") self.noise4 = SOUND_IMP_METAL4;
}
else if (self.brkimpsound == BTYPE_CERAMIC) {
if (self.noise1 == "") self.noise1 = SOUND_IMP_CERAMIC1;
if (self.noise2 == "") self.noise2 = SOUND_IMP_CERAMIC2;
if (self.noise3 == "") self.noise3 = SOUND_IMP_CERAMIC3;
if (self.noise4 == "") self.noise4 = SOUND_IMP_CERAMIC4;
}
else {
// Workout total amount of active impact sounds (custom can have <4)
// empty slots are filled up with empty sounds for precache reasons
if (self.noise2 != "") {
if (self.noise3 != "") {
if (self.noise4 != "") self.brkimpqty = 4;
else self.brkimpqty = 3;
}
else self.brkimpqty = 2;
}
else self.brkimpqty = 1;
// Cannot have no impact sounds, always setup one sound by default
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '80 8 1'; // Green/Brown
if (self.noise1 == "") self.noise1 = SOUND_IMP_ROCK1;
if (self.noise2 == "") self.noise2 = SOUND_EMPTY;
if (self.noise3 == "") self.noise3 = SOUND_EMPTY;
if (self.noise4 == "") self.noise4 = SOUND_EMPTY;
}
precache_sound (self.noise1);
precache_sound (self.noise2);
precache_sound (self.noise3);
precache_sound (self.noise4);
//----------------------------------------------------------------------
// make sure breakable objects are within range types (def=rock)
//----------------------------------------------------------------------
if (self.brkobjects < BMODTYPE_SELF) self.brkobjects = BMODTYPE_ROCK1;
// Two ways the models can be defined, using the default bmodel types
// or specifying the models via the brkobjs strings
// If the first string is empty, then the default = 4 (pre-defined)
// If first string defined, then count the model strings
// If the first string is not empty then the mapper has to specify
// exactly what rubble models to choose from (randomly)
// The auto fill option (style/brkobjects) only if brkobj1 = empty
if (self.brkobj1 == "") self.brkobjqty = 4; // Default
else {
// Need to check for custom breakable quantity first
if (self.brkobj2 != "") {
if (self.brkobj3 != "") {
if (self.brkobj4 != "") self.brkobjqty = 4;
else self.brkobjqty = 3;
}
else self.brkobjqty = 2;
}
else self.brkobjqty = 1;
}
// All the broken models come in sets of 4 for random variety
// if brkobjects is set to a custom value then model strings will be filled in
// with a blank string and the brkobjqty will be adjusted to actual quanity
if (self.brkobjects == BMODTYPE_ROCK1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '80 8 1'; // Green/Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK1B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK1C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK1D;
}
else if (self.brkobjects == BMODTYPE_ROCK2) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '48 8 1'; // Green
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK2A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK2B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK2C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK2D;
}
else if (self.brkobjects == BMODTYPE_ROCK3) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '20 8 1'; // Light Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK3D;
}
else if (self.brkobjects == BMODTYPE_ROCK4) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '168 8 1'; // Whiteish
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK4A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK4B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK4C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK4D;
}
else if (self.brkobjects == BMODTYPE_ROCK5) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '32 8 1'; // Blue
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK5A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK5B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK5C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK5D;
}
else if (self.brkobjects == BMODTYPE_ROCK6) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '1 8 1'; // Black
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK6A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_ROCK6B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_ROCK6C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_ROCK6D;
}
else if (self.brkobjects == BMODTYPE_WOOD1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '16 8 1'; // Dark Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_WOOD1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_WOOD1B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_WOOD1C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_WOOD1D;
}
else if (self.brkobjects == BMODTYPE_WOOD2) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '112 8 1'; // Light Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_WOOD2A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_WOOD2B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_WOOD2C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_WOOD2D;
}
else if (self.brkobjects == BMODTYPE_WOOD3) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '80 8 1'; // Green/Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_WOOD3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_WOOD3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_WOOD3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_WOOD3D;
}
else if (self.brkobjects == BMODTYPE_GLASS1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '32 8 1'; // Blue
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_GLASS1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_GLASS1B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_GLASS1C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_GLASS1D;
}
else if (self.brkobjects == BMODTYPE_GLASS2) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '64 8 1'; // Red
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_GLASS2A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_GLASS2B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_GLASS2C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_GLASS2D;
}
else if (self.brkobjects == BMODTYPE_GLASS3) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '96 8 1'; // Pink/Yellow
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_GLASS3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_GLASS3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_GLASS3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_GLASS3D;
}
else if (self.brkobjects == BMODTYPE_GLASS4) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '128 4 1'; // Purple
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_GLASS4A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_GLASS4B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_GLASS4C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_GLASS4D;
}
else if (self.brkobjects == BMODTYPE_METAL1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '16 8 1'; // Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_METAL1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_METAL1B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_METAL1C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_METAL1D;
}
else if (self.brkobjects == BMODTYPE_METAL2) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '80 8 1'; // Green/Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_METAL2A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_METAL2B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_METAL2C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_METAL2D;
}
else if (self.brkobjects == BMODTYPE_METAL3) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '48 8 1'; // Green
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_METAL3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_METAL3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_METAL3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_METAL3D;
}
else if (self.brkobjects == BMODTYPE_METAL4) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '48 8 1'; // Green
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_METAL4A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_METAL4B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_METAL4C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_METAL4D;
}
else if (self.brkobjects == BMODTYPE_METAL5) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '32 8 1'; // Blue
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_METAL5A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_METAL5B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_METAL5C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_METAL5D;
}
else if (self.brkobjects == BMODTYPE_BRICK1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '16 8 1'; // Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK1B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK1C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK1D;
}
else if (self.brkobjects == BMODTYPE_BRICK2) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '80 8 1'; // Green/Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK2A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK2B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK2C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK2D;
}
else if (self.brkobjects == BMODTYPE_BRICK3) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '48 8 1'; // Green
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK3D;
}
else if (self.brkobjects == BMODTYPE_BRICK4) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '0 8 1'; // Grey/White
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK4A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK4B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK4C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK4D;
}
else if (self.brkobjects == BMODTYPE_BRICK5) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '16 8 1'; // Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK5A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK5B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK5C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK5D;
}
else if (self.brkobjects == BMODTYPE_BRICK6) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '16 8 1'; // Brown
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK6A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK6B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK6C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK6D;
}
else if (self.brkobjects == BMODTYPE_BRICK7) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '32 8 1'; // Blue
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_BRICK7A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_BRICK7B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_BRICK7C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_BRICK7D;
}
else if (self.brkobjects == BMODTYPE_CERAMIC1) {
if (self.pos1_x + self.pos1_y == 0) self.pos1 = '96 8 1'; // Pink/Yellow
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_GLASS3A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_BRK_GLASS3B;
if (self.brkobj3 == "") self.brkobj3 = MODEL_BRK_GLASS3C;
if (self.brkobj4 == "") self.brkobj4 = MODEL_BRK_GLASS3D;
}
else {
// Cannot have no breakable models, always setup one model by default
if (self.brkobj1 == "") self.brkobj1 = MODEL_BRK_ROCK1A;
if (self.brkobj2 == "") self.brkobj2 = MODEL_EMPTY;
if (self.brkobj3 == "") self.brkobj3 = MODEL_EMPTY;
if (self.brkobj4 == "") self.brkobj4 = MODEL_EMPTY;
}
precache_model(self.brkobj1);
precache_model(self.brkobj2);
precache_model(self.brkobj3);
precache_model(self.brkobj4);
};
//----------------------------------------------------------------------
// Setup defaults
//----------------------------------------------------------------------
void() breakable_defaults =
{
// Setup breakable use blockers (trig once, delay damage)
self.waitmin = self.waitmin2 = FALSE;
// Setup rubble counter = count + random*cnt
if (self.count <= 0) self.count = 4;
if (self.cnt == 0) self.cnt = 4;
// If cnt = -1 then don't do any random factor, just exact amount
if (self.cnt < 0) self.height = self.count;
else self.height = self.count + rint(random()*self.cnt);
// Backup rubble count for later (point entity)
self.count = self.height;
// Special conditions - jump only, missile only, no health
if (self.brktrigjump < 0 || self.brktrigmissile < 0 || self.health < 0)
self.spawnflags = self.spawnflags | BREAK_NOSHOOT;
// Setup default health
if (self.health == 0) self.health = 1;
// Rubble damage overrides any explosion damage
if (self.spawnflags & BREAK_DAMAGE) {
if (self.dmg <= 0) self.dmg = 2;
}
else if (self.spawnflags & BREAK_EXPLOSION) {
if (self.dmg <= 0) self.dmg = DAMAGE_MONROCKET;
}
// Setup default base + additional velocity and angle spin
if (!self.brkvelbase) self.brkvelbase = '50 50 100';
if (!self.brkveladd) self.brkveladd = '100 100 150';
if (self.brkavel <= 0) self.brkavel = 200;
if (self.brkfade <= 0) self.brkfade = 4;
// angles has to be 0 0 0 otherwise brush model is twisted
// Is there any angle direction defined for impact force direction
if (self.angles_y) {
self.movedir_y = self.angles_y;
self.spawnflags = self.spawnflags | BREAK_MOVEDIR;
}
self.mangle = self.angles = '0 0 0';
// reset velocity / avelocity just in case of rogue key fields
self.velocity = self.avelocity = '0 0 0';
// Can fire other triggers, make sure no delay on breakable function
self.delay = 0;
};
//----------------------------------------------------------------------
void() funcbreakable_damageon =
{
self.takedamage = DAMAGE_YES;
self.th_pain = funcbreakable_pain;
self.th_die = funcbreakable_death;
};
//----------------------------------------------------------------------
void() breakable_on =
{
// Make sure use function reset
self.use = SUB_Null;
// Setup collision based on bmodel type
if (self.bsporigin) {
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
setmodel (self, self.model);
setorigin (self, self.origin);
setsize (self, self.mins , self.maxs);
// Used for sound, radius damage and explosion
self.attachment = spawn();
self.attachment.origin = bmodel_origin(self);
setorigin(self.attachment, self.attachment.origin);
}
else {
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_NONE;
setmodel (self, self.mdl);
setorigin (self, self.origin);
setsize (self, self.bbmins , self.bbmaxs);
self.angles = self.pos2;
// Used for sound, radius damage and explosion
self.attachment = self;
}
if (self.spawnflags & BREAK_NOSHOOT) {
self.takedamage = DAMAGE_NO;
self.th_pain = SUB_Null_pain;
self.th_die = SUB_Null;
}
else {
// Check for any damage pause before spawning new breakable
// This is to prevent any breakables from spawning and
// instantly being destroyed by radius damage from explosions
// Really designed for multiple break stages
if (self.brkdelaydamage > 0) {
self.takedamage = DAMAGE_NO;
self.th_pain = SUB_Null_pain;
self.th_die = SUB_Null;
self.think = funcbreakable_damageon;
self.nextthink = self.waitmin2 = time + self.brkdelaydamage;
}
else {
self.takedamage = DAMAGE_YES;
self.th_pain = funcbreakable_pain;
self.th_die = funcbreakable_death;
}
self.bleedcolour = self.pos1_x;
// Setup location (origin) to spawn particles
if (self.bsporigin) self.oldorigin = bmodel_origin(self);
else self.oldorigin = self.origin;
}
// Originally had this as a targetname condition because
// if the breakable is used then it needs a referenced name
// Since adding brktrigjump, brktrigmissile ent keys there are
// external trigger events which need breakable entities to react
self.use = funcbreakable_use;
// Jumping monsters don't often hit the breakable object easily
// Easier to trap jump impact triggering with a touch function
if (self.brktrigjump) self.touch = funcbreakable_touch;
};
//======================================================================
// Basic workhorse of breakable system (bmodel)
//======================================================================
void() func_breakable =
{
if (check_bmodel_keys()) return; // Check for bmodel errors
// reset out of range styles and setup default = BTYPE_ROCK (1)
if (self.style < BTYPE_ROCK || self.style > BTYPE_MAX) self.style = BTYPE_ROCK;
// precache all sound/model stuff
breakable_cache();
breakable_defaults();
// No point setting up a breakable if it cannot be triggered or shoot
if (self.targetname == "" && self.spawnflags & BREAK_NOSHOOT) {
dprint("\b[BRKMDL]\b Cannot be triggered or shoot, removed\n");
setmodel (self, self.model); // set size and link into world
setorigin (self, self.origin);
setsize (self, self.mins , self.maxs);
self.oldorigin = bmodel_origin(self);
spawn_marker(self.oldorigin, SPNMARK_YELLOW);
dprint("\b[BRKMDL]\b Origin - ");
dprint(vtos(self.oldorigin));
dprint("\n");
remove(self);
return;
}
self.classtype = CT_FUNCBREAK;
self.classgroup = CG_BREAKABLE;
self.bsporigin = TRUE; // bmodel origin 0,0,0
self.wait = -1; // Always work once, cannot unbreak
if (self.spawnflags & BREAK_STARTOFF) self.use = breakable_on;
else breakable_on();
};
//----------------------------------------------------------------------
// Point entity version of breakbles
// Good for producing rubble from areas unreachable players
// Earthquake and rubble and dust from ceilings
//----------------------------------------------------------------------
void() func_breakable_spawner =
{
// Check for targetname
if (self.targetname == "") {
dprint("\b[BRKTRIG]\b Missing targetname\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
// reset out of range styles and setup default = BTYPE_ROCK (1)
if (self.style < BTYPE_ROCK || self.style > BTYPE_MAX) self.style = BTYPE_ROCK;
// precache all sound/model stuff
breakable_cache();
breakable_defaults();
self.classtype = CT_FUNCBREAKSPN;
self.classgroup = CG_BREAKABLE;
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
if (self.brkvol_x < 8) self.brkvol_x = 8;
if (self.brkvol_y < 8) self.brkvol_y = 8;
if (self.brkvol_z < 8) self.brkvol_z = 8;
// Already a point entity, don't need to generate anything else
self.attachment = self;
self.use = funcbreakable_use;
};
//======================================================================
// General purpose bmodel with toggle states and solid/fade functions
// Could be used for all sorts of situations not involving breakables
//======================================================================
/*QUAKED func_breakable_wall (0 .5 .8) ? START_ON SOLID FADEOUT
Switchable bmodel for breakable setups with optional collision
-------- KEYS --------
targetname : trigger entity (works with entity state system)
wait : set to -1 for trigger once condition (def=0)
waitmin : random time to wait before fading out
-------- SPAWNFLAGS --------
START_ON : Will spawn visible and wait for trigger
SOLID : Will block player/monster movement
FADEOUT : Will fade out after a certain amount of time if visible!
-------- NOTES --------
Switchable bmodel for breakable setups with optional collision
=============================================================================*/
void() breakable_wall_state =
{
// Show model and setup collision state
if (self.state == STATE_ENABLED) {
// Does the bmodel require any solid collision?
if (self.spawnflags & BREAKWALL_SOLID) {
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
}
else {
// Bmodel visiable, no collision
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
}
// Add bmodel to the world
setmodel (self, self.mdl);
}
// hide model
else {
self.solid = SOLID_NOT;
self.movetype = MOVETYPE_NONE;
self.modelindex = 0;
self.model = "";
}
};
//----------------------------------------------------------------------
void() breakable_wall_use =
{
if (self.estate & ESTATE_BLOCK) return;
if (self.attack_finished > time) return;
// Setup to trigger once?
if (self.wait < 0) self.attack_finished = LARGE_TIMER;
// Toggle bmodel visible state
if (self.state == STATE_ENABLED) self.state = STATE_DISABLED;
else self.state = STATE_ENABLED;
// make sure model state is correct
breakable_wall_state();
// Is the breakable wall visible and designed to fade away?
if (self.spawnflags & BREAKWALL_FADEOUT && self.state == STATE_ENABLED) {
// Setup random timer and fade away!
self.nextthink = time + self.waitmin + random()*self.waitmin;
self.think = breakable_remove;
// Change the model type and state otherwise will not alpah fade
self.movetype = MOVETYPE_NONE;
self.solid = SOLID_BBOX;
}
};
//----------------------------------------------------------------------
void() func_breakable_wall =
{
if (check_bmodel_keys()) return; // Check for bmodel errors
self.classtype = CT_FUNCBREAKWALL;
self.classgroup = CG_BREAKABLE;
self.bsporigin = TRUE; // bmodel origin 0,0,0
self.angles = '0 0 0'; // Stop model twisting
self.mdl = self.model; // Save for later
if (check_bmodel_keys()) return; // Check for bmodel errors
// Make sure the bmodel is active in the world before changing state
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
setmodel (self, self.mdl);
// Something that fades away does it only once
if (self.spawnflags & BREAKWALL_FADEOUT) self.wait = -1;
// Default fade time = time+fade+random()*fade
if (!self.waitmin) self.waitmin = 4;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = breakable_wall_use;
self.estate = ESTATE_ON;
if (self.spawnflags & BREAKWALL_START_ON) self.state = STATE_ENABLED;
else self.state = STATE_DISABLED;
breakable_wall_state();
};
//======================================================================
// A point entity which triggers a breakable and monster together
//======================================================================
/*QUAKED trigger_monsterbreak (.8 .5 .8) (-8 -8 -8) (8 8 8) NODELAY WAKEANIM
Trigger (once) breakable and monster together
-------- KEYS --------
targetname : trigger entity (works with entity state system)
target : points to func_breakable (single target)
target2 : points to a monster (single target)
wait : time before breakable is triggered (def 0.2)
-------- SPAWNFLAGS --------
NODELAY : No delay between monster and breakable trigger
WAKEANIM : Will do special wakeup animation when triggered
-------- NOTES --------
Trigger (once) breakable and monster together
======================================================================*/
void() trig_monbreak_delay =
{
// Check for breakable targetname first
if (self.target != "") {
// Find breakable (single target only)
self.owner = find(world,targetname,self.target);
if (self.owner.classtype == CT_FUNCBREAK) {
self.owner.activate = self;
self = self.owner;
self.use();
}
}
};
//----------------------------------------------------------------------
void() trig_monbreak_use =
{
if (self.estate & ESTATE_BLOCK) return;
if (self.attack_finished > time) return;
// Trigger once
self.attack_finished = LARGE_TIMER;
// Any monster defined to wakeup?
if (self.target2 != "") {
// Find monster (single target only)
self.enemy = find(world,targetname,self.target2);
if (self.enemy.flags & FL_MONSTER) {
if (self.spawnflags & MONTRIG_WAKEUPANIM) self.enemy.wakeuptrigger = TRUE;
trigger_ent(self.enemy, other);
}
}
// Time to explode breakable?
if (self.spawnflags & MONTRIG_NODELAY) trig_monbreak_delay();
else {
// setup delay to trigger breakable wall
self.nextthink = time + self.wait;
self.think = trig_monbreak_delay;
}
};
//----------------------------------------------------------------------
void() trigger_monsterbreak =
{
self.classtype = CT_TRIGMONBREAK;
self.classgroup = CG_BREAKABLE;
if (self.wait <= 0) self.wait = 0.2;
// Setup Entity State functionality
if (self.targetname != "") self.use = entity_state_use;
self.estate_use = trig_monbreak_use;
self.estate = ESTATE_ON;
};
/*======================================================================
/*QUAKED misc_breakable_pot1 (1 .5 .25) (-24 -24 -32) (24 24 32) STARTOFF NODAMAGE EXPLOSION SILENT DAMAGE NOMOSTER NOSOUND NOROTATE
{ model(":progs/misc_pot1.mdl"); }
Breakable ceramic Pot 1 with handles
-------- KEYS --------
target : Additional targets to fire when model breaks
mangle : Override model orientation (Pitch Yaw Roll)
exactskin : 0=Default, 1=pattern 2, 2=pattern 3, 3= pattern 4
health : amount of damage to take before breaking (def 1)
count : minimum quantity to spawn (def 4)
cnt : random quantity to spawn (def 4) final qty = count + random(cnt)
dmg : explosive radius damage (emits from center of func object)
pos1 : x=start particle colour, y=random range, z=quantity
brkvelbase : Base amount for velocity of broken parts (def "50 50 100")
brkveladd : Random additions for velocity of broken parts (def "100 100 150")
brkavel : Amount of breaking object angle velocity (def 200)
brkfade : Fade time before rubble fades away (def 4+random()x4)
angles : direction to throw rubble (override default = impact direction)
brkgravity : will change the gravity for rubble, useful for underwater
-------- SPAWNFLAGS --------
STARTOFF : Will wait for trigger to spawn
NODAMAGE : Cannot be damaged or shot, trigger only
EXPLOSION : trigger sprite/particle explosion
SILENT : No initial breakage sound
DAMAGE : Spawning rubble can damage (def = 2, use dmg key for touch damage)
NOMONSTER : monsters cannot damage this breakable and/or spawning rubble will not damage monsters
NOSOUND : Spawning rubble has no impact sounds
NOROTATE : Spawning rubble has No Y rotation
-------- NOTES --------
Breakable ceramic Pot 1 with handles
======================================================================*/
void() misc_breakable_pot1 =
{
self.classtype = CT_FUNCBREAKMDL;
self.classgroup = CG_BREAKABLE;
self.bbmins = '-16 -16 -24';
self.bbmaxs = '16 16 24';
self.brkvol = '32 32 48';
self.mdl = "progs/misc_pot1.mdl";
precache_model(self.mdl);
self.skin = self.exactskin;
self.style = BTYPE_CERAMIC;
self.brkobjects = BMODTYPE_CUSTOM;
self.brkobj1 = "progs/misc_potshard1.mdl";
self.brkobj2 = "progs/misc_potshard2.mdl";
self.brkobj3 = "progs/misc_potshard3.mdl";
self.brkobj4 = "progs/misc_potshard4.mdl";
self.pos1 = '16 8 1'; // Brown
self.wait = -1; // Only break once
// Save any custom angles, otherwise random Y rotation
self.pos2 = '0 0 0';
if (CheckZeroVector(self.mangle) == FALSE) self.pos2 = self.mangle;
else self.pos2_y = rint(random()*359);
// precache all sound/model stuff
breakable_cache();
breakable_defaults();
if (self.spawnflags & BREAK_STARTOFF) self.use = breakable_on;
else breakable_on();
};