// g_sphere.c // pmack // april 1998 // defender - actively finds and shoots at enemies // hunter - waits until < 25% health and vore ball tracks person who hurt you // vengeance - kills person who killed you. #include "g_local.h" #define DEFENDER_LIFESPAN 600 #define HUNTER_LIFESPAN 600 #define VENGEANCE_LIFESPAN 600 #define MINIMUM_FLY_TIME 15 //#define MINIMUM_FLY_TIME 30 // FIXME - do we need to be calling ED_NewString at all? extern char *ED_NewString (char *string); void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker); void defender_think (edict_t *self); void hunter_think (edict_t *self); void vengeance_think (edict_t *self); void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); // ************************* // General Sphere Code // ************************* // ================= // ================= void sphere_think_explode (edict_t *self) { if(self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER)) { self->owner->client->owned_sphere = NULL; } BecomeExplosion1 (self); } // ================= // sphere_explode // ================= void sphere_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { // if(self->owner && self->owner->client) // gi.cprintf(self->owner, PRINT_HIGH, "Sphere timed out\n"); // gi.dprintf("player died, blowing up\n"); sphere_think_explode (self); } // ================= // sphere_if_idle_die - if the sphere is not currently attacking, blow up. // ================= void sphere_if_idle_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { if(!self->enemy) { // gi.dprintf("player died, blowing up\n"); sphere_think_explode(self); } } // ************************* // Sphere Movement // ************************* // ================= // ================= void sphere_fly (edict_t *self) { vec3_t dest; vec3_t dir; if(level.time >= self->wait) { // gi.dprintf("fly: timed out\n"); sphere_think_explode(self); return; } VectorCopy (self->owner->s.origin, dest); dest[2] = self->owner->absmax[2] + 4; if(level.time == (float)(int)level.time) { if(!visible(self, self->owner)) { VectorCopy(dest, self->s.origin); gi.linkentity(self); return; } } VectorSubtract (dest, self->s.origin, dir); VectorScale (dir, 5, self->velocity); } // ================= // ================= void sphere_chase (edict_t *self, int stupidChase) { vec3_t dest; vec3_t dir; float dist; if(level.time >= self->wait || (self->enemy && self->enemy->health < 1)) { sphere_think_explode(self); return; } VectorCopy (self->enemy->s.origin, dest); if(self->enemy->client) dest[2] += self->enemy->viewheight; if(visible(self, self->enemy) || stupidChase) { // if moving, hunter sphere uses active sound if(!stupidChase) self->s.sound = gi.soundindex ("spheres/h_active.wav"); VectorSubtract (dest, self->s.origin, dir); VectorNormalize (dir); vectoangles2(dir, self->s.angles); VectorScale (dir, 500, self->velocity); VectorCopy(dest, self->monsterinfo.saved_goal); } else if (VectorCompare (self->monsterinfo.saved_goal, vec3_origin)) { VectorSubtract(self->enemy->s.origin, self->s.origin, dir); dist = VectorNormalize(dir); vectoangles2(dir, self->s.angles); // if lurking, hunter sphere uses lurking sound self->s.sound = gi.soundindex ("spheres/h_lurk.wav"); VectorClear (self->velocity); } else { VectorSubtract(self->monsterinfo.saved_goal, self->s.origin, dir); dist = VectorNormalize(dir); if(dist > 1) { vectoangles2(dir, self->s.angles); if(dist > 500) VectorScale(dir, 500, self->velocity); else if (dist < 20) VectorScale(dir, (dist / FRAMETIME), self->velocity); else VectorScale(dir, dist, self->velocity); // if moving, hunter sphere uses active sound if(!stupidChase) self->s.sound = gi.soundindex ("spheres/h_active.wav"); } else { VectorSubtract(self->enemy->s.origin, self->s.origin, dir); dist = VectorNormalize(dir); vectoangles2(dir, self->s.angles); // if not moving, hunter sphere uses lurk sound if(!stupidChase) self->s.sound = gi.soundindex ("spheres/h_lurk.wav"); VectorClear(self->velocity); } } } // ************************* // Attack related stuff // ************************* // ================= // ================= void sphere_fire (edict_t *self, edict_t *enemy) { vec3_t dest; vec3_t dir; if(level.time >= self->wait || !enemy) { sphere_think_explode(self); return; } VectorCopy (enemy->s.origin, dest); self->s.effects |= EF_ROCKET; VectorSubtract (dest, self->s.origin, dir); VectorNormalize (dir); vectoangles2 ( dir, self->s.angles ); VectorScale (dir, 1000, self->velocity); self->touch = vengeance_touch; self->think = sphere_think_explode; self->nextthink = self->wait; } // ================= // ================= void sphere_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf, int mod) { if(self->spawnflags & SPHERE_DOPPLEGANGER) { if (other == self->teammaster) return; self->takedamage = DAMAGE_NO; self->owner = self->teammaster; self->teammaster = NULL; } else { if (other == self->owner) return; // PMM - don't blow up on bodies if (!strcmp(other->classname, "bodyque")) return; } if (surf && (surf->flags & SURF_SKY)) { G_FreeEdict (self); return; } if (other->takedamage) { T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 10000, 1, DAMAGE_DESTROY_ARMOR, mod); } else { T_RadiusDamage (self, self->owner, 512, self->owner, 256, mod); } sphere_think_explode (self); } // ================= // ================= void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { if(self->spawnflags & SPHERE_DOPPLEGANGER) sphere_touch (self, other, plane, surf, MOD_DOPPLE_VENGEANCE); else sphere_touch (self, other, plane, surf, MOD_VENGEANCE_SPHERE); } // ================= // ================= void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { edict_t *owner; // don't blow up if you hit the world.... sheesh. if(other==world) return; if(self->owner) { // if owner is flying with us, make sure they stop too. owner=self->owner; if(owner->flags & FL_SAM_RAIMI) { VectorClear(owner->velocity); owner->movetype = MOVETYPE_NONE; gi.linkentity(owner); } } if(self->spawnflags & SPHERE_DOPPLEGANGER) sphere_touch (self, other, plane, surf, MOD_DOPPLE_HUNTER); else sphere_touch (self, other, plane, surf, MOD_HUNTER_SPHERE); } // ================= // ================= void defender_shoot (edict_t *self, edict_t *enemy) { vec3_t dir; vec3_t start; if(!(enemy->inuse) || enemy->health <= 0) return; if(enemy == self->owner) return; VectorSubtract (enemy->s.origin, self->s.origin, dir); VectorNormalize (dir); if(self->monsterinfo.attack_finished > level.time) return; if(!visible(self, self->enemy)) return; VectorCopy(self->s.origin, start); start[2] += 2; // fire_blaster (self->owner, start, dir, sk_defender_blaster_damage->value, sk_defender_blaster_speed->value, EF_BLASTER|EF_TRACKER, false, BLASTER_GREEN); fire_blaster2 (self->owner, start, dir, sk_defender_blaster_damage->value, sk_defender_blaster_speed->value, EF_BLASTER, 0); self->monsterinfo.attack_finished = level.time + 0.4; } // ************************* // Activation Related Stuff // ************************* // ================= // ================= void body_gib (edict_t *self) { int n; gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); for (n= 0; n < 4; n++) ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 50, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/skull/tris.md2", 50, GIB_ORGANIC); } // ================= // ================= void hunter_pain (edict_t *self, edict_t *other, float kick, int damage) { edict_t *owner; float dist; vec3_t dir; if(self->enemy) return; owner = self->owner; if(!(self->spawnflags & SPHERE_DOPPLEGANGER)) { if(owner && (owner->health > 0)) return; //PMM if(other == owner) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("hunter: won't get mad at my owner!\n"); return; } //pmm } else { // if fired by a doppleganger, set it to 10 second timeout self->wait = level.time + MINIMUM_FLY_TIME; } if((self->wait - level.time) < MINIMUM_FLY_TIME) self->wait = level.time + MINIMUM_FLY_TIME; self->s.effects |= EF_BLASTER | EF_TRACKER; self->touch = hunter_touch; self->enemy = other; // if(g_showlogic && g_showlogic->value) // gi.dprintf("hunter_pain: mad at %s\n", other->classname); // if we're not owned by a player, no sam raimi // if we're spawned by a doppleganger, no sam raimi if((self->spawnflags & SPHERE_DOPPLEGANGER) || !(owner && owner->client)) return; // sam raimi cam is disabled if FORCE_RESPAWN is set. // sam raimi cam is also disabled if huntercam->value is 0. if(!((int)dmflags->value & DF_FORCE_RESPAWN) && (huntercam && (huntercam->value))) { VectorSubtract(other->s.origin, self->s.origin, dir); dist=VectorLength(dir); if(owner && (dist >= 192)) { // detach owner from body and send him flying owner->movetype = MOVETYPE_FLYMISSILE; // gib like we just died, even though we didn't, really. body_gib(owner); // move the sphere to the owner's current viewpoint. // we know it's a valid spot (or will be momentarily) VectorCopy(owner->s.origin, self->s.origin); self->s.origin[2] += owner->viewheight; // move the player's origin to the sphere's new origin VectorCopy(self->s.origin, owner->s.origin); VectorCopy(self->s.angles, owner->s.angles); VectorCopy(self->s.angles, owner->client->v_angle); VectorClear(owner->mins); VectorClear(owner->maxs); VectorSet(owner->mins, -5, -5, -5); VectorSet(owner->maxs, 5, 5, 5); owner->client->ps.fov = 140; owner->s.modelindex = 0; owner->s.modelindex2 = 0; owner->viewheight = 8; owner->solid = SOLID_NOT; owner->flags |= FL_SAM_RAIMI; gi.linkentity(owner); // PMM - set bounding box so we don't clip out of world // VectorSet(self->mins, -5, -5, -5); // VectorSet(self->maxs, 5, 5, 5); self->solid = SOLID_BBOX; gi.linkentity (self); } // else // gi.dprintf("too close for sam raimi cam\n"); } } // ================= // ================= void defender_pain (edict_t *self, edict_t *other, float kick, int damage) { //PMM if(other == self->owner) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("defender: won't get mad at my owner!\n"); return; } //pmm self->enemy = other; } // ================= // ================= void vengeance_pain (edict_t *self, edict_t *other, float kick, int damage) { if(self->enemy) return; if(!(self->spawnflags & SPHERE_DOPPLEGANGER)) { if(self->owner->health >= sk_vengeance_health_threshold->value) return; //PMM if(other == self->owner) { // if ((g_showlogic) && (g_showlogic->value)) // gi.dprintf ("vengeance: won't get mad at my owner!\n"); return; } //pmm } else { self->wait = level.time + MINIMUM_FLY_TIME; } if((self->wait - level.time) < MINIMUM_FLY_TIME) self->wait = level.time + MINIMUM_FLY_TIME; self->s.effects |= EF_ROCKET; self->touch = vengeance_touch; self->enemy = other; } // ************************* // Think Functions // ************************* // =================== // =================== void defender_think (edict_t *self) { if(!self->owner) { // gi.dprintf("think: no owner\n"); G_FreeEdict(self); return; } // if we've exited the level, just remove ourselves. if (level.intermissiontime) { sphere_think_explode(self); return; } if(self->owner->health <=0) { sphere_think_explode(self); return; } // if(level.time - self->timestamp > 1) // { // gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/dsphere.wav"), 0.6, ATTN_NORM, 0); // self->timestamp = level.time; // } self->s.frame++; if(self->s.frame>19) self->s.frame = 0; if(self->enemy) { if(self->enemy->health > 0) { // gi.dprintf( "shooting at %s\n", self->enemy->classname); defender_shoot (self, self->enemy); } else self->enemy = NULL; } // else // { // self->ideal_yaw+=3; // M_ChangeYaw (self); // } sphere_fly (self); if(self->inuse) self->nextthink = level.time + 0.1; } // ================= // ================= void hunter_think (edict_t *self) { edict_t *owner; vec3_t dir, ang; // if we've exited the level, just remove ourselves. if (level.intermissiontime) { sphere_think_explode(self); return; } owner = self->owner; if(!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER)) { // gi.dprintf("think: no owner\n"); G_FreeEdict(self); return; } if(owner) self->ideal_yaw = owner->s.angles[YAW]; else if(self->enemy) // fired by doppleganger { VectorSubtract(self->enemy->s.origin, self->s.origin, dir); vectoangles2(dir, ang); self->ideal_yaw = ang[YAW]; } M_ChangeYaw(self); // if(level.time - self->timestamp > 1) // { // gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/hsphere.wav"), 0.5, ATTN_NORM, 0); // self->timestamp = level.time; // } if(self->enemy) { sphere_chase (self, 0); // deal with sam raimi cam if(owner && (owner->flags & FL_SAM_RAIMI)) { if(self->inuse) { owner->movetype = MOVETYPE_FLYMISSILE; // VectorCopy(self->s.angles, owner->s.angles); // VectorCopy(self->s.angles, owner->client->v_angle); LookAtKiller (owner, self, self->enemy); // owner->viewheight = 22; // owner->client->v_angle[YAW]+=5; // owner is flying with us, move him too owner->movetype = MOVETYPE_FLYMISSILE; owner->viewheight = self->s.origin[2] - owner->s.origin[2]; // VectorCopy(self->s.angles, owner->s.angles); // VectorCopy(self->s.angles, owner->client->v_angle); VectorCopy(self->s.origin, owner->s.origin); VectorCopy(self->velocity, owner->velocity); VectorClear(owner->mins); VectorClear(owner->maxs); gi.linkentity(owner); } else // sphere timed out { VectorClear(owner->velocity); owner->movetype = MOVETYPE_NONE; gi.linkentity(owner); } } } else { // self->ideal_yaw+=3; // M_ChangeYaw (self); sphere_fly (self); } if(self->inuse) self->nextthink = level.time + 0.1; } // ================= // ================= void vengeance_think (edict_t *self) { // if we've exited the level, just remove ourselves. if (level.intermissiontime) { sphere_think_explode(self); return; } if(!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER)) { // gi.dprintf("think: no owner\n"); G_FreeEdict(self); return; } // if(level.time - self->timestamp > 1) // { // gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/vsphere.wav"), 0.5, ATTN_NORM, 0); // self->timestamp = level.time; // } if(self->enemy) { // sphere_fire (self, self->owner->enemy); sphere_chase (self, 1); } else sphere_fly (self); if(self->inuse) self->nextthink = level.time + 0.1; } // ************************* // Spawning / Creation // ************************* // monsterinfo_t // ================= // ================= edict_t *Sphere_Spawn (edict_t *owner, int spawnflags) { edict_t *sphere; sphere = G_Spawn(); VectorCopy(owner->s.origin, sphere->s.origin); sphere->s.origin[2] = owner->absmax[2]; sphere->s.angles[YAW] = owner->s.angles[YAW]; sphere->solid = SOLID_BBOX; sphere->clipmask = MASK_SHOT; sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE; sphere->movetype = MOVETYPE_FLYMISSILE; if(spawnflags & SPHERE_DOPPLEGANGER) sphere->teammaster = owner->teammaster; else sphere->owner = owner; sphere->classname = "sphere"; sphere->yaw_speed = 40; sphere->monsterinfo.attack_finished = 0; sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere //PMM sphere->takedamage = DAMAGE_NO; switch(spawnflags & SPHERE_TYPE) { case SPHERE_DEFENDER: sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2"); sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2"); sphere->s.sound = gi.soundindex ("spheres/d_idle.wav"); sphere->pain = defender_pain; sphere->wait = level.time + sk_defender_time->value; sphere->die = sphere_explode; sphere->think = defender_think; break; case SPHERE_HUNTER: sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2"); sphere->s.sound = gi.soundindex ("spheres/h_idle.wav"); sphere->wait = level.time + sk_hunter_time->value; sphere->pain = hunter_pain; sphere->die = sphere_if_idle_die; sphere->think = hunter_think; break; case SPHERE_VENGEANCE: sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2"); sphere->s.sound = gi.soundindex ("spheres/v_idle.wav"); sphere->wait = level.time + sk_vengeance_time->value; sphere->pain = vengeance_pain; sphere->die = sphere_if_idle_die; sphere->think = vengeance_think; VectorSet (sphere->avelocity, 30, 30, 0); break; default: gi.dprintf("Tried to create an invalid sphere\n"); G_FreeEdict(sphere); return NULL; } sphere->nextthink = level.time + 0.1; gi.linkentity (sphere); return sphere; } // ================= // Own_Sphere - attach the sphere to the client so we can // directly access it later // ================= void Own_Sphere (edict_t *self, edict_t *sphere) { if(!sphere) return; // ownership only for players if(self->client) { // if they don't have one if(!(self->client->owned_sphere)) { self->client->owned_sphere = sphere; } // they already have one, take care of the old one else { if(self->client->owned_sphere->inuse) { G_FreeEdict(self->client->owned_sphere); self->client->owned_sphere = sphere; } else { self->client->owned_sphere = sphere; } } } } // ================= // ================= void Defender_Launch (edict_t *self) { edict_t *sphere; sphere = Sphere_Spawn (self, SPHERE_DEFENDER); Own_Sphere (self, sphere); } // ================= // ================= void Hunter_Launch (edict_t *self) { edict_t *sphere; sphere = Sphere_Spawn (self, SPHERE_HUNTER); Own_Sphere (self, sphere); } // ================= // ================= void Vengeance_Launch (edict_t *self) { edict_t *sphere; sphere = Sphere_Spawn (self, SPHERE_VENGEANCE); Own_Sphere (self, sphere); }