#include "g_local.h" cvar_t *tpp; cvar_t *crossh; void ChasecamTrack (edict_t *ent); /* The ent is the owner of the chasecam */ void ChasecamStart (edict_t *ent) { /* This creates a tempory entity we can manipulate within this * function */ edict_t *chasecam; /* Don't work on a spectator! */ if (ent->client->resp.spectator) return; //Don't turn back on during intermission! if (level.intermissiontime) return; /* Tell everything that looks at the toggle that our chasecam is on * and working */ ent->client->chasetoggle = 1; /* Make our gun model "non-existent" so it's more realistic to the * player using the chasecam */ ent->client->ps.gunindex = 0; chasecam = G_Spawn (); chasecam->owner = ent; chasecam->solid = SOLID_NOT; chasecam->movetype = MOVETYPE_FLYMISSILE; ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; // this turns off Quake2's inclination to predict where the camera is going, // making a much smoother ride ent->svflags |= SVF_NOCLIENT; // this line tells Quake2 not to send the unnecessary info about the camera to other players /* Now, make the angles of the player model, (!NOT THE HUMAN VIEW!) be * copied to the same angle of the chasecam entity */ VectorCopy (ent->s.angles, chasecam->s.angles); /* Clear the size of the entity, so it DOES technically have a size, * but that of '0 0 0'-'0 0 0'. (xyz, xyz). mins = Minimum size, * maxs = Maximum size */ VectorClear (chasecam->mins); VectorClear (chasecam->maxs); /* Make the chasecam's origin (position) be the same as the player * entity's because as the camera starts, it will force itself out * slowly backwards from the player model */ VectorCopy (ent->s.origin, chasecam->s.origin); chasecam->classname = "chasecam"; chasecam->prethink = ChasecamTrack; ent->client->chasecam = chasecam; ent->client->oldplayer = G_Spawn(); MakeFakeCrosshair(ent); } /* ent = chasecam */ void ChasecamRestart (edict_t *ent) { /* Keep thinking this function to check all the time whether the * player is out of the water */ /* If the player is dead, the camera is not wanted... Kill me and stop * the function. (return;) */ if (ent->owner->health <= 0) { G_FreeEdict(ent); return; } /* If the player is still completly underwater, break the routine unless tpp has changed!*/ if (ent->owner->waterlevel && !tpp->value) return; //Put camera back ChasecamStart (ent->owner); //Remove this temporary ent G_FreeEdict (ent); } /* Here, the "ent" is referring to the client, the player that owns the * chasecam, and the "opt" string is telling the function whether to * totally get rid of the camera, or to put it into the background while * it checks if the player is out of the water or not. The "opt" could * have easily been a string, and might have used less memory, but it is * easier to have a string as it is clearer to the reader */ void ChasecamRemove (edict_t *ent, char *opt) { /* Stop the chasecam from moving */ VectorClear (ent->client->chasecam->velocity); /* Make the weapon model of the player appear on screen for 1st * person reality and aiming */ //Don't turn back on during intermission! if (!level.intermissiontime) if(ent->client->akimbo) ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->akimbo_model); else ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model); /* Make our invisible appearance the same model as the display entity * that mimics us while in chasecam mode */ ent->s.modelindex = ent->client->oldplayer->s.modelindex; ent->svflags &= ~SVF_NOCLIENT; DestroyFakeCrosshair (ent); if (!strcmp(opt, "background")) { ent->client->chasetoggle = 0; G_FreeEdict (ent->client->chasecam); if (ent->client->oldplayer->client) free(ent->client->oldplayer->client); G_FreeEdict (ent->client->oldplayer); ent->client->chasecam = G_Spawn (); ent->client->chasecam->owner = ent; ent->client->chasecam->solid = SOLID_NOT; ent->client->chasecam->movetype = MOVETYPE_FLYMISSILE; VectorClear (ent->client->chasecam->mins); VectorClear (ent->client->chasecam->maxs); ent->client->chasecam->classname = "chasecam"; ent->client->chasecam->prethink = ChasecamRestart; // begin checking for emergence from the water } else if (!strcmp(opt, "off")) { if (ent->client->chasetoggle) { if (ent->client->oldplayer->client) free(ent->client->oldplayer->client); G_FreeEdict (ent->client->oldplayer); } ent->client->chasetoggle = 0; G_FreeEdict (ent->client->chasecam); } } /* The "ent" is the chasecam */ void ChasecamTrack (edict_t *ent) { /* Create tempory vectors and trace variables */ trace_t tr; vec3_t spot1, spot2, dir; vec3_t forward, right, up,angles; int distance; int tot; ent->nextthink = level.time + 0.100; /* if our owner is under water, run the remove routine to repeatedly * check for emergment from water */ ent->owner->client->ps.gunindex = 0; // sorts out guns in ttp hud if (ent->owner->waterlevel && !tpp->value) { ChasecamRemove (ent->owner, "background"); return; } /* get the CLIENT's angle, and break it down into direction vectors, * of forward, right, and up. VERY useful */ VectorCopy(ent->owner->client->v_angle,angles); if (angles[PITCH] > 56) angles[PITCH] = 56; AngleVectors (angles, forward, right, up); VectorNormalize(forward); /* go starting at the player's origin, forward, ent->chasedist1 * distance, and save the location in vector spot2 */ VectorMA (ent->owner->s.origin, -ent->chasedist1, forward, spot2); /* make spot2 a bit higher, by adding viewheight to the Z coordinate */ spot2[2] += (ent->owner->viewheight + 16); // jump animation lifts if (!ent->owner->groundentity) spot2[2] += 16; /* make the tr traceline trace from the player model's position, to spot2, * ignoring the player, with a mask. */ tr = gi.trace (ent->owner->s.origin, vec3_origin, vec3_origin, spot2, ent->owner, MASK_SOLID); /* subtract the endpoint from the start point for length and * direction manipulation */ VectorSubtract (tr.endpos, ent->owner->s.origin, spot1); /* in this case, length */ ent->chasedist1 = VectorLength (spot1); /* go, starting from the end of the trace, 2 points forward (client * angles) and save the location in spot2 */ VectorMA (tr.endpos, 2, forward, spot2); /* make spot1 the same for tempory vector modification and make spot1 * a bit higher than spot2 */ VectorCopy (spot2, spot1); spot1[2] += 32; /* another trace from spot2 to spot1, ignoring player, no masks */ tr = gi.trace (spot2, vec3_origin, vec3_origin, spot1, ent->owner, MASK_SOLID); /* if we hit something, copy the trace end to spot2 and lower spot2 */ if (tr.fraction < 1.000) { VectorCopy (tr.endpos, spot2); spot2[2] -= 32; } /* subtract endpos spot2 from startpos the camera origin, saving it to * the dir vector, and normalize dir for a direction from the camera * origin, to the spot2 */ VectorSubtract (spot2, ent->s.origin, dir); distance = VectorLength (dir); VectorNormalize (dir); /* another traceline */ tr = gi.trace (ent->s.origin, vec3_origin, vec3_origin, spot2, ent->owner, MASK_SOLID); /* if we DON'T hit anyting, do some freaky stuff */ if (tr.fraction == 1.000) { /* subtract the endpos camera position, from the startpos, the * player, and save in spot1. Normalize spot1 for a direction, and * make that direction the angles of the chasecam for copying to the * clients view angle which is displayed to the client. (human) */ VectorSubtract (ent->s.origin, ent->owner->s.origin, spot1); VectorNormalize (spot1); VectorCopy (spot1, ent->s.angles); /* calculate the percentages of the distances, and make sure we're * not going too far, or too short, in relation to our panning * speed of the chasecam entity */ tot = (distance * 0.400); /* if we're going too fast, make us top speed */ if (tot > 5.200) { ent->velocity[0] = ((dir[0] * distance) * 5.2); ent->velocity[1] = ((dir[1] * distance) * 5.2); ent->velocity[2] = ((dir[2] * distance) * 5.2); } else { /* if we're NOT going top speed, but we're going faster than * 1, relative to the total, make us as fast as we're going */ if (tot > 1.000) { ent->velocity[0] = ((dir[0] * distance) * tot); ent->velocity[1] = ((dir[1] * distance) * tot); ent->velocity[2] = ((dir[2] * distance) * tot); } else { /* if we're not going faster than one, don't accelerate our * speed at all, make us go slow to our destination */ ent->velocity[0] = (dir[0] * distance); ent->velocity[1] = (dir[1] * distance); ent->velocity[2] = (dir[2] * distance); } } /* subtract endpos,player position, from chasecam position to get * a length to determine whether we should accelerate faster from * the player or not */ VectorSubtract (ent->owner->s.origin, ent->s.origin, spot1); if (VectorLength(spot1) < 20) { ent->velocity[0] *= 2; ent->velocity[1] *= 2; ent->velocity[2] *= 2; } } /* if we DID hit something in the tr.fraction call ages back, then * make the spot2 we created, the position for the chasecamera. */ else VectorCopy (spot2, ent->s.origin); /* add to the distance between the player and the camera */ ent->chasedist1 += 2; /* if we're too far away, give us a maximum distance */ if (ent->chasedist1 > (60.00 + ent->owner->client->zoom)) ent->chasedist1 = (60.00 + ent->owner->client->zoom); /* if we haven't gone anywhere since the last think routine, and we * are greater than 20 points in the distance calculated, add one to * the second chasedistance variable * The "ent->movedir" is a vector which is not used in this entity, so * we can use this a tempory vector belonging to the chasecam, which * can be carried through think routines. */ if (VectorCompare(ent->movedir, ent->s.origin)) { if (distance > 20) ent->chasedist2++; } /* if we've buggered up more than 3 times, there must be some mistake, * so restart the camera so we re-create a chasecam, destroy the old one, * slowly go outwards from the player, and keep thinking this routing in * the new camera entity */ if (ent->chasedist2 > 3) { if (ent->owner->client->oldplayer->client) free(ent->owner->client->oldplayer->client); G_FreeEdict (ent->owner->client->oldplayer); ChasecamStart (ent->owner); G_FreeEdict(ent); return; } /* Copy the position of the chasecam now, and stick it to the movedir * variable, for position checking when we rethink this function */ VectorCopy (ent->s.origin, ent->movedir); /* MUST LINK SINCE WE CHANGED THE ORIGIN! */ gi.linkentity (ent); UpdateFakeCrosshair (ent->owner); } void Cmd_Chasecam_Toggle (edict_t *ent) { if (!(ent->waterlevel && !tpp->value) && !ent->deadflag) { if (ent->client->chasetoggle) { DestroyFakeCrosshair(ent); ChasecamRemove (ent, "off"); } else { ChasecamStart (ent); MakeFakeCrosshair(ent); } } else if ((ent->waterlevel && !tpp->value) && !ent->deadflag) gi.cprintf (ent, PRINT_HIGH, "Camera cannot be modified while in water\n"); } void CheckChasecam_Viewent (edict_t *ent) { gclient_t *cl; vec3_t angles; /* Oldplayer is the fake player that everyone else sees. Create a fake client for the old player just in case its needed */ if (!ent->client->oldplayer->client) { cl = (gclient_t *) malloc(sizeof(gclient_t)); ent->client->oldplayer->client = cl; } /* Copy the angle and model from ourselves to the old player. Even though people can't see us we still have all this stuff */ if ((ent->client->chasetoggle == 1) && (ent->client->oldplayer)) { if (ent->client->use) VectorCopy (ent->client->oldplayer->s.angles, angles); ent->client->oldplayer->s = ent->s; //Copy player related info if (ent->client->use) VectorCopy (angles, ent->client->oldplayer->s.angles); ent->client->oldplayer->client->ps = ent->client->ps; VectorCopy(ent->client->v_angle, ent->client->oldplayer->client->v_angle); VectorCopy(ent->client->resp.cmd_angles, ent->client->oldplayer->client->resp.cmd_angles); gi.linkentity (ent->client->oldplayer); } } int nohud = 0; void Cmd_ToggleHud () { if (deathmatch->value) return; nohud = (1 - nohud); if (nohud) gi.configstring (CS_STATUSBAR, NULL); else gi.configstring (CS_STATUSBAR, matrix_statusbar); } static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) { vec3_t _distance; VectorCopy (distance, _distance); if (client->pers.hand == LEFT_HANDED) _distance[1] *= -1; else if (client->pers.hand == CENTER_HANDED) _distance[1] = 0; G_ProjectSource (point, _distance, forward, right, result); } void UpdateFakeCrosshair (edict_t *ent) { vec3_t offset,spot,forward,right,start; trace_t tr; if (!ent->crosshair) return; VectorSet(offset, 8, 8, ent->viewheight-8); if (ent->client->use) AngleVectors (ent->client->oldplayer->s.angles, forward, right, NULL); else AngleVectors (ent->client->v_angle, forward, right, NULL); VectorNormalize(forward); P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); VectorMA (start, 8192, forward, spot); tr = gi.trace (start, vec3_origin, vec3_origin, spot, ent, MASK_PLAYERSOLID); VectorCopy (tr.endpos, ent->crosshair->s.origin); gi.linkentity (ent->crosshair); } void MakeFakeCrosshair (edict_t *ent) { if (!crossh->value) return; ent->crosshair = G_Spawn (); ent->crosshair->solid = SOLID_NOT; ent->crosshair->movetype = MOVETYPE_NONE; ent->crosshair->s.renderfx = RF_FULLBRIGHT; if (ent->client->resp.team == TEAM_BLUE) ent->crosshair->s.effects = EF_QUAD; else ent->crosshair->s.effects = EF_PENT; gi.setmodel (ent->crosshair, "models/objects/gibs/sm_meat/tris.md2"); UpdateFakeCrosshair (ent); } void DestroyFakeCrosshair (edict_t *ent) { if (!ent->crosshair) return; G_FreeEdict(ent->crosshair); }