#include "g_local.h" #define INCLUDE_ETF_RIFLE 1 #define INCLUDE_PROX 1 //#define INCLUDE_FLAMETHROWER 1 //#define INCLUDE_INCENDIARY 1 #define INCLUDE_NUKE 1 #define INCLUDE_NBOMB 1 #define INCLUDE_MELEE 1 #define INCLUDE_TESLA 1 #define INCLUDE_BEAMS 1 extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed); extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); extern void droptofloor (edict_t *ent); extern void Grenade_Explode (edict_t *ent); //extern void Nbomb_Explode (edict_t *ent); extern void drawbbox (edict_t *ent); #ifdef INCLUDE_ETF_RIFLE /* ======================== fire_flechette ======================== */ void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t dir; if (other == self->owner) return; if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (self); return; } if (self->client) PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); if (other->takedamage) { //gi.dprintf("t_damage %s\n", other->classname); T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE); } else { if (!plane) VectorClear (dir); else VectorScale (plane->normal, 256, dir); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_FLECHETTE); gi.WritePosition (self->s.origin); gi.WriteDir (dir); gi.multicast (self->s.origin, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) ReflectSparks(TE_FLECHETTE, self->s.origin, dir); // T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE); } //Knightmare- added splash damage T_RadiusDamage(self, self->owner, self->radius_dmg, self, self->dmg_radius, MOD_ETF_SPLASH); G_FreeEdict (self); } void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) { edict_t *flechette; VectorNormalize (dir); flechette = G_Spawn(); flechette->classname = "flechette"; VectorCopy (start, flechette->s.origin); VectorCopy (start, flechette->s.old_origin); vectoangles2 (dir, flechette->s.angles); VectorScale (dir, speed, flechette->velocity); flechette->movetype = MOVETYPE_FLYMISSILE; flechette->clipmask = MASK_SHOT; flechette->solid = SOLID_BBOX; flechette->s.renderfx = RF_FULLBRIGHT; flechette->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow VectorClear (flechette->mins); VectorClear (flechette->maxs); flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2"); // flechette->s.sound = gi.soundindex (""); // FIXME - correct sound! flechette->owner = self; flechette->touch = flechette_touch; flechette->nextthink = level.time + 8000/speed; flechette->think = G_FreeEdict; flechette->dmg = damage; flechette->dmg_radius = damage_radius; flechette->radius_dmg = radius_damage; gi.linkentity (flechette); if (self->client) check_dodge (self, flechette->s.origin, dir, speed); } // SP_flechette should ONLY be used for flechettes that have changed // maps via trigger_transition. It should NOT be used for map entities. void flechette_delayed_start (edict_t *flechette) { if (g_edicts[1].linkcount) { VectorScale(flechette->movedir,flechette->moveinfo.speed,flechette->velocity); flechette->nextthink = level.time + 2; flechette->think = G_FreeEdict; gi.linkentity(flechette); } else flechette->nextthink = level.time + FRAMETIME; } void SP_flechette (edict_t *flechette) { flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2"); flechette->touch = flechette_touch; VectorCopy(flechette->velocity,flechette->movedir); VectorNormalize(flechette->movedir); flechette->moveinfo.speed = VectorLength(flechette->velocity); VectorClear(flechette->velocity); flechette->think = flechette_delayed_start; flechette->nextthink = level.time + FRAMETIME; gi.linkentity(flechette); } #endif // ************************** // PROX // ************************** #ifdef INCLUDE_PROX #define PROX_TIME_TO_LIVE 600 // 45, 30, 15, 10 #define PROX_TIME_DELAY 0.5 #define PROX_BOUND_SIZE 96 #define PROX_DAMAGE_RADIUS 192 #define PROX_HEALTH 20 #define PROX_DAMAGE 90 void Prox_Explode (edict_t *ent); //=============== //=============== // Knightmare- move prox and trigger field with host void prox_movewith_host (edict_t *self) { edict_t *host; vec3_t forward, right, up, offset; vec3_t host_angle_change, amove; int count = 0; // explode if parent has been destroyed if (!self->movewith_ent || !self->movewith_ent->inuse) { // gi.dprintf("prox_no_parent_die\n"); Prox_Explode (self); return; } host = self->movewith_ent; // if parent has disappeared (func_wall, etc.), then drop to ground if (host->svflags & SVF_NOCLIENT) { VectorClear(self->s.angles); self->movetype = MOVETYPE_TOSS; self->movewith_ent = NULL; self->movewith_set = 0; self->teamchain->movewith_ent = NULL; self->teamchain->movewith_set = 0; self->postthink = NULL; return; } movefield: if (!self) // more paranoia return; self->movetype = MOVETYPE_PUSH; if (!self->movewith_set) { VectorCopy(self->mins, self->org_mins); VectorCopy(self->maxs, self->org_maxs); VectorSubtract (self->s.origin, host->s.origin, self->movewith_offset); // Remeber child's and parent's angles when child was attached VectorCopy(host->s.angles, self->parent_attach_angles); VectorCopy(self->s.angles, self->child_attach_angles); self->movewith_set = 1; } // Get change in parent's angles from when child was attached, this tells us how far we need to rotate VectorSubtract(host->s.angles, self->parent_attach_angles, host_angle_change); AngleVectors(host_angle_change, forward, right, up); VectorNegate(right, right); VectorMA(host->s.origin, self->movewith_offset[0], forward, self->s.origin); VectorMA(self->s.origin, self->movewith_offset[1], right, self->s.origin); VectorMA(self->s.origin, self->movewith_offset[2], up, self->s.origin); VectorCopy(host->velocity, self->velocity); // If parent is spinning, add appropriate velocities VectorSubtract(self->s.origin, host->s.origin, offset); if (host->avelocity[PITCH] != 0) { self->velocity[2] -= offset[0] * host->avelocity[PITCH] * M_PI / 180; self->velocity[0] += offset[2] * host->avelocity[PITCH] * M_PI / 180; } if (host->avelocity[YAW] != 0) { self->velocity[0] -= offset[1] * host->avelocity[YAW] * M_PI / 180.; self->velocity[1] += offset[0] * host->avelocity[YAW] * M_PI / 180.; } if (host->avelocity[ROLL] != 0) { self->velocity[1] -= offset[2] * host->avelocity[ROLL] * M_PI / 180; self->velocity[2] += offset[1] * host->avelocity[ROLL] * M_PI / 180; } VectorScale (host->avelocity, FRAMETIME, amove); VectorAdd(self->child_attach_angles, host_angle_change, self->s.angles); // add rotation to angles // VectorAdd(self->s.angles, amove, self->s.angles); //add rotation to angles if (amove[YAW]) // Cross fingers here... move bounding box { float ca, sa, yaw; vec3_t p00, p01, p10, p11; // Adjust bounding box for yaw yaw = self->s.angles[YAW] * M_PI / 180.; ca = cos(yaw); sa = sin(yaw); p00[0] = self->org_mins[0]*ca - self->org_mins[1]*sa; p00[1] = self->org_mins[1]*ca + self->org_mins[0]*sa; p01[0] = self->org_mins[0]*ca - self->org_maxs[1]*sa; p01[1] = self->org_maxs[1]*ca + self->org_mins[0]*sa; p10[0] = self->org_maxs[0]*ca - self->org_mins[1]*sa; p10[1] = self->org_mins[1]*ca + self->org_maxs[0]*sa; p11[0] = self->org_maxs[0]*ca - self->org_maxs[1]*sa; p11[1] = self->org_maxs[1]*ca + self->org_maxs[0]*sa; self->mins[0] = p00[0]; self->mins[0] = min(self->mins[0],p01[0]); self->mins[0] = min(self->mins[0],p10[0]); self->mins[0] = min(self->mins[0],p11[0]); self->mins[1] = p00[1]; self->mins[1] = min(self->mins[1],p01[1]); self->mins[1] = min(self->mins[1],p10[1]); self->mins[1] = min(self->mins[1],p11[1]); self->maxs[0] = p00[0]; self->maxs[0] = max(self->maxs[0],p01[0]); self->maxs[0] = max(self->maxs[0],p10[0]); self->maxs[0] = max(self->maxs[0],p11[0]); self->maxs[1] = p00[1]; self->maxs[1] = max(self->maxs[1],p01[1]); self->maxs[1] = max(self->maxs[1],p10[1]); self->maxs[1] = max(self->maxs[1],p11[1]); } self->s.event = host->s.event; gi.linkentity (self); if (count < 1 && self->teamchain) // now move the trigger field { self = self->teamchain; count++; goto movefield; } } void Prox_Explode (edict_t *ent) { vec3_t origin; edict_t *owner; int type; // free the trigger field //PMM - changed teammaster to "mover" .. owner of the field is the prox if (ent->teamchain && ent->teamchain->owner == ent) G_FreeEdict(ent->teamchain); owner = ent; if (ent->teammaster) { owner = ent->teammaster; PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT); } // play quad sound if appopriate if (ent->dmg > sk_prox_damage->value) { if (ent->dmg < (4 * sk_prox_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); } ent->takedamage = DAMAGE_NO; T_RadiusDamage(ent, owner, ent->dmg, ent, sk_prox_radius->value, MOD_PROX); VectorMA (ent->s.origin, -0.02, ent->velocity, origin); if (ent->groundentity) type = TE_GRENADE_EXPLOSION; else type = TE_ROCKET_EXPLOSION; gi.WriteByte (svc_temp_entity); gi.WriteByte (type); gi.WritePosition (origin); gi.multicast (ent->s.origin, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) ReflectExplosion (type, origin); G_FreeEdict (ent); } //=============== //=============== void prox_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { //gi.dprintf("prox_die\n"); // if set off by another prox, delay a little (chained explosions) if (strcmp(inflictor->classname, "prox")) { self->takedamage = DAMAGE_NO; Prox_Explode(self); } else { self->takedamage = DAMAGE_NO; self->think = Prox_Explode; self->nextthink = level.time + FRAMETIME; } } //=============== //=============== void Prox_Field_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { edict_t *prox; if (!(other->svflags & SVF_MONSTER) && !other->client) return; // trigger the prox mine if it's still there, and still mine. prox = ent->owner; if (other == prox) // don't set self off return; if (prox->think == Prox_Explode) // we're set to blow! { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("%f - prox already gone off!\n", level.time); return; } if (prox->teamchain == ent) { gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); prox->think = Prox_Explode; prox->nextthink = level.time + PROX_TIME_DELAY; return; } ent->solid = SOLID_NOT; G_FreeEdict(ent); } //=============== //=============== void prox_seek (edict_t *ent) { if (level.time > ent->wait) { Prox_Explode(ent); } else { ent->s.frame++; if (ent->s.frame > 13) ent->s.frame = 9; ent->think = prox_seek; ent->nextthink = level.time + 0.1; } //If we're not attached to a host and may be bouncing around, move field with us if (!ent->movewith_ent && ent->teamchain) { VectorCopy (ent->s.origin, ent->teamchain->s.origin); gi.linkentity (ent->teamchain); } } //=============== //=============== void prox_open (edict_t *ent) { edict_t *search; search = NULL; // gi.dprintf("prox_open %d\n", ent->s.frame); // gi.dprintf("%f\n", ent->velocity[2]); if (ent->s.frame == 9) // end of opening animation { // set the owner to NULL so the owner can shoot it, etc. needs to be done here so the owner // doesn't get stuck on it while it's opening if fired at point blank wall ent->s.sound = 0; // Knightmare- the above never happens, and the owner pointer is needed for remote detonation //ent->owner = NULL; if (ent->teamchain) ent->teamchain->touch = Prox_Field_Touch; while ((search = findradius(search, ent->s.origin, sk_prox_radius->value + 10)) != NULL) { if (!search->classname) // tag token and other weird shit continue; // if (!search->takedamage) // continue; // if it's a monster or player with health > 0 // or it's a player start point // and we can see it // blow up if ( ( (((search->svflags & SVF_MONSTER) || (search->client)) && (search->health > 0)) || ( (deathmatch->value) && ( (!strcmp(search->classname, "info_player_deathmatch")) || (!strcmp(search->classname, "info_player_start")) || (!strcmp(search->classname, "info_player_coop")) || (!strcmp(search->classname, "misc_teleporter_dest")) ) ) ) && (visible (search, ent)) ) { gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); Prox_Explode (ent); return; } } if (strong_mines && (strong_mines->value)) ent->wait = level.time + sk_prox_life->value; else { switch (ent->dmg / (int)sk_prox_damage->value) { case 1: ent->wait = level.time + sk_prox_life->value; break; case 2: ent->wait = level.time + sk_prox_life->value / 2; break; case 4: ent->wait = level.time + sk_prox_life->value / 4; break; case 8: ent->wait = level.time + sk_prox_life->value / 8; break; default: // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("prox with unknown multiplier %d!\n", ent->dmg/PROX_DAMAGE); ent->wait = level.time + sk_prox_life->value; break; } } ent->think = prox_seek; ent->nextthink = level.time + 0.2; } else { if (ent->s.frame == 0) gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxopen.wav"), 1, ATTN_NORM, 0); //ent->s.sound = gi.soundindex ("weapons/proxopen.wav"); ent->s.frame++; ent->think = prox_open; ent->nextthink = level.time + 0.10; //was 0.05 } } //=============== //=============== #define STOP_EPSILON 0.1 void prox_land (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { edict_t *field; vec3_t dir; vec3_t forward, right, up; int makeslave = 0; int movetype = MOVETYPE_NONE; int stick_ok = 0; vec3_t land_point; qboolean havehost = false; // must turn off owner so owner can shoot it and set it off // moved to prox_open so owner can get away from it if fired at pointblank range into // wall // ent->owner = NULL; // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("land - %2.2f %2.2f %2.2f\n", ent->velocity[0], ent->velocity[1], ent->velocity[2]); if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict(ent); return; } if (plane != NULL && plane->normal) { VectorMA (ent->s.origin, -10.0, plane->normal, land_point); if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA)) { Prox_Explode (ent); return; } } if ((other->svflags & SVF_MONSTER) || other->client || (other->svflags & SVF_DAMAGEABLE)) { if (other != ent->teammaster) Prox_Explode(ent); return; } else if (other != world) { // Here we need to check to see if we can stop on this entity. // Note that plane can be NULL // PMM - code stolen from g_phys (ClipVelocity) vec3_t out; float backoff, change; int i; if (!plane || !plane->normal) // this happens if you hit a point object, maybe other cases { // Since we can't tell what's going to happen, just blow up // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("bad normal for surface, exploding!\n"); Prox_Explode(ent); return; } //Knightmare- stick to bmodels if (other->solid == SOLID_BSP && other->movetype != MOVETYPE_CONVEYOR && (other->movetype == MOVETYPE_PUSH || other->movetype == MOVETYPE_PUSHABLE)) { ent->movewith_ent = other; ent->s.effects &= ~EF_GRENADE; // remove smoke trail stick_ok = 1; havehost = true; ent->postthink = prox_movewith_host; } else if (other->movetype == MOVETYPE_CONVEYOR) havehost = true; else if ((other->movetype == MOVETYPE_PUSH) && (plane->normal[2] > 0.7)) stick_ok = 1; else stick_ok = 0; backoff = DotProduct (ent->velocity, plane->normal) * 1.5; for (i=0 ; i<3 ; i++) { change = plane->normal[i]*backoff; out[i] = ent->velocity[i] - change; if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) out[i] = 0; } if ((out[2] > 60) && !havehost) // always open up if attached to host return; if (havehost && other->movetype != MOVETYPE_CONVEYOR) movetype = MOVETYPE_PUSH; else if (other->movetype == MOVETYPE_CONVEYOR) { movetype = MOVETYPE_TOSS; ent->s.effects &= ~EF_GRENADE; //remove smoke trail stick_ok = 1; havehost = false; } else movetype = MOVETYPE_BOUNCE; // if we're here, we're going to stop on an entity if (stick_ok) { // it's a happy entity VectorCopy (vec3_origin, ent->velocity); VectorCopy (vec3_origin, ent->avelocity); } else // no-stick. teflon time { if (plane->normal[2] > 0.7) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("stuck on entity, blowing up!\n"); Prox_Explode(ent); return; } return; } } else if (other->s.modelindex != 1) return; vectoangles2 (plane->normal, dir); AngleVectors (dir, forward, right, up); if (gi.pointcontents (ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME)) { Prox_Explode (ent); return; } field = G_Spawn(); VectorCopy (ent->s.origin, field->s.origin); VectorClear(field->velocity); VectorClear(field->avelocity); VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE); VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE); field->movetype = MOVETYPE_NONE; field->solid = SOLID_TRIGGER; field->owner = ent; field->classname = "prox_field"; field->teammaster = ent; if (havehost) // Knightmare- set up field to move with host field->movewith_ent = other; gi.linkentity (field); VectorClear(ent->velocity); VectorClear(ent->avelocity); // rotate to vertical dir[PITCH] = dir[PITCH] + 90; if (other->movetype == MOVETYPE_CONVEYOR) VectorClear (ent->s.angles); else VectorCopy (dir, ent->s.angles); ent->takedamage = DAMAGE_AIM; ent->movetype = movetype; // either bounce or none, depending on whether we stuck to something ent->die = prox_die; ent->teamchain = field; ent->health = sk_prox_health->value; ent->nextthink = level.time + 0.05; ent->think = prox_open; ent->touch = NULL; ent->solid = SOLID_BBOX; // Knightmare- if host is moving, setup movewith immediately so we don't lag if (VectorLength(other->velocity) || VectorLength(other->avelocity)) prox_movewith_host (ent); // record who we're attached to // ent->teammaster = other; // if (other->movetype == MOVETYPE_PUSHABLE) // gi.dprintf("prox successfully attached to func_pushable\n"); gi.linkentity(ent); } //=============== //=============== void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed) { edict_t *prox; vec3_t dir; vec3_t forward, right, up; vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("start %s aim %s speed %d\n", vtos(start), vtos(aimdir), speed); prox = G_Spawn(); VectorCopy (start, prox->s.origin); VectorScale (aimdir, speed, prox->velocity); VectorMA (prox->velocity, 200 + crandom() * 10.0, up, prox->velocity); VectorMA (prox->velocity, crandom() * 10.0, right, prox->velocity); //Knightmare- add player's base velocity to prox if (add_velocity_throw->value && self->client) VectorAdd (prox->velocity, self->velocity, prox->velocity); else if (self->groundentity) VectorAdd (prox->velocity, self->groundentity->velocity, prox->velocity); VectorCopy (dir, prox->s.angles); prox->s.angles[PITCH]-=90; prox->movetype = MOVETYPE_BOUNCE; prox->solid = SOLID_BBOX; prox->s.effects |= EF_GRENADE; prox->clipmask = MASK_SHOT|CONTENTS_LAVA|CONTENTS_SLIME; prox->s.renderfx |= RF_IR_VISIBLE; //FIXME - this needs to be bigger. Has other effects, though. Maybe have to change origin to compensate // so it sinks in correctly. Also in lavacheck, might have to up the distance VectorSet (prox->mins, -6, -6, -6); VectorSet (prox->maxs, 6, 6, 6); prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2"); prox->owner = self; prox->teammaster = self; prox->touch = prox_land; // prox->nextthink = level.time + sk_prox_life->value; prox->think = Prox_Explode; prox->dmg = sk_prox_damage->value*damage_multiplier; prox->classname = "prox"; prox->svflags |= SVF_DAMAGEABLE; prox->flags |= FL_MECHANICAL; switch (damage_multiplier) { case 1: prox->nextthink = level.time + sk_prox_life->value; break; case 2: prox->nextthink = level.time + sk_prox_life->value; break; case 4: prox->nextthink = level.time + sk_prox_life->value; break; case 8: prox->nextthink = level.time + sk_prox_life->value; break; default: // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("prox with unknown multiplier %d!\n", damage_multiplier); prox->nextthink = level.time + sk_prox_life->value; break; } gi.linkentity (prox); } void Cmd_DetProx_f (edict_t *ent) { edict_t *blip = NULL; while ((blip = findradius(blip, ent->s.origin, WORLD_SIZE)) != NULL) // was 8192 { if (!strcmp(blip->classname, "prox") && blip->owner == ent) { blip->think = Prox_Explode; blip->nextthink = level.time + 0.1; } } } // NOTE: SP_prox should ONLY be used to spawn prox mines that change maps // via a trigger_transition. They should NOT be used for map entities. void prox_delayed_start (edict_t *prox) { if (g_edicts[1].linkcount) { VectorScale(prox->movedir,prox->moveinfo.speed,prox->velocity); prox->movetype = MOVETYPE_BOUNCE; prox->nextthink = level.time + sk_prox_life->value; prox->think = Prox_Explode; gi.linkentity(prox); } else prox->nextthink = level.time + FRAMETIME; } void SP_prox (edict_t *prox) { prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2"); prox->touch = prox_land; prox->movewith_ent = NULL; prox->movewith_set = 0; prox->postthink = NULL; // For SP, freeze prox until player spawns in if (game.maxclients == 1 && VectorLength (prox->velocity)) { prox->movetype = MOVETYPE_NONE; VectorCopy(prox->velocity,prox->movedir); VectorNormalize(prox->movedir); prox->moveinfo.speed = VectorLength(prox->velocity); VectorClear(prox->velocity); prox->think = prox_delayed_start; prox->nextthink = level.time + FRAMETIME; } else { prox->movetype = MOVETYPE_BOUNCE; prox->nextthink = level.time + sk_prox_life->value; prox->think = Prox_Explode; } gi.linkentity (prox); } #endif // ************************* // FLAMETHROWER // ************************* #ifdef INCLUDE_FLAMETHROWER #define FLAMETHROWER_RADIUS 8 void fire_remove (edict_t *ent) { if (ent == ent->owner->teamchain) ent->owner->teamchain = NULL; G_FreeEdict(ent); } void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) { edict_t *flame; vec3_t dir; vec3_t forward, right, up; vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); flame = G_Spawn(); // the origin is the first control point, put it speed forward. VectorMA(start, speed, forward, flame->s.origin); // record that velocity VectorScale (aimdir, speed, flame->velocity); VectorCopy (dir, flame->s.angles); flame->movetype = MOVETYPE_NONE; flame->solid = SOLID_NOT; VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); flame->s.sound = gi.soundindex ("weapons/flame.wav"); flame->owner = self; flame->dmg = damage; flame->classname = "flame"; // clear control points and velocities VectorCopy (flame->s.origin, flame->flameinfo.pos1); VectorCopy (flame->velocity, flame->flameinfo.vel1); VectorCopy (flame->s.origin, flame->flameinfo.pos2); VectorCopy (flame->velocity, flame->flameinfo.vel2); VectorCopy (flame->s.origin, flame->flameinfo.pos3); VectorCopy (flame->velocity, flame->flameinfo.vel3); VectorCopy (flame->s.origin, flame->flameinfo.pos4); // hook flame stream to owner self->teamchain = flame; gi.linkentity (flame); } // fixme - change to use start location, not entity origin void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed) { trace_t tr; // move the control points out the appropriate direction and velocity VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4); VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3); VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2); VectorAdd(flame->s.origin, flame->velocity, flame->flameinfo.pos1); // move the velocities for the control points VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); VectorCopy(flame->velocity, flame->flameinfo.vel1); // set velocity and location for new control point 0. VectorMA(start, speed, aimdir, flame->s.origin); VectorScale(aimdir, speed, flame->velocity); // // does it hit a wall? if so, when? // // player fire point to flame origin. tr = gi.trace(start, flame->mins, flame->maxs, flame->s.origin, flame, MASK_SHOT); if (tr.fraction == 1.0) { // origin to point 1 tr = gi.trace(flame->s.origin, flame->mins, flame->maxs, flame->flameinfo.pos1, flame, MASK_SHOT); if (tr.fraction == 1.0) { // point 1 to point 2 tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs, flame->flameinfo.pos2, flame, MASK_SHOT); if (tr.fraction == 1.0) { // point 2 to point 3 tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs, flame->flameinfo.pos3, flame, MASK_SHOT); if (tr.fraction == 1.0) { // point 3 to point 4, point 3 valid tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs, flame->flameinfo.pos4, flame, MASK_SHOT); if (tr.fraction < 1.0) // point 4 blocked { VectorCopy(tr.endpos, flame->flameinfo.pos4); } } else // point 3 blocked, point 2 valid { VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3); VectorCopy(tr.endpos, flame->flameinfo.pos3); VectorCopy(tr.endpos, flame->flameinfo.pos4); } } else // point 2 blocked, point 1 valid { VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2); VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3); VectorCopy(tr.endpos, flame->flameinfo.pos2); VectorCopy(tr.endpos, flame->flameinfo.pos3); VectorCopy(tr.endpos, flame->flameinfo.pos4); } } else // point 1 blocked, origin valid { VectorCopy(flame->velocity, flame->flameinfo.vel1); VectorCopy(flame->velocity, flame->flameinfo.vel2); VectorCopy(flame->velocity, flame->flameinfo.vel3); VectorCopy(tr.endpos, flame->flameinfo.pos1); VectorCopy(tr.endpos, flame->flameinfo.pos2); VectorCopy(tr.endpos, flame->flameinfo.pos3); VectorCopy(tr.endpos, flame->flameinfo.pos4); } } else // origin blocked! { // gi.dprintf("point 2 blocked\n"); VectorCopy(flame->velocity, flame->flameinfo.vel1); VectorCopy(flame->velocity, flame->flameinfo.vel2); VectorCopy(flame->velocity, flame->flameinfo.vel3); VectorCopy(tr.endpos, flame->s.origin); VectorCopy(tr.endpos, flame->flameinfo.pos1); VectorCopy(tr.endpos, flame->flameinfo.pos2); VectorCopy(tr.endpos, flame->flameinfo.pos3); VectorCopy(tr.endpos, flame->flameinfo.pos4); } if (tr.fraction < 1.0 && tr.ent->takedamage) { T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal, damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE); } gi.linkentity(flame); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_FLAME); gi.WriteShort(ent - g_edicts); gi.WriteShort(6); gi.WritePosition (start); gi.WritePosition (flame->s.origin); gi.WritePosition (flame->flameinfo.pos1); gi.WritePosition (flame->flameinfo.pos2); gi.WritePosition (flame->flameinfo.pos3); gi.WritePosition (flame->flameinfo.pos4); gi.multicast (flame->s.origin, MULTICAST_PVS); } /*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8) */ #define FLAMESHOOTER_VELOCITY 50 #define FLAMESHOOTER_DAMAGE 20 #define FLAMESHOOTER_BURST_VELOCITY 300 #define FLAMESHOOTER_BURST_DAMAGE 30 //#define FLAMESHOOTER_PUFF 1 #define FLAMESHOOTER_STREAM 1 void flameshooter_think (edict_t *self) { vec3_t forward, right, up; edict_t *flame; if (self->delay) { if (self->teamchain) fire_remove (self->teamchain); return; } self->s.angles[1] += self->speed; if (self->s.angles[1] > 135 || self->s.angles[1] < 45) self->speed = -self->speed; AngleVectors (self->s.angles, forward, right, up); #ifdef FLAMESHOOTER_STREAM flame = self->teamchain; if (!self->teamchain) fire_flame (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); else fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY); self->think = flameshooter_think; self->nextthink = level.time + 0.05; #else fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY); self->think = flameshooter_think; self->nextthink = level.time + 0.1; #endif } void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->delay) { self->delay = 0; self->think = flameshooter_think; self->nextthink = level.time + 0.1; } else self->delay = 1; } void SP_trap_flameshooter(edict_t *self) { vec3_t tempAngles; self->solid = SOLID_NOT; self->movetype = MOVETYPE_NONE; self->delay = 0; self->use = flameshooter_use; if (self->delay == 0) { self->think = flameshooter_think; self->nextthink = level.time + 0.1; } // self->flags |= FL_NOCLIENT; self->speed = 10; // self->speed = 0; // FIXME this stops the spraying VectorCopy(self->s.angles, tempAngles); if (!VectorCompare(self->s.angles, vec3_origin)) G_SetMovedir (self->s.angles, self->movedir); VectorCopy(tempAngles, self->s.angles); // gi.setmodel (self, self->model); gi.linkentity (self); } // ************************* // fire_burst // ************************* #define FLAME_BURST_MAX_SIZE 64 #define FLAME_BURST_FRAMES 20 #define FLAME_BURST_MIDPOINT 10 void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { int powerunits; int damage, radius; vec3_t origin; if (surf && (surf->flags & SURF_SKY)) { // gi.dprintf("Hit sky. Removed\n"); G_FreeEdict (ent); return; } if (other == ent->owner || ent == other) return; // don't let flame puffs blow each other up if (other->classname && !strcmp(other->classname, ent->classname)) return; if (ent->waterlevel) { // gi.dprintf("Hit water. Removed\n"); G_FreeEdict(ent); } if (!(other->svflags & SVF_MONSTER) && !other->client) { powerunits = FLAME_BURST_FRAMES - ent->s.frame; damage = powerunits * 6; radius = powerunits * 4; // T_RadiusDamage (inflictor, attacker, damage, ignore, radius) T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE); // gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius); // calculate position for the explosion entity VectorMA (ent->s.origin, -0.02, ent->velocity, origin); gi.WriteByte (svc_temp_entity); //gi.WriteByte (TE_PLAIN_EXPLOSION); gi.WriteByte (TE_EXPLOSION1); gi.WritePosition (origin); gi.multicast (ent->s.origin, MULTICAST_PVS); G_FreeEdict (ent); } } void fire_burst_think (edict_t *self) { int current_radius; if (self->waterlevel) { G_FreeEdict(self); return; } self->s.frame++; if (self->s.frame >= FLAME_BURST_FRAMES) { G_FreeEdict(self); return; } else if (self->s.frame < FLAME_BURST_MIDPOINT) { current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame; } else { current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame); } if (self->s.frame == 3) self->s.skinnum = 1; else if (self->s.frame == 7) self->s.skinnum = 2; else if (self->s.frame == 10) self->s.skinnum = 3; else if (self->s.frame == 13) self->s.skinnum = 4; else if (self->s.frame == 16) self->s.skinnum = 5; else if (self->s.frame == 19) self->s.skinnum = 6; if (current_radius < 8) current_radius = 8; else if (current_radius > FLAME_BURST_MAX_SIZE) current_radius = FLAME_BURST_MAX_SIZE; T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE); self->think = fire_burst_think; self->nextthink = level.time + 0.1; } void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) { edict_t *flame; vec3_t dir; vec3_t baseVel; vec3_t forward, right, up; vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); flame = G_Spawn(); VectorCopy(start, flame->s.origin); // VectorScale (aimdir, speed, flame->velocity); // scale down so only 30% of player's velocity is taken into account. VectorScale (self->velocity, 0.3, baseVel); VectorMA(baseVel, speed, aimdir, flame->velocity); VectorCopy (dir, flame->s.angles); flame->movetype = MOVETYPE_FLY; flame->solid = SOLID_TRIGGER; VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS); VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS); flame->s.sound = gi.soundindex ("weapons/flame.wav"); flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2"); flame->owner = self; flame->touch = fire_burst_touch; flame->think = fire_burst_think; flame->nextthink = level.time + 0.1; flame->dmg = damage; flame->classname = "flameburst"; flame->s.effects = EF_FIRE_PUFF; gi.linkentity (flame); } #endif // ************************* // INCENDIARY GRENADES // ************************* #ifdef INCLUDE_INCENDIARY void FireThink (edict_t *ent) { if (level.time > ent->wait) G_FreeEdict(ent); else { ent->s.frame++; if (ent->s.frame>10) ent->s.frame = 0; ent->nextthink = level.time + 0.05; ent->think = FireThink; } } #define FIRE_HEIGHT 64 #define FIRE_RADIUS 64 #define FIRE_DAMAGE 3 #define FIRE_DURATION 15 edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage) { edict_t *fire; fire = G_Spawn(); VectorCopy (fireOrigin, fire->s.origin); fire->movetype = MOVETYPE_TOSS; fire->solid = SOLID_TRIGGER; VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0); VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT); fire->s.sound = gi.soundindex ("weapons/incend.wav"); fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2"); fire->owner = fireOwner; fire->touch = hurt_touch; fire->nextthink = level.time + 0.05; fire->wait = level.time + fireDuration; fire->think = FireThink; // fire->nextthink = level.time + fireDuration; // fire->think = G_FreeEdict; fire->dmg = fireDamage; fire->classname = "incendiary_fire"; gi.linkentity (fire); // gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0); return fire; } /*static*/ void Incendiary_Explode (edict_t *ent) { vec3_t origin; if (ent->owner->client) PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); //FIXME: if we are onground then raise our Z just a bit since we are a point? T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE); VectorMA (ent->s.origin, -0.02, ent->velocity, origin); gi.WriteByte (svc_temp_entity); if (ent->groundentity) gi.WriteByte (TE_GRENADE_EXPLOSION); else gi.WriteByte (TE_ROCKET_EXPLOSION); gi.WritePosition (origin); gi.multicast (ent->s.origin, MULTICAST_PVS); StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE); G_FreeEdict (ent); } /*static*/ void Incendiary_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { if (other == ent->owner) return; if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (ent); return; } if (!(other->svflags & SVF_MONSTER) && !(ent->client)) // if (!other->takedamage) { if (ent->spawnflags & 1) { 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); } else { if (random() > 0.5) gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); else gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0); } return; } Incendiary_Explode (ent); } void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) { edict_t *grenade; vec3_t dir; vec3_t forward, right, up; vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); grenade = G_Spawn(); VectorCopy (start, grenade->s.origin); VectorScale (aimdir, speed, grenade->velocity); VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); VectorSet (grenade->avelocity, 300, 300, 300); grenade->movetype = MOVETYPE_BOUNCE; grenade->clipmask = MASK_SHOT; grenade->solid = SOLID_BBOX; grenade->s.effects |= EF_GRENADE; // if (self->client) // grenade->s.effects &= ~EF_TELEPORT; VectorClear (grenade->mins); VectorClear (grenade->maxs); grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2"); grenade->owner = self; grenade->touch = Incendiary_Touch; grenade->nextthink = level.time + timer; grenade->think = Incendiary_Explode; grenade->dmg = damage; grenade->dmg_radius = damage_radius; grenade->classname = "incendiary_grenade"; gi.linkentity (grenade); } #endif // ************************* // MELEE WEAPONS // ************************* #ifdef INCLUDE_MELEE void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod) { vec3_t forward, right, up; vec3_t v; vec3_t point; trace_t tr; vectoangles2 (aim, v); AngleVectors (v, forward, right, up); VectorNormalize (forward); VectorMA( start, reach, forward, point); //see if the hit connects tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT); if (tr.fraction == 1.0) { if (!quiet) gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/swish.wav"), 1, ATTN_NORM, 0); //FIXME some sound here? return; } if (tr.ent->takedamage == DAMAGE_YES || tr.ent->takedamage == DAMAGE_AIM) { // pull the player forward if you do damage VectorMA(self->velocity, 75, forward, self->velocity); VectorMA(self->velocity, 75, up, self->velocity); // do the damage // FIXME - make the damage appear at right spot and direction if (mod == MOD_CHAINFIST) T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod); else T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, mod); if (!quiet) gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/meatht.wav"), 1, ATTN_NORM, 0); } else { if (!quiet) gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_NORM, 0); VectorScale (tr.plane.normal, 256, point); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_GUNSHOT); gi.WritePosition (tr.endpos); gi.WriteDir (point); gi.multicast (tr.endpos, MULTICAST_PVS); } } #endif // ************************* // NUKE // ************************* #ifdef INCLUDE_NUKE #define NUKE_DELAY 4 #define NUKE_TIME_TO_LIVE 6 //#define NUKE_TIME_TO_LIVE 40 #define NUKE_RADIUS 512 #define NUKE_DAMAGE 400 #define NUKE_QUAKE_TIME 3 #define NUKE_QUAKE_STRENGTH 100 void Nuke_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; if (!strcmp(self->classname, "shock_sphere")) { e->velocity[0] += crandom()* 125; e->velocity[1] += crandom()* 125; e->velocity[2] = self->speed * (150.0 / e->mass); } else { e->velocity[0] += crandom()* 150; e->velocity[1] += crandom()* 150; e->velocity[2] = self->speed * (100.0 / e->mass); } } /* if (!strcmp(self->classname, "shock_sphere")) //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); } /*static*/ void Nuke_Explode (edict_t *ent) { // vec3_t origin; // nuke_framenum = level.framenum + 20; if (ent->teammaster->client) PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT); T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE); // VectorMA (ent->s.origin, -0.02, ent->velocity, origin); if (ent->dmg > NUKE_DAMAGE) { if (ent->dmg < (4 * NUKE_DAMAGE)) //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); if (ent->groundentity) gi.WriteByte (TE_GRENADE_EXPLOSION); else gi.WriteByte (TE_ROCKET_EXPLOSION); gi.WritePosition (origin); gi.multicast (ent->s.origin, MULTICAST_PVS); */ // BecomeExplosion1(ent); 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 = Nuke_Quake; ent->speed = NUKE_QUAKE_STRENGTH; ent->timestamp = level.time + NUKE_QUAKE_TIME; ent->nextthink = level.time + FRAMETIME; ent->last_move_time = 0; } void nuke_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { self->takedamage = DAMAGE_NO; if ((attacker) && !(strcmp(attacker->classname, "nuke"))) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("nuke nuked by a nuke, not nuking\n"); G_FreeEdict (self); return; } Nuke_Explode(self); } void Nuke_Think(edict_t *ent) { float attenuation, default_atten = 1.8; int damage_multiplier, muzzleflash; // gi.dprintf ("player range: %2.2f damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2); damage_multiplier = ent->dmg/NUKE_DAMAGE; switch (damage_multiplier) { case 1: attenuation = default_atten/1.4; muzzleflash = MZ_NUKE1; break; case 2: attenuation = default_atten/2.0; muzzleflash = MZ_NUKE2; break; case 4: attenuation = default_atten/3.0; muzzleflash = MZ_NUKE4; break; case 8: attenuation = default_atten/5.0; muzzleflash = MZ_NUKE8; break; default: attenuation = default_atten; muzzleflash = MZ_NUKE1; break; } if (ent->wait < level.time) Nuke_Explode(ent); else if (level.time >= (ent->wait - sk_nuke_life->value)) { ent->s.frame++; if (ent->s.frame > 11) ent->s.frame = 6; if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) { Nuke_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 = Nuke_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_nuke_life->value/2.0)) { // ent->s.sound = gi.soundindex ("weapons/nukewarn.wav"); // 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); // gi.dprintf ("time %2.2f\n", ent->wait-level.time); 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; // gi.dprintf ("time %2.2f\n", ent->wait-level.time); } } } 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 nuke_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_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed) { edict_t *nuke; 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); nuke = G_Spawn(); VectorCopy (start, nuke->s.origin); VectorScale (aimdir, speed, nuke->velocity); VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity); VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity); VectorClear (nuke->avelocity); VectorClear (nuke->s.angles); nuke->movetype = MOVETYPE_BOUNCE; nuke->clipmask = MASK_SHOT; nuke->solid = SOLID_BBOX; nuke->s.effects |= EF_GRENADE; nuke->s.renderfx |= RF_IR_VISIBLE; VectorSet (nuke->mins, -8, -8, 0); VectorSet (nuke->maxs, 8, 8, 16); nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2"); nuke->owner = self; nuke->teammaster = self; nuke->wait = level.time + sk_nuke_delay->value + sk_nuke_life->value; nuke->nextthink = level.time + FRAMETIME; nuke->think = Nuke_Think; nuke->touch = nuke_bounce; nuke->health = 10000; nuke->takedamage = DAMAGE_YES; nuke->svflags |= SVF_DAMAGEABLE; nuke->dmg = NUKE_DAMAGE * damage_modifier; if (damage_modifier == 1) nuke->dmg_radius = sk_nuke_radius->value; else nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(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 ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius); nuke->classname = "nuke"; nuke->die = nuke_die; gi.linkentity (nuke); } #endif // ************************* // BLU-86 (aka Fuel-Air Explosive, aka NEUTRON BOMB) // ************************* #ifdef INCLUDE_NBOMB #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; e->velocity[1] += crandom()* 150; e->velocity[2] = self->speed * (100.0 / e->mass); } if (level.time < self->timestamp) self->nextthink = level.time + FRAMETIME; else G_FreeEdict (self); } /*static*/ 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->die = nbomb_die; gi.linkentity (nbomb); } #endif // ************************* // TESLA // ************************* #ifdef INCLUDE_TESLA #define TESLA_TIME_TO_LIVE 600 #define TESLA_DAMAGE_RADIUS 128 #define TESLA_DAMAGE 3 // 3 #define TESLA_KNOCKBACK 8 #define TESLA_ACTIVATE_TIME 3 #define TESLA_EXPLOSION_DAMAGE_MULT 50 // this is the amount the damage is multiplied by for underwater explosions #define TESLA_EXPLOSION_RADIUS 200 void tesla_remove (edict_t *self) { edict_t *cur, *next; self->takedamage = DAMAGE_NO; if (self->teamchain) { cur = self->teamchain; while (cur) { next = cur->teamchain; G_FreeEdict ( cur ); cur = next; } } else if (self->air_finished) gi.dprintf ("tesla without a field!\n"); self->owner = self->teammaster; // Going away, set the owner correctly. // PGM - grenade explode does damage to self->enemy self->enemy = NULL; // play quad sound if quadded and an underwater explosion if ((self->dmg_radius) && (self->dmg > (sk_tesla_damage->value*TESLA_EXPLOSION_DAMAGE_MULT))) { if (self->dmg < 4 * (sk_tesla_damage->value*TESLA_EXPLOSION_DAMAGE_MULT)) //double sound gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0); else //quad sound gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); } Grenade_Explode(self); } void tesla_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { // gi.dprintf("tesla killed\n"); tesla_remove(self); } void tesla_blow (edict_t *self) { // T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA); self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT; self->dmg_radius = TESLA_EXPLOSION_RADIUS; tesla_remove(self); } void tesla_zap (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { } void tesla_think_active (edict_t *self) { int i,num; edict_t *touch[MAX_EDICTS], *hit; vec3_t dir, start; trace_t tr; if (level.time > self->air_finished) { tesla_remove(self); return; } VectorCopy(self->s.origin, start); start[2] += 12; // was 16 num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID); for (i=0;iinuse)) break; hit=touch[i]; if (!hit->inuse) continue; if (hit == self) continue; if (hit->health < 1) continue; // don't hit clients in single-player or coop if (hit->client) if (coop->value || !deathmatch->value) continue; if (!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client) continue; tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT); if (tr.fraction == 1 || tr.ent == hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE))) { VectorSubtract(hit->s.origin, start, dir); // PMM - play quad sound if it's above the "normal" damage if (self->dmg > sk_tesla_damage->value) { if (self->dmg < (4 * sk_tesla_damage->value)) //double sound gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0); else //quad sound gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); } // PGM - don't do knockback to walking monsters if ((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM))) T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, self->dmg, 0, 0, MOD_TESLA); else T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal, self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_LIGHTNING); gi.WriteShort (hit - g_edicts); // destination entity gi.WriteShort (self - g_edicts); // source entity gi.WritePosition (tr.endpos); gi.WritePosition (start); gi.multicast (start, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) ReflectLightning (hit, self, tr.endpos, start); } } if (self->inuse) { self->think = tesla_think_active; self->nextthink = level.time + FRAMETIME; } } void tesla_activate (edict_t *self) { edict_t *trigger; edict_t *search; if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER)) { tesla_blow (self); return; } // only check for spawn points in deathmatch if (deathmatch->value) { search = NULL; while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL) { //if (!search->takedamage) // continue; // if it's a monster or player with health > 0 // or it's a deathmatch start point // and we can see it // blow up if (search->classname) { if ( ( (!strcmp(search->classname, "info_player_deathmatch")) || (!strcmp(search->classname, "info_player_start")) || (!strcmp(search->classname, "info_player_coop")) || (!strcmp(search->classname, "misc_teleporter_dest")) ) && (visible (search, self)) ) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("Tesla to close to %s, removing!\n", search->classname); tesla_remove (self); return; } } } } trigger = G_Spawn(); // if (trigger->nextthink) // { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("tesla_activate: fixing nextthink\n"); // trigger->nextthink = 0; // } VectorCopy (self->s.origin, trigger->s.origin); VectorSet (trigger->mins, (sk_tesla_radius->value * -1), (sk_tesla_radius->value * -1), self->mins[2]); VectorSet (trigger->maxs, sk_tesla_radius->value, sk_tesla_radius->value, sk_tesla_radius->value); trigger->movetype = MOVETYPE_NONE; trigger->solid = SOLID_TRIGGER; trigger->owner = self; trigger->touch = tesla_zap; trigger->classname = "tesla trigger"; // doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains gi.linkentity (trigger); VectorClear (self->s.angles); // clear the owner if in deathmatch if (deathmatch->value) self->owner = NULL; self->teamchain = trigger; self->think = tesla_think_active; self->nextthink = level.time + FRAMETIME; self->air_finished = level.time + sk_tesla_life->value; } void tesla_think (edict_t *ent) { if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA)) { tesla_remove (ent); return; } VectorClear (ent->s.angles); // 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; if (!(ent->s.frame)) gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0); ent->s.frame++; if (ent->s.frame > 14) { ent->s.frame = 14; ent->think = tesla_activate; ent->nextthink = level.time + 0.1; } else { if (ent->s.frame > 9) { if (ent->s.frame == 10) { if (ent->owner && ent->owner->client) { PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM } ent->s.skinnum = 1; } else if (ent->s.frame == 12) ent->s.skinnum = 2; else if (ent->s.frame == 14) ent->s.skinnum = 3; } ent->think = tesla_think; ent->nextthink = level.time + 0.1; } } void tesla_lava (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t land_point; // catch bad pointers // if ( !plane || !plane->normal ) // return; if (plane != NULL && plane->normal) { VectorMA (ent->s.origin, -20.0, plane->normal, land_point); if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA)) { tesla_blow (ent); return; } } if (ent->s.effects & EF_GRENADE) // don't make bounce sound if resting on lift { 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); } } void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed) { edict_t *tesla; vec3_t dir; vec3_t forward, right, up; vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); tesla = G_Spawn(); VectorCopy (start, tesla->s.origin); VectorScale (aimdir, speed, tesla->velocity); VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity); VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity); //Knightmare- add player's base velocity to thrown tesla if (add_velocity_throw->value && self->client) VectorAdd (tesla->velocity, self->velocity, tesla->velocity); else if (self->groundentity) VectorAdd (tesla->velocity, self->groundentity->velocity, tesla->velocity); // VectorCopy (dir, tesla->s.angles); VectorClear (tesla->s.angles); tesla->movetype = MOVETYPE_BOUNCE; tesla->solid = SOLID_BBOX; tesla->s.effects |= EF_GRENADE; tesla->s.renderfx |= RF_IR_VISIBLE; // VectorClear (tesla->mins); // VectorClear (tesla->maxs); VectorSet (tesla->mins, -12, -12, 0); VectorSet (tesla->maxs, 12, 12, 20); tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2"); tesla->owner = self; // PGM - we don't want it owned by self YET. tesla->teammaster = self; tesla->wait = level.time + sk_tesla_life->value; tesla->think = tesla_think; tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; // blow up on contact with lava & slime code tesla->touch = tesla_lava; if (deathmatch->value) // PMM - lowered from 50 - 7/29/1998 tesla->health = sk_tesla_health->value; else tesla->health = sk_tesla_health->value; // FIXME - change depending on skill? tesla->takedamage = DAMAGE_YES; tesla->die = tesla_die; tesla->dmg = sk_tesla_damage->value * damage_multiplier; // tesla->dmg = 0; tesla->classname = "tesla"; tesla->svflags |= SVF_DAMAGEABLE; tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; tesla->flags |= FL_MECHANICAL; gi.linkentity (tesla); } // NOTE: SP_tesla should ONLY be used to tesla mines that change maps // via a trigger_transition. They should NOT be used for map entities. void tesla_delayed_start (edict_t *tesla) { if (g_edicts[1].linkcount) { VectorScale(tesla->movedir,tesla->moveinfo.speed,tesla->velocity); tesla->movetype = MOVETYPE_BOUNCE; tesla->think = tesla_think; tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; gi.linkentity(tesla); } else tesla->nextthink = level.time + FRAMETIME; } void SP_tesla (edict_t *tesla) { tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2"); tesla->touch = tesla_lava; // For SP, freeze tesla until player spawns in if (game.maxclients == 1) { tesla->movetype = MOVETYPE_NONE; VectorCopy(tesla->velocity,tesla->movedir); VectorNormalize(tesla->movedir); tesla->moveinfo.speed = VectorLength(tesla->velocity); VectorClear(tesla->velocity); tesla->think = tesla_delayed_start; tesla->nextthink = level.time + FRAMETIME; } else { tesla->movetype = MOVETYPE_BOUNCE; tesla->think = tesla_think; tesla->nextthink = level.time + TESLA_ACTIVATE_TIME; } gi.linkentity (tesla); } #endif // ************************* // HEATBEAM // ************************* #ifdef INCLUDE_BEAMS /*static*/ void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, int te_beam, int te_impact, int mod) { trace_t tr; vec3_t dir; vec3_t forward, right, up; vec3_t end; vec3_t water_start, endpoint; qboolean water = false, underwater = false; int content_mask = MASK_SHOT | MASK_WATER; vec3_t beam_endpt; // tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); // if (!(tr.fraction < 1.0)) // { vectoangles2 (aimdir, dir); AngleVectors (dir, forward, right, up); VectorMA (start, WORLD_SIZE, forward, end); // was 8192 if (gi.pointcontents (start) & MASK_WATER) { // gi.dprintf ("Heat beam under water\n"); underwater = true; VectorCopy (start, water_start); content_mask &= ~MASK_WATER; } tr = gi.trace (start, NULL, NULL, end, self, content_mask); // see if we hit water if (tr.contents & MASK_WATER) { water = true; VectorCopy (tr.endpos, water_start); if (!VectorCompare (start, tr.endpos)) { gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_HEATBEAM_SPARKS); // gi.WriteByte (50); gi.WritePosition (water_start); gi.WriteDir (tr.plane.normal); // gi.WriteByte (8); // gi.WriteShort (60); gi.multicast (tr.endpos, MULTICAST_PVS); } // re-trace ignoring water this time tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); } VectorCopy (tr.endpos, endpoint); // } // halve the damage if target underwater if (water) { damage = damage /2; } // send gun puff / flash if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) { if (tr.fraction < 1.0) { if (tr.ent->takedamage) { T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_ENERGY, mod); } else { if ((!water) && (strncmp (tr.surface->name, "sky", 3))) { // This is the truncated steam entry - uses 1+1+2 extra bytes of data gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_HEATBEAM_STEAM); // gi.WriteByte (20); gi.WritePosition (tr.endpos); gi.WriteDir (tr.plane.normal); // gi.WriteByte (0xe0); // gi.WriteShort (60); gi.multicast (tr.endpos, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) ReflectSparks (TE_HEATBEAM_STEAM, tr.endpos, tr.plane.normal); if (self->client) PlayerNoise(self, tr.endpos, PNOISE_IMPACT); } } } } // if went through water, determine where the end and make a bubble trail if ((water) || (underwater)) { vec3_t pos; VectorSubtract (tr.endpos, water_start, dir); VectorNormalize (dir); VectorMA (tr.endpos, -2, dir, pos); if (gi.pointcontents (pos) & MASK_WATER) VectorCopy (pos, tr.endpos); else tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); VectorAdd (water_start, tr.endpos, pos); VectorScale (pos, 0.5, pos); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BUBBLETRAIL2); // gi.WriteByte (8); gi.WritePosition (water_start); gi.WritePosition (tr.endpos); gi.multicast (pos, MULTICAST_PVS); } if ((!underwater) && (!water)) { VectorCopy (tr.endpos, beam_endpt); } else { VectorCopy (endpoint, beam_endpt); } // Knightmare- Gen cam code // if (self->client->chasetoggle) if (self->client && self->client->chaseactive) { gi.WriteByte (svc_temp_entity); gi.WriteByte (te_beam); gi.WriteShort (self->client->oldplayer - g_edicts); gi.WritePosition (start); gi.WritePosition (beam_endpt); gi.multicast (self->client->oldplayer->s.origin, MULTICAST_ALL); // Lazarus reflections if (level.num_reflectors) ReflectHeatBeam (te_beam, self->client->oldplayer, start, beam_endpt); } else { gi.WriteByte (svc_temp_entity); gi.WriteByte (te_beam); gi.WriteShort (self - g_edicts); gi.WritePosition (start); gi.WritePosition (beam_endpt); gi.multicast (self->s.origin, MULTICAST_ALL); // Lazarus reflections if (level.num_reflectors) ReflectHeatBeam (te_beam, self, start, beam_endpt); } } /* ================= fire_heat Fires a single heat beam. Zap. ================= */ void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster) { if (monster) fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); else fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM); } #endif // ************************* // BLASTER 2 // ************************* /* ================= fire_blaster2 Fires a single green blaster bolt. Used by monsters, generally. ================= */ void blaster2_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { int mod; int damagestat; if (other == self->owner) return; if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (self); return; } if (self->owner && self->owner->client) PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); if (other->takedamage) { // the only time players will be firing blaster2 bolts will be from the // defender sphere. if (self->owner->client) mod = MOD_DEFENDER_SPHERE; else mod = MOD_BLASTER2; if (self->owner) { damagestat = self->owner->takedamage; self->owner->takedamage = DAMAGE_NO; if (self->dmg >= 5) T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); self->owner->takedamage = damagestat; } else { if (self->dmg >= 5) T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0); T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); } } else { //PMM - yeowch this will get expensive if (self->dmg >= 5) T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BLASTER2); gi.WritePosition (self->s.origin); if (!plane) gi.WriteDir (vec3_origin); else gi.WriteDir (plane->normal); gi.multicast (self->s.origin, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) { if (!plane) ReflectSparks(TE_BLASTER2, self->s.origin, vec3_origin); else ReflectSparks(TE_BLASTER2, self->s.origin, plane->normal); } } G_FreeEdict (self); } void fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) { edict_t *bolt; trace_t tr; VectorNormalize (dir); bolt = G_Spawn(); VectorCopy (start, bolt->s.origin); VectorCopy (start, bolt->s.old_origin); vectoangles2 (dir, bolt->s.angles); VectorScale (dir, speed, bolt->velocity); bolt->movetype = MOVETYPE_FLYMISSILE; bolt->clipmask = MASK_SHOT; bolt->solid = SOLID_BBOX; bolt->s.effects |= effect; bolt->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow VectorClear (bolt->mins); VectorClear (bolt->maxs); if (effect) bolt->s.effects |= EF_TRACKER; bolt->dmg_radius = 128; bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2"); bolt->touch = blaster2_touch; bolt->owner = self; bolt->nextthink = level.time + 2; bolt->think = G_FreeEdict; bolt->dmg = damage; bolt->classname = "bolt2"; gi.linkentity (bolt); if (self->client) check_dodge (self, bolt->s.origin, dir, speed); tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); if (tr.fraction < 1.0 && !(self->flags & FL_TURRET_OWNER)) { VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); bolt->touch (bolt, tr.ent, NULL, NULL); } } // NOTE: SP_bolt2 should ONLY be used for green monster blaster bolts that have // changed maps via trigger_transition. It should NOT be used for map // entities. void bolt2_delayed_start (edict_t *bolt) { if (g_edicts[1].linkcount) { VectorScale(bolt->movedir,bolt->moveinfo.speed,bolt->velocity); bolt->nextthink = level.time + 2; bolt->think = G_FreeEdict; gi.linkentity(bolt); } else bolt->nextthink = level.time + FRAMETIME; } void SP_bolt2 (edict_t *bolt) { bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2"); bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); bolt->touch = blaster2_touch; VectorCopy(bolt->velocity,bolt->movedir); VectorNormalize(bolt->movedir); bolt->moveinfo.speed = VectorLength(bolt->velocity); VectorClear(bolt->velocity); bolt->think = bolt2_delayed_start; bolt->nextthink = level.time + FRAMETIME; gi.linkentity(bolt); } // ************************* // tracker // ************************* /* void tracker_boom_think (edict_t *self) { self->s.frame--; if (self->s.frame < 0) G_FreeEdict(self); else self->nextthink = level.time + 0.1; } void tracker_boom_spawn (vec3_t origin) { edict_t *boom; boom = G_Spawn(); VectorCopy (origin, boom->s.origin); boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2"); boom->s.skinnum = 1; boom->s.frame = 2; boom->classname = "tracker boom"; gi.linkentity (boom); boom->think = tracker_boom_think; boom->nextthink = level.time + 0.1; //PMM // boom->s.renderfx |= RF_TRANSLUCENT; boom->s.effects |= EF_SPHERETRANS; //pmm } */ #define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK) #define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY) #define TRACKER_DAMAGE_TIME 0.5 // seconds void tracker_pain_daemon_think (edict_t *self) { static vec3_t pain_normal = { 0, 0, 1 }; int hurt; if (!self->inuse) return; if ((level.time - self->timestamp) > TRACKER_DAMAGE_TIME) { if (!self->enemy->client) self->enemy->s.effects &= ~EF_TRACKERTRAIL; G_FreeEdict (self); } else { if (self->enemy->health > 0) { // gi.dprintf("ouch %x\n", self); T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal, self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); // if we kill the player, we'll be removed. if (self->inuse) { // if we killed a monster, gib them. if (self->enemy->health < 1) { if (self->enemy->gib_health) hurt = - self->enemy->gib_health; else hurt = 500; // gi.dprintf("non-player killed. ensuring gib! %d\n", hurt); T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER); } if (self->enemy->client) self->enemy->client->tracker_pain_framenum = level.framenum + 1; else self->enemy->s.effects |= EF_TRACKERTRAIL; self->nextthink = level.time + FRAMETIME; } } else { if (!self->enemy->client) self->enemy->s.effects &= ~EF_TRACKERTRAIL; G_FreeEdict (self); } } } void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage) { edict_t *daemon; if (enemy == NULL) return; daemon = G_Spawn(); daemon->classname = "pain daemon"; daemon->think = tracker_pain_daemon_think; daemon->nextthink = level.time + FRAMETIME; daemon->timestamp = level.time; daemon->owner = owner; daemon->enemy = enemy; daemon->dmg = damage; } void tracker_explode (edict_t *self, cplane_t *plane) { vec3_t dir; if (!plane) VectorClear (dir); else VectorScale (plane->normal, 256, dir); gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_TRACKER_EXPLOSION); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_PVS); // Lazarus reflections if (level.num_reflectors) ReflectExplosion (TE_TRACKER_EXPLOSION, self->s.origin); // gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0); // tracker_boom_spawn(self->s.origin); G_FreeEdict (self); } void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { float damagetime; if (other == self->owner) return; if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (self); return; } if (self->client) PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); if (other->takedamage) { if ((other->svflags & SVF_MONSTER) || other->client) { if (other->health > 0) // knockback only for living creatures { // PMM - kickback was times 4 .. reduced to 3 // now this does no damage, just knockback T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, /* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); if (!(other->flags & (FL_FLY|FL_SWIM))) other->velocity[2] += 140; damagetime = ((float)self->dmg)*FRAMETIME; damagetime = damagetime / TRACKER_DAMAGE_TIME; // gi.dprintf ("damage is %f\n", damagetime); tracker_pain_daemon_spawn (self->owner, other, (int)damagetime); } else // lots of damage (almost autogib) for dead bodies { T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); } } else // full damage in one shot for inanimate objects { T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER); } } tracker_explode (self, plane); return; } void tracker_fly (edict_t *self) { vec3_t dest; vec3_t dir; vec3_t center; if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1)) { tracker_explode (self, NULL); return; } /* VectorCopy (self->enemy->s.origin, dest); if (self->enemy->client) dest[2] += self->enemy->viewheight; */ // PMM - try to hunt for center of enemy, if possible and not client if (self->enemy->client) { VectorCopy (self->enemy->s.origin, dest); dest[2] += self->enemy->viewheight; } // paranoia else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin)) { VectorCopy (self->enemy->s.origin, dest); } else { VectorMA (vec3_origin, 0.5, self->enemy->absmin, center); VectorMA (center, 0.5, self->enemy->absmax, center); VectorCopy (center, dest); } VectorSubtract (dest, self->s.origin, dir); VectorNormalize (dir); vectoangles2 (dir, self->s.angles); VectorScale (dir, self->speed, self->velocity); VectorCopy(dest, self->monsterinfo.saved_goal); self->nextthink = level.time + 0.1; } void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy) { edict_t *tracker; trace_t tr; VectorNormalize (dir); tracker = G_Spawn(); VectorCopy (start, tracker->s.origin); VectorCopy (start, tracker->s.old_origin); vectoangles2 (dir, tracker->s.angles); VectorScale (dir, speed, tracker->velocity); tracker->movetype = MOVETYPE_FLYMISSILE; tracker->clipmask = MASK_SHOT; tracker->solid = SOLID_BBOX; tracker->speed = speed; tracker->s.effects = EF_TRACKER; tracker->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow tracker->s.sound = gi.soundindex ("weapons/disrupt.wav"); VectorClear (tracker->mins); VectorClear (tracker->maxs); tracker->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2"); tracker->touch = tracker_touch; tracker->enemy = enemy; tracker->owner = self; tracker->dmg = damage; tracker->classname = "tracker"; gi.linkentity (tracker); if (enemy) { tracker->nextthink = level.time + 0.1; tracker->think = tracker_fly; } else { tracker->nextthink = level.time + 10; tracker->think = G_FreeEdict; } if (self->client) check_dodge (self, tracker->s.origin, dir, speed); tr = gi.trace (self->s.origin, NULL, NULL, tracker->s.origin, tracker, MASK_SHOT); if (tr.fraction < 1.0) { VectorMA (tracker->s.origin, -10, dir, tracker->s.origin); tracker->touch (tracker, tr.ent, NULL, NULL); } } // SP_tracker should ONLY be used for disruptors that have changed // maps via trigger_transition. It should NOT be used for map entities. void tracker_delayed_start (edict_t *tracker) { if (g_edicts[1].linkcount) { VectorScale(tracker->movedir,tracker->moveinfo.speed,tracker->velocity); tracker->nextthink = level.time + 10; tracker->think = G_FreeEdict; gi.linkentity(tracker); } else tracker->nextthink = level.time + FRAMETIME; } void SP_tracker (edict_t *tracker) { tracker->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2"); tracker->s.sound = gi.soundindex ("weapons/disrupt.wav"); tracker->touch = tracker_touch; VectorCopy(tracker->velocity,tracker->movedir); VectorNormalize(tracker->movedir); tracker->moveinfo.speed = VectorLength(tracker->velocity); VectorClear(tracker->velocity); tracker->think = tracker_delayed_start; tracker->nextthink = level.time + FRAMETIME; gi.linkentity(tracker); }