1421 lines
61 KiB
Plaintext
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();
|
|
};
|