/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package lwjake2.client;

import lwjake2.Defines;
import lwjake2.Globals;
import lwjake2.game.entity_state_t;
import lwjake2.game.player_state_t;
import lwjake2.game.pmove_t;
import lwjake2.qcommon.Com;
import lwjake2.qcommon.FS;
import lwjake2.qcommon.MSG;
import lwjake2.util.Math3D;

/**
 * CL_ents
 */
//	   cl_ents.c -- entity parsing and management
/*
 * =========================================================================
 * 
 * FRAME PARSING
 * 
 * =========================================================================
 */
public class CL_ents {

	static int bitcounts[] = new int[32]; /// just for protocol profiling

	static int bfg_lightramp[] = { 300, 400, 600, 300, 150, 75 };

	/*
	 * ================= CL_ParseEntityBits
	 * 
	 * Returns the entity number and the header bits =================
	 */
	public static int ParseEntityBits(int bits[]) {
		int b, total;
		int i;
		int number;

		total = MSG.ReadByte(Globals.net_message);
		if ((total & Defines.U_MOREBITS1) != 0) {
		    
			b = MSG.ReadByte(Globals.net_message);
			total |= b << 8;
		}
		if ((total & Defines.U_MOREBITS2) != 0) {
		    
			b = MSG.ReadByte(Globals.net_message);
			total |= b << 16;
		}
		if ((total & Defines.U_MOREBITS3) != 0) {
		    
			b = MSG.ReadByte(Globals.net_message);
			total |= b << 24;
		}

		// count the bits for net profiling
		for (i = 0; i < 32; i++)
			if ((total & (1 << i)) != 0)
				bitcounts[i]++;

		if ((total & Defines.U_NUMBER16) != 0)
			number = MSG.ReadShort(Globals.net_message);
		else
			number = MSG.ReadByte(Globals.net_message);

		bits[0] = total;

		return number;
	}

	/*
	 * ================== CL_ParseDelta
	 * 
	 * Can go from either a baseline or a previous packet_entity
	 * ==================
	 */
	public static void ParseDelta(entity_state_t from, entity_state_t to, int number, int bits) {
		// set everything to the state we are delta'ing from
		to.set(from);

		Math3D.VectorCopy(from.origin, to.old_origin);
		to.number = number;

		if ((bits & Defines.U_MODEL) != 0)
			to.modelindex = MSG.ReadByte(Globals.net_message);
		if ((bits & Defines.U_MODEL2) != 0)
			to.modelindex2 = MSG.ReadByte(Globals.net_message);
		if ((bits & Defines.U_MODEL3) != 0)
			to.modelindex3 = MSG.ReadByte(Globals.net_message);
		if ((bits & Defines.U_MODEL4) != 0)
			to.modelindex4 = MSG.ReadByte(Globals.net_message);

		if ((bits & Defines.U_FRAME8) != 0)
			to.frame = MSG.ReadByte(Globals.net_message);
		if ((bits & Defines.U_FRAME16) != 0)
			to.frame = MSG.ReadShort(Globals.net_message);

		if ((bits & Defines.U_SKIN8) != 0 && (bits & Defines.U_SKIN16) != 0) //used
																			 // for
																			 // laser
																			 // colors
			to.skinnum = MSG.ReadLong(Globals.net_message);
		else if ((bits & Defines.U_SKIN8) != 0)
			to.skinnum = MSG.ReadByte(Globals.net_message);
		else if ((bits & Defines.U_SKIN16) != 0)
			to.skinnum = MSG.ReadShort(Globals.net_message);

		if ((bits & (Defines.U_EFFECTS8 | Defines.U_EFFECTS16)) == (Defines.U_EFFECTS8 | Defines.U_EFFECTS16))
			to.effects = MSG.ReadLong(Globals.net_message);
		else if ((bits & Defines.U_EFFECTS8) != 0)
			to.effects = MSG.ReadByte(Globals.net_message);
		else if ((bits & Defines.U_EFFECTS16) != 0)
			to.effects = MSG.ReadShort(Globals.net_message);

		if ((bits & (Defines.U_RENDERFX8 | Defines.U_RENDERFX16)) == (Defines.U_RENDERFX8 | Defines.U_RENDERFX16))
			to.renderfx = MSG.ReadLong(Globals.net_message);
		else if ((bits & Defines.U_RENDERFX8) != 0)
			to.renderfx = MSG.ReadByte(Globals.net_message);
		else if ((bits & Defines.U_RENDERFX16) != 0)
			to.renderfx = MSG.ReadShort(Globals.net_message);

		if ((bits & Defines.U_ORIGIN1) != 0)
			to.origin[0] = MSG.ReadCoord(Globals.net_message);
		if ((bits & Defines.U_ORIGIN2) != 0)
			to.origin[1] = MSG.ReadCoord(Globals.net_message);
		if ((bits & Defines.U_ORIGIN3) != 0)
			to.origin[2] = MSG.ReadCoord(Globals.net_message);

		if ((bits & Defines.U_ANGLE1) != 0)
			to.angles[0] = MSG.ReadAngle(Globals.net_message);
		if ((bits & Defines.U_ANGLE2) != 0)
			to.angles[1] = MSG.ReadAngle(Globals.net_message);
		if ((bits & Defines.U_ANGLE3) != 0)
			to.angles[2] = MSG.ReadAngle(Globals.net_message);

		if ((bits & Defines.U_OLDORIGIN) != 0)
			MSG.ReadPos(Globals.net_message, to.old_origin);

		if ((bits & Defines.U_SOUND) != 0)
			to.sound = MSG.ReadByte(Globals.net_message);

		if ((bits & Defines.U_EVENT) != 0)
			to.event = MSG.ReadByte(Globals.net_message);
		else
			to.event = 0;

		if ((bits & Defines.U_SOLID) != 0)
			to.solid = MSG.ReadShort(Globals.net_message);
	}

