2019-08-31 19:18:15 -07:00
|
|
|
/*
|
2020-04-07 05:46:23 -07:00
|
|
|
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
|
2019-08-31 19:18:15 -07:00
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
2018-12-30 17:00:38 -08:00
|
|
|
|
2019-09-06 20:37:06 -07:00
|
|
|
/*QUAKED func_breakable (0 .5 .8) ? SF_TRIGGER SF_TOUCH SF_PRESSURE
|
|
|
|
"targetname" Name
|
|
|
|
"target" Target when triggered.
|
|
|
|
"killtarget" Target to kill when triggered.
|
|
|
|
"material" Material it's made of.
|
|
|
|
"delay" Delay in seconds of when it breaks under pressure.
|
|
|
|
"explodemagnitude" Strength of the explosion.
|
|
|
|
|
|
|
|
Brush volume that can break into lots of little pieces.
|
|
|
|
|
2020-03-23 23:26:49 -07:00
|
|
|
Material list:
|
|
|
|
|
|
|
|
0 = Glass
|
|
|
|
1 = Wood
|
|
|
|
2 = Metal
|
|
|
|
3 = Flesh
|
|
|
|
4 = Cinder
|
|
|
|
5 = Tile
|
|
|
|
6 = Computer
|
|
|
|
7 = Glass (Unbreakable)
|
|
|
|
8 = Rock
|
|
|
|
9 = None
|
|
|
|
|
2019-09-06 20:37:06 -07:00
|
|
|
When SF_TOUCH is set, it'll break when an entity runs into it at high
|
|
|
|
velocities (damage is speed in units * 0.01).
|
|
|
|
|
|
|
|
When SF_PRESSURE is set, it'll collapse once someone is standing on top of it.
|
|
|
|
At that point the "delay" key will decide after how many seconds the object
|
|
|
|
breaks.
|
|
|
|
|
|
|
|
The strength of the explosion decides the radius (magnitude * 2.5) and the
|
|
|
|
maximum damage the explosion will do (you have to stand in the center for that).
|
2018-12-30 17:00:38 -08:00
|
|
|
*/
|
|
|
|
|
|
|
|
enumflags
|
|
|
|
{
|
|
|
|
SF_TRIGGER,
|
|
|
|
SF_TOUCH,
|
2020-09-08 16:56:46 -07:00
|
|
|
SF_PRESSURE
|
2018-12-30 17:00:38 -08:00
|
|
|
};
|
|
|
|
|
2019-10-18 19:30:29 -07:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
BREAKMT_GLASS,
|
|
|
|
BREAKMT_WOOD,
|
|
|
|
BREAKMT_METAL,
|
|
|
|
BREAKMT_FLESH,
|
|
|
|
BREAKMT_CINDER,
|
|
|
|
BREAKMT_TILE,
|
|
|
|
BREAKMT_COMPUTER,
|
|
|
|
BREAKMT_GLASS_UNBREAKABLE,
|
|
|
|
BREAKMT_ROCK,
|
|
|
|
BREAKMT_NONE
|
|
|
|
};
|
|
|
|
|
2020-05-30 05:57:01 -07:00
|
|
|
#ifdef VALVE
|
|
|
|
const string funcbreakable_objtable[22] = {
|
|
|
|
"",
|
|
|
|
"spawnfunc_item_battery",
|
|
|
|
"spawnfunc_item_healthkit",
|
|
|
|
"weapon_9mmhandgun",
|
|
|
|
"weapon_9mmclip",
|
|
|
|
"weapon_9mmAR",
|
|
|
|
"spawnfunc_ammo_9mmAR",
|
|
|
|
"spawnfunc_ammo_ARgrenades",
|
|
|
|
"weapon_shotgun",
|
|
|
|
"spawnfunc_ammo_buckshot",
|
|
|
|
"weapon_crossbow",
|
|
|
|
"spawnfunc_ammo_crossbow",
|
|
|
|
"weapon_357",
|
|
|
|
"spawnfunc_ammo_357",
|
|
|
|
"weapon_rpg",
|
|
|
|
"spawnfunc_ammo_rpgclip",
|
|
|
|
"spawnfunc_ammo_gaussclip",
|
|
|
|
"weapon_hegrenade",
|
|
|
|
"weapon_tripmine",
|
|
|
|
"weapon_satchel",
|
|
|
|
"weapon_snark",
|
|
|
|
"weapon_hornetgun"
|
|
|
|
};
|
|
|
|
#else
|
|
|
|
const string funcbreakable_objtable[] = {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2019-01-04 17:02:12 -08:00
|
|
|
class func_breakable:CBaseTrigger
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2020-08-07 14:15:37 -07:00
|
|
|
int m_iMaterial;
|
2019-01-02 17:26:39 -08:00
|
|
|
float m_flDelay;
|
2019-01-10 02:16:04 -08:00
|
|
|
float m_flExplodeMag;
|
2020-05-30 05:57:01 -07:00
|
|
|
string m_strBreakSpawn;
|
|
|
|
|
2019-01-02 17:26:39 -08:00
|
|
|
/*entity m_pressAttacker;
|
|
|
|
int m_pressType;
|
|
|
|
int m_pressDamage;*/
|
|
|
|
|
2020-04-12 06:50:42 -07:00
|
|
|
void(void) func_breakable;
|
|
|
|
virtual void(void) Respawn;
|
|
|
|
virtual void(void) Explode;
|
2020-08-10 03:32:18 -07:00
|
|
|
virtual void(entity, int) Trigger;
|
2020-04-12 06:50:42 -07:00
|
|
|
virtual void(void) PlayerTouch;
|
|
|
|
/*virtual void(void) PressureDeath;*/
|
2020-08-31 01:56:31 -07:00
|
|
|
virtual void(void) Pain;
|
|
|
|
virtual void(void) Death;
|
2020-09-08 16:56:46 -07:00
|
|
|
virtual void(string, string) SpawnKey;
|
2018-12-30 17:00:38 -08:00
|
|
|
};
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
2020-08-31 01:56:31 -07:00
|
|
|
func_breakable::Pain(void)
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2019-01-02 17:26:39 -08:00
|
|
|
if (spawnflags & SF_TRIGGER) {
|
|
|
|
return;
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
|
|
|
if (serverkeyfloat("*bspversion") != 30) {
|
|
|
|
return;
|
|
|
|
}
|
2019-01-04 17:02:12 -08:00
|
|
|
|
|
|
|
switch (m_iMaterial) {
|
2019-10-18 19:30:29 -07:00
|
|
|
case BREAKMT_GLASS:
|
|
|
|
case BREAKMT_COMPUTER:
|
|
|
|
case BREAKMT_GLASS_UNBREAKABLE:
|
2019-01-04 17:02:12 -08:00
|
|
|
sound(self, CHAN_VOICE, sprintf("debris/glass%d.wav", random(1, 4)), 1.0, ATTN_NORM);
|
2018-12-30 17:00:38 -08:00
|
|
|
break;
|
2019-10-18 19:30:29 -07:00
|
|
|
case BREAKMT_WOOD:
|
2019-01-04 17:02:12 -08:00
|
|
|
sound(self, CHAN_VOICE, sprintf("debris/wood%d.wav", random(1, 4)), 1.0, ATTN_NORM);
|
2018-12-30 17:00:38 -08:00
|
|
|
break;
|
2019-10-18 19:30:29 -07:00
|
|
|
case BREAKMT_METAL:
|
2019-01-04 17:02:12 -08:00
|
|
|
sound(self, CHAN_VOICE, sprintf("debris/metal%d.wav", random(1, 4)), 1.0, ATTN_NORM);
|
2018-12-30 17:00:38 -08:00
|
|
|
break;
|
2019-10-18 19:30:29 -07:00
|
|
|
case BREAKMT_FLESH:
|
2019-01-04 17:02:12 -08:00
|
|
|
float fRand = floor(random(1, 8));
|
2019-01-02 17:26:39 -08:00
|
|
|
/* There never was a flesh4.wav */
|
2019-01-04 17:02:12 -08:00
|
|
|
if (fRand == 4) {
|
2018-12-30 17:00:38 -08:00
|
|
|
fRand = 5;
|
|
|
|
}
|
2019-01-04 17:02:12 -08:00
|
|
|
sound(self, CHAN_VOICE, sprintf("debris/flesh%d.wav", fRand), 1.0, ATTN_NORM);
|
2018-12-30 17:00:38 -08:00
|
|
|
break;
|
2019-10-18 19:30:29 -07:00
|
|
|
case BREAKMT_CINDER:
|
|
|
|
case BREAKMT_ROCK:
|
2019-01-04 17:02:12 -08:00
|
|
|
sound(self, CHAN_VOICE, sprintf("debris/concrete%d.wav", random(1, 4)), 1.0, ATTN_NORM);
|
2018-12-30 17:00:38 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 12:01:24 -07:00
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
|
|
|
func_breakable::Explode(void)
|
2019-03-19 12:01:24 -07:00
|
|
|
{
|
2020-05-30 05:57:01 -07:00
|
|
|
vector rp = absmin + (0.5 * (absmax - absmin));
|
2020-05-07 22:44:40 -07:00
|
|
|
FX_BreakModel(vlen(size) / 10, absmin, absmax, [0,0,0], m_iMaterial);
|
2020-05-30 05:57:01 -07:00
|
|
|
FX_Explosion(rp);
|
|
|
|
Damage_Radius(rp, this, m_flExplodeMag, m_flExplodeMag * 2.5f, TRUE, 0);
|
2020-08-10 03:32:18 -07:00
|
|
|
CBaseTrigger::UseTargets(this, TRIG_TOGGLE);
|
2019-03-19 12:01:24 -07:00
|
|
|
CBaseEntity::Hide();
|
|
|
|
}
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
2020-08-31 01:56:31 -07:00
|
|
|
func_breakable::Death(void)
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2020-05-30 05:57:01 -07:00
|
|
|
static void break_spawnobject(void) {
|
|
|
|
/* these might get overwritten by the entity spawnfunction */
|
|
|
|
vector neworg = self.origin;
|
|
|
|
|
|
|
|
/* become the classname assigned */
|
|
|
|
CBaseEntity t = (CBaseEntity)self;
|
|
|
|
callfunction(self.classname);
|
|
|
|
|
|
|
|
/* apply the saved values back */
|
|
|
|
t.origin = t.m_oldOrigin = neworg;
|
|
|
|
|
|
|
|
/* spawn anew */
|
|
|
|
if (t.Respawn)
|
|
|
|
t.Respawn();
|
|
|
|
}
|
|
|
|
|
2019-10-18 19:30:29 -07:00
|
|
|
if (m_iMaterial == BREAKMT_GLASS_UNBREAKABLE) {
|
2019-01-02 17:26:39 -08:00
|
|
|
return;
|
|
|
|
}
|
2020-05-30 05:57:01 -07:00
|
|
|
|
2018-12-30 17:00:38 -08:00
|
|
|
health = 0;
|
2020-03-25 14:35:05 -07:00
|
|
|
eActivator = g_eAttacker;
|
2020-03-03 13:50:07 -08:00
|
|
|
|
2020-05-30 05:57:01 -07:00
|
|
|
if (m_strBreakSpawn != "" && isfunction(m_strBreakSpawn)) {
|
|
|
|
entity unit = spawn();
|
|
|
|
unit.classname = m_strBreakSpawn;
|
|
|
|
unit.think = break_spawnobject;
|
|
|
|
unit.nextthink = time;
|
|
|
|
unit.real_owner = this;
|
|
|
|
setorigin(unit, absmin + (0.5 * (absmax - absmin)));
|
|
|
|
print(sprintf("breakable spawning %s\n", unit.classname));
|
|
|
|
}
|
|
|
|
|
2019-03-19 12:01:24 -07:00
|
|
|
/* This may seem totally absurd. That's because it is. It's very
|
|
|
|
* unreliable but exploding breakables in close proximity it WILL cause
|
|
|
|
* an OVERFLOW because we'll be busy running through thousands
|
|
|
|
* of entities in total when one breakable damages another in a frame.
|
|
|
|
* The only way around this is to set a hard-coded limit of loops per
|
|
|
|
* frame and that would break functionality. */
|
2019-01-10 02:16:04 -08:00
|
|
|
if (m_flExplodeMag) {
|
2019-03-19 12:01:24 -07:00
|
|
|
think = Explode;
|
|
|
|
nextthink = time + random(0.0,0.5);
|
|
|
|
} else {
|
2020-05-07 22:44:40 -07:00
|
|
|
FX_BreakModel(vlen(size) / 10, absmin, absmax, [0,0,0], m_iMaterial);
|
2020-08-10 03:32:18 -07:00
|
|
|
/* TODO: ability to have whoever destroyed the crate be the activator */
|
|
|
|
CBaseTrigger::UseTargets(this, TRIG_TOGGLE);
|
2019-03-19 12:01:24 -07:00
|
|
|
CBaseEntity::Hide();
|
2019-01-10 02:16:04 -08:00
|
|
|
}
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
2020-08-10 03:32:18 -07:00
|
|
|
func_breakable::Trigger(entity act, int state)
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2020-03-29 12:39:32 -07:00
|
|
|
if (health > 0)
|
2020-08-31 01:56:31 -07:00
|
|
|
func_breakable::Death();
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
|
|
|
func_breakable::PlayerTouch(void)
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2020-08-07 05:07:38 -07:00
|
|
|
static void TriggerWrap(void) {
|
2020-08-10 03:32:18 -07:00
|
|
|
/* TODO: 'this' should be the person who touched the ent instead */
|
|
|
|
Trigger(this, TRIG_TOGGLE);
|
2020-08-07 05:07:38 -07:00
|
|
|
}
|
|
|
|
|
2019-01-04 17:02:12 -08:00
|
|
|
if (other.classname == classname) {
|
2018-12-30 17:00:38 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-31 01:56:31 -07:00
|
|
|
if (other.solid == SOLID_CORPSE) {
|
|
|
|
if (vlen(other.velocity) > 100) {
|
|
|
|
Trigger(this, TRIG_ON);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:26:39 -08:00
|
|
|
if (spawnflags & SF_TOUCH) {
|
|
|
|
int fDamage = (float)(vlen(other.velocity) * 0.01f);
|
|
|
|
|
|
|
|
if (fDamage >= health) {
|
|
|
|
touch = __NULL__;
|
2019-09-28 16:54:29 -07:00
|
|
|
Damage_Apply(this, other, fDamage, 0, DMG_CRUSH);
|
2020-05-30 05:57:01 -07:00
|
|
|
|
2019-10-18 19:30:29 -07:00
|
|
|
if ((m_iMaterial == BREAKMT_GLASS) || (m_iMaterial == BREAKMT_COMPUTER)) {
|
2019-09-28 16:54:29 -07:00
|
|
|
Damage_Apply(other, this, fDamage / 4, 0, DMG_CRUSH);
|
2019-01-02 17:26:39 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-13 14:03:57 -07:00
|
|
|
if ((spawnflags & SF_PRESSURE) && (other.absmin[2] >= maxs[2] - 2)) {
|
2019-01-03 20:56:14 -08:00
|
|
|
touch = __NULL__;
|
2020-08-07 05:07:38 -07:00
|
|
|
think = TriggerWrap;
|
2019-01-02 17:26:39 -08:00
|
|
|
|
|
|
|
if (m_flDelay == 0) {
|
|
|
|
m_flDelay = 0.1f;
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
2019-01-02 17:26:39 -08:00
|
|
|
|
|
|
|
nextthink = time + m_flDelay;
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
|
|
|
func_breakable::Respawn(void)
|
2019-01-02 17:26:39 -08:00
|
|
|
{
|
2020-04-22 14:33:18 -07:00
|
|
|
SetMovetype(MOVETYPE_NONE);
|
2020-09-08 16:56:46 -07:00
|
|
|
SetSolid(SOLID_BSP);
|
2019-11-08 17:09:17 -08:00
|
|
|
|
2020-04-22 14:33:18 -07:00
|
|
|
SetModel(m_oldModel);
|
|
|
|
SetOrigin(m_oldOrigin);
|
2019-01-02 17:26:39 -08:00
|
|
|
touch = PlayerTouch;
|
2019-01-03 20:56:14 -08:00
|
|
|
think = __NULL__;
|
|
|
|
|
2019-01-04 17:02:12 -08:00
|
|
|
if (spawnflags & SF_TRIGGER) {
|
2019-01-02 17:26:39 -08:00
|
|
|
takedamage = DAMAGE_NO;
|
|
|
|
} else {
|
|
|
|
takedamage = DAMAGE_YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
health = m_oldHealth;
|
|
|
|
|
2019-01-04 17:02:12 -08:00
|
|
|
if (!health) {
|
2019-01-02 17:26:39 -08:00
|
|
|
health = 15;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 05:07:38 -07:00
|
|
|
void
|
2020-09-08 16:56:46 -07:00
|
|
|
func_breakable::SpawnKey(string strKey, string strValue)
|
2018-12-30 17:00:38 -08:00
|
|
|
{
|
2020-09-08 16:56:46 -07:00
|
|
|
switch (strKey) {
|
|
|
|
case "material":
|
|
|
|
m_iMaterial = stoi(strValue);
|
|
|
|
break;
|
|
|
|
case "delay":
|
|
|
|
m_flDelay = stof(strValue);
|
|
|
|
break;
|
|
|
|
case "explodemagnitude":
|
|
|
|
m_flExplodeMag = stof(strValue);
|
|
|
|
break;
|
|
|
|
case "spawnobject":
|
|
|
|
int oid = stoi(strValue);
|
|
|
|
|
|
|
|
if (oid >= funcbreakable_objtable.length) {
|
|
|
|
m_strBreakSpawn = "";
|
|
|
|
print(sprintf("^1func_breakable^7:" \
|
|
|
|
"spawnobject %i out of bounds! fix your mod!\n", oid));
|
|
|
|
} else {
|
|
|
|
m_strBreakSpawn = funcbreakable_objtable[oid];
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
2020-09-08 16:56:46 -07:00
|
|
|
break;
|
|
|
|
case "spawnonbreak":
|
|
|
|
m_strBreakSpawn = strValue;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
CBaseTrigger::SpawnKey(strKey, strValue);
|
|
|
|
break;
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|
2020-09-08 16:56:46 -07:00
|
|
|
}
|
2020-03-03 13:50:07 -08:00
|
|
|
|
2020-09-08 16:56:46 -07:00
|
|
|
void
|
|
|
|
func_breakable::func_breakable(void)
|
|
|
|
{
|
|
|
|
CBaseTrigger::CBaseTrigger();
|
2018-12-30 17:00:38 -08:00
|
|
|
}
|