/* * Copyright (c) 2016-2020 Marco Hladik * * 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. */ /*QUAKED func_breakable (0 .5 .8) ? SF_TRIGGER SF_TOUCH SF_PRESSURE Brush volume that can break into lots of little pieces. -------- KEYS -------- "targetname" : Name "target" : Target when triggered. "killtarget" : Target to kill when triggered. "health" : Health of the entity. "material" : Material it's made of (See material-list below). "delay" : Delay in seconds of when it breaks under pressure. "explodemagnitude" : Strength of the explosion. -------- SPAWNFLAGS -------- SF_TRIGGER : Breakable is invincible and has to be triggered in order to break. SF_TOUCH : Break when an entity collides into it at high speed (damage is speed in units * 0.01). SF_PRESSURE : Initiate break once someone is standing on top of it. -------- NOTES -------- The strength of the explosion decides the radius (magnitude * 2.5) and the maximum damage the explosion will do in the center. It has a linear fall-off. Material-list: 0 : Glass 1 : Wood 2 : Metal 3 : Flesh 4 : Cinder 5 : Tile 6 ; Computer 7 : Glass (Unbreakable) 8 : Rock 9 : None -------- TRIVIA -------- This entity was introduced in Half-Life (1998). */ enumflags { SF_TRIGGER, SF_TOUCH, SF_PRESSURE }; enum { BREAKMT_GLASS, BREAKMT_WOOD, BREAKMT_METAL, BREAKMT_FLESH, BREAKMT_CINDER, BREAKMT_TILE, BREAKMT_COMPUTER, BREAKMT_GLASS_UNBREAKABLE, BREAKMT_ROCK, BREAKMT_NONE }; #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 class func_breakable:CBaseTrigger { float m_flDelay; float m_flExplodeMag; float m_flExplodeRad; string m_strBreakSpawn; /*entity m_pressAttacker; int m_pressType; int m_pressDamage;*/ void(void) func_breakable; virtual void(void) Respawn; virtual void(void) Explode; virtual void(entity, int) Trigger; virtual void(void) PlayerTouch; /*virtual void(void) PressureDeath;*/ virtual void(void) Pain; virtual void(void) Death; virtual void(string, string) SpawnKey; }; void func_breakable::Pain(void) { if (spawnflags & SF_TRIGGER) { return; } if (serverkeyfloat("*bspversion") != BSPVER_HL) { return; } switch (m_iMaterial) { case BREAKMT_GLASS: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_glass"); break; case BREAKMT_COMPUTER: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_computer"); break; case BREAKMT_GLASS_UNBREAKABLE: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_glassunbreakable"); break; case BREAKMT_WOOD: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_wood"); break; case BREAKMT_METAL: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_metal"); break; case BREAKMT_FLESH: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_flesh"); break; case BREAKMT_CINDER: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_cinder"); break; case BREAKMT_ROCK: Sound_Play(this, CHAN_VOICE, "func_breakable.impact_rock"); break; } } void func_breakable::Explode(void) { vector rp = absmin + (0.5 * (absmax - absmin)); FX_BreakModel(vlen(size) / 10, absmin, absmax, [0,0,0], m_iMaterial); FX_Explosion(rp); Damage_Radius(rp, this, m_flExplodeMag, m_flExplodeRad, TRUE, 0); UseTargets(this, TRIG_TOGGLE, 0.0f); /* delay... ignored. */ Hide(); } void func_breakable::Death(void) { 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.GetSpawnOrigin() = neworg; /* spawn anew */ if (t.Respawn) t.Respawn(); } if (m_iMaterial == BREAKMT_GLASS_UNBREAKABLE) { return; } health = 0; eActivator = g_dmg_eAttacker; 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))); } /* 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. */ if (m_flExplodeMag) { think = Explode; nextthink = time + random(0.0,0.5); } else { FX_BreakModel(vlen(size) / 10, absmin, absmax, [0,0,0], m_iMaterial); /* TODO: ability to have whoever destroyed the crate be the activator */ UseTargets(this, TRIG_TOGGLE, 0.0f); Hide(); } } void func_breakable::Trigger(entity act, int state) { if (health > 0) func_breakable::Death(); } void func_breakable::PlayerTouch(void) { static void TriggerWrap(void) { /* TODO: 'this' should be the person who touched the ent instead */ Trigger(this, TRIG_TOGGLE); } if (other.classname == classname) { return; } if (other.solid == SOLID_CORPSE) { if (vlen(other.velocity) > 100) { Trigger(this, TRIG_ON); } } if (spawnflags & SF_TOUCH) { int fDamage = (float)(vlen(other.velocity) * 0.01f); if (fDamage >= health) { touch = __NULL__; Damage_Apply(this, other, fDamage, 0, DMG_CRUSH); if ((m_iMaterial == BREAKMT_GLASS) || (m_iMaterial == BREAKMT_COMPUTER)) { Damage_Apply(other, this, fDamage / 4, 0, DMG_CRUSH); } } } if ((spawnflags & SF_PRESSURE) && (other.absmin[2] >= maxs[2] - 2)) { touch = __NULL__; think = TriggerWrap; if (m_flDelay == 0) { m_flDelay = 0.1f; } nextthink = time + m_flDelay; } } void func_breakable::Respawn(void) { SetMovetype(MOVETYPE_NONE); SetSolid(SOLID_BSP); SetModel(GetSpawnModel()); SetOrigin(GetSpawnOrigin()); touch = PlayerTouch; think = __NULL__; if (spawnflags & SF_TRIGGER) { takedamage = DAMAGE_NO; } else { takedamage = DAMAGE_YES; } health = GetSpawnHealth(); if (!health) { health = 15; } if (HasPropData()) { health = GetPropData(PROPINFO_HEALTH); m_flExplodeMag = GetPropData(PROPINFO_EXPLOSIVE_DMG); m_flExplodeRad = GetPropData(PROPINFO_EXPLOSIVE_RADIUS); } } void func_breakable::SpawnKey(string strKey, string strValue) { switch (strKey) { case "material": m_iMaterial = stoi(strValue); break; case "explodemagnitude": m_flExplodeMag = stof(strValue); m_flExplodeRad = m_flExplodeMag * 2.5f; 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]; } break; case "spawnonbreak": m_strBreakSpawn = strValue; break; default: CBaseTrigger::SpawnKey(strKey, strValue); break; } } void func_breakable::func_breakable(void) { CBaseTrigger::CBaseTrigger(); /* contrary to what some map designers think, angles are not supported */ GetSpawnAngles() = angles = [0,0,0]; switch (m_iMaterial) { case BREAKMT_GLASS: Sound_Precache("func_breakable.impact_glass"); break; case BREAKMT_COMPUTER: Sound_Precache("func_breakable.impact_computer"); break; case BREAKMT_GLASS_UNBREAKABLE: Sound_Precache("func_breakable.impact_glassunbreakable"); break; case BREAKMT_WOOD: Sound_Precache("func_breakable.impact_wood"); break; case BREAKMT_METAL: Sound_Precache("func_breakable.impact_metal"); break; case BREAKMT_FLESH: Sound_Precache("func_breakable.impact_flesh"); break; case BREAKMT_CINDER: Sound_Precache("func_breakable.impact_cinder"); break; case BREAKMT_ROCK: Sound_Precache("func_breakable.impact_rock"); break; } }