/* * Copyright (c) 2016-2022 Vera Visions LLC. * * 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 const string funcbreakable_surftable[] = { "gs_material_glass", "gs_material_wood", "gs_material_metal", "gs_material_flesh", "gs_material_cinderblock", "gs_material_tile", "gs_material_computer", "gs_material_unbreakableglass", "gs_material_rocks" }; class func_breakable:NSSurfacePropEntity { float m_flDelay; float m_flExplodeMag; float m_flExplodeRad; string m_strBreakSpawn; bool m_bCanTouch; /*entity m_pressAttacker; int m_pressType; int m_pressDamage;*/ void(void) func_breakable; /* overrides */ virtual void(float) Save; virtual void(string, string) Restore; virtual void(string, string) SpawnKey; virtual void(void) Spawned; virtual void(void) Respawn; virtual void(void) Pain; virtual void(void) Death; virtual void(entity, int) Trigger; virtual void(void) Explode; virtual void(entity) Touch; /*virtual void(void) PressureDeath;*/ }; void func_breakable::func_breakable(void) { m_flDelay = 0.0f; m_flExplodeMag = 0.0f; m_flExplodeRad = 0.0f; m_strBreakSpawn = __NULL__; m_bCanTouch = false; } void func_breakable::Save(float handle) { super::Save(handle); SaveFloat(handle, "m_flDelay", m_flDelay); SaveFloat(handle, "m_flExplodeMag", m_flExplodeMag); SaveFloat(handle, "m_flExplodeRad", m_flExplodeRad); SaveString(handle, "m_strBreakSpawn", m_strBreakSpawn); SaveBool(handle, "m_bCanTouch", m_bCanTouch); } void func_breakable::Restore(string strKey, string strValue) { switch (strKey) { case "m_flDelay": m_flDelay = ReadFloat(strValue); break; case "m_flExplodeMag": m_flExplodeMag = ReadFloat(strValue); break; case "m_flExplodeRad": m_flExplodeRad = ReadFloat(strValue); break; case "m_strBreakSpawn": m_strBreakSpawn = ReadString(strValue); break; case "m_bCanTouch": m_bCanTouch = ReadBool(strValue); break; default: super::Restore(strKey, strValue); } } void func_breakable::SpawnKey(string strKey, string strValue) { switch (strKey) { case "material": float id = stof(strValue); SetSurfaceData(funcbreakable_surftable[id]); SetPropData(funcbreakable_surftable[id]); 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: super::SpawnKey(strKey, strValue); break; } } void func_breakable::Spawned(void) { super::Spawned(); /* func_breakable defaults to glass */ if (classname == "func_breakable") { SetPropData(funcbreakable_surftable[0]); SetSurfaceData(funcbreakable_surftable[0]); } else { SetPropData(funcbreakable_surftable[1]); SetSurfaceData(funcbreakable_surftable[1]); } /* precache impact sound */ Sound_Precache(GetSurfaceData(SURFDATA_SND_BULLETIMPACT)); } void func_breakable::Respawn(void) { SetMovetype(MOVETYPE_NONE); SetSolid(SOLID_BSP); SetModel(GetSpawnModel()); SetOrigin(GetSpawnOrigin()); ClearAngles(); ReleaseThink(); m_bCanTouch = true; if (HasSpawnFlags(SF_TRIGGER)) { SetTakedamage(DAMAGE_NO); } else { SetTakedamage(DAMAGE_YES); } /* initially set the health to that of the ent-data */ float spawnhealth = GetSpawnHealth(); if (HasPropData() == TRUE && spawnhealth <= 0) { /* assign propdata health */ SetHealth(GetPropData(PROPINFO_HEALTH)); m_flExplodeMag = GetPropData(PROPINFO_EXPLOSIVE_DMG); m_flExplodeRad = GetPropData(PROPINFO_EXPLOSIVE_RADIUS); } else { SetHealth(spawnhealth); } /* unassigned health isn't valid */ if (GetHealth() <= 0) SetHealth(15); } void func_breakable::Pain(void) { if (HasSpawnFlags(SF_TRIGGER)) { return; } Sound_Play(this, CHAN_VOICE, GetSurfaceData(SURFDATA_SND_BULLETIMPACT)); } void func_breakable::Explode(void) { vector rp = WorldSpaceCenter(); vector vecDir = vectoangles(rp - g_dmg_vecLocation); BreakModel_Spawn(absmin, absmax, [0,0,0], m_flExplodeMag, vlen(size) / 10, GetPropData(PROPINFO_BREAKMODEL)); FX_Explosion(rp); Damage_Radius(rp, this, m_flExplodeMag, m_flExplodeRad, TRUE, 0); UseTargets(this, TRIG_TOGGLE, 0.0f); /* delay... ignored. */ Disappear(); SetTakedamage(DAMAGE_NO); } 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 */ NSEntity t = (NSEntity)self; callfunction(self.classname); /* apply the saved values back */ t.origin = t.GetSpawnOrigin() = neworg; /* spawn anew */ if (t.Respawn) t.Respawn(); } if (material == BREAKMT_GLASS_UNBREAKABLE) { return; } 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, WorldSpaceCenter()); } /* 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) { ScheduleThink(Explode, random(0.0f, 0.5f)); } else { vector vecDir = vectoangles(WorldSpaceCenter() - g_dmg_vecLocation); BreakModel_Spawn(absmin, absmax, vecDir, g_dmg_iDamage * 2.5, vlen(size) / 10, GetPropData(PROPINFO_BREAKMODEL)); Disappear(); SetTakedamage(DAMAGE_NO); UseTargets(eActivator, TRIG_TOGGLE, 0.0f); } } void func_breakable::Trigger(entity act, int state) { if (GetHealth() > 0) Death(); } void func_breakable::Touch(entity eToucher) { static void TriggerWrap(void) { /* TODO: 'this' should be the person who touched the ent instead */ Trigger(this, TRIG_TOGGLE); } if (m_bCanTouch == false) return; if (eToucher.classname == classname) { return; } if (eToucher.solid == SOLID_CORPSE) { if (vlen(eToucher.velocity) > 100) { Trigger(this, TRIG_ON); } } if (HasSpawnFlags(SF_TOUCH)) { int fDamage = (float)(vlen(eToucher.velocity) * 0.01f); if (fDamage >= health) { m_bCanTouch = false; Damage_Apply(this, eToucher, fDamage, 0, DMG_CRUSH); if ((GetSurfaceData(SURFDATA_MATERIAL) == GSMATERIAL_GLASS) || (GetSurfaceData(SURFDATA_MATERIAL) == GSMATERIAL_COMPUTER)) { Damage_Apply(eToucher, this, fDamage / 4, 0, DMG_CRUSH); } } } if (HasSpawnFlags(SF_PRESSURE) && (eToucher.absmin[2] >= maxs[2] - 2)) { m_bCanTouch = false; if (m_flDelay <= 0) { m_flDelay = 0.1f; } ScheduleThink(TriggerWrap, m_flDelay); } }