Quake Style - Quake 3 Tutorials
Freeze Tag - Freeze Tag Part 2
Making noclip work for dead players.

Making Noclip Work for Dead

In order to get the player who's dead and noclipping around working I had to change a few things in the server AND client code. This means if people don't have the client they will move around funny and will thing the mod is not right and complain. Actually that's an unfortunate side effect. To figure out how to get it so you can fly around, I searched the code for stuff like PM_DEAD, TEAM_SPECTATOR, and STAT_HEALTH. We ended up with bg_pmove.c PM_UpdateViewAngles

	if ( ps->pm_type == PM_INTERMISSION ) {
		return;		// no view changes at all
	}

//freeze
	if ( ps->pm_type == PM_NOCLIP ) {
	} else {
//freeze

	if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) {
		return;		// no view changes at all
	}

//freeze
	}
//freeze

	// circularly clamp the angles with deltas
	for (i=0 ; i<3 ; i++) {

cg_view.c CG_DrawActiveFrame

	cg.clientFrame++;

	// update cg.predictedPlayerState
	CG_PredictPlayerState();

//freeze
	if ( cg.snap->ps.pm_type == PM_NOCLIP ) {
		cg.renderingThirdPerson = qfalse;
	} else {
//freeze

	// decide on third person view
	cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0);

//freeze
	}
//freeze

	// build cg.refdef
	inwater = CG_CalcViewValues();

cg_view.c CG_OffsetFirstPersonView

	origin = cg.refdef.vieworg;
	angles = cg.refdefViewAngles;


//freeze
	if ( cg.snap->ps.pm_type == PM_NOCLIP ) {
	} else {
//freeze

	// if dead, fix the angle and don't add any kick
	if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) {
		angles[ROLL] = 40;
		angles[PITCH] = -15;
		angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW];
		origin[2] += cg.predictedPlayerState.viewheight;
		return;
	}

//freeze
	}
//freeze


	// add angles based on weapon kick

The first change let's them look around, the second makes it so they aren't in a third person view, and the third makes sure their view isn't stuck at one angle. Trust me, it took a LONG time to find this exact code and get noclip to work! I first tried making the player a spectator but it started turning into way too many code changes. Let's make the noclip player move like a spectator though! In bg_pmove.c PmoveSingle we have

	if ( pm->ps->pm_type >= PM_DEAD ) {
		pm->cmd.forwardmove = 0;
		pm->cmd.rightmove = 0;
		pm->cmd.upmove = 0;
	}

	if ( pm->ps->pm_type == PM_SPECTATOR ) {
		PM_CheckDuck ();
		PM_FlyMove ();
		PM_DropTimers ();
		return;
	}

	if ( pm->ps->pm_type == PM_NOCLIP ) {

/*freeze
		PM_NoclipMove ();
freeze*/

		if ( pm->ps->pm_time ) {
			PM_DeadMove();
		} else {
			PM_CheckDuck();
			PM_FlyMove();
		}
		pm->ps->weapon = WP_NONE;

//freeze
		PM_DropTimers ();
		return;
	}

	if (pm->ps->pm_type == PM_FREEZE) {
		return;		// no movement at all
	}


We have the player act like a dead guy (no movement) when pm_time is still there (this decrements, unlike normal level.time stuff which check level.time as it increments). This keeps them still for that 4 seconds we talked about (set in player_freeze). Otherwise let's let them do exactly as a spectator would. Remove any weapons they have also. This killed a bug I had where players could float around and still bunch people. In PM_Friction

	// apply flying friction
	if ( pm->ps->powerups[PW_FLIGHT] || pm->ps->pm_type == PM_SPECTATOR ) {
		drop += speed*pm_flightfriction*pml.frametime;
	}

//freeze
	if ( pm->ps->pm_type == PM_NOCLIP ) {
		drop += speed * pm_flightfriction * pml.frametime;
	}
//freeze

	// scale the velocity
	newspeed = speed - drop;

Again we make the noclip experience like that of spectator, otherwise you go sliding off with nothing to slow you down. Now make stuff look right for the client. In cg_draw.c CG_Draw2D

	if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
		CG_DrawSpectator();
		CG_DrawCrosshair();
		CG_DrawCrosshairNames();
	} else {
		// don't draw any status if dead
		if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) {
			CG_DrawStatusBar();
			CG_DrawAmmoWarning();
			CG_DrawCrosshair();
			CG_DrawCrosshairNames();
			CG_DrawWeaponSelect();
			CG_DrawHoldableItem();
			CG_DrawReward();
		}
		if ( cgs.gametype >= GT_TEAM ) {
			CG_DrawTeamInfo();
		}

//freeze
		if ( cg.snap->ps.pm_type == PM_NOCLIP ) {
			CG_DrawSpectator();
			CG_DrawCrosshairNames();
		}
//freeze

	}

Again we copy what spectator was doing. In CG_DrawSpectator


//freeze
	if ( cg.snap->ps.pm_type == PM_NOCLIP ) {
		if ( cg.predictedPlayerState.powerups[ PW_BALL ] ) {
			CG_DrawBigString( 320 - 8 * 8, 24, "freezing", 1.0F );
		}

		return;
	}
//freeze

	CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F);
	if ( cgs.gametype == GT_TOURNAMENT ) {
		CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F);

Show "freezing" text if this player's noclipping and frozen. Note we check for that important PW_BALL setting! Now in CG_ScanForCrosshairEntity, if we're looking at a frozen body, because the body's SOLID, we can show who it belongs to.
 
	CG_Trace( &trace, start, vec3_origin, vec3_origin, end, 
		cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY );
	if ( trace.entityNum >= MAX_CLIENTS ) {

//freeze
		entityState_t	*es;

		es = &cg_entities[ trace.entityNum ].currentState;
// If this body's frozen and the player who "made" this body is frozen then show that player's name
		if ( es->powerups & ( 1 << PW_BALL ) && cg_entities[ es->otherEntityNum ].currentState.powerups & ( 1 << PW_BALL ) ) {
			cg.crosshairClientNum = es->otherEntityNum;
			cg.crosshairClientTime = cg.time;
		}
//freeze

		return;
	}

There's still the problem of not being able to move through doors or transports like the spectator can do. I haven't worked this out yet.



-- Credits:
   Tutorial by Doolittle
   Return to QS Tutorials

-- Important:
   If you do use something from QuakeStyle in your mod, please give us credit.
   Our code is copyrighted, but we give permission to everyone to use it in any way they see fit, as long as we are recognized.