#include "attack" namespace WeaponCustom { class Color { uint8 r, g, b, a; Color() { r = g = b = a = 0; } Color(uint8 r, uint8 g, uint8 b) { this.r = r; this.g = g; this.b = b; this.a = 255; } Color(uint8 r, uint8 g, uint8 b, uint8 a) { this.r = r; this.g = g; this.b = b; this.a = a; } Color(float r, float g, float b, float a) { this.r = uint8(r); this.g = uint8(g); this.b = uint8(b); this.a = uint8(a); } Color (Vector v) { this.r = uint8(v.x); this.g = uint8(v.y); this.b = uint8(v.z); this.a = 255; } string ToString() { return "" + r + " " + g + " " + b + " " + a; } Vector getRGB() { return Vector(r, g, b); } } Color RED = Color(255,0,0); Color GREEN = Color(0,255,0); Color BLUE = Color(0,0,255); Color YELLOW = Color(255,255,0); Color ORANGE = Color(255,127,0); Color PURPLE = Color(127,0,255); Color PINK = Color(255,0,127); Color TEAL = Color(0,255,255); Color WHITE = Color(255,255,255); Color BLACK = Color(0,0,0); Color GRAY = Color(127,127,127); void print(string text) { g_Game.AlertMessage( at_console, text); } void println(string text) { print(text + "\n"); } void debug(string text) { if (debug_mode) print(text); } void debugln(string text) { if (debug_mode) println(text); } string logPrefix = "WEAPON_CUSTOM ERROR: "; // convert output from Vector.ToString() back into a Vector Vector parseVector(string s) { array values = s.Split(" "); Vector v(0,0,0); if (values.length() > 0) v.x = atof( values[0] ); if (values.length() > 1) v.y = atof( values[1] ); if (values.length() > 2) v.z = atof( values[2] ); return v; } // convert output from Vector.ToString() back into a Vector Color parseColor(string s) { array values = s.Split(" "); Color c(0,0,0,0); if (values.length() > 0) c.r = atoi( values[0] ); if (values.length() > 1) c.g = atoi( values[1] ); if (values.length() > 2) c.b = atoi( values[2] ); if (values.length() > 3) c.a = atoi( values[3] ); return c; } Vector resizeVector( Vector v, float length ) { float d = length / sqrt( (v.x*v.x) + (v.y*v.y) + (v.z*v.z) ); v.x *= d; v.y *= d; v.z *= d; return v; } array rotationMatrix(Vector axis, float angle) { axis = axis.Normalize(); float s = sin(angle); float c = cos(angle); float oc = 1.0 - c; array mat = { oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 0.0, 0.0, 0.0, 1.0 }; return mat; } // water splashes and bubble trails for bullets void water_bullet_effects(Vector vecSrc, Vector vecEnd) { // bubble trails bool startInWater = g_EngineFuncs.PointContents(vecSrc) == CONTENTS_WATER; bool endInWater = g_EngineFuncs.PointContents(vecEnd) == CONTENTS_WATER; if (startInWater or endInWater) { Vector bubbleStart = vecSrc; Vector bubbleEnd = vecEnd; Vector bubbleDir = bubbleEnd - bubbleStart; float waterLevel; // find water level relative to trace start Vector waterPos = startInWater ? bubbleStart : bubbleEnd; waterLevel = g_Utility.WaterLevel(waterPos, waterPos.z, waterPos.z + 1024); waterLevel -= bubbleStart.z; // get percentage of distance travelled through water float waterDist = 1.0f; if (!startInWater or !endInWater) waterDist -= waterLevel / (bubbleEnd.z - bubbleStart.z); if (!endInWater) waterDist = 1.0f - waterDist; // clip trace to just the water portion if (!startInWater) bubbleStart = bubbleEnd - bubbleDir*waterDist; else if (!endInWater) bubbleEnd = bubbleStart + bubbleDir*waterDist; // a shitty attempt at recreating the splash effect Vector waterEntry = endInWater ? bubbleStart : bubbleEnd; if (!startInWater or !endInWater) te_spritespray(waterEntry, Vector(0,0,1), g_watersplash_spr, 1, 64, 0); // waterlevel must be relative to the starting point if (!startInWater or !endInWater) waterLevel = bubbleStart.z > bubbleEnd.z ? 0 : bubbleEnd.z - bubbleStart.z; // calculate bubbles needed for an even distribution int numBubbles = int( (bubbleEnd - bubbleStart).Length() / 128.0f ); numBubbles = Math.max(1, Math.min(255, numBubbles)); te_bubbletrail(bubbleStart, bubbleEnd, "sprites/bubble.spr", waterLevel, numBubbles, 16.0f); } } // create rotation matrix from euler angles array eulerMatrix(Vector angles) { angles.x = Math.DegreesToRadians(angles.x); angles.y = Math.DegreesToRadians(angles.y); angles.z = Math.DegreesToRadians(-angles.z); float ch = cos(angles.x); float sh = sin(angles.x); float ca = cos(angles.y); float sa = sin(angles.y); float cb = cos(angles.z); float sb = sin(angles.z); array mat = { ch * ca, sh*sb - ch*sa*cb, ch*sa*sb + sh*cb, 0.0, sa, ca*cb, -ca*sb, 0.0, -sh*ca, sh*sa*cb + ch*sb, -sh*sa*sb + ch*cb, 0.0, 0.0, 0.0, 0.0, 1.0 }; return mat; } // multiply a matrix with a vector (assumes w component of vector is 1.0f) Vector matMultVector(array rotMat, Vector v) { Vector outv; outv.x = rotMat[0]*v.x + rotMat[4]*v.y + rotMat[8]*v.z + rotMat[12]; outv.y = rotMat[1]*v.x + rotMat[5]*v.y + rotMat[9]*v.z + rotMat[13]; outv.z = rotMat[2]*v.x + rotMat[6]*v.y + rotMat[10]*v.z + rotMat[14]; return outv; } // http://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix // TODO: Only 3x3 inversion is needed array invertMatrix(array m) { array inv(16); inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]; inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]; inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]; inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]; inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]; inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]; inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]; inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]; inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6]; inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6]; inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5]; inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5]; inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6]; inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6]; inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5]; inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5]; float det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; if (det == 0) { println("Matrix inversion failed (determinant is zero)"); return m; } det = 1.0 / det; for (int i = 0; i < 16; i++) m[i] = inv[i] * det; return m; } // Randomize the direction of a vector by some amount // Max degrees = 360, which makes a full sphere Vector spreadDir(Vector dir, float degrees, int spreadFunc=SPREAD_UNIFORM) { float spread = Math.DegreesToRadians(degrees) * 0.5f; float x, y; Vector vecAiming = dir; if (spreadFunc == SPREAD_GAUSSIAN) { g_Utility.GetCircularGaussianSpread( x, y ); x *= Math.RandomFloat(-spread, spread); y *= Math.RandomFloat(-spread, spread); } else if (spreadFunc == SPREAD_UNIFORM) { float c = Math.RandomFloat(0, Math.PI*2); // random point on circle float r = Math.RandomFloat(-1, 1); // random radius x = cos(c) * r * spread; y = sin(c) * r * spread; } // get "up" vector relative to aim direction Vector pitAxis = CrossProduct(dir, Vector(0, 0, 1)).Normalize(); // get left vector of aim dir Vector yawAxis = CrossProduct(dir, pitAxis).Normalize(); // get up vector relative to aim dir // Apply rotation around arbitrary "up" axis array yawRotMat = rotationMatrix(yawAxis, x); vecAiming = matMultVector(yawRotMat, vecAiming).Normalize(); // Apply rotation around "left/right" axis array pitRotMat = rotationMatrix(pitAxis, y); vecAiming = matMultVector(pitRotMat, vecAiming).Normalize(); return vecAiming; } class DecalTarget { Vector pos; TraceResult tr; string texture; EHandle ent; // for when the target is a brush entity, not the world (0 = world) } class BeamImpact { Vector pos; CBaseEntity@ ent; bool collision = true; } class BeamShot { Vector startPos; TraceResult tr; CBaseEntity@ ent; } // Traces out in every direction in hopes of finding a surface DecalTarget getProjectileDecalTarget(CBaseEntity@ ent, Vector pos, float searchDist) { DecalTarget decalTarget = DecalTarget(); decalTarget.pos = ent !is null ? ent.pev.origin : pos; TraceResult tr; Vector src = decalTarget.pos; float bboxSize = 0; if (ent !is null) bboxSize = abs(ent.pev.maxs.x - ent.pev.mins.x); Vector[] dirs = { // Box sides Vector(1, 0, 0), Vector(-1, 0, 0), Vector(0, 1, 0), Vector(0, -1, 0), Vector(0, 0, 1), Vector(0, 0, -1), // Box corners Vector( -0.577350, -0.577350, -0.577350), Vector(0.577350, -0.577350, -0.577350), Vector(-0.577350, 0.577350, -0.577350), Vector(-0.577350, -0.577350, 0.577350), Vector(0.577350, -0.577350, 0.577350), Vector(0.577350, 0.577350, -0.577350), Vector(0.577350, 0.577350, 0.577350), Vector(-0.577350, 0.577350, 0.577350) }; for (uint i = 0; i < dirs.length(); i++) { Vector end = src + dirs[i]*(bboxSize+searchDist); edict_t@ edict = ent !is null ? ent.edict() : null; g_Utility.TraceLine( src, end, ignore_monsters, edict, tr ); //te_beampoints(src, end); if (tr.flFraction < 1.0 ) { decalTarget.pos = tr.vecEndPos; decalTarget.tr = tr; if (tr.pHit !is null) { decalTarget.ent = EHandle(g_EntityFuncs.Instance( tr.pHit )); // get the texture too, we might need that for something decalTarget.texture = g_Utility.TraceTexture( tr.pHit, src, end ); } return decalTarget; } } return decalTarget; } // rotates a point around 0,0,0 using YXZ euler rotation order Vector rotatePoint(Vector pos, Vector angles) { Vector yawAxis = Vector(0,0,1); Vector pitAxis = Vector(0,1,0); Vector rollAxis = Vector(1,0,0); array yawRotMat = rotationMatrix(yawAxis, Math.DegreesToRadians(angles.y)); pitAxis = matMultVector(yawRotMat, pitAxis); rollAxis = matMultVector(yawRotMat, rollAxis); array pitRotMat = rotationMatrix(pitAxis, Math.DegreesToRadians(angles.x)); rollAxis = matMultVector(pitRotMat, rollAxis); array rollRotMat = rotationMatrix(rollAxis, Math.DegreesToRadians(angles.z)); pos = matMultVector(yawRotMat, pos); pos = matMultVector(pitRotMat, pos); pos = matMultVector(rollRotMat, pos); return pos; } // Given a point that has been rotated around 0,0,0 by "angles", figure out // where the point would be if we were to unapply all of those rotations. // This is probably a super naive way of doing it (me no is good at math). Vector unwindPoint(Vector pos, Vector angles) { Vector yawAxis = Vector(0,0,1); Vector pitAxis = Vector(0,1,0); Vector rollAxis = Vector(1,0,0); angles.x = Math.DegreesToRadians(angles.x); angles.y = Math.DegreesToRadians(angles.y); angles.z = Math.DegreesToRadians(angles.z); // get rotation axes from angles array yawRotMat = rotationMatrix(yawAxis, angles.y); pitAxis = matMultVector(yawRotMat, pitAxis); rollAxis = matMultVector(yawRotMat, rollAxis); array pitRotMat = rotationMatrix(pitAxis, angles.x); rollAxis = matMultVector(pitRotMat, rollAxis); // create matrices that undo the rotations yawRotMat = rotationMatrix(yawAxis, -angles.y); pitRotMat = rotationMatrix(pitAxis, -angles.x); array rollRotMat = rotationMatrix(rollAxis, -angles.z); // apply opposite rotations in reverse order pos = matMultVector(rollRotMat, pos); pos = matMultVector(pitRotMat, pos); pos = matMultVector(yawRotMat, pos); return pos; } class WeaponCustomProjectile : ScriptBaseAnimating { float thinkDelay = 0.05; weapon_custom_shoot@ shoot_opts; ProjectileOptions@ options; EHandle spriteAttachment; // we'll need to kill this before we die (lol murder) bool attached; EHandle target; // entity attached to Vector attachStartOri; // Our initial position when attaching to the entity Vector targetStartOri; // initial position of the entity we attached to Vector attachStartDir; // our initial direction when attaching to the entity int attachTime = 0; bool move_snd_playing = false; string pickup_classname; bool weaponPickup = false; float pickupRadius = 64.0f; float nextBubbleTime = 0; float bubbleDelay = 0.07; float nextTrailEffectTime = 0; float nextBounceEffect = 0; void Spawn() { @options = shoot_opts.projectile; self.pev.movetype = options.gravity != 0 ? MOVETYPE_BOUNCE : MOVETYPE_BOUNCEMISSILE; self.pev.solid = SOLID_BBOX; g_EntityFuncs.SetModel( self, pev.model ); pev.mins = Vector(-options.size, -options.size, -options.size); pev.maxs = Vector(options.size, options.size, options.size); pev.angles = pev.angles + options.angles; //pev.avelocity = options.avel; //pev.friction = 1.0f - options.elasticity; pev.frame = 0; pev.sequence = 0; pev.air_finished = 0; // set to 1 externally when this needs to die self.ResetSequenceInfo(); SetThink( ThinkFunction( MoveThink ) ); self.pev.nextthink = g_Engine.time + thinkDelay; move_snd_playing = options.move_snd.play(self, CHAN_BODY); } void MoveThink() { float nextThink = g_Engine.time + thinkDelay; if (pev.air_finished > 0) { uninstall_steam_and_kill_yourself(); return; } if (attached and target) { CBaseEntity@ tar = target; // rotate position around target Vector newOri = attachStartOri + (tar.pev.origin - targetStartOri); newOri = rotatePoint(newOri - tar.pev.origin, -tar.pev.angles) + tar.pev.origin; // rotate orientation around target Vector newDir = rotatePoint(attachStartDir, -tar.pev.angles); g_EngineFuncs.VecToAngles(newDir, self.pev.angles); pev.origin = newOri; // prevent sudden jerking due to movement lagging behind the Touch() event attachTime++; if (attachTime > 2) { pev.velocity = Vector(0,0,0); pev.movetype = MOVETYPE_FLY; } if (shoot_opts.hook_type != HOOK_DISABLED) { CBaseEntity@ owner = g_EntityFuncs.Instance( self.pev.owner ); Vector dir = (pev.origin - owner.pev.origin).Normalize(); Vector repelDir = dir; repelDir.z = 0; //if (DotProduct(dir, owner.pev.velocity.Normalize()) < 0) // owner.pev.velocity = -owner.pev.velocity; float x = tar.pev.maxs.x - tar.pev.mins.x; float y = tar.pev.maxs.y - tar.pev.mins.y; float z = tar.pev.maxs.z - tar.pev.mins.z; float size = x*y*z; float playerSize = 32*32*72; //println("SIZE: " + x + "x" + y + "x" + z + " " + size); bool pullMode = shoot_opts.hook_pull_mode == HOOK_MODE_PULL or shoot_opts.hook_pull_mode == HOOK_MODE_PULL_LEAST_WEIGHT; bool pullUser = shoot_opts.hook_pull_mode == HOOK_MODE_PULL_LEAST_WEIGHT and playerSize <= size; pullUser = pullUser or shoot_opts.hook_pull_mode == HOOK_MODE_PULL or tar.IsBSPModel(); if (pullMode) { if (pullUser) owner.pev.velocity = owner.pev.velocity + dir*shoot_opts.hook_force; else tar.pev.velocity = tar.pev.velocity - dir*shoot_opts.hook_force*0.5; } else owner.pev.velocity = owner.pev.velocity + repelDir*shoot_opts.hook_force; if (pullUser) { if (owner.pev.velocity.Length() > shoot_opts.hook_max_speed) owner.pev.velocity = resizeVector(owner.pev.velocity, shoot_opts.hook_max_speed); if (pullMode and owner.pev.flags & FL_ONGROUND != 0 and dir.z > 0) { // The player is going to be stubborn and glue itself to the ground. // Make sure that forcing the player upward won't jam it into something solid TraceResult tr; Vector vecSrc = owner.pev.origin; Vector vecEnd = owner.pev.origin + Vector(0,0,2); g_Utility.TraceHull( vecSrc, vecEnd, dont_ignore_monsters, human_hull, owner.edict(), tr ); if ( tr.flFraction >= 1.0 ) owner.pev.origin.z += 2; // cool, we're all clear } } nextThink = g_Engine.time; // don't let gravity overpower the pull force too easily } if (!tar.IsBSPModel() and !tar.IsAlive()) { attached = false; target = null; if (shoot_opts.hook_type != HOOK_DISABLED) { uninstall_steam_and_kill_yourself(); return; } pev.movetype = options.gravity != 0 ? MOVETYPE_BOUNCE : MOVETYPE_BOUNCEMISSILE; } } else { if (attached) { attached = false; if (shoot_opts.hook_type != HOOK_DISABLED) { uninstall_steam_and_kill_yourself(); return; } pev.movetype = options.gravity != 0 ? MOVETYPE_BOUNCE : MOVETYPE_BOUNCEMISSILE; } if (shoot_opts.pev.spawnflags & FL_SHOOT_PROJ_NO_ORIENT == 0) g_EngineFuncs.VecToAngles(self.pev.velocity, self.pev.angles); } if (move_snd_playing and pev.velocity.Length() == 0) options.move_snd.stop(self, CHAN_BODY); bool noBubbles = shoot_opts.pev.spawnflags & FL_SHOOT_NO_BUBBLES != 0; bool inWater = g_EngineFuncs.PointContents(pev.origin) == CONTENTS_WATER; if (!attached and !noBubbles and inWater) { if (nextBubbleTime < g_Engine.time) { Vector pos = pev.origin; float waterLevel = g_Utility.WaterLevel(pos, pos.z, pos.z + 1024) - pos.z; te_bubbletrail(pos, pos, "sprites/bubble.spr", waterLevel, 1, 16.0f); nextBubbleTime = g_Engine.time + bubbleDelay; } } if (inWater and options.water_friction != 0) { float speed = self.pev.velocity.Length(); if (speed > 0) self.pev.velocity = resizeVector(self.pev.velocity, speed - speed*options.water_friction); } else if (!inWater and options.air_friction != 0) { float speed = self.pev.velocity.Length(); if (speed > 0) self.pev.velocity = resizeVector(self.pev.velocity, speed - speed*options.air_friction); } if (shoot_opts.effect4.valid and nextTrailEffectTime < g_Engine.time) { nextTrailEffectTime = g_Engine.time + options.trail_effect_freq; CBaseEntity@ owner = g_EntityFuncs.Instance( self.pev.owner ); EHandle howner = owner; custom_effect(self.pev.origin, shoot_opts.effect4, EHandle(self), howner, howner, pev.velocity.Normalize(), shoot_opts.friendly_fire ? 1 : 0); } if (weaponPickup) { if (pev.velocity.Length() < 128) { if ( !attached and pev.flags & FL_ONGROUND != 0 ) { pev.angles.x = 0; pev.angles.z = 0; } CBaseEntity@ ent = null; do { @ent = g_EntityFuncs.FindEntityInSphere(ent, pev.origin, pickupRadius, "player", "classname"); if (ent !is null) { CBasePlayer@ plr = cast(ent); plr.SetItemPickupTimes(0); if (plr.HasNamedPlayerItem(pickup_classname) !is null) { // play the pickup sound even if they already have the weapon g_SoundSystem.EmitSoundDyn( plr.edict(), CHAN_ITEM, "items/gunpickup2.wav", 1.0, ATTN_NORM, 0, 100 ); } else plr.GiveNamedItem(pickup_classname, 0, 0); uninstall_steam_and_kill_yourself(); } } while (ent !is null); } } self.pev.nextthink = nextThink; } void uninstall_steam_and_kill_yourself() { if (move_snd_playing) options.move_snd.stop(self, CHAN_BODY); g_EntityFuncs.Remove(self); if (spriteAttachment) g_EntityFuncs.Remove(spriteAttachment); } void DamageTarget(CBaseEntity@ ent, bool friendlyFire) { if (ent is null or ent.entindex() == 0 or shoot_opts.shoot_type == SHOOT_MELEE) return; CBaseEntity@ owner = g_EntityFuncs.Instance( self.pev.owner ); // damage done before hitgroup multipliers float baseDamage = shoot_opts.damage; if (baseDamage == 0) return; baseDamage = applyDamageModifiers(baseDamage, ent, owner, shoot_opts); TraceResult tr; Vector vecSrc = pev.origin; Vector vecAiming = pev.velocity.Normalize(); Vector vecEnd = vecSrc + vecAiming * pev.velocity.Length()*2; g_Utility.TraceLine( vecSrc, vecEnd, dont_ignore_monsters, self.edict(), tr ); CBaseEntity@ pHit = tr.pHit !is null ? g_EntityFuncs.Instance( tr.pHit ) : null; //te_beampoints(vecSrc, tr.vecEndPos); if ( tr.flFraction >= 1.0 or pHit.entindex() != ent.entindex()) { // This does a trace in the form of a box so there is a much higher chance of hitting something // From crowbar.cpp in the hlsdk: g_Utility.TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, self.edict(), tr ); if ( tr.flFraction < 1.0 ) { // Calculate the point of intersection of the line (or hull) and the object we hit // This is and approximation of the "best" intersection @pHit = g_EntityFuncs.Instance( tr.pHit ); if ( pHit is null or pHit.IsBSPModel() ) g_Utility.FindHullIntersection( vecSrc, tr, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, self.edict() ); vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) vecAiming = (vecEnd - vecSrc).Normalize(); } } Vector oldVel = ent.pev.velocity; int dmgType = shoot_opts.damageType(DMG_CLUB); g_WeaponFuncs.ClearMultiDamage(); // fixes TraceAttack() crash for some reason ent.TraceAttack(owner.pev, baseDamage, vecAiming, tr, dmgType); if (friendlyFire) { // set both classes in case this a pvp map where classes are always changing int oldClass1 = owner.GetClassification(0); int oldClass2 = ent.GetClassification(0); owner.KeyValue("classify", CLASS_PLAYER); ent.KeyValue("classify", CLASS_ALIEN_MILITARY); g_WeaponFuncs.ApplyMultiDamage(owner.pev, owner.pev); owner.KeyValue("classify", oldClass1); ent.KeyValue("classify", oldClass2); } else g_WeaponFuncs.ApplyMultiDamage(owner.pev, owner.pev); if (dmgType & DMG_LAUNCH == 0) // prevent high damage from launching unless we ask for it ent.pev.velocity = oldVel; WeaponSound@ impact_snd; if ((ent.IsMonster() or ent.IsPlayer()) and !ent.IsMachine()) @impact_snd = shoot_opts.getRandomMeleeFleshSound(); else @impact_snd = shoot_opts.getRandomMeleeHitSound(); if (impact_snd !is null) impact_snd.play(self, CHAN_WEAPON); } void ConvertToWeapon() { if (options.type == PROJECTILE_WEAPON) { pev.angles.z = 0; weaponPickup = true; if (move_snd_playing) options.move_snd.stop(self, CHAN_BODY); } } bool isValidHookSurface(CBaseEntity@ pOther) { if (pOther.IsBSPModel()) { if (shoot_opts.hook_targets == HOOK_MONSTERS_ONLY) return false; if (shoot_opts.hook_texture_filter.Length() > 0) { DecalTarget dt = getProjectileDecalTarget(self, Vector(0,0,0), 1); string hitTex = dt.texture.ToLowercase(); string matchTex = shoot_opts.hook_texture_filter.ToLowercase(); if (hitTex.Find(matchTex) != 0) return false; } } else { if (shoot_opts.hook_targets == HOOK_WORLD_ONLY or !pOther.IsAlive()) return false; } return true; } void Touch( CBaseEntity@ pOther ) { if (attached) return; int event = PROJ_ACT_BOUNCE; weapon_custom_effect@ effect = shoot_opts.effect1; if (pOther.IsBSPModel()) event = options.world_event; else { @effect = @shoot_opts.effect2; event = options.monster_event; } pev.velocity = pev.velocity*options.elasticity; if (weaponPickup) { // no more special effects after the first impact (pretend we're a weaponbox) WeaponSound@ rico_snd = effect.getRandomSound(); if (rico_snd !is null) rico_snd.play(self.pev.origin, CHAN_STATIC); pev.avelocity.x *= -0.9; return; } DamageTarget(pOther, shoot_opts.friendly_fire); knockBack(pOther, pev.velocity.Normalize() * shoot_opts.knockback); // don't spam bounce sounds when rolling on ground if (event != PROJ_ACT_BOUNCE or nextBounceEffect < g_Engine.time) { nextBounceEffect = g_Engine.time + options.bounce_effect_delay; CBaseEntity@ owner = g_EntityFuncs.Instance( self.pev.owner ); EHandle howner = owner; EHandle htarget = pOther; custom_effect(self.pev.origin, effect, EHandle(self), htarget, howner, Vector(0,0,0), shoot_opts.friendly_fire ? 1 : 0); } ConvertToWeapon(); switch(event) { case PROJ_ACT_IMPACT: uninstall_steam_and_kill_yourself(); return; case PROJ_ACT_ATTACH: if (shoot_opts.hook_type != HOOK_DISABLED and !isValidHookSurface(pOther)) { uninstall_steam_and_kill_yourself(); return; } target = pOther; attachStartOri = unwindPoint(pev.origin - pOther.pev.origin, -pOther.pev.angles) + pOther.pev.origin; attachStartDir = unwindPoint(pev.velocity.Normalize(), -pOther.pev.angles); targetStartOri = pOther.pev.origin; self.pev.solid = SOLID_NOT; pev.velocity = Vector(0,0,0); pev.avelocity = Vector(0,0,0); attached = true; // attach to center of monster if (shoot_opts.hook_type != HOOK_DISABLED and pOther.IsMonster()) { CBaseMonster@ mon = cast(pOther); float height = mon.pev.maxs.z - mon.pev.mins.z; attachStartOri = mon.pev.origin + Vector(0,0,height*0.5f); } return; } } } int getRandomPitch(int variance) { return Math.RandomLong(100-variance, 100+variance); } // custom effect flags int FL_EFFECT_FRIENDLY_FIRE = 1; int FL_EFFECT_DELAY_FINISHED = 2; void custom_effect(Vector pos, weapon_custom_effect@ effect, EHandle creator, EHandle target, EHandle owner, Vector vDir, int flags, DecalTarget@ dt=null) { if (!effect.valid) return; if (dt is null) { @dt = @getProjectileDecalTarget(creator.IsValid() ? creator.GetEntity() : null, !creator.IsValid() ? pos : Vector(0,0,0), 32); } bool delayFinished = (flags & FL_EFFECT_DELAY_FINISHED) != 0; bool friendlyFire = (flags & FL_EFFECT_FRIENDLY_FIRE) != 0; if (effect.delay > 0 and !delayFinished) { g_Scheduler.SetTimeout("custom_effect", effect.delay, pos, @effect, creator, target, owner, vDir, flags | FL_EFFECT_DELAY_FINISHED, @dt); return; } // velocity for explosion-type effects Vector vel = delayFinished or !creator.IsValid() ? Vector(0,0,0) : creator.GetEntity().pev.velocity; bool inWater = g_EngineFuncs.PointContents(pos) == CONTENTS_WATER; Vector dir = !creator.IsValid() ? dt.tr.vecPlaneNormal : creator.GetEntity().pev.velocity.Normalize(); if (effect.pev.spawnflags & FL_EFFECT_EXPLOSION != 0) { Vector exp_pos = pos; if (dt.ent) exp_pos = exp_pos + dt.tr.vecPlaneNormal*effect.explode_offset; custom_explosion(exp_pos, vel, effect, dt.pos, dt.ent, owner, inWater, friendlyFire); } if (effect.pev.spawnflags & FL_EFFECT_LIGHTS != 0) { int l_size = int(effect.explode_light_adv.x); int l_life = int(effect.explode_light_adv.y); int l_decay = int(effect.explode_light_adv.z); if (l_size > 0 and l_life > 0) { te_dlight(pos, l_size, effect.explode_light_color, l_life, l_decay); if (creator.IsValid()) te_elight(creator, pos, l_size*10, effect.explode_light_color, l_life, l_decay); } int l_size2 = int(effect.explode_light_adv2.x); int l_life2 = int(effect.explode_light_adv2.y); int l_decay2 = int(effect.explode_light_adv2.z); if (l_size2 > 0) { te_dlight(pos, l_size2, effect.explode_light_color2, l_life2, l_decay2); if (creator.IsValid()) te_elight(creator, pos, l_size2*10, effect.explode_light_color2, l_life2, l_decay2); } } if (effect.pev.spawnflags & FL_EFFECT_SPARKS != 0) { te_sparks(pos); } if (effect.pev.spawnflags & FL_EFFECT_RICOCHET != 0) { te_ricochet(pos, 0); } if (effect.pev.spawnflags & FL_EFFECT_TARBABY != 0) { te_tarexplosion(pos); } if (effect.pev.spawnflags & FL_EFFECT_TARBABY2 != 0) { te_explosion2(pos); } if (effect.pev.spawnflags & FL_EFFECT_BURST != 0) { te_particlebust(pos, effect.burst_radius, effect.burst_color, effect.burst_life); } if (effect.pev.spawnflags & FL_EFFECT_LAVA != 0) { te_lavasplash(pos); } if (effect.pev.spawnflags & FL_EFFECT_TELEPORT != 0) { te_teleport(pos); } if (effect.glow_spr.Length() > 0) { te_glowsprite(pos, effect.glow_spr, effect.glow_spr_life, effect.glow_spr_scale, effect.glow_spr_opacity); } if (effect.spray_count > 0 and effect.spray_sprite.Length() > 0) { te_spritespray(pos, dir, effect.spray_sprite, effect.spray_count, effect.spray_speed, effect.spray_rand); } if (effect.implode_count > 0) { te_implosion(pos, effect.implode_radius, effect.implode_count, effect.implode_life); } if (effect.rico_part_count > 0) { te_spritetrail(pos, pos + dt.tr.vecPlaneNormal, effect.rico_part_spr, effect.rico_part_count, 0, effect.rico_part_scale, effect.rico_part_speed, effect.rico_part_speed/2); } if (effect.blood_stream != 0) { int stream_power = effect.blood_stream; Vector bdir = vDir; if (bdir == Vector()) bdir = dir; if (stream_power < 0) { stream_power = -stream_power; bdir = bdir * -1; } int bcolor = 0; if (target) { CBaseEntity@ targetEnt = target; bcolor = targetEnt.BloodColor(); if (bcolor == BLOOD_COLOR_RED) // 247 bcolor = 222; // the enum val is wrong } te_bloodstream(pos, bdir, bcolor, stream_power); } if (effect.rico_trace_count > 0) { te_streaksplash(pos, dt.tr.vecPlaneNormal, effect.rico_trace_color, effect.rico_trace_count, effect.rico_trace_speed, effect.rico_trace_rand); } if (effect.rico_decal != DECAL_NONE and dt.ent) { string decal = getBulletDecalOverride(dt.ent, getDecal(effect.rico_decal)); if (effect.pev.spawnflags & FL_EFFECT_GUNSHOT_RICOCHET != 0) te_gunshotdecal(pos, dt.ent, decal); else te_decal(pos, dt.ent, decal); } if (effect.explode_gibs > 0 and effect.explode_gib_mdl.Length() > 0) { te_breakmodel(pos, Vector(2,2,2), dir*effect.explode_gib_speed, effect.explode_gib_rand, effect.explode_gib_mdl, effect.explode_gibs, 5, effect.explode_gib_mat | effect.explode_gib_effects); } if (effect.explode_bubbles > 0 and (inWater or effect.pev.spawnflags & FL_EFFECT_BUBBLES_IN_AIR != 0)) { Vector mins = pos + effect.explode_bubble_mins; Vector maxs = pos + effect.explode_bubble_maxs; float height = (maxs.z - mins.z)*2.0f; if (inWater) height = g_Utility.WaterLevel(pos, pos.z, pos.z + 1024) - mins.z; string spr = effect.explode_bubble_spr; if (spr.Length() == 0) spr = "sprites/bubble.spr"; int count = effect.explode_bubbles; float speed = effect.explode_bubble_speed; g_Scheduler.SetTimeout("delayed_bubbles", effect.explode_bubble_delay, mins, maxs, height, spr, count, speed); } if (effect.shake_radius > 0) { g_PlayerFuncs.ScreenShake(pos, effect.shake_amp, effect.shake_freq, effect.shake_time, effect.shake_radius); } WeaponSound@ rico_snd = effect.getRandomSound(); if (rico_snd !is null) rico_snd.play(pos, CHAN_STATIC); if (effect.next_effect !is null) custom_effect(pos, effect.next_effect, creator, target, owner, vDir, flags & ~FL_EFFECT_DELAY_FINISHED, dt); } void custom_explosion(Vector pos, Vector vel, weapon_custom_effect@ effect, Vector decalPos, CBaseEntity@ decalEnt, EHandle owner, bool inWater, bool friendlyFire) { if (!effect.valid) return; int smokeScale = int(effect.explode_smoke_spr_scale * 10.0f); int smokeFps = int(effect.explode_smoke_spr_fps); int expScale = int(effect.explode_spr_scale * 10.0f); int expFps = int(effect.explode_spr_fps); string expSprite = effect.explode_spr; if (inWater and effect.explode_water_spr.Length() > 0) expSprite = effect.explode_water_spr; int life = 8; if (expSprite.Length() > 0) { switch(effect.explosion_style) { case EXPLODE_SPRITE_PARTICLES: case EXPLODE_SPRITE: { int flags = 2 | 4; // no sound or lights if (effect.explosion_style == EXPLODE_SPRITE) flags |= 8; // no particles te_explosion(pos, expSprite, expScale, expFps, flags); break; } case EXPLODE_DISK: te_beamdisk(pos, effect.explode_beam_radius, expSprite, effect.explode_beam_frame, effect.explode_beam_fps, effect.explode_beam_life, effect.explode_beam_width, effect.explode_beam_noise, effect.explode_beam_color, effect.explode_beam_scroll); break; case EXPLODE_CYLINDER: te_beamcylinder(pos, effect.explode_beam_radius, expSprite, effect.explode_beam_frame, effect.explode_beam_fps, effect.explode_beam_life, effect.explode_beam_width, effect.explode_beam_noise, effect.explode_beam_color, effect.explode_beam_scroll); break; case EXPLODE_TORUS: te_beamtorus(pos, effect.explode_beam_radius, expSprite, effect.explode_beam_frame, effect.explode_beam_fps, effect.explode_beam_life, effect.explode_beam_width, effect.explode_beam_noise, effect.explode_beam_color, effect.explode_beam_scroll); break; } } if (effect.explode_radius > 0 and effect.explode_damage > 0 and owner) { CBaseEntity@ ownerEnt = owner; float radius = effect.explode_radius; float dmg = effect.explode_damage; if (friendlyFire) { // set class of all players to opposite of attacker, just until after we call RadiusDamage array victims; array oldClassify; CBaseEntity@ victim = null; do { @victim = g_EntityFuncs.FindEntityByClassname(victim, "player"); if (victim !is null) { victims.insertLast(victim); oldClassify.insertLast(victim.GetClassification(0)); victim.KeyValue("classify", CLASS_ALIEN_MILITARY); } } while (victim !is null); ownerEnt.KeyValue("classify", CLASS_PLAYER); RadiusDamage(pos, ownerEnt.pev, ownerEnt.pev, dmg, radius, 0, effect.damageType()); for (uint i = 0; i < victims.length(); i++) victims[i].KeyValue("classify", oldClassify[i]); } else { RadiusDamage(pos, ownerEnt.pev, ownerEnt.pev, dmg, radius, 0, effect.damageType()); } } if (effect.explode_smoke_spr.Length() > 0) g_Scheduler.SetTimeout("delayed_smoke", effect.explode_smoke_delay, pos, effect.explode_smoke_spr, smokeScale, smokeFps); } // a basic set of directions for a sphere (up/down/left/right/front/back with 1 in-between step) // This isn't good enough for large explosions, but hopefully FindEntityInSphere will work at that point. array sphereDirs = {Vector(1,0,0).Normalize(), Vector(0,1,0).Normalize(), Vector(0,0,1).Normalize(), Vector(-1,0,0).Normalize(), Vector(0,-1,0).Normalize(), Vector(0,0,-1).Normalize(), Vector(1,1,0).Normalize(), Vector(-1,1,0).Normalize(), Vector(1,-1,0).Normalize(), Vector(-1,-1,0).Normalize(), Vector(1,0,1).Normalize(), Vector(-1,0,1).Normalize(), Vector(1,0,-1).Normalize(), Vector(-1,0,-1).Normalize(), Vector(0,1,1).Normalize(), Vector(0,-1,1).Normalize(), Vector(0,1,-1).Normalize(), Vector(0,-1,-1).Normalize()}; void RadiusDamage( Vector vecSrc, entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) { CBaseEntity@ pEntity = null; TraceResult tr; float flAdjustedDamage, falloff; Vector vecSpot; if ( flRadius > 0 ) falloff = flDamage / flRadius; else falloff = 1.0; bool bInWater = (g_EngineFuncs.PointContents(vecSrc) == CONTENTS_WATER); vecSrc.z += 1;// in case grenade is lying on the ground if ( pevAttacker is null ) @pevAttacker = @pevInflictor; dictionary attacked; // iterate on all entities in the vicinity. while ((@pEntity = g_EntityFuncs.FindEntityInSphere( pEntity, vecSrc, flRadius, "*", "classname" )) != null) { attacked[pEntity.entindex()] = true; if ( pEntity.pev.takedamage != DAMAGE_NO ) { // UNDONE: this should check a damage mask, not an ignore if ( iClassIgnore != CLASS_NONE && pEntity.Classify() == iClassIgnore ) {// houndeyes don't hurt other houndeyes with their attack continue; } // blast's don't tavel into or out of water if (bInWater && pEntity.pev.waterlevel == 0) continue; if (!bInWater && pEntity.pev.waterlevel == 3) continue; vecSpot = pEntity.BodyTarget( vecSrc ); g_Utility.TraceLine( vecSrc, vecSpot, dont_ignore_monsters, g_EntityFuncs.Instance(pevInflictor).edict(), tr ); if ( tr.flFraction == 1.0 || g_EntityFuncs.Instance(tr.pHit).entindex() == g_EntityFuncs.Instance(pEntity.edict()).entindex() ) {// the explosion can 'see' this entity, so hurt them! if (tr.fStartSolid != 0) { // if we're stuck inside them, fixup the position and distance tr.vecEndPos = vecSrc; tr.flFraction = 0.0; } // decrease damage for an ent that's farther from the bomb. flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; flAdjustedDamage = flDamage - flAdjustedDamage; if ( flAdjustedDamage < 0 ) { flAdjustedDamage = 0; } if (tr.flFraction != 1.0) { g_WeaponFuncs.ClearMultiDamage( ); pEntity.TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), tr, bitsDamageType ); g_WeaponFuncs.ApplyMultiDamage( pevInflictor, pevAttacker ); } else { pEntity.TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); } } } } // Now cast a few rays to make sure we hit the obvious targets. This is needed // for things like tall func_breakables. For example, if the origin is at the // bottom but the explosion origin is at the top. FindEntityInSphere won't // detect it even if the explosion is touching the surface of the brush. for (uint i = 0; i < sphereDirs.size(); i++) { //te_beampoints(vecSrc, vecSrc + sphereDirs[i]*flRadius); g_Utility.TraceLine( vecSrc, vecSrc + sphereDirs[i]*flRadius, dont_ignore_monsters, g_EntityFuncs.Instance(pevInflictor).edict(), tr ); CBaseEntity@ pHit = g_EntityFuncs.Instance(tr.pHit); if (pHit is null or attacked.exists(pHit.entindex()) or pHit.entindex() == 0) continue; attacked[pHit.entindex()] = true; if (tr.fStartSolid != 0) { // if we're stuck inside them, fixup the position and distance tr.vecEndPos = vecSrc; tr.flFraction = 0.0; } // decrease damage for an ent that's farther from the bomb. flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; flAdjustedDamage = flDamage - flAdjustedDamage; if ( flAdjustedDamage < 0 ) { flAdjustedDamage = 0; } if (tr.flFraction != 1.0) { g_WeaponFuncs.ClearMultiDamage( ); pHit.TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), tr, bitsDamageType ); g_WeaponFuncs.ApplyMultiDamage( pevInflictor, pevAttacker ); } else { pHit.TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); } } } void animate_view_angles(EHandle h_plr, Vector start_angle, Vector add_angle, float startTime, float endTime) { if (!h_plr) return; CBaseEntity@ plr = h_plr; if (plr is null) return; float totalTime = endTime - startTime; float progress = 1; if (totalTime > 0) progress = (g_Engine.time - startTime) / totalTime; if (progress > 1) progress = 1; plr.pev.angles.x = (add_angle.x != 0) ? start_angle.x + add_angle.x*progress : plr.pev.v_angle.x; plr.pev.angles.y = (add_angle.y != 0) ? start_angle.y + add_angle.y*progress : plr.pev.v_angle.y; plr.pev.angles.z = (add_angle.z != 0) ? start_angle.z + add_angle.z*progress : plr.pev.v_angle.z; plr.pev.fixangle = FAM_FORCEVIEWANGLES; if (progress < 1) g_Scheduler.SetTimeout("animate_view_angles", 0, h_plr, start_angle, add_angle, startTime, endTime); } void player_sprites_effect(EHandle h_plr, string spr, int count) { if (!h_plr) return; CBaseEntity@ ent = h_plr; if (ent is null or !ent.IsPlayer()) return; CBasePlayer@ plr = cast(ent); te_playersprites(plr, spr, count); } void player_revert_glow(EHandle h_plr, Vector oldGlow, float oldGlowAmt, bool useOldGlow) { if (!h_plr) return; CBaseEntity@ ent = h_plr; if (ent is null or !ent.IsPlayer()) return; if (useOldGlow) { ent.pev.renderamt = oldGlowAmt; ent.pev.rendercolor = oldGlow; } else ent.pev.renderfx = 0; } void custom_user_effect(EHandle h_plr, EHandle h_wep, weapon_custom_user_effect@ effect, bool delayFinished=false) { if (effect is null or !h_plr) return; CBaseEntity@ plrEnt = h_plr; CBaseEntity@ wepEnt = h_wep; CBasePlayer@ plr = cast(plrEnt); CBasePlayerWeapon@ wep = cast(wepEnt); WeaponCustomBase@ c_wep = cast(CastToScriptClass(wepEnt)); if (effect.delay > 0 and !delayFinished) { g_Scheduler.SetTimeout("custom_user_effect", effect.delay, h_plr, h_wep, effect, true); return; } if (effect.pev.spawnflags & FL_UEFFECT_KILL_ACTIVE != 0) { // TODO: Kill active effects } WeaponSound@ snd = effect.getRandomSound(); if (snd !is null) { bool userOnly = effect.pev.spawnflags & FL_UEFFECT_USER_SOUNDS != 0; snd.play(plrEnt, CHAN_STATIC, 1, -1, 0, userOnly); } // damage 'em if (effect.self_damage != 0) { Vector oldVel = plrEnt.pev.velocity; plrEnt.TakeDamage(plrEnt.pev, plrEnt.pev, effect.self_damage, effect.damageType()); // Idk why this even happens. No matter what the damage type is you get launched into the air plrEnt.pev.velocity = oldVel; } // punch 'em if (plrEnt.IsPlayer()) plrEnt.pev.punchangle = effect.punch_angle; // push 'em Math.MakeVectors( plrEnt.pev.v_angle ); Vector push = effect.push_vel; plrEnt.pev.velocity = plrEnt.pev.velocity + g_Engine.v_right*push.x + Vector(0,0,1)*push.y + g_Engine.v_forward*push.z; // rotate 'em if (effect.add_angle != Vector(0,0,0) or effect.add_angle_rand != Vector(0,0,0) and plrEnt.IsPlayer()) { float startTime = g_Engine.time; float endTime = startTime + effect.add_angle_time; Vector r = effect.add_angle_rand; Vector randAngle = Vector(Math.RandomFloat(-r.x, r.x), Math.RandomFloat(-r.y,r.y), Math.RandomFloat(-r.z,r.z)); Vector addAngle = effect.add_angle + randAngle; g_Scheduler.SetTimeout("animate_view_angles", 0, h_plr, plrEnt.pev.v_angle, addAngle, startTime, endTime); } // indicate something if (effect.action_sprite.Length() > 0 and plrEnt.IsPlayer()) plr.ShowOverheadSprite(effect.action_sprite, effect.action_sprite_height, effect.action_sprite_time); // firstperson anim if (h_wep and wep !is null and plrEnt.IsAlive() and plrEnt.IsPlayer() and wep.pev.owner !is null) { if (effect.v_model.Length() > 0 or effect.p_model.Length() > 0 or effect.w_model.Length() > 0 or effect.w_model_body >= 0) { c_wep.v_model_override = effect.v_model; c_wep.p_model_override = effect.p_model; c_wep.w_model_override = effect.w_model; if (effect.w_model_body >= 0) c_wep.w_model_body_override = effect.w_model_body; c_wep.Deploy(true); } if (effect.wep_anim != -1) wep.SendWeaponAnim( effect.wep_anim, 0, c_wep.w_body() ); c_wep.TogglePrimaryFire(effect.primary_mode); } if (effect.hud_text.Length() > 0 and plrEnt.IsPlayer()) { g_PlayerFuncs.PrintKeyBindingString(plr, effect.hud_text); } // thirdperson anim if (effect.anim != -1 and plrEnt.IsPlayer()) { plr.m_Activity = ACT_RELOAD; plr.pev.sequence = effect.anim; plr.pev.frame = effect.anim_frame; plr.ResetSequenceInfo(); plr.pev.framerate = effect.anim_speed; } if (effect.fade_mode != -1 and plrEnt.IsPlayer()) { g_PlayerFuncs.ScreenFade(plr, effect.fade_color.getRGB(), effect.fade_time, effect.fade_hold, effect.fade_color.a, effect.fade_mode); } //if (effect.pev.spawnflags & //plr.EnableControl(false); //plr.pev.flags ^= 4096; if (effect.player_sprite_count > 0 and effect.player_sprite.Length() > 0 and plrEnt.IsPlayer()) { int numIntervals = int(effect.player_sprite_time / effect.player_sprite_freq); player_sprites_effect(h_plr, effect.player_sprite, effect.player_sprite_count); g_Scheduler.SetInterval("player_sprites_effect", effect.player_sprite_freq, numIntervals, h_plr, effect.player_sprite, effect.player_sprite_count); } if (effect.glow_time > 0) { // Remember old glow setting in case user is normally glowing due to map logic or server script Vector oldGlow = plr.pev.rendercolor; float oldGlowAmt = plr.pev.renderamt; bool isGlowing = plr.pev.renderfx == 19; plr.pev.renderfx = 19; plr.pev.renderamt = effect.glow_amt; plr.pev.rendercolor = effect.glow_color; g_Scheduler.SetTimeout("player_revert_glow", effect.glow_time, h_plr, oldGlow, oldGlowAmt, isGlowing); } if (effect.beam_mode != UBEAM_DISABLED and wep !is null) { CreateUserBeam(c_wep.state, @effect); } string targetStr = effect.pev.target; if (targetStr.Length() > 0) { CBaseEntity@ effectEnt = cast(@effect); g_EntityFuncs.FireTargets(targetStr, plrEnt, effectEnt, USE_TYPE(effect.triggerstate)); } if (effect.next_effect !is null) { custom_user_effect(h_plr, h_wep, @effect.next_effect, false); } } void delayed_smoke(Vector origin, string sprite, int scale, int fps) { te_smoke(origin, sprite, scale, fps); } void delayed_bubbles(Vector mins, Vector maxs, float height, string spr, int count, float speed) { te_bubbles(mins, maxs, height, spr, count, speed); } void delay_remove(EHandle ent) { g_EntityFuncs.Remove(ent); } void delay_touch(EHandle ent, EHandle toucher) { if (ent.IsValid() and toucher.IsValid()) ent.GetEntity().Touch(toucher); } void killProjectile(EHandle projectile, EHandle sprite, weapon_custom_shoot@ shoot_opts) { ProjectileOptions@ options = shoot_opts.projectile; if (projectile) { CBaseEntity@ ent = projectile; CBaseEntity@ owner = g_EntityFuncs.Instance( ent.pev.owner ); EHandle howner = owner; EHandle target; custom_effect(ent.pev.origin, shoot_opts.effect3, EHandle(ent), target, howner, Vector(0,0,0), shoot_opts.friendly_fire ? 1 : 0); ent.pev.air_finished = 1; // kill signal //g_EntityFuncs.Remove(ent); } if (sprite) { CBaseEntity@ ent = sprite; g_EntityFuncs.Remove(ent); } } void removeWeapon(CBasePlayerWeapon@ wep) { //wep.Killed(wep.pev, 0); wep.m_hPlayer.GetEntity().RemovePlayerItem(wep); } void WaterSoundEffects(Vector pos, SoundArgs@ args) { if (!args.underwaterEffects) return; if (g_EngineFuncs.PointContents(pos) == CONTENTS_WATER) { args.pitch = Math.max(0, args.pitch - Math.RandomLong(20, 30)); args.volume *= 0.7f; } } void playSoundDelay(SoundArgs@ args) { if (args.attachToEnt) { if (!args.ent) return; CBaseEntity@ ent = args.ent; WaterSoundEffects(ent.pev.origin + ent.pev.view_ofs, @args); g_SoundSystem.EmitSoundDyn( ent.edict(), args.channel, args.file, args.volume, args.attn, args.flags, args.pitch, args.target_ent ); } else { WaterSoundEffects(args.pos, @args); g_SoundSystem.PlaySound( null, args.channel, args.file, args.volume, args.attn, args.flags, args.pitch, args.target_ent, true, args.pos ); } } class SoundArgs { bool valid = false; bool attachToEnt = true; Vector pos; EHandle ent; int target_ent=0; // target_ent_unreliable param SOUND_CHANNEL channel = CHAN_STATIC; string file; float volume = 1.0f; float attn = ATTN_NORM; int flags = 0; int pitch = 100; float delay = 0; WeaponSound@ next_snd; bool underwaterEffects = true; } class WeaponSound { string file; weapon_custom_sound@ options; SoundArgs getSoundArgs(Vector pos, CBaseEntity@ ent, SOUND_CHANNEL channel=CHAN_STATIC, float volMult=1.0f, int pitchOverride=-1, int additionalFlags=0, bool attachToEnt=false, bool userOnly=false) { if (file.Length() == 0) return SoundArgs(); float volume = 1.0f; float attn = ATTN_NORM; int flags = additionalFlags; int pitch = getPitch(); float delay = 0.0f; SOUND_CHANNEL chan = channel; WeaponSound@ nextSnd; bool underwaterEffects = true; if (options !is null) { @nextSnd = @options.next_snd; underwaterEffects = options.pev.spawnflags & FL_SOUND_NO_WATER_EFFECT == 0; delay = options.pev.friction; volume = options.pev.health / 100.0f; if (options.pev.sequence > -1) chan = SOUND_CHANNEL(options.pev.sequence); switch(options.pev.body) { case 1: attn = ATTN_IDLE; break; case 2: attn = ATTN_STATIC; break; case 3: attn = ATTN_NORM; break; case 4: attn = 0.3f; break; case 5: attn = ATTN_NONE; break; } if (options.pev.skin == 2) flags |= SND_FORCE_SINGLE; if (options.pev.skin == 3) flags |= SND_FORCE_LOOP; } if (pitchOverride != -1) pitch = pitchOverride; SoundArgs args; args.valid = true; args.attachToEnt = attachToEnt; args.pos = pos; args.delay = delay; args.ent = ent; args.channel = chan; args.file = file; args.volume = volume*volMult; args.attn = attn; args.flags = flags; args.pitch = pitch; args.target_ent = (ent !is null and userOnly) ? ent.entindex() : 0; args.underwaterEffects = underwaterEffects; @args.next_snd = @nextSnd; return args; } bool play(CBaseEntity@ ent, SOUND_CHANNEL channel=CHAN_STATIC, float volMult=1.0f, int pitchOverride=-1, int additionalFlags=0, bool userOnly=false) { SoundArgs args = getSoundArgs(Vector(0,0,0), ent, channel, volMult, pitchOverride, additionalFlags, true, userOnly); if (!args.valid) return false; //println("PLAY: " + args.file); if (args.delay > 0) g_Scheduler.SetTimeout("playSoundDelay", args.delay, @args); else { WaterSoundEffects(ent.pev.origin + ent.pev.view_ofs, @args); g_SoundSystem.EmitSoundDyn( ent.edict(), args.channel, args.file, args.volume, args.attn, args.flags, args.pitch, args.target_ent ); } if (args.next_snd !is null) args.next_snd.play(ent, channel, volMult, pitchOverride, additionalFlags, userOnly); return true; } bool play(Vector pos, SOUND_CHANNEL channel=CHAN_STATIC, float volMult=1.0f, int pitchOverride=-1, int additionalFlags=0) { SoundArgs args = getSoundArgs(pos, null, channel, volMult, pitchOverride, additionalFlags, false); if (!args.valid) return false; if (args.delay > 0) g_Scheduler.SetTimeout("playSoundDelay", args.delay, @args); else { WaterSoundEffects(args.pos, @args); g_SoundSystem.PlaySound( null, args.channel, args.file, args.volume, args.attn, args.flags, args.pitch, args.target_ent, true, pos ); } if (args.next_snd !is null) args.next_snd.play(pos, channel, volMult, pitchOverride, additionalFlags); return true; } int getPitch() { if (options !is null) { int pitch_rand = options.pev.renderfx; return options.pev.rendermode + Math.RandomLong(-pitch_rand, pitch_rand); } return 100; } float getVolume() { if (options !is null) { return options.pev.health / 100.0f; } return 1.0f; } void stop(CBaseEntity@ ent, SOUND_CHANNEL channel=CHAN_STATIC) { if (file.Length() <= 0) return; if (options !is null and options.pev.sequence > -1) channel = SOUND_CHANNEL(options.pev.sequence); g_SoundSystem.StopSound(ent.edict(), channel, file); } } array parseSounds(string val) { array strings = val.Split(";"); array sounds; for (uint i = 0; i < strings.length(); i++) { WeaponSound s; s.file = strings[i]; sounds.insertLast(s); } return sounds; } enum impact_decal_type { DECAL_NONE = -2, DECAL_BIGBLOOD = 0, DECAL_BIGSHOT, DECAL_BLOOD, DECAL_BLOODHAND, DECAL_BULLETPROOF, DECAL_GLASSBREAK, DECAL_LETTERS, DECAL_SMALLCRACK, DECAL_LARGECRACK, DECAL_LARGEDENT, DECAL_SMALLDENT, DECAL_DING, DECAL_RUST, DECAL_FEET, DECAL_GARGSTOMP, DECAL_GUASS, DECAL_GRAFITTI, DECAL_HANDICAP, DECAL_MOMMABLOB, DECAL_SMALLSCORTCH, DECAL_MEDIUMSCORTCH, DECAL_TINYSCORTCH, DECAL_OIL, DECAL_LARGESCORTCH, DECAL_SMALLSHOT, DECAL_NUMBERS, DECAL_TINYSCORTCH2, DECAL_SMALLSCORTCH2, DECAL_SPIT, DECAL_BIGABLOOD, DECAL_TARGET, DECAL_TIRE, DECAL_ABLOOD, DECAL_TYPES, } array< array > g_decals = { {"{bigblood1", "{bigblood2"}, {"{bigshot1", "{bigshot2", "{bigshot3", "{bigshot4", "{bigshot5"}, {"{blood1", "{blood2", "{blood3", "{blood4", "{blood5", "{blood6", "{blood7", "{blood8"}, {"{bloodhand1", "{bloodhand2", "{bloodhand3", "{bloodhand4", "{bloodhand5", "{bloodhand6"}, {"{bproof1"}, {"{break1", "{break2", "{break3"}, {"{capsa", "{capsb", "{capsc", "{capsd", "{capse", "{capsf", "{capsg", "{capsh", "{capsi", "{capsj", "{capsk", "{capsl", "{capsm", "{capsn", "{capso", "{capsp", "{capsq", "{capsr", "{capss", "{capst", "{capsu", "{capsv", "{capsw", "{capsx", "{capsy", "{capsz"}, {"{crack1", "{crack2"}, {"{crack3", "{crack4"}, {"{dent1", "{dent2"}, {"{dent3", "{dent4", "{dent5", "{dent6"}, {"{ding3", "{ding4", "{ding5", "{ding6", "{ding7", "{ding8", "{ding9"}, {"{ding10", "{ding11"}, {"{foot_l", "{foot_r"}, {"{gargstomp"}, {"{gaussshot1"}, {"{graf001", "{graf002", "{graf003", "{graf004", "{graf005"}, {"{handi"}, {"{mommablob"}, {"{ofscorch1", "{ofscorch2", "{ofscorch3"}, {"{ofscorch4", "{ofscorch5", "{ofscorch6"}, {"{ofsmscorch1", "{ofsmscorch2", "{ofsmscorch3"}, {"{oil1", "{oil2"}, {"{scorch1", "{scorch2", "{scorch3"}, {"{shot1", "{shot2", "{shot3", "{shot4", "{shot5"}, {"{small#s0", "{small#s1", "{small#s2", "{small#s3", "{small#s4", "{small#s5", "{small#s6", "{small#s7", "{small#s8", "{small#s9"}, {"{smscorch1", "{smscorch2"}, {"{smscorch3"}, {"{spit1", "{spit2"}, {"{spr_splt1", "{spr_splt2", "{spr_splt3"}, {"{target", "{target2"}, {"{tire1", "{tire2"}, {"{yblood1", "{yblood2", "{yblood3", "{yblood4", "{yblood5", "{yblood6"}, }; string getDecal(int decalType) { if (decalType < 0 or decalType >= int(g_decals.length())) decalType = DECAL_SMALLSHOT; array decals = g_decals[decalType]; return decals[ Math.RandomLong(0, decals.length()-1) ]; } bool isBreakableEntity(CBaseEntity@ ent) { if (ent.pev.classname == "func_breakable") return true; if (ent.pev.classname == "func_door" or ent.pev.classname == "func_door_rotating") return true; // TODO: Figure out how to check "breakable" keyvalue return false; } int getMonsterClass(CBaseMonster@ mon) { if (mon.Classify() == CLASS_PLAYER_ALLY) { // PLAYER_ALLY masks the actual monster class, so remove that for a sec mon.SetPlayerAlly(false); int c = mon.Classify(); mon.SetPlayerAlly(true); return c; } return mon.Classify(); } bool isHuman(CBaseEntity@ ent) { if (ent.IsMonster()) { CBaseMonster@ mon = cast(ent); int c = getMonsterClass(mon); switch(c) { case CLASS_PLAYER: case CLASS_HUMAN_PASSIVE: case CLASS_HUMAN_MILITARY: return true; } } return false; } bool isAlien(CBaseEntity@ ent) { if (ent.IsMonster()) { CBaseMonster@ mon = cast(ent); int c = getMonsterClass(mon); switch(c) { case CLASS_ALIEN_MILITARY: case CLASS_ALIEN_PASSIVE: case CLASS_ALIEN_MONSTER: case CLASS_ALIEN_PREY: case CLASS_ALIEN_PREDATOR: case CLASS_PLAYER_BIOWEAPON: case CLASS_ALIEN_BIOWEAPON: case CLASS_XRACE_PITDRONE: case CLASS_XRACE_SHOCK: case CLASS_BARNACLE: return true; } } return false; } bool isRepairable(CBaseEntity@ breakable) { if (breakable.pev.classname == "func_door") return true; // no way to check breakable key that I know of if (breakable.pev.classname == "func_breakable") return breakable.pev.spawnflags & 8 != 0; return false; } bool shouldHealTarget(CBaseEntity@ target, CBaseEntity@ healer, weapon_custom_shoot@ shoot_opts) { if (shoot_opts.heal_mode == HEAL_OFF) return false; int mode = shoot_opts.heal_mode; int heals = shoot_opts.heal_targets; bool healAll = heals == HEALT_EVERYTHING; bool healMachines = heals == HEALT_MACHINES or heals == HEALT_MACHINES_AND_BREAKABLES; bool healHumans = heals == HEALT_HUMANS or heals == HEALT_HUMANS_AND_ALIENS; bool healAliens = heals == HEALT_ALIENS or heals == HEALT_HUMANS_AND_ALIENS; bool healBreakables = heals == HEALT_BREAKABLES or heals == HEALT_MACHINES_AND_BREAKABLES; // breakables ignore friendly status for whatever reason if (target.IsBSPModel() and isRepairable(target) and (healBreakables or healAll)) return true; int rel = healer.IRelationship(target); bool isFriendly = rel == R_AL or rel == R_NO; bool healFriend = mode == HEAL_FRIENDS or mode == HEAL_REVIVE_FRIENDS or mode == HEAL_ALL; bool healFoe = mode == HEAL_FOES or mode == HEAL_REVIVE_FOES or mode == HEAL_ALL; if ((isFriendly and healFriend) or (!isFriendly and healFoe)) { if (healAll) return true; if (target.IsMachine() and healMachines) return true; if (isHuman(target) and healHumans) return true; if (isAlien(target) and healAliens) return true; } return false; } bool shouldAttack(CBaseEntity@ target, CBaseEntity@ plr, weapon_custom_shoot@ shoot_opts) { bool shoot_on_damage = shoot_opts.pev.spawnflags & FL_SHOOT_IF_NOT_DAMAGE != 0; if (!shouldHealTarget(target, plr, shoot_opts) and !shoot_on_damage) return false; return true; } float applyDamageModifiers(float damage, CBaseEntity@ target, CBaseEntity@ plr, weapon_custom_shoot@ shoot_opts) { // don't do any damage if target is friendly and npc_kill is set to 0 or 2 bool ignoreDmg = false; if (target.IsMonster()) { CBaseMonster@ mon = cast(target); if (mon.CheckAttacker(plr)) { damage = 0; } } bool didHeal = false; if (shouldHealTarget(target, plr, shoot_opts)) { didHeal = true; damage = -damage; } // Award player with poitns (TODO: Account for hitgroup multipliers) plr.pev.frags += target.GetPointsForDamage(didHeal ? -damage : damage); return damage; } void knockBack(CBaseEntity@ target, Vector vel) { if (target.IsMonster() and !target.IsMachine()) target.pev.velocity = target.pev.velocity + vel; } void monitorWeaponbox(CBaseEntity@ wep) { if (wep !is null) { println("BODY: " + wep.pev.classname); wep.pev.body = 3; wep.pev.sequence = 8; g_Scheduler.SetTimeout("monitorWeaponbox", 0.1, @wep); } else { println("LEL NULL ENT"); } } float heal(CBaseEntity@ target, weapon_custom_shoot@ opts, float amt) { float oldHealth = target.pev.health; if (opts.heal_mode < HEAL_REVIVE_FRIENDS) { target.pev.health += amt; if (target.pev.health > target.pev.max_health) target.pev.health = target.pev.max_health; } return oldHealth - target.pev.health; } float revive(CBaseEntity@ target, weapon_custom_shoot@ opts) { target.EndRevive(0); target.pev.health = target.pev.max_health * (opts.damage/100.0f); return target.pev.health; } CBaseEntity@ getReviveTarget(Vector center, float radius, CBaseEntity@ healer, weapon_custom_shoot@ shoot_opts) { CBaseEntity@ ent = null; do { @ent = g_EntityFuncs.FindEntityInSphere(ent, center, radius, "*", "classname"); if (ent !is null) { if (ent.IsMonster() and !ent.IsAlive()) { if (shouldHealTarget(ent, healer, shoot_opts)) { return @ent; } } } } while (ent !is null); return null; } // force projectile to follow player's crosshairs void projectile_follow_aim(EHandle h_plr, EHandle h_proj, weapon_custom_shoot@ opts, float timeLeft) { if (!h_plr or !h_proj) return; CBaseEntity@ plrEnt = h_plr; CBasePlayer@ plr = cast(@plrEnt); CBaseEntity@ proj = h_proj; if (plr is null or proj is null) return; // TO_BE_SCARED_OF: Can this cause a race condition (other code using engine vectors at the same time)? Vector vecSrc = plr.GetGunPosition(); Math.MakeVectors( plr.pev.v_angle ); // get crosshair target location TraceResult tr; Vector vecEnd = vecSrc + g_Engine.v_forward * 65536; g_Utility.TraceLine( vecSrc, vecEnd, dont_ignore_monsters, proj.edict(), tr ); Vector dir = proj.pev.velocity.Normalize(); Vector targetDir; if (opts.projectile.follow_mode == FOLLOW_CROSSHAIRS) { targetDir = (tr.vecEndPos - proj.pev.origin).Normalize(); } else if (opts.projectile.follow_mode == FOLLOW_ENEMIES) { CBaseEntity@ enemy = g_EntityFuncs.Instance( proj.pev.enemy ); CBaseEntity@ ent = null; float bestDot = 0; float bestRange = 9999999.0f; float radius = opts.projectile.follow_radius; if (radius <= 0) radius = 9999999; // find closest enemy (TODO: give low dot products a weight to prefer not turning a lot) if (enemy is null) { do { @ent = g_EntityFuncs.FindEntityInSphere(ent, proj.pev.origin, radius, "*", "classname"); if (ent !is null and ent.IsMonster() and ent.IsAlive()) { if (ent.entindex() == plr.entindex()) continue; if (ent.pev.classname == "squadmaker") continue; int rel = plr.IRelationship(ent); bool isFriendly = rel == R_AL or rel == R_NO; if (!isFriendly or true) { float dist = (ent.pev.origin - proj.pev.origin).Length(); if (dist < bestRange) { bestRange = dist; @proj.pev.enemy = ent.edict(); } } } } while (ent !is null); @enemy = g_EntityFuncs.Instance( proj.pev.enemy ); } if (enemy !is null) targetDir = (enemy.BodyTarget(enemy.pev.origin) - proj.pev.origin).Normalize(); else targetDir = dir; } float speed = proj.pev.velocity.Length(); Vector axis = CrossProduct(dir, targetDir).Normalize(); float dot = DotProduct(targetDir, dir); float angle = -acos(dot); if (dot == -1) angle = Math.PI / 2.0f; if (dot == 1 or angle != angle) angle = 0; float maxAngle = opts.projectile.follow_angle * Math.PI / 180.0f; angle = Math.max(-maxAngle, Math.min(maxAngle, angle)); if (abs(angle) > 0.001f) { // Apply rotation around arbitrary axis array rotMat = rotationMatrix(axis, angle); dir = matMultVector(rotMat, dir).Normalize(); proj.pev.velocity = dir*speed; g_EngineFuncs.VecToAngles(proj.pev.velocity, proj.pev.angles); } bool oneMoreTime = false; if (timeLeft > 0) { timeLeft -= 0.05; if (timeLeft <= 0) { oneMoreTime = true; timeLeft = -1; } } if (timeLeft >= 0 or oneMoreTime) g_Scheduler.SetTimeout("projectile_follow_aim", opts.projectile.follow_time.z, h_plr, h_proj, @opts, timeLeft); } // breakable glass uses a special decal when shot string getBulletDecalOverride(CBaseEntity@ ent, string currentDecal) { TraceResult tr; if (ent !is null and isBreakableEntity(ent)) { if (ent.pev.playerclass == 1) // learned this from HLSDK func_break.cpp line 158 return getDecal(DECAL_GLASSBREAK); if (ent.TakeDamage(ent.pev, ent.pev, 0, DMG_GENERIC) == 0) // TODO: Don't do this, it makes sound return getDecal(DECAL_BULLETPROOF); // only unbreakable glass can't take damage } return currentDecal; } void loadSoundSettings(WeaponSound@ snd) { if (snd !is null and snd.options !is null) return; CBaseEntity@ ent = g_EntityFuncs.FindEntityByTargetname(null, snd.file); if (ent !is null) { snd.file = ent.pev.message; @snd.options = cast(CastToScriptClass(ent)); snd.options.loadExternalSoundSettings(); } } weapon_custom_effect@ loadEffectSettings(weapon_custom_effect@ effect, string name="") { if (effect !is null and effect.valid) return @effect; string searchStr = effect !is null ? effect.name : name; CBaseEntity@ ent = g_EntityFuncs.FindEntityByTargetname(null, searchStr); if (ent !is null) { weapon_custom_effect@ ef = cast(CastToScriptClass(ent)); ef.loadExternalSoundSettings(); ef.valid = true; ef.name = searchStr; @effect = @ef; ef.loadExternalEffectSettings(); // load chained effects return @ef; } else if (string(searchStr).Length() > 0) { println("WEAPON_CUSTOM ERROR: Failed to find weapon_custom_effect " + searchStr); } return @effect; } weapon_custom_user_effect@ loadUserEffectSettings(weapon_custom_user_effect@ effect, string name="") { if (effect !is null and effect.valid) return @effect; CBaseEntity@ ent = g_EntityFuncs.FindEntityByTargetname(null, name); if (ent !is null) { weapon_custom_user_effect@ ef = cast(CastToScriptClass(ent)); ef.loadExternalSoundSettings(); ef.valid = true; @effect = @ef; ef.loadExternalUserEffectSettings(); // load chained effects return @ef; } else if (string(name).Length() > 0) { println("WEAPON_CUSTOM ERROR: Failed to find weapon_custom_user_effect " + name); } return @effect; } void loadSoundSettings(array@ sound_list) { for (uint k = 0; k < sound_list.length(); k++) loadSoundSettings(sound_list[k]); } // Temporary Entity Effects (minimized) void te_explosion(Vector pos, string sprite="sprites/zerogxplode.spr", int scale=10, int frameRate=15, int flags=0, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_EXPLOSION);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(scale);m.WriteByte(frameRate);m.WriteByte(flags);m.End(); } void te_smoke(Vector pos, string sprite="sprites/steam1.spr", int scale=10, int frameRate=15, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_SMOKE);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(scale);m.WriteByte(frameRate);m.End(); } void _te_beamcircle(Vector pos, float radius, string sprite, uint8 startFrame, uint8 frameRate, uint8 life, uint8 width, uint8 noise, Color c, uint8 scrollSpeed, NetworkMessageDest msgType, edict_t@ dest, int beamType) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(beamType);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z + radius);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(startFrame);m.WriteByte(frameRate);m.WriteByte(life);m.WriteByte(width);m.WriteByte(noise);m.WriteByte(c.r);m.WriteByte(c.g);m.WriteByte(c.b);m.WriteByte(c.a);m.WriteByte(scrollSpeed);m.End(); } void te_beamtorus(Vector pos, float radius, string sprite="sprites/laserbeam.spr", uint8 startFrame=0, uint8 frameRate=16, uint8 life=8, uint8 width=8, uint8 noise=0, Color c=PURPLE, uint8 scrollSpeed=0, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_beamcircle(pos, radius, sprite, startFrame, frameRate, life, width, noise, c, scrollSpeed, msgType, dest, TE_BEAMTORUS); } void te_beamdisk(Vector pos, float radius, string sprite="sprites/laserbeam.spr", uint8 startFrame=0, uint8 frameRate=16, uint8 life=8, uint8 width=8, uint8 noise=0, Color c=PURPLE, uint8 scrollSpeed=0, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_beamcircle(pos, radius, sprite, startFrame, frameRate, life, width, noise, c, scrollSpeed, msgType, dest, TE_BEAMDISK); } void te_beamcylinder(Vector pos, float radius, string sprite="sprites/laserbeam.spr", uint8 startFrame=0, uint8 frameRate=16, uint8 life=8, uint8 width=8, uint8 noise=0, Color c=PURPLE, uint8 scrollSpeed=0, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_beamcircle(pos, radius, sprite, startFrame, frameRate, life, width, noise, c, scrollSpeed, msgType, dest, TE_BEAMCYLINDER); } void te_breakmodel(Vector pos, Vector size, Vector velocity, uint8 speedNoise=16, string model="models/hgibs.mdl", uint8 count=8, uint8 life=0, uint8 flags=20, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BREAKMODEL);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(size.x);m.WriteCoord(size.y);m.WriteCoord(size.z);m.WriteCoord(velocity.x);m.WriteCoord(velocity.y);m.WriteCoord(velocity.z);m.WriteByte(speedNoise);m.WriteShort(g_EngineFuncs.ModelIndex(model));m.WriteByte(count);m.WriteByte(life);m.WriteByte(flags);m.End(); } void te_dlight(Vector pos, uint8 radius=16, Color c=PURPLE, uint8 life=255, uint8 decayRate=4, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_DLIGHT);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteByte(radius);m.WriteByte(c.r);m.WriteByte(c.g);m.WriteByte(c.b);m.WriteByte(life);m.WriteByte(decayRate);m.End(); } void _te_decal(Vector pos, CBaseEntity@ plr, CBaseEntity@ brushEnt, string decal, NetworkMessageDest msgType, edict_t@ dest, int decalType) { int decalIdx = g_EngineFuncs.DecalIndex(decal); int entIdx = brushEnt is null ? 0 : brushEnt.entindex(); if (decalIdx == -1) { if (plr !is null) decalIdx = 0; else { println("Invalid decal: " + decalIdx); return; } } if (decalIdx > 511) { println("Decal index too high (" + decalIdx + ")! Max decal index is 511."); return; } if (decalIdx > 255) { decalIdx -= 255; if (decalType == TE_DECAL) decalType = TE_DECALHIGH; else if (decalType == TE_WORLDDECAL) decalType = TE_WORLDDECALHIGH; else println("Decal type " + decalType + " doesn't support indicies > 255"); } if (decalType == TE_DECAL and entIdx == 0) decalType = TE_WORLDDECAL; if (decalType == TE_DECALHIGH and entIdx == 0) decalType = TE_WORLDDECALHIGH; NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest); m.WriteByte(decalType); if (plr !is null) m.WriteByte(plr.entindex()); m.WriteCoord(pos.x); m.WriteCoord(pos.y); m.WriteCoord(pos.z); switch(decalType) { case TE_DECAL: case TE_DECALHIGH: m.WriteByte(decalIdx); m.WriteShort(entIdx); break; case TE_GUNSHOTDECAL: case TE_PLAYERDECAL: m.WriteShort(entIdx); m.WriteByte(decalIdx); break; default: m.WriteByte(decalIdx); } m.End(); } void te_decal(Vector pos, CBaseEntity@ brushEnt=null, string decal="{handi", NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_decal(pos, null, brushEnt, decal, msgType, dest, TE_DECAL); } void te_gunshotdecal(Vector pos, CBaseEntity@ brushEnt=null, string decal="{handi", NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_decal(pos, null, brushEnt, decal, msgType, dest, TE_GUNSHOTDECAL); } void te_usertracer(Vector pos, Vector dir, float speed=6000.0f, uint8 life=32, uint color=4, uint8 length=12, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { Vector velocity = dir*speed;NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_USERTRACER);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(velocity.x);m.WriteCoord(velocity.y);m.WriteCoord(velocity.z);m.WriteByte(life);m.WriteByte(color);m.WriteByte(length);m.End();} void te_tracer(Vector start, Vector end, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_TRACER);m.WriteCoord(start.x);m.WriteCoord(start.y);m.WriteCoord(start.z);m.WriteCoord(end.x);m.WriteCoord(end.y);m.WriteCoord(end.z);m.End(); } void te_spritetrail(Vector start, Vector end, string sprite="sprites/hotglow.spr", uint8 count=2, uint8 life=0, uint8 scale=1, uint8 speed=16, uint8 speedNoise=8, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_SPRITETRAIL);m.WriteCoord(start.x);m.WriteCoord(start.y);m.WriteCoord(start.z);m.WriteCoord(end.x);m.WriteCoord(end.y);m.WriteCoord(end.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(count);m.WriteByte(life);m.WriteByte(scale);m.WriteByte(speedNoise);m.WriteByte(speed);m.End(); } void te_streaksplash(Vector start, Vector dir, uint8 color=250, uint16 count=256, uint16 speed=2048, uint16 speedNoise=128, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_STREAK_SPLASH);m.WriteCoord(start.x);m.WriteCoord(start.y);m.WriteCoord(start.z);m.WriteCoord(dir.x);m.WriteCoord(dir.y);m.WriteCoord(dir.z);m.WriteByte(color);m.WriteShort(count);m.WriteShort(speed);m.WriteShort(speedNoise);m.End(); } void te_glowsprite(Vector pos, string sprite="sprites/glow01.spr", uint8 life=1, uint8 scale=10, uint8 alpha=255, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_GLOWSPRITE);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(life);m.WriteByte(scale);m.WriteByte(alpha);m.End(); } void te_bloodsprite(Vector pos, string sprite1="sprites/bloodspray.spr", string sprite2="sprites/blood.spr", uint8 color=70, uint8 scale=3, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BLOODSPRITE);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite1));m.WriteShort(g_EngineFuncs.ModelIndex(sprite2));m.WriteByte(color);m.WriteByte(scale);m.End(); } void te_beampoints(Vector start, Vector end, string sprite="sprites/laserbeam.spr", uint8 frameStart=0, uint8 frameRate=100, uint8 life=20, uint8 width=2, uint8 noise=0, Color c=GREEN, uint8 scroll=32, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BEAMPOINTS);m.WriteCoord(start.x);m.WriteCoord(start.y);m.WriteCoord(start.z);m.WriteCoord(end.x);m.WriteCoord(end.y);m.WriteCoord(end.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(frameStart);m.WriteByte(frameRate);m.WriteByte(life);m.WriteByte(width);m.WriteByte(noise);m.WriteByte(c.r);m.WriteByte(c.g);m.WriteByte(c.b);m.WriteByte(c.a);m.WriteByte(scroll);m.End(); } void _te_pointeffect(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null, int effect=TE_SPARKS) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(effect);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.End(); } void te_sparks(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_pointeffect(pos, msgType, dest, TE_SPARKS); } void te_bubbletrail(Vector start, Vector end, string sprite="sprites/bubble.spr", float height=128.0f, uint8 count=16, float speed=16.0f, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BUBBLETRAIL);m.WriteCoord(start.x);m.WriteCoord(start.y);m.WriteCoord(start.z);m.WriteCoord(end.x);m.WriteCoord(end.y);m.WriteCoord(end.z);m.WriteCoord(height);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(count);m.WriteCoord(speed);m.End(); } void te_bubbles(Vector mins, Vector maxs, float height=256.0f, string sprite="sprites/bubble.spr", uint8 count=64, float speed=16.0f, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BUBBLES);m.WriteCoord(mins.x);m.WriteCoord(mins.y);m.WriteCoord(mins.z);m.WriteCoord(maxs.x);m.WriteCoord(maxs.y);m.WriteCoord(maxs.z);m.WriteCoord(height);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(count);m.WriteCoord(speed);m.End(); } void te_spritespray(Vector pos, Vector velocity, string sprite="sprites/bubble.spr", uint8 count=8, uint8 speed=16, uint8 noise=255, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_SPRITE_SPRAY);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(velocity.x);m.WriteCoord(velocity.y);m.WriteCoord(velocity.z);m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(count);m.WriteByte(speed);m.WriteByte(noise);m.End(); } void te_ricochet(Vector pos, uint8 scale=10, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_ARMOR_RICOCHET);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteByte(scale);m.End(); } void te_tarexplosion(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) {_te_pointeffect(pos, msgType, dest, TE_TAREXPLOSION);} void te_explosion2(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_EXPLOSION2);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteByte(0);m.WriteByte(127);m.End();} void te_particlebust(Vector pos, uint16 radius=128, uint8 color=250, uint8 life=5, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) {NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_PARTICLEBURST);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteShort(radius);m.WriteByte(color);m.WriteByte(life);m.End();} void te_lavasplash(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { _te_pointeffect(pos, msgType, dest, TE_LAVASPLASH); } void te_teleport(Vector pos, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) {_te_pointeffect(pos, msgType, dest, TE_TELEPORT);} void te_implosion(Vector pos, uint8 radius=255, uint8 count=32, uint8 life=5, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null){NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_IMPLOSION);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteByte(radius);m.WriteByte(count);m.WriteByte(life);m.End();} void te_playersprites(CBasePlayer@ target, string sprite="sprites/bubble.spr", uint8 count=16, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) {NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_PLAYERSPRITES);m.WriteShort(target.entindex());m.WriteShort(g_EngineFuncs.ModelIndex(sprite));m.WriteByte(count);m.WriteByte(0);m.End();} void te_bloodstream(Vector pos, Vector dir, uint8 color=70, uint8 speed=64, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) { NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_BLOODSTREAM);m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(dir.x);m.WriteCoord(dir.y);m.WriteCoord(dir.z);m.WriteByte(color);m.WriteByte(speed);m.End();} void te_elight(CBaseEntity@ target, Vector pos, float radius=1024.0f, Color c=PURPLE, uint8 life=16, float decayRate=2000.0f, NetworkMessageDest msgType=MSG_BROADCAST, edict_t@ dest=null) {NetworkMessage m(msgType, NetworkMessages::SVC_TEMPENTITY, dest);m.WriteByte(TE_ELIGHT);m.WriteShort(target.entindex());m.WriteCoord(pos.x);m.WriteCoord(pos.y);m.WriteCoord(pos.z);m.WriteCoord(radius);m.WriteByte(c.r);m.WriteByte(c.g);m.WriteByte(c.b);m.WriteByte(life);m.WriteCoord(decayRate);m.End();} }