#include "g_local.h" /* ============================================================================== SHOCKWAVE ============================================================================== */ void ShockQuake (edict_t *self) { int i; edict_t *ent; for (i = 1, ent = g_edicts+1; i < globals.num_edicts; i++, ent++) { if ( !ent->inuse || !ent->client || !ent->groundentity ) continue; ent->groundentity = NULL; ent->velocity[0] += crandom() * 125.0f; ent->velocity[1] += crandom() * 125.0f; ent->velocity[2] += self->speed * (150.0f / ent->mass); } /* if (!strcmp(self->classname, "shocksphere")) // remove shock sphere after x bounces { if (self->count > sk_shockwave_bounces->value) G_FreeEdict (self); return; //don't loop }*/ if (level.time < self->timestamp) self->nextthink = level.time + FRAMETIME; else G_FreeEdict (self); } void shock_effect_think (edict_t *self) { if (++self->s.frame < 19) self->nextthink = level.time + FRAMETIME; else { self->s.frame = 0; self->nextthink = level.time + FRAMETIME; } self->count--; // fade out #ifdef KMQUAKE2_ENGINE_MOD if (self->count <= 6) self->s.alpha -= 0.10; if (self->s.alpha < 0.10) self->s.alpha = 0.10; #else if (self->count == 5) { self->s.effects |= EF_SPHERETRANS; self->s.renderfx &= ~RF_TRANSLUCENT; } #endif // remove after 6 secs if (self->count == 0) G_FreeEdict (self); // inflict field damage on surroundings T_RadiusDamage(self, self->owner, self->radius_dmg, NULL, self->dmg_radius, MOD_SHOCK_SPLASH); } void shock_effect_center_think (edict_t *self) { self->nextthink = level.time + FRAMETIME; if ((self->count % 5) == 0) { if (self->count > 10) // double effect for first 40 seconds { gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_NUKEBLAST); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_ALL); } gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_NUKEBLAST); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_ALL); } // fade out #ifdef KMQUAKE2_ENGINE_MOD if (self->count <= 10) self->s.alpha -= 0.10; self->s.alpha = max(self->s.alpha, 1/255); // remove after 5 secs self->count--; if (self->count == 0 || self->s.alpha <= 1/255) G_FreeEdict (self); #else if (self->count == 10) self->s.renderfx |= RF_TRANSLUCENT; if (self->count == 5) { self->s.effects |= EF_SPHERETRANS; self->s.renderfx &= ~RF_TRANSLUCENT; } // remove after 5 secs self->count--; if (self->count == 0) G_FreeEdict (self); #endif } void ShockEffect (edict_t *source, edict_t *attacker, float damage, float radius, cplane_t *plane) { edict_t *ent; edict_t *center; vec3_t hit_point; if (plane->normal) { // put origin of effect 32 units away from last hit surface VectorMA (source->s.origin, 32.0, plane->normal, hit_point); } ent = G_Spawn(); // same origin as exploding shock sphere if (plane->normal) VectorCopy (hit_point, ent->s.origin); else VectorCopy (source->s.origin, ent->s.origin); ent->radius_dmg = sk_shockwave_effect_damage->value; ent->dmg_radius = sk_shockwave_effect_radius->value; ent->owner = attacker; ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_NOT; VectorSet (ent->mins, -8, -8, 8); VectorSet (ent->maxs, 8, 8, 8); ent->s.modelindex = gi.modelindex ("models/objects/shockfield/tris.md2"); #ifdef KMQUAKE2_ENGINE_MOD ent->s.alpha = 0.70; #else ent->s.renderfx |= RF_TRANSLUCENT; #endif ent->s.renderfx |= RF_NOSHADOW|RF_FULLBRIGHT; ent->s.effects = EF_FLAG2; ent->count = 60; // lasts 6 seconds ent->think = shock_effect_think; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (ent); // center light burst effect center = G_Spawn(); // same origin as exploding shock sphere if (plane->normal) VectorCopy (hit_point, center->s.origin); else VectorCopy (source->s.origin, center->s.origin); center->movetype = MOVETYPE_NONE; center->solid = SOLID_NOT; VectorSet (center->mins, -8, -8, 8); VectorSet (center->maxs, 8, 8, 8); center->s.modelindex = gi.modelindex ("sprites/s_trap.sp2"); center->s.effects |= EF_FLAG2 | EF_ANIM_ALLFAST; #ifdef KMQUAKE2_ENGINE_MOD center->s.alpha = 0.90; #endif center->count = 50; // lasts 5 seconds center->think = shock_effect_center_think; center->nextthink = level.time + 2 * FRAMETIME; ent->teamchain = center; gi.linkentity (center); } #ifndef KMQUAKE2_ENGINE_MOD void ShockSplashThink (edict_t *self) { self->s.frame++; if (self->s.frame > 8) { G_FreeEdict(self); return; } self->nextthink = level.time + FRAMETIME; } #else // Gotta have this for extractfuncs... void ShockSplashThink (edict_t *self) { } #endif void ShockSplash(edict_t *self, cplane_t *plane) { #ifdef KMQUAKE2_ENGINE_MOD // use new client-side effect vec3_t shockdir; if (!plane->normal) AngleVectors(self->s.angles, shockdir, NULL, NULL); else VectorCopy (plane->normal, shockdir); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_SHOCKSPLASH); gi.WritePosition (self->s.origin); gi.WriteDir (shockdir); gi.multicast (self->s.origin, MULTICAST_ALL); // Lazarus reflections if (level.num_reflectors) ReflectSparks (TE_SHOCKSPLASH, self->s.origin, shockdir); #else edict_t *shockring; int i; gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/shockhit.wav"), 1, ATTN_NONE, 0); for (i = 0; i < 5; i++) { shockring = G_Spawn(); shockring->classname = "shock_ring"; if (plane->normal) { // put origin of impact effect 16*i+1 units away from last hit surface VectorMA (self->s.origin, 16.0*(i+1), plane->normal, shockring->s.origin); vectoangles(plane->normal, shockring->s.angles); } else { VectorCopy (self->s.origin, shockring->s.origin); VectorCopy (self->s.angles, shockring->s.angles); } shockring->solid = SOLID_NOT; shockring->movetype = MOVETYPE_NONE; shockring->owner = self; shockring->s.modelindex = gi.modelindex("models/objects/shocksplash/tris.md2"); shockring->s.frame = (4-i); shockring->s.effects |= EF_SPHERETRANS; shockring->s.renderfx |= (RF_FULLBRIGHT | RF_NOSHADOW); shockring->nextthink = level.time + FRAMETIME; shockring->think = ShockSplashThink; gi.linkentity (shockring); } #endif } void shock_sphere_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t origin; edict_t *impact; // ignore other projectiles if ((other->movetype == MOVETYPE_FLYMISSILE) || (other->movetype == MOVETYPE_BOUNCE) || (other->movetype == MOVETYPE_WALLBOUNCE)) return; ent->count++; // add to count ent->movetype = MOVETYPE_BOUNCE; if (other == ent->owner) return; // detonate if hit a monster if (other->takedamage) ent->count = sk_shockwave_bounces->value + 1; if ( (ent->velocity[0] < 20) && (ent->velocity[0] > -20) && (ent->velocity[1] < 20) && (ent->velocity[1] > -20) && (ent->velocity[2] < 20) && (ent->velocity[2] > -20) ) ent->count = sk_shockwave_bounces->value + 1; if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (ent); return; } if (ent->owner->client) PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); // calculate position for the explosion entity VectorMA (ent->s.origin, -0.02, ent->velocity, origin); if (other->takedamage) T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_SHOCK_SPHERE); T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_SHOCK_SPLASH); // spawn impact impact = G_Spawn(); impact->classname = "shock_impact"; VectorCopy (ent->s.origin, impact->s.origin); gi.linkentity (impact); impact->think = ShockQuake; impact->speed = 250; impact->nextthink = level.time + FRAMETIME; if ((ent->count <= sk_shockwave_bounces->value) && !other->takedamage) //no shock rings if hit a monster { ShockSplash (ent, plane); // Spawn shock rings if (impact) impact->timestamp = level.time + 3; } else // don't exploode until final hit or hit a monster { gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/shockexp.wav"), 1, ATTN_NONE, 0); ShockEffect (ent, ent->owner, ent->radius_dmg, ent->dmg_radius, plane); if (impact) impact->timestamp = level.time + 6; // remove after x bounces G_FreeEdict (ent); } } void shock_sphere_think (edict_t *sphere) { sphere->avelocity[PITCH] = 80; sphere->avelocity[ROLL] = 80; sphere->nextthink = level.time + FRAMETIME; } void fire_shock_sphere (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) { edict_t *sphere; sphere = G_Spawn(); sphere->classname = "shocksphere"; sphere->class_id = ENTITY_SHOCK_SPHERE; VectorCopy (start, sphere->s.origin); VectorCopy (dir, sphere->movedir); vectoangles (dir, sphere->s.angles); VectorScale (dir, speed, sphere->velocity); sphere->avelocity[PITCH] = 80; sphere->avelocity[ROLL] = 80; sphere->movetype = MOVETYPE_WALLBOUNCE; sphere->count = 0; sphere->clipmask = MASK_SHOT; sphere->solid = SOLID_BBOX; VectorClear (sphere->mins); VectorClear (sphere->maxs); VectorSet (sphere->mins, -14, -14, -14); VectorSet (sphere->maxs, 14, 14, 14); sphere->s.modelindex = gi.modelindex ("models/objects/shocksphere/tris.md2"); sphere->s.renderfx |= RF_IR_VISIBLE; sphere->owner = self; sphere->touch = shock_sphere_touch; sphere->timestamp = level.time; sphere->nextthink = level.time + 0.1; sphere->think = shock_sphere_think; sphere->dmg = damage; sphere->radius_dmg = radius_damage; sphere->dmg_radius = damage_radius; if (self->client) check_dodge (self, sphere->s.origin, dir, speed); gi.linkentity (sphere); } // NOTE: SP_shocksphere should ONLY be used to spawn shockspheres that change maps // via a trigger_transition. It should NOT be used for map entities. void shocksphere_delayed_start (edict_t *shocksphere) { if (g_edicts[1].linkcount) { VectorScale(shocksphere->movedir,shocksphere->moveinfo.speed,shocksphere->velocity); if (shocksphere->count > 0) shocksphere->movetype = MOVETYPE_BOUNCE; else shocksphere->movetype = MOVETYPE_WALLBOUNCE; shocksphere->nextthink = level.time + FRAMETIME; shocksphere->think = shock_sphere_think; gi.linkentity(shocksphere); } else shocksphere->nextthink = level.time + FRAMETIME; } void SP_shocksphere (edict_t *shocksphere) { shocksphere->s.modelindex = gi.modelindex ("models/objects/shocksphere/tris.md2"); shocksphere->touch = shock_sphere_touch; // For SP, freeze shocksphere until player spawns in if (game.maxclients == 1) { shocksphere->movetype = MOVETYPE_NONE; VectorCopy(shocksphere->velocity,shocksphere->movedir); VectorNormalize(shocksphere->movedir); shocksphere->moveinfo.speed = VectorLength(shocksphere->velocity); VectorClear(shocksphere->velocity); shocksphere->think = shocksphere_delayed_start; shocksphere->nextthink = level.time + FRAMETIME; } else { if (shocksphere->count > 0) shocksphere->movetype = MOVETYPE_BOUNCE; else shocksphere->movetype = MOVETYPE_WALLBOUNCE; shocksphere->nextthink = level.time + FRAMETIME; shocksphere->think = shock_sphere_think; } gi.linkentity (shocksphere); } /* ============================================================================== BLU-86 (aka NEUTRON BOMB) ============================================================================== */ #define NBOMB_DELAY 4 #define NBOMB_TIME_TO_LIVE 6 //#define NBOMB_TIME_TO_LIVE 40 #define NBOMB_RADIUS 256 #define NBOMB_DAMAGE 5000 #define NBOMB_QUAKE_TIME 3 #define NBOMB_QUAKE_STRENGTH 100 void Nbomb_Quake (edict_t *self) { int i; edict_t *e; if (self->last_move_time < level.time) { gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0); self->last_move_time = level.time + 0.5; } for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) { if (!e->inuse) continue; if (!e->client) continue; if (!e->groundentity) continue; e->groundentity = NULL; e->velocity[0] += crandom()* 150.0f; e->velocity[1] += crandom()* 150.0f; e->velocity[2] = self->speed * (100.0f / e->mass); } if (level.time < self->timestamp) self->nextthink = level.time + FRAMETIME; else G_FreeEdict (self); } void Nbomb_Explode (edict_t *ent) { if (ent->teammaster->client) PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT); T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NBOMB); if (ent->dmg > sk_nbomb_damage->value) { if (ent->dmg < (4 * sk_nbomb_damage->value)) // double sound gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0); else // quad sound gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); } gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_EXPLOSION1_BIG); gi.WritePosition (ent->s.origin); gi.multicast (ent->s.origin, MULTICAST_PVS); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_NUKEBLAST); gi.WritePosition (ent->s.origin); gi.multicast (ent->s.origin, MULTICAST_ALL); // Lazarus reflections if (level.num_reflectors) { ReflectExplosion (TE_EXPLOSION1_BIG, ent->s.origin); // ReflectExplosion (TE_NUKEBLAST, ent->s.origin); } // become a quake ent->svflags |= SVF_NOCLIENT; ent->noise_index = gi.soundindex ("world/rumble.wav"); ent->think = Nbomb_Quake; ent->speed = NBOMB_QUAKE_STRENGTH; ent->timestamp = level.time + NBOMB_QUAKE_TIME; ent->nextthink = level.time + FRAMETIME; ent->last_move_time = 0; } void nbomb_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { self->takedamage = DAMAGE_NO; if ( (attacker) && !(strcmp(attacker->classname, "nbomb")) ) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("nbomb nuked by a nbomb, not nuking\n"); G_FreeEdict (self); return; } Nbomb_Explode(self); } void Nbomb_Think (edict_t *ent) { float attenuation, default_atten = 1.8; int muzzleflash; attenuation = default_atten/3.0; muzzleflash = MZ_NUKE4; if (ent->wait < level.time) Nbomb_Explode(ent); else if (level.time >= (ent->wait - sk_nbomb_life->value)) { if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) { Nbomb_Explode (ent); return; } // Knightmare- remove smoke trail if we've stopped moving if ( ent->groundentity && !VectorLength(ent->velocity) ) ent->s.effects &= ~EF_GRENADE; // but restore it if we go flying else if ( !ent->groundentity ) ent->s.effects |= EF_GRENADE; ent->think = Nbomb_Think; ent->nextthink = level.time + 0.1; ent->health = 1; ent->owner = NULL; gi.WriteByte (svc_muzzleflash); gi.WriteShort (ent-g_edicts); gi.WriteByte (muzzleflash); gi.multicast (ent->s.origin, MULTICAST_PVS); if (ent->timestamp <= level.time) { /* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0); ent->timestamp += 10.0; } */ if ((ent->wait - level.time) <= (sk_nbomb_life->value / 2.0)) { // gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); ent->timestamp = level.time + 0.3; } else { gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); ent->timestamp = level.time + 0.5; } } } else { if (ent->timestamp <= level.time) { gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0); // gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0); // gi.dprintf ("time %2.2f\n", ent->wait-level.time); ent->timestamp = level.time + 1.0; } ent->nextthink = level.time + FRAMETIME; } } void nbomb_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { if (random() > 0.5) gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); else gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); } extern byte P_DamageModifier(edict_t *ent); void fire_nbomb (edict_t *self, vec3_t start, vec3_t aimdir, int speed) { edict_t *nbomb; vec3_t dir; vec3_t forward, right, up; int damage_modifier; damage_modifier = (int)P_DamageModifier (self); vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); nbomb = G_Spawn(); VectorCopy (start, nbomb->s.origin); VectorScale (aimdir, speed, nbomb->velocity); VectorMA (nbomb->velocity, 200 + crandom() * 10.0, up, nbomb->velocity); VectorMA (nbomb->velocity, crandom() * 10.0, right, nbomb->velocity); // Knightmare- add player's base velocity to nbomb if (self->groundentity) VectorAdd (nbomb->velocity, self->groundentity->velocity, nbomb->velocity); VectorClear (nbomb->avelocity); VectorClear (nbomb->s.angles); nbomb->movetype = MOVETYPE_BOUNCE; nbomb->clipmask = MASK_SHOT; nbomb->solid = SOLID_BBOX; nbomb->s.effects |= EF_GRENADE; nbomb->s.renderfx |= RF_IR_VISIBLE; VectorSet (nbomb->mins, -8, -8, -16); VectorSet (nbomb->maxs, 8, 8, 14); nbomb->s.modelindex = gi.modelindex ("models/items/ammo/nbomb/tris.md2"); nbomb->owner = self; nbomb->teammaster = self; nbomb->wait = level.time + sk_nbomb_delay->value + sk_nbomb_life->value; nbomb->nextthink = level.time + FRAMETIME; nbomb->think = Nbomb_Think; nbomb->touch = nbomb_bounce; nbomb->health = 10000; nbomb->takedamage = DAMAGE_YES; nbomb->svflags |= SVF_DAMAGEABLE; nbomb->dmg = sk_nbomb_damage->value * damage_modifier; if (damage_modifier == 1) nbomb->dmg_radius = sk_nbomb_radius->value; else nbomb->dmg_radius = sk_nbomb_radius->value + sk_nbomb_radius->value * (0.25*(float)damage_modifier); // this yields 1.0, 1.5, 2.0, 3.0 times radius // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("nbomb modifier = %d, damage = %d, radius = %f\n", damage_modifier, nbomb->dmg, nbomb->dmg_radius); nbomb->classname = "nbomb"; nbomb->class_id = ENTITY_NBOMB; nbomb->die = nbomb_die; gi.linkentity (nbomb); }