void Sky_DrawFace (int axis)
{
	glpoly_t	*p;
	MathVector3f_t verts[4];
	int			i, j, start;
	float		di,qi,dj,qj;
	MathVector3f_t v_up, v_right, temp, temp2;

	Sky_SetBoxVert(-1.0f,-1.0f,axis,verts[0]);
	Sky_SetBoxVert(-1.0f,1.0f,axis,verts[1]);
	Sky_SetBoxVert(1.0f,1.0f,axis,verts[2]);
	Sky_SetBoxVert(1.0f,-1.0f,axis,verts[3]);

	start = Hunk_LowMark ();
	p = (glpoly_t*)Hunk_Alloc(sizeof(glpoly_t));

	Math_VectorSubtract(verts[2],verts[3], v_up);
	Math_VectorSubtract(verts[2],verts[1], v_right);

	di = Math_Max((int)r_sky_quality.value,1);
	qi = 1.0f/di;
	dj = (axis < 4) ? di*2 : di; // Subdivide vertically more than horizontally on skybox sides
	qj = 1.0f/dj;

	for (i=0; i<di; i++)
	{
		for (j=0; j<dj; j++)
		{
			if (i*qi < skymins[0][axis]/2+0.5 - qi || i*qi > skymaxs[0][axis]/2+0.5 ||
				j*qj < skymins[1][axis]/2+0.5 - qj || j*qj > skymaxs[1][axis]/2+0.5)
				continue;

			//if (i&1 ^ j&1) continue; //checkerboard test
			Math_VectorScale(v_right, qi*i, temp);
			Math_VectorScale(v_up, qj*j, temp2);
			Math_VectorAdd(temp,temp2,temp);
			Math_VectorAdd(verts[0],temp,p->verts[0]);

			Math_VectorScale(v_up, qj, temp);
			Math_VectorAdd(p->verts[0],temp,p->verts[1]);

			Math_VectorScale(v_right, qi, temp);
			Math_VectorAdd(p->verts[1],temp,p->verts[2]);

			Math_VectorAdd(p->verts[0],temp,p->verts[3]);

			Sky_DrawFaceQuad(p);
		}
	}

	Hunk_FreeToLowMark(start);
}
void Crossbow_Projectile(ServerEntity_t *ent)
{
	// [11/2/2012] Revised and fixed ~hogsy
	MathVector3f_t mvDirection;
	ServerEntity_t *eArrow;

	eArrow = Entity_Spawn();
	if(eArrow)
	{
		eArrow->local.eOwner = ent;

		eArrow->v.movetype	= MOVETYPE_FLY;

		eArrow->Physics.iSolid	= SOLID_BBOX;

		Math_MVToVector(Weapon_Aim(ent), mvDirection);
		Math_VectorScale(mvDirection, 2000.0f, eArrow->v.velocity);

		Entity_SetModel(eArrow,"models/arrow.md2");
		Entity_SetSizeVector(eArrow,g_mvOrigin3f,g_mvOrigin3f);

		// [25/6/2012] Simplified ~hogsy
		Math_VectorCopy(ent->v.origin,eArrow->v.origin);
		eArrow->v.origin[2] += 15.0f;

		Math_MVToVector(Math_VectorToAngles(ent->v.velocity),ent->v.angles);

		eArrow->v.TouchFunction = arrow_touch;
	}
}
// [4/7/2012] Renamed to Discus_SpawnProjectile ~hogsy
void Discus_SpawnProjectile(ServerEntity_t *ent,vec3_t org)
{
	ServerEntity_t *eDiscus;
	MathVector3f_t mvDirection;

	eDiscus = Entity_Spawn();

	eDiscus->v.cClassname = "discus";
	eDiscus->v.movetype = MOVETYPE_FLYBOUNCE;
	eDiscus->Physics.iSolid = SOLID_BBOX;
	eDiscus->v.effects = EF_MOTION_ROTATE;
	eDiscus->v.enemy = ent;

	// [4/8/2012] Updated to use owner instead ~hogsy
	eDiscus->local.eOwner = ent;

	eDiscus->local.hit = 0;

	Math_MVToVector(Weapon_Aim(ent), mvDirection);
	Math_VectorScale(mvDirection, 700.0f, eDiscus->v.velocity);

	Math_MVToVector(Math_VectorToAngles(eDiscus->v.velocity), eDiscus->v.angles);

	eDiscus->v.TouchFunction = Discus_ProjectileTouch;

	Entity_SetModel(eDiscus, "models/w_daedalus.md2");
	Entity_SetSizeVector(eDiscus, g_mvOrigin3f, g_mvOrigin3f);
	Entity_SetOrigin(eDiscus, org);

	Sound(ent,CHAN_WEAPON,"weapons/discus/discusthrow.wav",255,ATTN_NORM);
}
void Point_PropTouch(ServerEntity_t *eEntity, ServerEntity_t *eOther)
{
	if(!eOther->v.iHealth)
		return;

	Math_VectorClear(eEntity->v.velocity);
	Math_VectorScale(eOther->v.velocity,0.25f,eEntity->v.velocity);
}
// [4/8/2012] Renamed to SideWinder_SpawnMissle ~hogsy
void SideWinder_SpawnMissle(edict_t *ent,float fSpeed,float ox)
{
	// [26/2/2012] Revised and fixed ~hogsy
	vec3_t	vOrg;
	edict_t *eMissile = Entity_Spawn();

	/*	TODO:
			Spawn a flare at our position too
		~hogsy
	*/

	eMissile->v.cClassname	= "sidewindermissile";
	eMissile->v.movetype	= MOVETYPE_FLYMISSILE;
	eMissile->v.effects		= EF_PARTICLE_SMOKE|EF_DIMLIGHT;

	eMissile->Physics.iSolid	= SOLID_BBOX;
	eMissile->Physics.eIgnore	= ent;

	eMissile->local.speed	= SIDEWINDER_MAXSPEED;
	eMissile->local.eOwner	= ent;
	eMissile->local.count	= 0;
	// [4/8/2012] Change our speed depending on what contents we're within ~hogsy
	eMissile->local.speed	= fSpeed;

	Math_VectorScale(Engine.Aim(ent),eMissile->local.speed,eMissile->v.velocity);

	Math_AngleVectors(ent->v.v_angle,
		// [4/8/2012] Set up our angle vectors ~hogsy
		eMissile->v.vForward,
		eMissile->local.vRight,
		eMissile->local.vUp);
	Math_VectorCopy(ent->v.v_angle,eMissile->v.angles);

	Entity_SetModel(eMissile,"models/sidewinder_missile.md2");

	Math_VectorCopy(ent->v.origin,vOrg);

	vOrg[0] += eMissile->v.vForward[0]*8+eMissile->local.vRight[0]*ox;
	vOrg[1] += eMissile->v.vForward[1]*8+eMissile->local.vRight[1]*ox;
	vOrg[2] += eMissile->v.vForward[2]*24;

	Entity_SetSizeVector(eMissile,mv3Origin,mv3Origin);
	Entity_SetOrigin(eMissile,vOrg);

	// [4/8/2012] Time at which we'll be removed if nothing hit ~hogsy
	eMissile->local.fSpawnDelay = (float)(Server.dTime+8.0);

	eMissile->v.TouchFunction	= SideWinder_MissileExplode;
	eMissile->v.dNextThink		= Server.dTime+0.05;
	eMissile->v.think			= SideWinder_Think;

	// [4/8/2012] Moved so we do this last! ~hogsy
	Engine.LinkEntity(eMissile,false);
}
/*	TODO:
		- Firstly save each iteration of our water, so we can keep track of colour etc.
		- Recalc colour for different dynamic moving lights.
*/
void Warp_DrawWaterPoly(glpoly_t *p)
{
	VideoObject_t	*voWaterPoly;
	vec3_t			vWave,vLightColour;
	float			*v;
	int				i;

	voWaterPoly = malloc(p->numverts*sizeof(VideoObject_t));
	if(!voWaterPoly)
	{
		Sys_Error("Failed to allocate water poly!\n");
		return;
	}

	v = p->verts[0];

	for(i = 0; i < p->numverts; i++,v += VERTEXSIZE)
	{
		voWaterPoly[i].vTextureCoord[0][0]	= WARPCALC(v[3],v[4]);
		voWaterPoly[i].vTextureCoord[0][1]	= WARPCALC(v[4],v[3]);
		voWaterPoly[i].vColour[3] = Math_Clamp(0, r_wateralpha.value, 1.0f);

		Math_VectorCopy(v,vWave);

		// Shitty lit water, use dynamic light points in the future...
		{
			MathVector_t	mvLightColour;

			// Use vWave position BEFORE we move it, otherwise the water will flicker.
			mvLightColour = Light_GetSample(vWave);

			Math_MVToVector(mvLightColour,vLightColour);
			Math_VectorScale(vLightColour,1.0f/200.0f,vLightColour);
			Math_VectorDivide(vLightColour,0.2f,vLightColour);
		}

		Math_VectorCopy(vLightColour,voWaterPoly[i].vColour);

		// [20/1/2013] Added in subtle water bobbing, based on this code http://www.quake-1.com/docs/quakesrc.org/26.html ~hogsy
		vWave[2] =	v[2]+
					2.0f*(float)sin(v[0]*0.025f+cl.time)*(float)sin(v[2]*0.05f+cl.time)+
					2.0f*(float)sin(v[1]*0.025f+cl.time*2.0f)*(float)sin(v[2]*0.05f+cl.time);

		Math_VectorCopy(vWave,voWaterPoly[i].vVertex);
	}

	Video_DrawObject(voWaterPoly,VIDEO_PRIMITIVE_TRIANGLE_FAN,p->numverts);

	free(voWaterPoly);
}
/*	Toss, bounce, and fly movement.  When onground, do nothing.
*/
void Physics_Toss(edict_t *ent)
{
	trace_t	trace;
	vec3_t	move;
	float	backoff;

	// Regular thinking
	if(!Server_RunThink(ent) || (ent->v.flags & FL_ONGROUND))
		return;

	Game->Physics_CheckVelocity(ent);

	// Add gravity
	if(ent->v.movetype != MOVETYPE_FLY
	&& ent->v.movetype != MOVETYPE_FLYMISSILE
	&& ent->v.movetype != MOVETYPE_FLYBOUNCE)
		Game->Physics_SetGravity(ent);

	// Move angles
	Math_VectorMA(ent->v.angles,host_frametime,ent->v.avelocity,ent->v.angles);

	// Move origin
	Math_VectorScale(ent->v.velocity,host_frametime,move);
	trace = SV_PushEntity(ent, move);
	if(trace.fraction == 1.0f || ent->free)
		return;

	if(ent->v.movetype == MOVETYPE_FLYBOUNCE)
		backoff = 2.0f;
	else if (ent->v.movetype == MOVETYPE_BOUNCE)
		backoff = 1.5f;
	else
		backoff = 1.0f;

	ClipVelocity (ent->v.velocity, trace.plane.normal, ent->v.velocity, backoff);

	// Stop if on ground
	if(trace.plane.normal[2] > 0.7 || (ent->v.movetype != MOVETYPE_BOUNCE && ent->v.movetype != MOVETYPE_FLYBOUNCE))
		if(ent->v.velocity[2] < 60.0f)
		{
			ent->v.flags		= ent->v.flags | FL_ONGROUND;
			ent->v.groundentity = trace.ent;

			Math_VectorCopy(mv3Origin,ent->v.velocity);
			Math_VectorCopy(mv3Origin,ent->v.avelocity);
		}

	Game->Physics_CheckWaterTransition(ent);
}
void C4Vizatergo_PrimaryAttack(ServerEntity_t *eOwner)
{
	MathVector3f_t vOrigin;
	MathVector3f_t mvDirection;
	ServerEntity_t *c4ball = Entity_Spawn();

	Sound(eOwner,CHAN_AUTO,"weapons/c4/c4fire.wav",255,ATTN_NORM);
	Sound(eOwner,CHAN_AUTO,"weapons/c4/c4cock.wav",255,ATTN_NORM);

	Weapon_Animate(eOwner,C4Animation_Fire1);
	Weapon_ViewPunch(eOwner, 7, true);

	eOwner->v.iPrimaryAmmo = eOwner->local.iC4Ammo -= 1;

	c4ball->v.cClassname	= "c4ball";
	c4ball->v.movetype		= MOVETYPE_BOUNCE;

	c4ball->local.style = AMMO_C4BOMBS;		// Cleaner way to tell if this can explode or not :V ~hogsy
	c4ball->local.iC4Ammo = 1;				// [11/8/2013] Since style is used for other shit too LAWL ~hogsy
	c4ball->local.eOwner = eOwner;

	// Set the physical properties.
	c4ball->Physics.iSolid = SOLID_BBOX;
	c4ball->Physics.fMass = 0.9f;
	c4ball->Physics.eIgnore = eOwner;
	c4ball->Physics.fGravity = SERVER_GRAVITY;

	Math_MVToVector(Weapon_Aim(eOwner), mvDirection);
	Math_VectorScale(mvDirection, C4VIZATERGO_MAX_RANGE, c4ball->v.velocity);

	c4ball->v.velocity[pY] += 20.0f;

	Math_MVToVector(Math_VectorToAngles(c4ball->v.velocity),c4ball->v.angles);
	Math_VectorCopy(eOwner->v.origin,vOrigin);

	c4ball->v.TouchFunction = C4Vizatergo_C4BallTouch;
	c4ball->v.think = C4Vizatergo_Think;
	c4ball->v.dNextThink = Server.dTime + 2.5;

	Entity_SetModel(c4ball,"models/c4ammo.md2");
	Entity_SetSizeVector(c4ball,g_mvOrigin3f,g_mvOrigin3f);
	Entity_SetOrigin(c4ball,vOrigin);

	if(eOwner->local.attackb_finished > Server.dTime)	// No attack boost...
		eOwner->local.dAttackFinished = Server.dTime+0.6;
	else
		eOwner->local.dAttackFinished = Server.dTime+1.2;
}
void SV_WallFriction (edict_t *ent, trace_t *trace)
{
	vec3_t		forward, right, up;
	float		d, i;
	vec3_t		into, side;

	Math_AngleVectors(ent->v.v_angle, forward, right, up);
	d = Math_DotProduct (trace->plane.normal, forward);

	d += 0.5;
	if (d >= 0)
		return;

	// Cut the tangential velocity
	i = Math_DotProduct (trace->plane.normal, ent->v.velocity);
	Math_VectorScale (trace->plane.normal, i, into);
	Math_VectorSubtract (ent->v.velocity, into, side);

	ent->v.velocity[0] = side[0] * (1 + d);
	ent->v.velocity[1] = side[1] * (1 + d);
}
void Physics_WallFriction(ServerEntity_t *eEntity, trace_t *trLine)
{
	MathVector3f_t forward, right, up;
	float d, i;
	MathVector3f_t into, side;

	Math_AngleVectors(eEntity->v.v_angle, forward, right, up);
	d = Math_DotProduct(trLine->plane.normal, forward);

	d += 0.5;
	if (d >= 0)
		return;

	// Cut the tangential velocity.
	i = Math_DotProduct(trLine->plane.normal, eEntity->v.velocity);
	Math_VectorScale(trLine->plane.normal, i, into);
	Math_VectorSubtract(eEntity->v.velocity, into, side);

	eEntity->v.velocity[0] = side[0] * (1 + d);
	eEntity->v.velocity[1] = side[1] * (1 + d);
}
void Physics_AddFriction(edict_t *eEntity,vec3_t vVelocity,vec3_t vOrigin)
{
	float	*vel;
	float	speed, newspeed, control;
	vec3_t	start, stop;
	float	friction;
	trace_t	trace;

	vel = vVelocity;

	speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
	if(!speed)
		return;

	// If the leading edge is over a dropoff, increase friction
	start[0] = stop[0] = vOrigin[0] + vel[0]/speed*16.0f;
	start[1] = stop[1] = vOrigin[1] + vel[1]/speed*16.0f;
	start[2] = vOrigin[2] + eEntity->v.mins[2];
	stop[2] = start[2] - 34;

	trace = SV_Move(start,mv3Origin,mv3Origin,stop,true,eEntity);
	if(trace.fraction == 1.0f)
		friction = eEntity->Physics.fFriction*sv_edgefriction.value;
	else
		friction = eEntity->Physics.fFriction;

	// Apply friction
	control = speed < cvPhysicsStopSpeed.value ? cvPhysicsStopSpeed.value : speed;
	newspeed = speed - host_frametime*control*friction;

	if(newspeed < 0)
		newspeed = 0;
	newspeed /= speed;

	Math_VectorScale(vel,newspeed,vel);
}
Example #12
0
winding_t *BasePolyForPlane (plane_t *p)
{
	int			i, x;
	vec_t		max, v;
	vec3_t		org, vright, vup;
	winding_t	*w;

	// find the major axis
	max = -BOGUS_RANGE;
	x = -1;
	for (i=0 ; i<3; i++)
	{
		v = fabs(p->normal[i]);
		if (v > max)
		{
			x = i;
			max = v;
		}
	}

	if (x==-1)
		Error("BasePolyForPlane: no axis found");

	Math_VectorCopy(vec3_origin,vup);
	switch (x)
	{
	case 0:
	case 1:
		vup[2] = 1;
		break;
	case 2:
		vup[0] = 1;
		break;
	}


	v = Math_DotProduct(vup,p->normal);
	Math_VectorMA(vup,-v,p->normal,vup);
	Math_VectorNormalize(vup);

	Math_VectorScale(p->normal,p->dist,org);

	Math_CrossProduct(vup,p->normal,vright);

	Math_VectorScale(vup,8192,vup);
	Math_VectorScale(vright,8192,vright);

// project a really big	axis aligned box onto the plane
	w = NewWinding (4);

	Math_VectorSubtract(org,vright,w->points[0]);
	Math_VectorAdd(w->points[0],vup,w->points[0]);

	Math_VectorAdd(org,vright,w->points[1]);
	Math_VectorAdd(w->points[1],vup,w->points[1]);

	Math_VectorAdd(org,vright,w->points[2]);
	Math_VectorSubtract(w->points[2],vup,w->points[2]);

	Math_VectorSubtract(org,vright,w->points[3]);
	Math_VectorSubtract(w->points[3],vup,w->points[3]);

	w->numpoints = 4;

	return w;
}
int SV_FlyMove (edict_t *ent, float time, trace_t *steptrace)
{
	int			bumpcount, numbumps;
	vec3_t		dir;
	float		d;
	int			numplanes;
	vec3_t		planes[MAX_CLIP_PLANES];
	vec3_t		primal_velocity, original_velocity, new_velocity;
	int			i, j;
	trace_t		trace;
	vec3_t		end;
	float		time_left;
	int			blocked;

	numbumps = 4;

	blocked = 0;
	Math_VectorCopy (ent->v.velocity, original_velocity);
	Math_VectorCopy (ent->v.velocity, primal_velocity);
	numplanes = 0;

	time_left = time;

	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
	{
		if (!ent->v.velocity[0] && !ent->v.velocity[1] && !ent->v.velocity[2])
			break;

		for (i=0 ; i<3 ; i++)
			end[i] = ent->v.origin[i] + time_left * ent->v.velocity[i];

		trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, FALSE, ent);
		if(trace.bAllSolid)
		{
			// Entity is trapped in another solid
			Math_VectorCopy(mv3Origin, ent->v.velocity);
			return 3;
		}

		if (trace.fraction > 0)
		{
			// Actually covered some distance
			Math_VectorCopy (trace.endpos, ent->v.origin);
			Math_VectorCopy (ent->v.velocity, original_velocity);
			numplanes = 0;
		}

		if (trace.fraction == 1)
			 break;		// moved the entire distance

		if(!trace.ent)
		{
			Sys_Error ("SV_FlyMove: !trace.ent");
			return 0;
		}

		if (trace.plane.normal[2] > 0.7)
		{
			blocked |= 1;		// floor
			if (trace.ent->Physics.iSolid == SOLID_BSP)
			{
				ent->v.flags		|= FL_ONGROUND;
				ent->v.groundentity = trace.ent;
			}
		}

		if (!trace.plane.normal[2])
		{
			blocked |= 2;		// step
			if (steptrace)
				*steptrace = trace;	// save for player extrafriction
		}

		// Run the impact function
		Physics_Impact(ent,trace.ent);
		if (ent->free)
			break;		// removed by the impact function

		time_left -= time_left * trace.fraction;

		// Cliped to another plane
		if(numplanes >= MAX_CLIP_PLANES)
		{
			// This shouldn't really happen
			Math_VectorCopy(mv3Origin, ent->v.velocity);
			return 3;
		}

		Math_VectorCopy (trace.plane.normal, planes[numplanes]);
		numplanes++;

		// Modify original_velocity so it parallels all of the clip planes
		for(i = 0; i < numplanes; i++)
		{
			ClipVelocity (original_velocity, planes[i], new_velocity, 1);
			for (j=0 ; j<numplanes ; j++)
				if (j != i)
				{
					if(Math_DotProduct(new_velocity,planes[j]) < 0)
						break;	// not ok
				}
			if (j == numplanes)
				break;
		}

		if (i != numplanes)
			Math_VectorCopy (new_velocity, ent->v.velocity);
		else
		{
			// Go along the crease
			if (numplanes != 2)
			{
				Math_VectorCopy(mv3Origin, ent->v.velocity);
				return 7;
			}

			Math_CrossProduct(planes[0], planes[1], dir);
			d = Math_DotProduct(dir, ent->v.velocity);
			Math_VectorScale(dir, d, ent->v.velocity);
		}

		// if original velocity is against the original velocity, stop dead
		// to avoid tiny occilations in sloping corners
		if(Math_DotProduct(ent->v.velocity,primal_velocity) <= 0)
		{
			Math_VectorCopy(mv3Origin,ent->v.velocity);
			return blocked;
		}
	}

	return blocked;
}
/*	Uses both primary burst and mega burst!
*/
void IonRifle_PrimaryAttack(edict_t *eOwner)
{
	switch(eOwner->local.iFireMode)
	{
	case 1:
		Weapon_Animate(eOwner,efIonRifleBlastFire);

		eOwner->v.punchangle[0] -= (float)(((rand()%10)+5));

		eOwner->local.ionblaster_ammo -= 5;

		{
			edict_t	*eIonBall = Entity_Spawn();
			if(eIonBall)
			{
				vec3_t	vOrigin;

				eIonBall->v.cClassname		= "ionball";
				eIonBall->v.movetype		= MOVETYPE_FLY;
				eIonBall->v.effects			= EF_LIGHT_GREEN;
				eIonBall->v.TouchFunction	= IonRifle_IonBallTouch;

				eIonBall->Model.fScale = 2.0f;

				eIonBall->Physics.iSolid	= SOLID_BBOX;
				eIonBall->Physics.eIgnore	= eOwner;

				eIonBall->local.eOwner	= eOwner;
				eIonBall->local.style	= 1;		// [29/1/2014] Preserve our firemode ~hogsy

				Math_VectorCopy(eOwner->v.origin,vOrigin);

				vOrigin[2] += 25.0f;

				Entity_SetModel(eIonBall,"models/ionball.md2");
				Entity_SetSizeVector(eIonBall,mv3Origin,mv3Origin);
				Entity_SetOrigin(eIonBall,vOrigin);

				{
					vec_t	*vAim = Engine.Aim(eOwner);

					Math_VectorScale(vAim,IONRIFLE_MAX_RANGE,eIonBall->v.velocity);
					Math_VectorCopy(vAim,eIonBall->v.angles);
				}

				Engine.LinkEntity(eIonBall,false);
			}
		}

		if(eOwner->local.attackb_finished > Server.dTime)
			eOwner->local.dAttackFinished = Server.dTime+0.10;
		else
			eOwner->local.dAttackFinished = Server.dTime+0.5;
		break;
	default:	// Simple bursts
		Sound(eOwner,CHAN_AUTO,"weapons/laser.wav",255,ATTN_NORM);

		switch(eOwner->local.iBarrelCount)
		{
		case 0:
			Weapon_Animate(eOwner,efIonRiflePulseFireA);
			break;
		case 1:
			Weapon_Animate(eOwner,efIonRiflePulseFireB);
			break;
		case 2:
			Weapon_Animate(eOwner,efIonRiflePulseFireC);
			break;
		case 3:
			Weapon_Animate(eOwner,efIonRiflePulseFireD);
			break;
		case 4:
			Weapon_Animate(eOwner,efIonRiflePulseFireE);
		}

		// [25/9/2013] Punch the view back ~hogsy
		eOwner->v.punchangle[0] -= (float)(((rand()%5)+1)/10.0f);

		eOwner->local.ionblaster_ammo--;

		// [29/1/2014] Let us cycle through each barrel on an individual basis ~hogsy
		eOwner->local.iBarrelCount++;
		if(eOwner->local.iBarrelCount >= 4)
			eOwner->local.iBarrelCount = 0;

		{
			edict_t	*eIonBall = Entity_Spawn();
			if(eIonBall)
			{
				vec3_t	vOrigin;

				eIonBall->v.cClassname		= "ionball";
				eIonBall->v.movetype		= MOVETYPE_FLY;
				eIonBall->v.effects			= EF_LIGHT_GREEN;
				eIonBall->v.TouchFunction	= IonRifle_IonBallTouch;

				eIonBall->Model.fScale	= 0.3f;

				eIonBall->Physics.iSolid	= SOLID_BBOX;
				eIonBall->Physics.eIgnore	= eOwner;

				eIonBall->local.eOwner	= eOwner;
				eIonBall->local.style	= 0;		// [29/1/2014] Preserve our firemode ~hogsy

				Math_VectorCopy(eOwner->v.origin,vOrigin);

				vOrigin[2] += 25.0f;

				Entity_SetModel(eIonBall,"models/ionball.md2");
				Entity_SetSizeVector(eIonBall,mv3Origin,mv3Origin);
				Entity_SetOrigin(eIonBall,vOrigin);

				{
					vec_t	*vAim = Engine.Aim(eOwner);

					Math_VectorScale(vAim,IONRIFLE_MAX_RANGE,eIonBall->v.velocity);
					Math_VectorCopy(vAim,eIonBall->v.avelocity);
				}

				Engine.LinkEntity(eIonBall,false);
			}
		}

		if(eOwner->local.attackb_finished > Server.dTime)
			eOwner->local.dAttackFinished = Server.dTime+0.5;
		else
			eOwner->local.dAttackFinished = Server.dTime+0.3;
	}

	// [17/11/2013] Update ammo counts... ~hogsy
	eOwner->v.iPrimaryAmmo = eOwner->local.ionblaster_ammo;
}