// g_misc.c #include "g_local.h" /*QUAKED func_group (0 0 0) ? Used to group brushes together just for editor convenience. */ //===================================================== void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) { ent->count ^= 1; // toggle state // gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); gi.SetAreaPortalState (ent->style, ent->count); } /*QUAKED func_areaportal (0 0 0) ? This is a non-visible object that divides the world into areas that are seperated when this portal is not activated. Usually enclosed in the middle of a door. */ void SP_func_areaportal (edict_t *ent) { ent->use = Use_Areaportal; ent->count = 0; // always start closed; } //===================================================== /* ================= Misc functions ================= */ void VelocityForDamage (int damage, vec3_t v) { v[0] = 100.0 * crandom(); v[1] = 100.0 * crandom(); v[2] = 200.0 + 100.0 * random(); if (damage < 50) VectorScale (v, 0.7, v); else VectorScale (v, 1.2, v); } void ClipGibVelocity (edict_t *ent) { if (ent->velocity[0] < -300) ent->velocity[0] = -300; else if (ent->velocity[0] > 300) ent->velocity[0] = 300; if (ent->velocity[1] < -300) ent->velocity[1] = -300; else if (ent->velocity[1] > 300) ent->velocity[1] = 300; if (ent->velocity[2] < 200) ent->velocity[2] = 200; // always some upwards else if (ent->velocity[2] > 500) ent->velocity[2] = 500; } /* ================= gibs ================= */ //Knightmare- gib fade #ifdef KMQUAKE2_ENGINE_MOD void gib_fade2 (edict_t *self); void gib_fade (edict_t *self) { if (self->s.effects & EF_BLASTER) // Remove glow from gekk gibs { self->s.effects &= ~EF_BLASTER; self->s.renderfx &= ~RF_NOSHADOW; } if (self->s.renderfx & RF_TRANSLUCENT) self->s.alpha = 0.70f; else if (self->s.effects & EF_SPHERETRANS) self->s.alpha = 0.30f; else if ( !(self->s.alpha) || (self->s.alpha <= 0.0f) || (self->s.alpha > 1.0f) ) self->s.alpha = 1.00f; // gib_fade2 (self); self->nextthink = level.time + 0.2; self->think = gib_fade2; } void gib_fade2 (edict_t *self) { self->s.alpha -= 0.05f; self->s.alpha = max(self->s.alpha, 1/255); if (self->s.alpha <= 1/255) { G_FreeEdict (self); return; } self->nextthink = level.time + 0.2; self->think = gib_fade2; gi.linkentity (self); } #else void gib_fade2 (edict_t *self) { self->s.effects |= EF_SPHERETRANS; self->s.renderfx &= ~RF_TRANSLUCENT; self->nextthink = level.time + 2; self->think = G_FreeEdict; gi.linkentity(self); } void gib_fade (edict_t *self) { if (self->s.effects & EF_BLASTER) // Remove glow from gekk gibs self->s.effects &= ~EF_BLASTER; self->s.renderfx = RF_TRANSLUCENT; self->nextthink = level.time + 2; self->think = gib_fade2; gi.linkentity(self); } #endif void gib_think (edict_t *self) { self->s.frame++; self->nextthink = level.time + FRAMETIME; if (self->s.frame == 10) { self->think = G_FreeEdict; self->nextthink = level.time + 8 + random()*10; } } void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t normal_angles, right; if (!self->groundentity) return; self->touch = NULL; if (plane) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); vectoangles (plane->normal, normal_angles); AngleVectors (normal_angles, NULL, right, NULL); vectoangles (right, self->s.angles); if (self->s.modelindex == sm_meat_index) { self->s.frame++; self->think = gib_think; self->nextthink = level.time + FRAMETIME; } } } void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { G_FreeEdict (self); } void ThrowGib (edict_t *self, char *gibname, int damage, int type) { edict_t *gib; vec3_t vd; vec3_t origin; vec3_t size; float vscale; gib = G_Spawn(); VectorScale (self->size, 0.5, size); VectorAdd (self->absmin, size, origin); gib->s.origin[0] = origin[0] + crandom() * size[0]; gib->s.origin[1] = origin[1] + crandom() * size[1]; gib->s.origin[2] = origin[2] + crandom() * size[2]; gi.setmodel (gib, gibname); gib->clipmask = MASK_SHOT; VectorSet (gib->mins, -4, -4, -4); VectorSet (gib->maxs, 4, 4, 4); gib->solid = SOLID_TRIGGER; // Knightmare- was SOLID_NOT if (self->blood_type == 1) { gib->s.effects |= EF_GREENGIB|EF_BLASTER; gib->s.renderfx |= RF_NOSHADOW; } else if (self->blood_type == 2) gib->s.effects |= EF_GRENADE; else gib->s.effects |= EF_GIB; // inherit translucency flags from parent entity if (self->s.renderfx & RF_TRANSLUCENT) gib->s.renderfx |= RF_TRANSLUCENT; if (self->s.effects & EF_SPHERETRANS) gib->s.effects |= EF_SPHERETRANS; #ifdef KMQUAKE2_ENGINE_MOD // Knightmare- translucent monsters throw translucent gibs if ( (self->s.alpha) && (self->s.alpha > 0.0f) && (self->s.alpha < 1.0f) ) gib->s.alpha = self->s.alpha; #endif gib->flags |= FL_NO_KNOCKBACK; gib->svflags |= SVF_GIB; // Knightmare- gib flag gib->takedamage = DAMAGE_NO; // was DAMAGE_YES; gib->die = gib_die; if (type == GIB_ORGANIC) { gib->movetype = MOVETYPE_TOSS; gib->touch = gib_touch; vscale = 0.5; } else { gib->movetype = MOVETYPE_BOUNCE; vscale = 1.0; } VelocityForDamage (damage, vd); VectorMA (self->velocity, vscale, vd, gib->velocity); ClipGibVelocity (gib); gib->avelocity[0] = random()*600; gib->avelocity[1] = random()*600; gib->avelocity[2] = random()*600; gib->think = gib_fade; // G_FreeEdict; gib->nextthink = level.time + 10 + random()*10; gi.linkentity (gib); } void ThrowHead (edict_t *self, char *gibname, int damage, int type) { vec3_t vd; float vscale; self->s.skinnum = 0; self->s.frame = 0; VectorClear (self->mins); VectorClear (self->maxs); self->s.modelindex2 = 0; gi.setmodel (self, gibname); self->clipmask = MASK_SHOT; if (self->blood_type == 1) { VectorSet (self->mins, -4, -4, -4); VectorSet (self->maxs, 4, 4, 4); } else { VectorSet (self->mins, -4, -4, 0); VectorSet (self->maxs, 4, 4, 8); } self->solid = SOLID_TRIGGER; // Knightmare- was SOLID_NOT if (self->blood_type == 1) { self->s.effects |= EF_GREENGIB|EF_BLASTER; self->s.renderfx |= RF_NOSHADOW; } else if (self->blood_type == 2) self->s.effects |= EF_GRENADE; else self->s.effects |= EF_GIB; self->s.effects &= ~EF_FLIES; self->s.sound = 0; self->flags |= FL_NO_KNOCKBACK; self->svflags &= ~SVF_MONSTER; self->svflags |= SVF_GIB; // Knightmare- gib flag self->takedamage = DAMAGE_NO; // was DAMAGE_YES; self->die = gib_die; if (type == GIB_ORGANIC) { self->movetype = MOVETYPE_TOSS; self->touch = gib_touch; vscale = 0.5; } else { self->movetype = MOVETYPE_BOUNCE; vscale = 1.0; } VelocityForDamage (damage, vd); VectorMA (self->velocity, vscale, vd, self->velocity); ClipGibVelocity (self); self->avelocity[YAW] = crandom()*600; self->think = gib_fade; //G_FreeEdict; self->nextthink = level.time + 10 + random()*10; gi.linkentity (self); } void ThrowClientHead (edict_t *self, int damage) { vec3_t vd; char *gibname; if (rand()&1) { gibname = "models/objects/gibs/head2/tris.md2"; self->s.skinnum = 1; // second skin is player } else { gibname = "models/objects/gibs/skull/tris.md2"; self->s.skinnum = 0; } self->s.origin[2] += 32; self->s.frame = 0; gi.setmodel (self, gibname); // VectorSet (self->mins, -16, -16, 0); // VectorSet (self->maxs, 16, 16, 16); VectorSet (self->mins, -4, -4, -4); VectorSet (self->maxs, 4, 4, 4); self->takedamage = DAMAGE_NO; self->solid = SOLID_TRIGGER; // Knightmare- was SOLID_NOT self->s.effects = EF_GIB; self->s.sound = 0; self->flags |= FL_NO_KNOCKBACK; self->svflags |= SVF_GIB; // Knightmare- gib flag self->movetype = MOVETYPE_BOUNCE; VelocityForDamage (damage, vd); VectorAdd (self->velocity, vd, self->velocity); if (self->client) // bodies in the queue don't have a client anymore { self->client->anim_priority = ANIM_DEATH; self->client->anim_end = self->s.frame; } gi.linkentity (self); } /* ================= debris ================= */ void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { G_FreeEdict (self); } void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) { edict_t *chunk; vec3_t v; chunk = G_Spawn(); VectorCopy (origin, chunk->s.origin); gi.setmodel (chunk, modelname); // inherit translucency flags from parent entity if (self->s.renderfx & RF_TRANSLUCENT) chunk->s.renderfx |= RF_TRANSLUCENT; if (self->s.effects & EF_SPHERETRANS) chunk->s.effects |= EF_SPHERETRANS; #ifdef KMQUAKE2_ENGINE_MOD // Knightmare- translucent entities throw translucent debris if ( (self->s.alpha) && (self->s.alpha > 0.0f) && (self->s.alpha < 1.0f) ) chunk->s.alpha = self->s.alpha; #endif v[0] = 100 * crandom(); v[1] = 100 * crandom(); v[2] = 100 + 100 * crandom(); VectorMA (self->velocity, speed, v, chunk->velocity); chunk->movetype = MOVETYPE_BOUNCE; VectorSet (chunk->mins, -1, -1, -1); VectorSet (chunk->maxs, 1, 1, 1); chunk->solid = SOLID_TRIGGER; // Knightmare- was SOLID_NOT chunk->avelocity[0] = random()*600; chunk->avelocity[1] = random()*600; chunk->avelocity[2] = random()*600; chunk->think = gib_fade; //G_FreeEdict; chunk->nextthink = level.time + 5 + random()*5; chunk->s.frame = 0; chunk->flags = 0; chunk->classname = "debris"; chunk->svflags |= SVF_GIB; // Knightmare- gib flag chunk->takedamage = DAMAGE_NO; // was DAMAGE_YES chunk->die = debris_die; gi.linkentity (chunk); } void BecomeExplosion1 (edict_t *self) { gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_EXPLOSION1); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_PVS); G_FreeEdict (self); } void BecomeExplosion2 (edict_t *self) { gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_EXPLOSION2); gi.WritePosition (self->s.origin); gi.multicast (self->s.origin, MULTICAST_PVS); G_FreeEdict (self); } /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT Target: next path corner Pathtarget: gets used when an entity that has this path_corner targeted touches it */ void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t v; edict_t *next; if (other->movetarget != self) return; if (other->enemy) return; if (self->pathtarget) { char *savetarget; savetarget = self->target; self->target = self->pathtarget; G_UseTargets (self, other); self->target = savetarget; } if (self->target) next = G_PickTarget(self->target); else next = NULL; if ((next) && (next->spawnflags & 1)) { VectorCopy (next->s.origin, v); v[2] += next->mins[2]; v[2] -= other->mins[2]; VectorCopy (v, other->s.origin); next = G_PickTarget(next->target); } other->goalentity = other->movetarget = next; if (self->wait) { if(other->goalentity) { VectorSubtract (other->goalentity->s.origin, other->s.origin, v); other->ideal_yaw = vectoyaw (v); } other->monsterinfo.pausetime = level.time + self->wait; other->monsterinfo.stand (other); return; } if (!other->movetarget) { other->monsterinfo.pausetime = level.time + 100000000; other->monsterinfo.stand (other); } else { VectorSubtract (other->goalentity->s.origin, other->s.origin, v); other->ideal_yaw = vectoyaw (v); } } void SP_path_corner (edict_t *self) { if (!self->targetname) { gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); G_FreeEdict (self); return; } self->solid = SOLID_TRIGGER; self->touch = path_corner_touch; VectorSet (self->mins, -8, -8, -8); VectorSet (self->maxs, 8, 8, 8); self->svflags |= SVF_NOCLIENT; gi.linkentity (self); } /*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold Makes this the target of a monster and it will head here when first activated before going after the activator. If hold is selected, it will stay here. */ void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { edict_t *activator; if (other->movetarget != self) return; if (self->target) { other->target = self->target; other->goalentity = other->movetarget = G_PickTarget(other->target); if (!other->goalentity) { gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); other->movetarget = self; } self->target = NULL; } else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) { other->monsterinfo.pausetime = level.time + 100000000; other->monsterinfo.aiflags |= AI_STAND_GROUND; other->monsterinfo.stand (other); } if (other->movetarget == self) { other->target = NULL; other->movetarget = NULL; other->goalentity = other->enemy; other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; } if (self->pathtarget) { char *savetarget; savetarget = self->target; self->target = self->pathtarget; if (other->enemy && other->enemy->client) activator = other->enemy; else if (other->oldenemy && other->oldenemy->client) activator = other->oldenemy; else if (other->activator && other->activator->client) activator = other->activator; else activator = other; G_UseTargets (self, activator); self->target = savetarget; } } void SP_point_combat (edict_t *self) { if (deathmatch->value) { G_FreeEdict (self); return; } self->solid = SOLID_TRIGGER; self->touch = point_combat_touch; VectorSet (self->mins, -8, -8, -16); VectorSet (self->maxs, 8, 8, 16); self->svflags = SVF_NOCLIENT; gi.linkentity (self); }; /*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) Just for the debugging level. Don't use */ void TH_viewthing(edict_t *ent) { ent->s.frame = (ent->s.frame + 1) % 7; ent->nextthink = level.time + FRAMETIME; } void SP_viewthing(edict_t *ent) { gi.dprintf ("viewthing spawned\n"); ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; ent->s.renderfx = RF_FRAMELERP; VectorSet (ent->mins, -16, -16, -24); VectorSet (ent->maxs, 16, 16, 32); ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); gi.linkentity (ent); ent->nextthink = level.time + 0.5; ent->think = TH_viewthing; return; } /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for spotlights, etc. */ void SP_info_null (edict_t *self) { G_FreeEdict (self); }; /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target for lightning. */ void SP_info_notnull (edict_t *self) { VectorCopy (self->s.origin, self->absmin); VectorCopy (self->s.origin, self->absmax); }; /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF Non-displayed light. Default light value is 300. Default style is 0. If targeted, will toggle between on and off. Default _cone value is 10 (used to set size of light for spotlights) */ #define START_OFF 1 /*static*/ void light_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->spawnflags & START_OFF) { gi.configstring (CS_LIGHTS+self->style, "m"); self->spawnflags &= ~START_OFF; } else { gi.configstring (CS_LIGHTS+self->style, "a"); self->spawnflags |= START_OFF; } } void SP_light (edict_t *self) { // no targeted lights in deathmatch, because they cause global messages if (!self->targetname || deathmatch->value) { G_FreeEdict (self); return; } if (self->style >= 32) { self->use = light_use; if (self->spawnflags & START_OFF) gi.configstring (CS_LIGHTS+self->style, "a"); else gi.configstring (CS_LIGHTS+self->style, "m"); } } /*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST This is just a solid wall if not inhibited TRIGGER_SPAWN the wall will not be present until triggered it will then blink in to existance; it will kill anything that was in it's way TOGGLE only valid for TRIGGER_SPAWN walls this allows the wall to be turned on and off START_ON only valid for TRIGGER_SPAWN walls the wall will initially be present */ void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) { if (self->solid == SOLID_NOT) { self->solid = SOLID_BSP; self->svflags &= ~SVF_NOCLIENT; KillBox (self); } else { self->solid = SOLID_NOT; self->svflags |= SVF_NOCLIENT; } gi.linkentity (self); if (!(self->spawnflags & 2)) self->use = NULL; } void SP_func_wall (edict_t *self) { self->movetype = MOVETYPE_PUSH; gi.setmodel (self, self->model); if (self->spawnflags & 8) self->s.effects |= EF_ANIM_ALL; if (self->spawnflags & 16) self->s.effects |= EF_ANIM_ALLFAST; // just a wall if ((self->spawnflags & 7) == 0) { self->solid = SOLID_BSP; gi.linkentity (self); return; } // it must be TRIGGER_SPAWN if (!(self->spawnflags & 1)) { // gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); self->spawnflags |= 1; } // yell if the spawnflags are odd if (self->spawnflags & 4) { if (!(self->spawnflags & 2)) { gi.dprintf("func_wall START_ON without TOGGLE\n"); self->spawnflags |= 2; } } self->use = func_wall_use; if (self->spawnflags & 4) { self->solid = SOLID_BSP; } else { self->solid = SOLID_NOT; self->svflags |= SVF_NOCLIENT; } gi.linkentity (self); } /*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST This is solid bmodel that will fall if it's support it removed. */ void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { // only squash thing we fall on top of if (!plane) return; if (plane->normal[2] < 1.0) return; if (other->takedamage == DAMAGE_NO) return; T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); } void func_object_release (edict_t *self) { self->movetype = MOVETYPE_TOSS; self->touch = func_object_touch; } void func_object_use (edict_t *self, edict_t *other, edict_t *activator) { self->solid = SOLID_BSP; self->svflags &= ~SVF_NOCLIENT; self->use = NULL; KillBox (self); func_object_release (self); } void SP_func_object (edict_t *self) { gi.setmodel (self, self->model); self->mins[0] += 1; self->mins[1] += 1; self->mins[2] += 1; self->maxs[0] -= 1; self->maxs[1] -= 1; self->maxs[2] -= 1; if (!self->dmg) self->dmg = 100; if (self->spawnflags == 0) { self->solid = SOLID_BSP; self->movetype = MOVETYPE_PUSH; self->think = func_object_release; self->nextthink = level.time + 2 * FRAMETIME; } else { self->solid = SOLID_NOT; self->movetype = MOVETYPE_PUSH; self->use = func_object_use; self->svflags |= SVF_NOCLIENT; } if (self->spawnflags & 2) self->s.effects |= EF_ANIM_ALL; if (self->spawnflags & 4) self->s.effects |= EF_ANIM_ALLFAST; self->clipmask = MASK_MONSTERSOLID; gi.linkentity (self); } /*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST Any brush that you want to explode or break apart. If you want an ex0plosion, set dmg and it will do a radius explosion of that amount at the center of the bursh. If targeted it will not be shootable. health defaults to 100. mass defaults to 75. This determines how much debris is emitted when it explodes. You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16). So 800 gives the most. */ void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { vec3_t origin; vec3_t chunkorigin; vec3_t size; int count; int mass; // bmodel origins are (0 0 0), we need to adjust that here VectorScale (self->size, 0.5, size); VectorAdd (self->absmin, size, origin); VectorCopy (origin, self->s.origin); self->takedamage = DAMAGE_NO; if (self->dmg) T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); VectorNormalize (self->velocity); VectorScale (self->velocity, 150, self->velocity); // start chunks towards the center VectorScale (size, 0.5, size); mass = self->mass; if (!mass) mass = 75; // big chunks if (mass >= 100) { count = mass / 100; if (count > 8) count = 8; while(count--) { chunkorigin[0] = origin[0] + crandom() * size[0]; chunkorigin[1] = origin[1] + crandom() * size[1]; chunkorigin[2] = origin[2] + crandom() * size[2]; ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); } } // small chunks count = mass / 25; if (count > 16) count = 16; while(count--) { chunkorigin[0] = origin[0] + crandom() * size[0]; chunkorigin[1] = origin[1] + crandom() * size[1]; chunkorigin[2] = origin[2] + crandom() * size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); } G_UseTargets (self, attacker); if (self->dmg) BecomeExplosion1 (self); else G_FreeEdict (self); } void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) { func_explosive_explode (self, self, other, self->health, vec3_origin); } void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) { self->solid = SOLID_BSP; self->svflags &= ~SVF_NOCLIENT; self->use = NULL; KillBox (self); gi.linkentity (self); } void SP_func_explosive (edict_t *self) { if (deathmatch->value) { // auto-remove for deathmatch G_FreeEdict (self); return; } self->movetype = MOVETYPE_PUSH; gi.modelindex ("models/objects/debris1/tris.md2"); gi.modelindex ("models/objects/debris2/tris.md2"); gi.setmodel (self, self->model); if (self->spawnflags & 1) { self->svflags |= SVF_NOCLIENT; self->solid = SOLID_NOT; self->use = func_explosive_spawn; } else { self->solid = SOLID_BSP; if (self->targetname) self->use = func_explosive_use; } if (self->spawnflags & 2) self->s.effects |= EF_ANIM_ALL; if (self->spawnflags & 4) self->s.effects |= EF_ANIM_ALLFAST; if (self->use != func_explosive_use) { if (!self->health) self->health = 100; self->die = func_explosive_explode; self->takedamage = DAMAGE_YES; } gi.linkentity (self); } /*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) Large exploding box. You can override its mass (100), health (80), and dmg (150). */ qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink); void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { float ratio; vec3_t v; vec3_t move; float yaw, dist; if (other->groundentity == self || !other->client) return; ratio = (float)other->mass / (float)self->mass; VectorSubtract (self->s.origin, other->s.origin, v); yaw = vectoyaw(v); dist = 20 * ratio * FRAMETIME; yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; SV_movestep(self, move, true); } void barrel_explode (edict_t *self) { vec3_t org; float spd; vec3_t save; T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); VectorCopy (self->s.origin, save); VectorMA (self->absmin, 0.5, self->size, self->s.origin); // a few big chunks spd = 1.5 * (float)self->dmg / 200.0; org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); // bottom corners spd = 1.75 * (float)self->dmg / 200.0; VectorCopy (self->absmin, org); ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); VectorCopy (self->absmin, org); org[0] += self->size[0]; ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); VectorCopy (self->absmin, org); org[1] += self->size[1]; ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); VectorCopy (self->absmin, org); org[0] += self->size[0]; org[1] += self->size[1]; ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); // a bunch of little chunks spd = 2 * self->dmg / 200; org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); VectorCopy (save, self->s.origin); if (self->groundentity) BecomeExplosion2 (self); else BecomeExplosion1 (self); } void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { self->takedamage = DAMAGE_NO; self->nextthink = level.time + 2 * FRAMETIME; self->think = barrel_explode; self->activator = attacker; } void SP_misc_explobox (edict_t *self) { if (deathmatch->value) { // auto-remove for deathmatch G_FreeEdict (self); return; } gi.modelindex ("models/objects/debris1/tris.md2"); gi.modelindex ("models/objects/debris2/tris.md2"); gi.modelindex ("models/objects/debris3/tris.md2"); self->solid = SOLID_BBOX; self->movetype = MOVETYPE_FALLFLOAT; self->model = "models/objects/barrels/tris.md2"; self->s.modelindex = gi.modelindex (self->model); VectorSet (self->mins, -16, -16, 0); VectorSet (self->maxs, 16, 16, 40); if (!self->mass) self->mass = 400; if (!self->health) self->health = 10; if (!self->dmg) self->dmg = 150; self->die = barrel_delay; self->takedamage = DAMAGE_YES; self->monsterinfo.aiflags = AI_NOSTEP; self->touch = barrel_touch; self->think = M_droptofloor; self->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (self); } // // miscellaneous specialty items // /*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) */ void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) { /* gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BOSSTPORT); gi.WritePosition (ent->s.origin); gi.multicast (ent->s.origin, MULTICAST_PVS); */ G_FreeEdict (ent); } void misc_blackhole_think (edict_t *self) { if (++self->s.frame < 19) self->nextthink = level.time + FRAMETIME; else { self->s.frame = 0; self->nextthink = level.time + FRAMETIME; } } void SP_misc_blackhole (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_NOT; VectorSet (ent->mins, -64, -64, 0); VectorSet (ent->maxs, 64, 64, 8); ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); ent->s.renderfx = RF_TRANSLUCENT|RF_NOSHADOW; ent->use = misc_blackhole_use; ent->think = misc_blackhole_think; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (ent); } /*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) */ void misc_eastertank_think (edict_t *self) { if (++self->s.frame < 293) self->nextthink = level.time + FRAMETIME; else { self->s.frame = 254; self->nextthink = level.time + FRAMETIME; } } void SP_misc_eastertank (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet (ent->mins, -32, -32, -16); VectorSet (ent->maxs, 32, 32, 32); ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); ent->s.frame = 254; ent->think = misc_eastertank_think; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (ent); } /*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) */ void misc_easterchick_think (edict_t *self) { if (++self->s.frame < 247) self->nextthink = level.time + FRAMETIME; else { self->s.frame = 208; self->nextthink = level.time + FRAMETIME; } } void SP_misc_easterchick (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet (ent->mins, -32, -32, 0); VectorSet (ent->maxs, 32, 32, 32); ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); ent->s.frame = 208; ent->think = misc_easterchick_think; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (ent); } /*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) */ void misc_easterchick2_think (edict_t *self) { if (++self->s.frame < 287) self->nextthink = level.time + FRAMETIME; else { self->s.frame = 248; self->nextthink = level.time + FRAMETIME; } } void SP_misc_easterchick2 (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet (ent->mins, -32, -32, 0); VectorSet (ent->maxs, 32, 32, 32); ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); ent->s.frame = 248; ent->think = misc_easterchick2_think; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity (ent); } /*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) Not really a monster, this is the Tank Commander's decapitated body. There should be a item_commander_head that has this as it's target. */ void commander_body_think (edict_t *self) { if (++self->s.frame < 24) self->nextthink = level.time + FRAMETIME; else self->nextthink = 0; if (self->s.frame == 22) gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); } void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) { self->think = commander_body_think; self->nextthink = level.time + FRAMETIME; gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); } void commander_body_drop (edict_t *self) { self->movetype = MOVETYPE_TOSS; self->s.origin[2] += 2; } void SP_monster_commander_body (edict_t *self) { self->movetype = MOVETYPE_NONE; self->solid = SOLID_BBOX; self->model = "models/monsters/commandr/tris.md2"; self->s.modelindex = gi.modelindex (self->model); VectorSet (self->mins, -32, -32, 0); VectorSet (self->maxs, 32, 32, 48); self->use = commander_body_use; self->takedamage = DAMAGE_YES; self->flags = FL_GODMODE; self->s.renderfx |= RF_FRAMELERP; gi.linkentity (self); gi.soundindex ("tank/thud.wav"); gi.soundindex ("tank/pain.wav"); self->think = commander_body_drop; self->nextthink = level.time + 5 * FRAMETIME; } /*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) The origin is the bottom of the banner. The banner is 128 tall. */ void misc_banner_think (edict_t *ent) { ent->s.frame = (ent->s.frame + 1) % 16; ent->nextthink = level.time + FRAMETIME; } void SP_misc_banner (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_NOT; ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); ent->s.renderfx |= RF_NOSHADOW; ent->s.frame = rand() % 16; gi.linkentity (ent); ent->think = misc_banner_think; ent->nextthink = level.time + FRAMETIME; } /*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED This is the dead player model. Comes in 6 exciting different poses! */ void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; if (self->health > -80) return; 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", damage, GIB_ORGANIC); ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); } void SP_misc_deadsoldier (edict_t *ent) { if (deathmatch->value) { // auto-remove for deathmatch G_FreeEdict (ent); return; } ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); // Defaults to frame 0 if (ent->spawnflags & 2) ent->s.frame = 1; else if (ent->spawnflags & 4) ent->s.frame = 2; else if (ent->spawnflags & 8) ent->s.frame = 3; else if (ent->spawnflags & 16) ent->s.frame = 4; else if (ent->spawnflags & 32) ent->s.frame = 5; else ent->s.frame = 0; VectorSet (ent->mins, -16, -16, 0); VectorSet (ent->maxs, 16, 16, 16); ent->deadflag = DEAD_DEAD; ent->takedamage = DAMAGE_YES; ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; ent->die = misc_deadsoldier_die; ent->monsterinfo.aiflags |= AI_GOOD_GUY; gi.linkentity (ent); } /*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) SMOKE This is the Viper for the flyby bombing. It is trigger_spawned, so you must have something use it for it to show up. There must be a path for it to follow once it is activated. "speed" How fast the Viper should fly */ extern void train_use (edict_t *self, edict_t *other, edict_t *activator); extern void func_train_find (edict_t *self); void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) { self->svflags &= ~SVF_NOCLIENT; self->use = train_use; train_use (self, other, activator); } void SP_misc_viper (edict_t *ent) { if (!ent->target) { gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); G_FreeEdict (ent); return; } if (ent->spawnflags & 1) { ent->s.effects |= EF_ROCKET; ent->spawnflags &= ~1; // turn this off so that it doesn't mess up the trains } if (!ent->speed) ent->speed = 300; ent->movetype = MOVETYPE_PUSH; if (ent->spawnflags & 2) { ent->solid = SOLID_BBOX; } else { ent->solid = SOLID_NOT; } if (ent->model) { ent->s.modelindex = gi.modelindex (ent->model); } else { ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); } if (ent->model2) { ent->s.modelindex2 = gi.modelindex (ent->model2); } if (ent->model3) { ent->s.modelindex3 = gi.modelindex (ent->model3); } if (ent->model4) { ent->s.modelindex4 = gi.modelindex (ent->model4); } if (!(ent->spawnflags & 4)) { VectorSet (ent->mins, -16, -16, 0); VectorSet (ent->maxs, 16, 16, 32); } ent->think = func_train_find; ent->nextthink = level.time + FRAMETIME; ent->use = misc_viper_use; ent->svflags |= SVF_NOCLIENT; ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; gi.linkentity (ent); } /*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) This is a large stationary viper as seen in Paul's intro */ void SP_misc_bigviper (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet (ent->mins, -176, -120, -24); VectorSet (ent->maxs, 176, 120, 72); ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); gi.linkentity (ent); } /*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) "dmg" how much boom should the bomb make? */ void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { G_UseTargets (self, self->activator); self->s.origin[2] = self->absmin[2] + 1; T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); BecomeExplosion2 (self); } void misc_viper_bomb_prethink (edict_t *self) { vec3_t v; float diff; self->groundentity = NULL; diff = self->timestamp - level.time; if (diff < -1.0) diff = -1.0; VectorScale (self->moveinfo.dir, 1.0 + diff, v); v[2] = diff; diff = self->s.angles[2]; vectoangles (v, self->s.angles); self->s.angles[2] = diff + 10; } void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) { edict_t *viper; self->solid = SOLID_BBOX; self->svflags &= ~SVF_NOCLIENT; self->s.effects |= EF_ROCKET; self->use = NULL; self->movetype = MOVETYPE_TOSS; self->prethink = misc_viper_bomb_prethink; self->touch = misc_viper_bomb_touch; self->activator = activator; viper = G_Find (NULL, FOFS(classname), "misc_viper"); VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); self->timestamp = level.time; VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); } void SP_misc_viper_bomb (edict_t *self) { self->movetype = MOVETYPE_NONE; self->solid = SOLID_NOT; VectorSet (self->mins, -8, -8, -8); VectorSet (self->maxs, 8, 8, 8); self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); if (!self->dmg) self->dmg = 1000; self->use = misc_viper_bomb_use; self->svflags |= SVF_NOCLIENT; gi.linkentity (self); } /*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) This is a Storgg ship for the flybys. It is trigger_spawned, so you must have something use it for it to show up. There must be a path for it to follow once it is activated. "speed" How fast it should fly */ extern void train_use (edict_t *self, edict_t *other, edict_t *activator); extern void func_train_find (edict_t *self); void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) { self->svflags &= ~SVF_NOCLIENT; self->use = train_use; train_use (self, other, activator); } void SP_misc_strogg_ship (edict_t *ent) { if (!ent->target) { gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); G_FreeEdict (ent); return; } if (!ent->speed) ent->speed = 300; ent->movetype = MOVETYPE_PUSH; ent->solid = SOLID_NOT; ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); VectorSet (ent->mins, -16, -16, 0); VectorSet (ent->maxs, 16, 16, 32); ent->think = func_train_find; ent->nextthink = level.time + FRAMETIME; ent->use = misc_strogg_ship_use; ent->svflags |= SVF_NOCLIENT; ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; gi.linkentity (ent); } /*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) */ void misc_satellite_dish_think (edict_t *self) { self->s.frame++; if (self->s.frame < 38) self->nextthink = level.time + FRAMETIME; } void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) { self->s.frame = 0; self->think = misc_satellite_dish_think; self->nextthink = level.time + FRAMETIME; } void SP_misc_satellite_dish (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet (ent->mins, -64, -64, 0); VectorSet (ent->maxs, 64, 64, 128); ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); ent->use = misc_satellite_dish_use; gi.linkentity (ent); } /*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) */ void SP_light_mine1 (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); ent->s.renderfx |= RF_NOSHADOW; gi.linkentity (ent); } /*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) */ void SP_light_mine2 (edict_t *ent) { ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); ent->s.renderfx |= RF_NOSHADOW; gi.linkentity (ent); } /*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the target_spawner */ void SP_misc_gib_arm (edict_t *ent) { gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); ent->solid = SOLID_NOT; ent->s.effects |= EF_GIB; ent->takedamage = DAMAGE_YES; ent->die = gib_die; ent->movetype = MOVETYPE_TOSS; ent->svflags |= SVF_MONSTER; ent->deadflag = DEAD_DEAD; ent->avelocity[0] = random()*200; ent->avelocity[1] = random()*200; ent->avelocity[2] = random()*200; ent->think = G_FreeEdict; ent->nextthink = level.time + 30; gi.linkentity (ent); } /*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the target_spawner */ void SP_misc_gib_leg (edict_t *ent) { gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); ent->solid = SOLID_NOT; ent->s.effects |= EF_GIB; ent->takedamage = DAMAGE_YES; ent->die = gib_die; ent->movetype = MOVETYPE_TOSS; ent->svflags |= SVF_MONSTER; ent->deadflag = DEAD_DEAD; ent->avelocity[0] = random()*200; ent->avelocity[1] = random()*200; ent->avelocity[2] = random()*200; ent->think = G_FreeEdict; ent->nextthink = level.time + 30; gi.linkentity (ent); } /*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the target_spawner */ void SP_misc_gib_head (edict_t *ent) { gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); ent->solid = SOLID_NOT; ent->s.effects |= EF_GIB; ent->takedamage = DAMAGE_YES; ent->die = gib_die; ent->movetype = MOVETYPE_TOSS; ent->svflags |= SVF_MONSTER; ent->deadflag = DEAD_DEAD; ent->avelocity[0] = random()*200; ent->avelocity[1] = random()*200; ent->avelocity[2] = random()*200; ent->think = G_FreeEdict; ent->nextthink = level.time + 30; gi.linkentity (ent); } //===================================================== /*QUAKED target_character (0 0 1) ? used with target_string (must be on same "team") "count" is position in the string (starts at 1) */ void SP_target_character (edict_t *self) { self->movetype = MOVETYPE_PUSH; gi.setmodel (self, self->model); self->solid = SOLID_BSP; self->s.frame = 12; gi.linkentity (self); return; } /*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) */ void target_string_use (edict_t *self, edict_t *other, edict_t *activator) { edict_t *e; int n, l; char c; l = (int)strlen(self->message); for (e = self->teammaster; e; e = e->teamchain) { if (!e->count) continue; n = e->count - 1; if (n > l) { e->s.frame = 12; continue; } c = self->message[n]; if (c >= '0' && c <= '9') e->s.frame = c - '0'; else if (c == '-') e->s.frame = 10; else if (c == ':') e->s.frame = 11; else e->s.frame = 12; } } void SP_target_string (edict_t *self) { if (!self->message) self->message = ""; self->use = target_string_use; } /*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE target a target_string with this The default is to be a time of day clock TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" If START_OFF, this entity must be used before it starts "style" 0 "xx" 1 "xx:xx" 2 "xx:xx:xx" */ #define CLOCK_MESSAGE_SIZE 16 // don't let field width of any clock messages change, or it // could cause an overwrite after a game load /*static*/ void func_clock_reset (edict_t *self) { self->activator = NULL; if (self->spawnflags & 1) { self->health = 0; self->wait = self->count; } else if (self->spawnflags & 2) { self->health = self->count; self->wait = 0; } } // Skuller's hack to fix crash on exiting biggun typedef struct zhead_s { struct zhead_s *prev, *next; short magic; short tag; // for group free int size; } zhead_t; /*static*/ void func_clock_format_countdown (edict_t *self) { zhead_t *z = ( zhead_t * )self->message - 1; int size = z->size - sizeof (zhead_t); if (size < CLOCK_MESSAGE_SIZE) { gi.TagFree (self->message); self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); //gi.dprintf ("WARNING: func_clock_format_countdown: self->message is too small: %i\n", size); } // end Skuller's hack if (self->style == 0) { Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); return; } if (self->style == 1) { Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); if (self->message[3] == ' ') self->message[3] = '0'; return; } if (self->style == 2) { Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); if (self->message[3] == ' ') self->message[3] = '0'; if (self->message[6] == ' ') self->message[6] = '0'; return; } } void func_clock_think (edict_t *self) { if (!self->enemy) { self->enemy = G_Find (NULL, FOFS(targetname), self->target); if (!self->enemy) return; } if (self->spawnflags & 1) { func_clock_format_countdown (self); self->health++; } else if (self->spawnflags & 2) { func_clock_format_countdown (self); self->health--; } else { struct tm *ltime; time_t gmtime; time(&gmtime); ltime = localtime(&gmtime); Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); if (self->message[3] == ' ') self->message[3] = '0'; if (self->message[6] == ' ') self->message[6] = '0'; } self->enemy->message = self->message; self->enemy->use (self->enemy, self, self); if (((self->spawnflags & 1) && (self->health > self->wait)) || ((self->spawnflags & 2) && (self->health < self->wait))) { if (self->pathtarget) { char *savetarget; char *savemessage; savetarget = self->target; savemessage = self->message; self->target = self->pathtarget; self->message = NULL; G_UseTargets (self, self->activator); self->target = savetarget; self->message = savemessage; } if (!(self->spawnflags & 8)) return; func_clock_reset (self); if (self->spawnflags & 4) return; } self->nextthink = level.time + 1; } void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) { if (!(self->spawnflags & 8)) self->use = NULL; if (self->activator) return; self->activator = activator; self->think (self); } void SP_func_clock (edict_t *self) { if (!self->target) { gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); G_FreeEdict (self); return; } if ((self->spawnflags & 2) && (!self->count)) { gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); G_FreeEdict (self); return; } if ((self->spawnflags & 1) && (!self->count)) self->count = 60*60;; func_clock_reset (self); self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); self->think = func_clock_think; if (self->spawnflags & 4) self->use = func_clock_use; else self->nextthink = level.time + 1; } //================================================================================= void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { edict_t *dest; int i; if (!other->client) return; dest = G_Find (NULL, FOFS(targetname), self->target); if (!dest) { gi.dprintf ("Couldn't find destination\n"); return; } // unlink to make sure it can't possibly interfere with KillBox gi.unlinkentity (other); VectorCopy (dest->s.origin, other->s.origin); VectorCopy (dest->s.origin, other->s.old_origin); other->s.origin[2] += 10; // clear the velocity and hold them in place briefly VectorClear (other->velocity); other->client->ps.pmove.pm_time = 160>>3; // hold time other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; // draw the teleport splash at source and on the player self->owner->s.event = EV_PLAYER_TELEPORT; other->s.event = EV_PLAYER_TELEPORT; // set angles for (i=0 ; i<3 ; i++) other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); VectorClear (other->s.angles); VectorClear (other->client->ps.viewangles); VectorClear (other->client->v_angle); // kill anything at the destination KillBox (other); gi.linkentity (other); } /*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. */ void SP_misc_teleporter (edict_t *ent) { edict_t *trig; if (!ent->target) { gi.dprintf ("teleporter without a target.\n"); G_FreeEdict (ent); return; } gi.setmodel (ent, "models/objects/dmspot/tris.md2"); ent->s.skinnum = 1; ent->s.effects = EF_TELEPORTER; ent->s.sound = gi.soundindex ("world/amb10.wav"); ent->solid = SOLID_BBOX; VectorSet (ent->mins, -32, -32, -24); VectorSet (ent->maxs, 32, 32, -16); gi.linkentity (ent); trig = G_Spawn (); trig->touch = teleporter_touch; trig->solid = SOLID_TRIGGER; trig->target = ent->target; trig->owner = ent; VectorCopy (ent->s.origin, trig->s.origin); VectorSet (trig->mins, -8, -8, 8); VectorSet (trig->maxs, 8, 8, 24); gi.linkentity (trig); } /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point teleporters at these. */ void SP_misc_teleporter_dest (edict_t *ent) { gi.setmodel (ent, "models/objects/dmspot/tris.md2"); ent->s.skinnum = 0; ent->solid = SOLID_BBOX; // ent->s.effects |= EF_FLIES; VectorSet (ent->mins, -32, -32, -24); VectorSet (ent->maxs, 32, 32, -16); gi.linkentity (ent); }