	/*
	 * ================== CL_DeltaEntity
	 * 
	 * Parses deltas from the given base and adds the resulting entity to the
	 * current frame ==================
	 */
	public static void DeltaEntity(frame_t frame, int newnum, entity_state_t old, int bits) {
		centity_t ent;
		entity_state_t state;

		ent = Globals.cl_entities[newnum];

		state = Globals.cl_parse_entities[Globals.cl.parse_entities & (Defines.MAX_PARSE_ENTITIES - 1)];
		Globals.cl.parse_entities++;
		frame.num_entities++;

		ParseDelta(old, state, newnum, bits);

		// some data changes will force no lerping
		if (state.modelindex != ent.current.modelindex || state.modelindex2 != ent.current.modelindex2
				|| state.modelindex3 != ent.current.modelindex3 || state.modelindex4 != ent.current.modelindex4
				|| Math.abs(state.origin[0] - ent.current.origin[0]) > 512 || Math.abs(state.origin[1] - ent.current.origin[1]) > 512
				|| Math.abs(state.origin[2] - ent.current.origin[2]) > 512 || state.event == Defines.EV_PLAYER_TELEPORT
				|| state.event == Defines.EV_OTHER_TELEPORT) {
			ent.serverframe = -99;
		}

		if (ent.serverframe != Globals.cl.frame.serverframe - 1) { // wasn't in
																   // last
																   // update, so
																   // initialize
																   // some
																   // things
			ent.trailcount = 1024; // for diminishing rocket / grenade trails
			// duplicate the current state so lerping doesn't hurt anything
			ent.prev.set(state);
			if (state.event == Defines.EV_OTHER_TELEPORT) {
				Math3D.VectorCopy(state.origin, ent.prev.origin);
				Math3D.VectorCopy(state.origin, ent.lerp_origin);
			} else {
				Math3D.VectorCopy(state.old_origin, ent.prev.origin);
				Math3D.VectorCopy(state.old_origin, ent.lerp_origin);
			}
		} else { // shuffle the last state to previous
			// Copy !
			ent.prev.set(ent.current);
		}

		ent.serverframe = Globals.cl.frame.serverframe;
		// Copy !
		ent.current.set(state);
	}

	// call by reference
	private static final int[] iw = {0};
	/*
	 * ================== CL_ParsePacketEntities
	 * 
	 * An svc_packetentities has just been parsed, deal with the rest of the
	 * data stream. ==================
	 */
	public static void ParsePacketEntities(frame_t oldframe, frame_t newframe) {
		int newnum;
		int bits = 0;

		entity_state_t oldstate = null;
		int oldnum;

		newframe.parse_entities = Globals.cl.parse_entities;
		newframe.num_entities = 0;

		// delta from the entities present in oldframe
		int oldindex = 0;
		if (oldframe == null)
			oldnum = 99999;
		else {
			// oldindex == 0. hoz
			//			if (oldindex >= oldframe.num_entities)
			//				oldnum = 99999;
			//			else {
			oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
			oldnum = oldstate.number;
			//			}
		}

		while (true) {
			//int iw[] = { bits };
			iw[0] = bits;
			newnum = ParseEntityBits(iw);
			bits = iw[0];

			if (newnum >= Defines.MAX_EDICTS)
				Com.Error(Defines.ERR_DROP, "CL_ParsePacketEntities: bad number:" + newnum);

			if (Globals.net_message.readcount > Globals.net_message.cursize)
				Com.Error(Defines.ERR_DROP, "CL_ParsePacketEntities: end of message");

			if (0 == newnum)
				break;

			while (oldnum < newnum) { // one or more entities from the old
									  // packet are unchanged
				if (Globals.cl_shownet.value == 3)
					Com.Printf("   unchanged: " + oldnum + "\n");
				DeltaEntity(newframe, oldnum, oldstate, 0);

				oldindex++;

				if (oldindex >= oldframe.num_entities)
					oldnum = 99999;
				else {
					oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
					oldnum = oldstate.number;
				}
			}

			if ((bits & Defines.U_REMOVE) != 0) { // the entity present in
												  // oldframe is not in the
												  // current frame
				if (Globals.cl_shownet.value == 3)
					Com.Printf("   remove: " + newnum + "\n");
				if (oldnum != newnum)
					Com.Printf("U_REMOVE: oldnum != newnum\n");

				oldindex++;

				if (oldindex >= oldframe.num_entities)
					oldnum = 99999;
				else {
					oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
					oldnum = oldstate.number;
				}
				continue;
			}

			if (oldnum == newnum) { // delta from previous state
				if (Globals.cl_shownet.value == 3)
					Com.Printf("   delta: " + newnum + "\n");
				DeltaEntity(newframe, newnum, oldstate, bits);

				oldindex++;

				if (oldindex >= oldframe.num_entities)
					oldnum = 99999;
				else {
					oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
					oldnum = oldstate.number;
				}
				continue;
			}

			if (oldnum > newnum) { // delta from baseline
				if (Globals.cl_shownet.value == 3)
					Com.Printf("   baseline: " + newnum + "\n");
				DeltaEntity(newframe, newnum, Globals.cl_entities[newnum].baseline, bits);
				continue;
			}

		}

		// any remaining entities in the old frame are copied over
		while (oldnum != 99999) { // one or more entities from the old packet
								  // are unchanged
			if (Globals.cl_shownet.value == 3)
				Com.Printf("   unchanged: " + oldnum + "\n");
			DeltaEntity(newframe, oldnum, oldstate, 0);

			oldindex++;

			if (oldindex >= oldframe.num_entities)
				oldnum = 99999;
			else {
				oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
				oldnum = oldstate.number;
			}
		}
	}

	/*
	 * =================== CL_ParsePlayerstate ===================
	 */
	public static void ParsePlayerstate(frame_t oldframe, frame_t newframe) {
		int flags;
		player_state_t state;
		int i;
		int statbits;

		state = newframe.playerstate;

		// clear to old value before delta parsing
		if (oldframe != null)
			state.set(oldframe.playerstate);
		else
			//memset (state, 0, sizeof(*state));
			state.clear();

		flags = MSG.ReadShort(Globals.net_message);

		//
		// parse the pmove_state_t
		//
		if ((flags & Defines.PS_M_TYPE) != 0)
			state.pmove.pm_type = MSG.ReadByte(Globals.net_message);

		if ((flags & Defines.PS_M_ORIGIN) != 0) {
			state.pmove.origin[0] = MSG.ReadShort(Globals.net_message);
			state.pmove.origin[1] = MSG.ReadShort(Globals.net_message);
			state.pmove.origin[2] = MSG.ReadShort(Globals.net_message);
		}

		if ((flags & Defines.PS_M_VELOCITY) != 0) {
			state.pmove.velocity[0] = MSG.ReadShort(Globals.net_message);
			state.pmove.velocity[1] = MSG.ReadShort(Globals.net_message);
			state.pmove.velocity[2] = MSG.ReadShort(Globals.net_message);
		}

		if ((flags & Defines.PS_M_TIME) != 0) {
			state.pmove.pm_time = (byte) MSG.ReadByte(Globals.net_message);
		}

		if ((flags & Defines.PS_M_FLAGS) != 0)
			state.pmove.pm_flags = (byte) MSG.ReadByte(Globals.net_message);

		if ((flags & Defines.PS_M_GRAVITY) != 0)
			state.pmove.gravity = MSG.ReadShort(Globals.net_message);

		if ((flags & Defines.PS_M_DELTA_ANGLES) != 0) {
			state.pmove.delta_angles[0] = MSG.ReadShort(Globals.net_message);
			state.pmove.delta_angles[1] = MSG.ReadShort(Globals.net_message);
			state.pmove.delta_angles[2] = MSG.ReadShort(Globals.net_message);
		}

		if (Globals.cl.attractloop)
			state.pmove.pm_type = Defines.PM_FREEZE; // demo playback

		//
		// parse the rest of the player_state_t
		//
		if ((flags & Defines.PS_VIEWOFFSET) != 0) {
			state.viewoffset[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.viewoffset[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.viewoffset[2] = MSG.ReadChar(Globals.net_message) * 0.25f;
		}

		if ((flags & Defines.PS_VIEWANGLES) != 0) {
			state.viewangles[0] = MSG.ReadAngle16(Globals.net_message);
			state.viewangles[1] = MSG.ReadAngle16(Globals.net_message);
			state.viewangles[2] = MSG.ReadAngle16(Globals.net_message);
		}

		if ((flags & Defines.PS_KICKANGLES) != 0) {

			state.kick_angles[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.kick_angles[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.kick_angles[2] = MSG.ReadChar(Globals.net_message) * 0.25f;

		}

		if ((flags & Defines.PS_WEAPONINDEX) != 0) {
			state.gunindex = MSG.ReadByte(Globals.net_message);
		}

		if ((flags & Defines.PS_WEAPONFRAME) != 0) {
			state.gunframe = MSG.ReadByte(Globals.net_message);
			state.gunoffset[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.gunoffset[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.gunoffset[2] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.gunangles[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.gunangles[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
			state.gunangles[2] = MSG.ReadChar(Globals.net_message) * 0.25f;
		}

		if ((flags & Defines.PS_BLEND) != 0) {
			state.blend[0] = MSG.ReadByte(Globals.net_message) / 255.0f;
			state.blend[1] = MSG.ReadByte(Globals.net_message) / 255.0f;
			state.blend[2] = MSG.ReadByte(Globals.net_message) / 255.0f;
			state.blend[3] = MSG.ReadByte(Globals.net_message) / 255.0f;
		}

		if ((flags & Defines.PS_FOV) != 0)
			state.fov = MSG.ReadByte(Globals.net_message);

		if ((flags & Defines.PS_RDFLAGS) != 0)
			state.rdflags = MSG.ReadByte(Globals.net_message);

		// parse stats
		statbits = MSG.ReadLong(Globals.net_message);
		for (i = 0; i < Defines.MAX_STATS; i++)
			if ((statbits & (1 << i)) != 0)
				state.stats[i] = MSG.ReadShort(Globals.net_message);
	}

	/*
	 * ================== CL_FireEntityEvents
	 * 
	 * ==================
	 */
	public static void FireEntityEvents(frame_t frame) {
		entity_state_t s1;
		int pnum, num;

		for (pnum = 0; pnum < frame.num_entities; pnum++) {
			num = (frame.parse_entities + pnum) & (Defines.MAX_PARSE_ENTITIES - 1);
			s1 = Globals.cl_parse_entities[num];
			if (s1.event != 0)
				CL_fx.EntityEvent(s1);

			// EF_TELEPORTER acts like an event, but is not cleared each frame
			if ((s1.effects & Defines.EF_TELEPORTER) != 0)
				CL_fx.TeleporterParticles(s1);
		}
	}

	/*
	 * ================ CL_ParseFrame ================
	 */
	public static void ParseFrame() {
		int cmd;
		int len;
		frame_t old;

		//memset( cl.frame, 0, sizeof(cl.frame));
		Globals.cl.frame.reset();

		Globals.cl.frame.serverframe = MSG.ReadLong(Globals.net_message);
		Globals.cl.frame.deltaframe = MSG.ReadLong(Globals.net_message);
		Globals.cl.frame.servertime = Globals.cl.frame.serverframe * 100;

		// BIG HACK to let old demos continue to work
		if (Globals.cls.serverProtocol != 26)
			Globals.cl.surpressCount = MSG.ReadByte(Globals.net_message);

		if (Globals.cl_shownet.value == 3)
			Com.Printf("   frame:" + Globals.cl.frame.serverframe + "  delta:" + Globals.cl.frame.deltaframe + "\n");

		// If the frame is delta compressed from data that we
		// no longer have available, we must suck up the rest of
		// the frame, but not use it, then ask for a non-compressed
		// message
		if (Globals.cl.frame.deltaframe <= 0) {
			Globals.cl.frame.valid = true; // uncompressed frame
			old = null;
			Globals.cls.demowaiting = false; // we can start recording now
		} else {
			old = Globals.cl.frames[Globals.cl.frame.deltaframe & Defines.UPDATE_MASK];
			if (!old.valid) { // should never happen
				Com.Printf("Delta from invalid frame (not supposed to happen!).\n");
			}
			if (old.serverframe != Globals.cl.frame.deltaframe) { // The frame
																  // that the
																  // server did
																  // the delta
																  // from
				// is too old, so we can't reconstruct it properly.
				Com.Printf("Delta frame too old.\n");
			} else if (Globals.cl.parse_entities - old.parse_entities > Defines.MAX_PARSE_ENTITIES - 128) {
				Com.Printf("Delta parse_entities too old.\n");
			} else
				Globals.cl.frame.valid = true; // valid delta parse
		}

		// clamp time
		if (Globals.cl.time > Globals.cl.frame.servertime)
			Globals.cl.time = Globals.cl.frame.servertime;
		else if (Globals.cl.time < Globals.cl.frame.servertime - 100)
			Globals.cl.time = Globals.cl.frame.servertime - 100;

		// read areabits
		len = MSG.ReadByte(Globals.net_message);
		MSG.ReadData(Globals.net_message, Globals.cl.frame.areabits, len);

		// read playerinfo
		cmd = MSG.ReadByte(Globals.net_message);
		CL_parse.SHOWNET(CL_parse.svc_strings[cmd]);
		if (cmd != Defines.svc_playerinfo)
			Com.Error(Defines.ERR_DROP, "CL_ParseFrame: not playerinfo");
		ParsePlayerstate(old, Globals.cl.frame);

		// read packet entities
		cmd = MSG.ReadByte(Globals.net_message);
		CL_parse.SHOWNET(CL_parse.svc_strings[cmd]);
		if (cmd != Defines.svc_packetentities)
			Com.Error(Defines.ERR_DROP, "CL_ParseFrame: not packetentities");

		ParsePacketEntities(old, Globals.cl.frame);

		// save the frame off in the backup array for later delta comparisons
		Globals.cl.frames[Globals.cl.frame.serverframe & Defines.UPDATE_MASK].set(Globals.cl.frame);

		if (Globals.cl.frame.valid) {
			// getting a valid frame message ends the connection process
			if (Globals.cls.state != Defines.ca_active) {
				Globals.cls.state = Defines.ca_active;
				Globals.cl.force_refdef = true;

				Globals.cl.predicted_origin[0] = Globals.cl.frame.playerstate.pmove.origin[0] * 0.125f;
				Globals.cl.predicted_origin[1] = Globals.cl.frame.playerstate.pmove.origin[1] * 0.125f;
				Globals.cl.predicted_origin[2] = Globals.cl.frame.playerstate.pmove.origin[2] * 0.125f;

				Math3D.VectorCopy(Globals.cl.frame.playerstate.viewangles, Globals.cl.predicted_angles);
				if (Globals.cls.disable_servercount != Globals.cl.servercount && Globals.cl.refresh_prepped)
					SCR.EndLoadingPlaque(); // get rid of loading plaque
			}
			Globals.cl.sound_prepped = true; // can start mixing ambient sounds

			// fire entity events
			FireEntityEvents(Globals.cl.frame);
			CL_pred.CheckPredictionError();
		}
	}

	/*
	 * ==========================================================================
	 * 
	 * INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS
	 * 
	 * ==========================================================================
	 */

	// stack variable
	private static final entity_t ent = new entity_t();
	/*
	 * =============== 
	 * CL_AddPacketEntities
	 * ===============
	 */
	static void AddPacketEntities(frame_t frame) {
		entity_state_t s1;
		float autorotate;
		int i;
		int pnum;
		centity_t cent;
		int autoanim;
		clientinfo_t ci;
		int effects, renderfx;

		// bonus items rotate at a fixed rate
		autorotate = Math3D.anglemod(Globals.cl.time / 10);

		// brush models can auto animate their frames
		autoanim = 2 * Globals.cl.time / 1000;

		//memset( ent, 0, sizeof(ent));
		ent.clear();

		for (pnum = 0; pnum < frame.num_entities; pnum++) {
			s1 = Globals.cl_parse_entities[(frame.parse_entities + pnum) & (Defines.MAX_PARSE_ENTITIES - 1)];

			cent = Globals.cl_entities[s1.number];

			effects = s1.effects;
			renderfx = s1.renderfx;

			// set frame
			if ((effects & Defines.EF_ANIM01) != 0)
				ent.frame = autoanim & 1;
			else if ((effects & Defines.EF_ANIM23) != 0)
				ent.frame = 2 + (autoanim & 1);
			else if ((effects & Defines.EF_ANIM_ALL) != 0)
				ent.frame = autoanim;
			else if ((effects & Defines.EF_ANIM_ALLFAST) != 0)
				ent.frame = Globals.cl.time / 100;
			else
				ent.frame = s1.frame;

			// quad and pent can do different things on client
			if ((effects & Defines.EF_PENT) != 0) {
				effects &= ~Defines.EF_PENT;
				effects |= Defines.EF_COLOR_SHELL;
				renderfx |= Defines.RF_SHELL_RED;
			}

			if ((effects & Defines.EF_QUAD) != 0) {
				effects &= ~Defines.EF_QUAD;
				effects |= Defines.EF_COLOR_SHELL;
				renderfx |= Defines.RF_SHELL_BLUE;
			}
			//	  ======
			//	   PMM
			if ((effects & Defines.EF_DOUBLE) != 0) {
				effects &= ~Defines.EF_DOUBLE;
				effects |= Defines.EF_COLOR_SHELL;
				renderfx |= Defines.RF_SHELL_DOUBLE;
			}

			if ((effects & Defines.EF_HALF_DAMAGE) != 0) {
				effects &= ~Defines.EF_HALF_DAMAGE;
				effects |= Defines.EF_COLOR_SHELL;
				renderfx |= Defines.RF_SHELL_HALF_DAM;
			}
			//	   pmm
			//	  ======
			ent.oldframe = cent.prev.frame;
			ent.backlerp = 1.0f - Globals.cl.lerpfrac;

			if ((renderfx & (Defines.RF_FRAMELERP | Defines.RF_BEAM)) != 0) {
				// step origin discretely, because the frames
				// do the animation properly
				Math3D.VectorCopy(cent.current.origin, ent.origin);
				Math3D.VectorCopy(cent.current.old_origin, ent.oldorigin);
			} else { // interpolate origin
				for (i = 0; i < 3; i++) {
					ent.origin[i] = ent.oldorigin[i] = cent.prev.origin[i] + Globals.cl.lerpfrac
							* (cent.current.origin[i] - cent.prev.origin[i]);
				}
			}

			// create a new entity

			// tweak the color of beams
			if ((renderfx & Defines.RF_BEAM) != 0) { // the four beam colors are
													 // encoded in 32 bits of
													 // skinnum (hack)
				ent.alpha = 0.30f;
				ent.skinnum = (s1.skinnum >> ((Globals.rnd.nextInt(4)) * 8)) & 0xff;
				Math.random();
				ent.model = null;
			} else {
				// set skin
				if (s1.modelindex == 255) { // use custom player skin
					ent.skinnum = 0;
					ci = Globals.cl.clientinfo[s1.skinnum & 0xff];
					ent.skin = ci.skin;
					ent.model = ci.model;
					if (null == ent.skin || null == ent.model) {
						ent.skin = Globals.cl.baseclientinfo.skin;
						ent.model = Globals.cl.baseclientinfo.model;
					}

					//	  ============
					//	  PGM
					if ((renderfx & Defines.RF_USE_DISGUISE) != 0) {
						if (ent.skin.name.startsWith("players/male")) {
							ent.skin = Globals.re.RegisterSkin("players/male/disguise.pcx");
							ent.model = Globals.re.RegisterModel("players/male/tris.md2");
						} else if (ent.skin.name.startsWith("players/female")) {
							ent.skin = Globals.re.RegisterSkin("players/female/disguise.pcx");
							ent.model = Globals.re.RegisterModel("players/female/tris.md2");
						} else if (ent.skin.name.startsWith("players/cyborg")) {
							ent.skin = Globals.re.RegisterSkin("players/cyborg/disguise.pcx");
							ent.model = Globals.re.RegisterModel("players/cyborg/tris.md2");
						}
					}
					//	  PGM
					//	  ============
				} else {
					ent.skinnum = s1.skinnum;
					ent.skin = null;
					ent.model = Globals.cl.model_draw[s1.modelindex];
				}
			}

			// only used for black hole model right now, FIXME: do better
			if (renderfx == Defines.RF_TRANSLUCENT)
				ent.alpha = 0.70f;

			// render effects (fullbright, translucent, etc)
			if ((effects & Defines.EF_COLOR_SHELL) != 0)
				ent.flags = 0; // renderfx go on color shell entity
			else
				ent.flags = renderfx;

			// calculate angles
			if ((effects & Defines.EF_ROTATE) != 0) { // some bonus items
													  // auto-rotate
				ent.angles[0] = 0;
				ent.angles[1] = autorotate;
				ent.angles[2] = 0;
			}
			// RAFAEL
			else if ((effects & Defines.EF_SPINNINGLIGHTS) != 0) {
				ent.angles[0] = 0;
				ent.angles[1] = Math3D.anglemod(Globals.cl.time / 2) + s1.angles[1];
				ent.angles[2] = 180;
				{
					float[] forward = { 0, 0, 0 };
					float[] start = { 0, 0, 0 };

					Math3D.AngleVectors(ent.angles, forward, null, null);
					Math3D.VectorMA(ent.origin, 64, forward, start);
					V.AddLight(start, 100, 1, 0, 0);
				}
			} else { // interpolate angles
				float a1, a2;

				for (i = 0; i < 3; i++) {
					a1 = cent.current.angles[i];
					a2 = cent.prev.angles[i];
					ent.angles[i] = Math3D.LerpAngle(a2, a1, Globals.cl.lerpfrac);
				}
			}

			if (s1.number == Globals.cl.playernum + 1) {
				ent.flags |= Defines.RF_VIEWERMODEL; // only draw from mirrors
				// FIXME: still pass to refresh

				if ((effects & Defines.EF_FLAG1) != 0)
					V.AddLight(ent.origin, 225, 1.0f, 0.1f, 0.1f);
				else if ((effects & Defines.EF_FLAG2) != 0)
					V.AddLight(ent.origin, 225, 0.1f, 0.1f, 1.0f);
				else if ((effects & Defines.EF_TAGTRAIL) != 0) //PGM
					V.AddLight(ent.origin, 225, 1.0f, 1.0f, 0.0f); //PGM
				else if ((effects & Defines.EF_TRACKERTRAIL) != 0) //PGM
					V.AddLight(ent.origin, 225, -1.0f, -1.0f, -1.0f); //PGM

				continue;
			}

			// if set to invisible, skip
			if (s1.modelindex == 0)
				continue;

			if ((effects & Defines.EF_BFG) != 0) {
				ent.flags |= Defines.RF_TRANSLUCENT;
				ent.alpha = 0.30f;
			}

			// RAFAEL
			if ((effects & Defines.EF_PLASMA) != 0) {
				ent.flags |= Defines.RF_TRANSLUCENT;
				ent.alpha = 0.6f;
			}

			if ((effects & Defines.EF_SPHERETRANS) != 0) {
				ent.flags |= Defines.RF_TRANSLUCENT;
				// PMM - *sigh* yet more EF overloading
				if ((effects & Defines.EF_TRACKERTRAIL) != 0)
					ent.alpha = 0.6f;
				else
					ent.alpha = 0.3f;
			}
			//	  pmm

			// add to refresh list
			V.AddEntity(ent);

			// color shells generate a seperate entity for the main model
			if ((effects & Defines.EF_COLOR_SHELL) != 0) {
				/*
				 * PMM - at this point, all of the shells have been handled if
				 * we're in the rogue pack, set up the custom mixing, otherwise
				 * just keep going if(Developer_searchpath(2) == 2) { all of the
				 * solo colors are fine. we need to catch any of the
				 * combinations that look bad (double & half) and turn them into
				 * the appropriate color, and make double/quad something special
				 *  
				 */
				if ((renderfx & Defines.RF_SHELL_HALF_DAM) != 0) {
					if (FS.Developer_searchpath(2) == 2) {
						// ditch the half damage shell if any of red, blue, or
						// double are on
						if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE)) != 0)
							renderfx &= ~Defines.RF_SHELL_HALF_DAM;
					}
				}

				if ((renderfx & Defines.RF_SHELL_DOUBLE) != 0) {
					if (FS.Developer_searchpath(2) == 2) {
						// lose the yellow shell if we have a red, blue, or
						// green shell
						if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_GREEN)) != 0)
							renderfx &= ~Defines.RF_SHELL_DOUBLE;
						// if we have a red shell, turn it to purple by adding
						// blue
						if ((renderfx & Defines.RF_SHELL_RED) != 0)
							renderfx |= Defines.RF_SHELL_BLUE;
						// if we have a blue shell (and not a red shell), turn
						// it to cyan by adding green
						else if ((renderfx & Defines.RF_SHELL_BLUE) != 0)
							// go to green if it's on already, otherwise do cyan
							// (flash green)
							if ((renderfx & Defines.RF_SHELL_GREEN) != 0)
								renderfx &= ~Defines.RF_SHELL_BLUE;
							else
								renderfx |= Defines.RF_SHELL_GREEN;
					}
				}
				//				}
				// pmm
				ent.flags = renderfx | Defines.RF_TRANSLUCENT;
				ent.alpha = 0.30f;
				V.AddEntity(ent);
			}

			ent.skin = null; // never use a custom skin on others
			ent.skinnum = 0;
			ent.flags = 0;
			ent.alpha = 0;

			// duplicate for linked models
			if (s1.modelindex2 != 0) {
				if (s1.modelindex2 == 255) { // custom weapon
					ci = Globals.cl.clientinfo[s1.skinnum & 0xff];
					i = (s1.skinnum >> 8); // 0 is default weapon model
					if (0 == Globals.cl_vwep.value || i > Defines.MAX_CLIENTWEAPONMODELS - 1)
						i = 0;
					ent.model = ci.weaponmodel[i];
					if (null == ent.model) {
						if (i != 0)
							ent.model = ci.weaponmodel[0];
						if (null == ent.model)
							ent.model = Globals.cl.baseclientinfo.weaponmodel[0];
					}
				} else
					ent.model = Globals.cl.model_draw[s1.modelindex2];

				// PMM - check for the defender sphere shell .. make it
				// translucent
				// replaces the previous version which used the high bit on
				// modelindex2 to determine transparency
				if (Globals.cl.configstrings[Defines.CS_MODELS + (s1.modelindex2)].equalsIgnoreCase("models/items/shell/tris.md2")) {
					ent.alpha = 0.32f;
					ent.flags = Defines.RF_TRANSLUCENT;
				}
				// pmm

				V.AddEntity(ent);

				//PGM - make sure these get reset.
				ent.flags = 0;
				ent.alpha = 0;
				//PGM
			}
			if (s1.modelindex3 != 0) {
				ent.model = Globals.cl.model_draw[s1.modelindex3];
				V.AddEntity(ent);
			}
			if (s1.modelindex4 != 0) {
				ent.model = Globals.cl.model_draw[s1.modelindex4];
				V.AddEntity(ent);
			}

			if ((effects & Defines.EF_POWERSCREEN) != 0) {
				ent.model = CL_tent.cl_mod_powerscreen;
				ent.oldframe = 0;
				ent.frame = 0;
				ent.flags |= (Defines.RF_TRANSLUCENT | Defines.RF_SHELL_GREEN);
				ent.alpha = 0.30f;
				V.AddEntity(ent);
			}

			// add automatic particle trails
			if ((effects & ~Defines.EF_ROTATE) != 0) {
				if ((effects & Defines.EF_ROCKET) != 0) {
					CL_fx.RocketTrail(cent.lerp_origin, ent.origin, cent);
					V.AddLight(ent.origin, 200, 1, 1, 0);
				}
				// PGM - Do not reorder EF_BLASTER and EF_HYPERBLASTER.
				// EF_BLASTER | EF_TRACKER is a special case for EF_BLASTER2...
				// Cheese!
				else if ((effects & Defines.EF_BLASTER) != 0) {
					//					CL_BlasterTrail (cent.lerp_origin, ent.origin);
					//	  PGM
					if ((effects & Defines.EF_TRACKER) != 0) // lame...
															 // problematic?
					{
						CL_newfx.BlasterTrail2(cent.lerp_origin, ent.origin);
						V.AddLight(ent.origin, 200, 0, 1, 0);
					} else {
						CL_fx.BlasterTrail(cent.lerp_origin, ent.origin);
						V.AddLight(ent.origin, 200, 1, 1, 0);
					}
					//	  PGM
				} else if ((effects & Defines.EF_HYPERBLASTER) != 0) {
					if ((effects & Defines.EF_TRACKER) != 0) // PGM overloaded
															 // for blaster2.
						V.AddLight(ent.origin, 200, 0, 1, 0); // PGM
					else
						// PGM
						V.AddLight(ent.origin, 200, 1, 1, 0);
				} else if ((effects & Defines.EF_GIB) != 0) {
					CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
				} else if ((effects & Defines.EF_GRENADE) != 0) {
					CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
				} else if ((effects & Defines.EF_FLIES) != 0) {
					CL_fx.FlyEffect(cent, ent.origin);
				} else if ((effects & Defines.EF_BFG) != 0) {

					if ((effects & Defines.EF_ANIM_ALLFAST) != 0) {
						CL_fx.BfgParticles(ent);
						i = 200;
					} else {
						i = bfg_lightramp[s1.frame];
					}
					V.AddLight(ent.origin, i, 0, 1, 0);
				}
				// RAFAEL
				else if ((effects & Defines.EF_TRAP) != 0) {
					ent.origin[2] += 32;
					CL_fx.TrapParticles(ent);
					i = (Globals.rnd.nextInt(100)) + 100;
					V.AddLight(ent.origin, i, 1, 0.8f, 0.1f);
				} else if ((effects & Defines.EF_FLAG1) != 0) {
					CL_fx.FlagTrail(cent.lerp_origin, ent.origin, 242);
					V.AddLight(ent.origin, 225, 1, 0.1f, 0.1f);
				} else if ((effects & Defines.EF_FLAG2) != 0) {
					CL_fx.FlagTrail(cent.lerp_origin, ent.origin, 115);
					V.AddLight(ent.origin, 225, 0.1f, 0.1f, 1);
				}
				//	  ======
				//	  ROGUE
				else if ((effects & Defines.EF_TAGTRAIL) != 0) {
					CL_newfx.TagTrail(cent.lerp_origin, ent.origin, 220);
					V.AddLight(ent.origin, 225, 1.0f, 1.0f, 0.0f);
				} else if ((effects & Defines.EF_TRACKERTRAIL) != 0) {
					if ((effects & Defines.EF_TRACKER) != 0) {
						float intensity;

						intensity = (float) (50 + (500 * (Math.sin(Globals.cl.time / 500.0) + 1.0)));
						// FIXME - check out this effect in rendition
						if (Globals.vidref_val == Defines.VIDREF_GL)
							V.AddLight(ent.origin, intensity, -1.0f, -1.0f, -1.0f);
						else
							V.AddLight(ent.origin, -1.0f * intensity, 1.0f, 1.0f, 1.0f);
					} else {
						CL_newfx.Tracker_Shell(cent.lerp_origin);
						V.AddLight(ent.origin, 155, -1.0f, -1.0f, -1.0f);
					}
				} else if ((effects & Defines.EF_TRACKER) != 0) {
					CL_newfx.TrackerTrail(cent.lerp_origin, ent.origin, 0);
					// FIXME - check out this effect in rendition
					if (Globals.vidref_val == Defines.VIDREF_GL)
						V.AddLight(ent.origin, 200, -1, -1, -1);
					else
						V.AddLight(ent.origin, -200, 1, 1, 1);
				}
				//	  ROGUE
				//	  ======
				// RAFAEL
				else if ((effects & Defines.EF_GREENGIB) != 0) {
					CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
				}
				// RAFAEL
				else if ((effects & Defines.EF_IONRIPPER) != 0) {
					CL_fx.IonripperTrail(cent.lerp_origin, ent.origin);
					V.AddLight(ent.origin, 100, 1, 0.5f, 0.5f);
				}
				// RAFAEL
				else if ((effects & Defines.EF_BLUEHYPERBLASTER) != 0) {
					V.AddLight(ent.origin, 200, 0, 0, 1);
				}
				// RAFAEL
				else if ((effects & Defines.EF_PLASMA) != 0) {
					if ((effects & Defines.EF_ANIM_ALLFAST) != 0) {
						CL_fx.BlasterTrail(cent.lerp_origin, ent.origin);
					}
					V.AddLight(ent.origin, 130, 1, 0.5f, 0.5f);
				}
			}

			Math3D.VectorCopy(ent.origin, cent.lerp_origin);
		}
	}
	
	// stack variable
	private static final entity_t gun = new entity_t();
	/*
	 * ============== CL_AddViewWeapon ==============
	 */
	static void AddViewWeapon(player_state_t ps, player_state_t ops) {
		int i;

		// allow the gun to be completely removed
		if (0 == Globals.cl_gun.value)
			return;

		// Nope. -flibit
		// don't draw gun if in wide angle view
		// if (ps.fov > 90)
		//	return;

		//memset( gun, 0, sizeof(gun));
		gun.clear();

		if (Globals.gun_model != null)
			gun.model = Globals.gun_model; // development tool
		else
			gun.model = Globals.cl.model_draw[ps.gunindex];

		if (gun.model == null)
			return;

		// set up gun position
		for (i = 0; i < 3; i++) {
			gun.origin[i] = Globals.cl.refdef.vieworg[i] + ops.gunoffset[i] + Globals.cl.lerpfrac
					* (ps.gunoffset[i] - ops.gunoffset[i]);
			gun.angles[i] = Globals.cl.refdef.viewangles[i] + Math3D.LerpAngle(ops.gunangles[i], ps.gunangles[i], Globals.cl.lerpfrac);
		}

		if (Globals.gun_frame != 0) {
			gun.frame = Globals.gun_frame; // development tool
			gun.oldframe = Globals.gun_frame; // development tool
		} else {
			gun.frame = ps.gunframe;
			if (gun.frame == 0)
				gun.oldframe = 0; // just changed weapons, don't lerp from old
			else
				gun.oldframe = ops.gunframe;
		}

		gun.flags = Defines.RF_MINLIGHT | Defines.RF_DEPTHHACK | Defines.RF_WEAPONMODEL;
		gun.backlerp = 1.0f - Globals.cl.lerpfrac;
		Math3D.VectorCopy(gun.origin, gun.oldorigin); // don't lerp at all
		V.AddEntity(gun);
	}

	/*
	 * =============== CL_CalcViewValues
	 * 
	 * Sets cl.refdef view values ===============
	 */
	static void CalcViewValues() {
		int i;
		float lerp, backlerp;
		frame_t oldframe;
		player_state_t ps, ops;

		// find the previous frame to interpolate from
		ps = Globals.cl.frame.playerstate;

		i = (Globals.cl.frame.serverframe - 1) & Defines.UPDATE_MASK;
		oldframe = Globals.cl.frames[i];

		if (oldframe.serverframe != Globals.cl.frame.serverframe - 1 || !oldframe.valid)
			oldframe = Globals.cl.frame; // previous frame was dropped or
										 // involid
		ops = oldframe.playerstate;

		// see if the player entity was teleported this frame
		if (Math.abs(ops.pmove.origin[0] - ps.pmove.origin[0]) > 256 * 8
				|| Math.abs(ops.pmove.origin[1] - ps.pmove.origin[1]) > 256 * 8
				|| Math.abs(ops.pmove.origin[2] - ps.pmove.origin[2]) > 256 * 8)
			ops = ps; // don't interpolate

		lerp = Globals.cl.lerpfrac;

		// calculate the origin
		if ((Globals.cl_predict.value != 0) && 0 == (Globals.cl.frame.playerstate.pmove.pm_flags & pmove_t.PMF_NO_PREDICTION)) { // use
																																 // predicted
																																 // values
			int delta;

			backlerp = 1.0f - lerp;
			for (i = 0; i < 3; i++) {
				Globals.cl.refdef.vieworg[i] = Globals.cl.predicted_origin[i] + ops.viewoffset[i] + Globals.cl.lerpfrac
						* (ps.viewoffset[i] - ops.viewoffset[i]) - backlerp * Globals.cl.prediction_error[i];
			}

			// smooth out stair climbing
			delta = (int) (Globals.cls.realtime - Globals.cl.predicted_step_time);
			if (delta < 100)
				Globals.cl.refdef.vieworg[2] -= Globals.cl.predicted_step * (100 - delta) * 0.01;
		} else { // just use interpolated values
			for (i = 0; i < 3; i++)
				Globals.cl.refdef.vieworg[i] = ops.pmove.origin[i] * 0.125f + ops.viewoffset[i] + lerp
						* (ps.pmove.origin[i] * 0.125f + ps.viewoffset[i] - (ops.pmove.origin[i] * 0.125f + ops.viewoffset[i]));
		}

		// if not running a demo or on a locked frame, add the local angle
		// movement
		if (Globals.cl.frame.playerstate.pmove.pm_type < Defines.PM_DEAD) { // use
																			// predicted
																			// values
			for (i = 0; i < 3; i++)
				Globals.cl.refdef.viewangles[i] = Globals.cl.predicted_angles[i];
		} else { // just use interpolated values
			for (i = 0; i < 3; i++)
				Globals.cl.refdef.viewangles[i] = Math3D.LerpAngle(ops.viewangles[i], ps.viewangles[i], lerp);
		}

		for (i = 0; i < 3; i++)
			Globals.cl.refdef.viewangles[i] += Math3D.LerpAngle(ops.kick_angles[i], ps.kick_angles[i], lerp);

		Math3D.AngleVectors(Globals.cl.refdef.viewangles, Globals.cl.v_forward, Globals.cl.v_right, Globals.cl.v_up);

		// interpolate field of view
		Globals.cl.refdef.fov_x = ops.fov + lerp * (ps.fov - ops.fov);

		// don't interpolate blend color
		for (i = 0; i < 4; i++)
			Globals.cl.refdef.blend[i] = ps.blend[i];

		// add the weapon
		AddViewWeapon(ps, ops);
	}

	/*
	 * =============== CL_AddEntities
	 * 
	 * Emits all entities, particles, and lights to the refresh ===============
	 */
	static void AddEntities() {
		if (Globals.cls.state != Defines.ca_active)
			return;

		if (Globals.cl.time > Globals.cl.frame.servertime) {
			if (Globals.cl_showclamp.value != 0)
				Com.Printf("high clamp " + (Globals.cl.time - Globals.cl.frame.servertime) + "\n");
			Globals.cl.time = Globals.cl.frame.servertime;
			Globals.cl.lerpfrac = 1.0f;
		} else if (Globals.cl.time < Globals.cl.frame.servertime - 100) {
			if (Globals.cl_showclamp.value != 0)
				Com.Printf("low clamp " + (Globals.cl.frame.servertime - 100 - Globals.cl.time) + "\n");
			Globals.cl.time = Globals.cl.frame.servertime - 100;
			Globals.cl.lerpfrac = 0;
		} else
			Globals.cl.lerpfrac = 1.0f - (Globals.cl.frame.servertime - Globals.cl.time) * 0.01f;

		if (Globals.cl_timedemo.value != 0)
			Globals.cl.lerpfrac = 1.0f;

		/*
		 * is ok.. CL_AddPacketEntities (cl.frame); CL_AddTEnts ();
		 * CL_AddParticles (); CL_AddDLights (); CL_AddLightStyles ();
		 */

		CalcViewValues();
		// PMM - moved this here so the heat beam has the right values for the
		// vieworg, and can lock the beam to the gun
		AddPacketEntities(Globals.cl.frame);

		CL_tent.AddTEnts();
		CL_fx.AddParticles();
		CL_fx.AddDLights();
		CL_fx.AddLightStyles();
	}

	/*
	 * =============== CL_GetEntitySoundOrigin
	 * 
	 * Called to get the sound spatialization origin ===============
	 */
	public static void GetEntitySoundOrigin(int ent, float[] org) {
		centity_t old;

		if (ent < 0 || ent >= Defines.MAX_EDICTS)
			Com.Error(Defines.ERR_DROP, "CL_GetEntitySoundOrigin: bad ent");
		old = Globals.cl_entities[ent];
		Math3D.VectorCopy(old.lerp_origin, org);

		// FIXME: bmodel issues...
	}
}