/*
   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.

 */

#include "../g_local.h"
#include "ai_local.h"

// explosion fx
static void barrel_explode( edict_t *self )
{
	vec3_t org;
	float spd;
	vec3_t save;

	T_RadiusDamage( self, self->activator, NULL, self->dmg, self->dmg, 0, NULL, self->dmg+40, MOD_BARREL );

	VectorCopy( self->s.origin, save );
	VectorMA( self->r.absmin, 0.5, self->r.size, self->s.origin );

	// a few big chunks
	spd = 1.5 * (float)self->dmg / 200.0;
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris1/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris1/tris.md2", spd, org );

	// bottom corners
	spd = 1.75 * (float)self->dmg / 200.0;
	VectorCopy( self->r.absmin, org );
	ThrowDebris( self, "models/objects/debris3/tris.md2", spd, org );
	VectorCopy( self->r.absmin, org );
	org[0] += self->r.size[0];
	ThrowDebris( self, "models/objects/debris3/tris.md2", spd, org );
	VectorCopy( self->r.absmin, org );
	org[1] += self->r.size[1];
	ThrowDebris( self, "models/objects/debris3/tris.md2", spd, org );
	VectorCopy( self->r.absmin, org );
	org[0] += self->r.size[0];
	org[1] += self->r.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->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );
	org[0] = self->s.origin[0] + crandom() * self->r.size[0];
	org[1] = self->s.origin[1] + crandom() * self->r.size[1];
	org[2] = self->s.origin[2] + crandom() * self->r.size[2];
	ThrowDebris( self, "models/objects/debris2/tris.md2", spd, org );

	VectorCopy( save, self->s.origin );
	if( self->groundentity )
		G_TurnEntityIntoEvent( self, EV_EXPLOSION2, 0 );
	else
		BecomeExplosion1( self );
}

static 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 + 150;
	self->think = barrel_explode;
	self->activator = attacker;
}

//ACE

//==========================================
// M_default_Move
// movement following paths code
//==========================================
static void M_default_Move( edict_t *self, usercmd_t *ucmd )
{
	int current_node_flags = 0;
	int next_node_flags = 0;
	int current_link_type = 0;
	//	int i;

	current_node_flags = nodes[self->ai.current_node].flags;
	next_node_flags = nodes[self->ai.next_node].flags;
	if( AI_PlinkExists( self->ai.current_node, self->ai.next_node ) )
	{
		current_link_type = AI_PlinkMoveType( self->ai.current_node, self->ai.next_node );
		//Com_Printf("%s\n", AI_LinkString( current_link_type ));
	}

	// Falling off ledge
	if( !self->groundentity && !self->is_step && !self->is_swim )
	{
		AI_ChangeAngle( self );
		/*
		   if (current_link_type == LINK_JUMPPAD ) {
		    ucmd->forwardmove = 1;
		   } else if( current_link_type == LINK_JUMP ) {
		    self->velocity[0] = self->ai.move_vector[0] * 280;
		    self->velocity[1] = self->ai.move_vector[1] * 280;
		   } else {
		    self->velocity[0] = self->ai.move_vector[0] * 160;
		    self->velocity[1] = self->ai.move_vector[1] * 160;
		   }
		 */
		return;
	}

	// jumping over (keep fall before this)
	if( current_link_type == LINK_JUMP && self->groundentity )
	{
		trace_t trace;
		vec3_t v1, v2;
		//check floor in front, if there's none... Jump!
		VectorCopy( self->s.origin, v1 );
		VectorCopy( self->ai.move_vector, v2 );
		VectorNormalize( v2 );
		VectorMA( v1, 16, v2, v1 );
		v1[2] += self->r.mins[2];
		G_Trace( &trace, v1, tv( -2, -2, -AI_JUMPABLE_HEIGHT ), tv( 2, 2, 0 ), v1, self, MASK_AISOLID );
		//trace = gi.trace( v1, tv(-2, -2, -AI_JUMPABLE_HEIGHT), tv(2, 2, 0), v1, self, MASK_AISOLID );
		if( !trace.startsolid && trace.fraction == 1.0 )
		{
			//jump!
			ucmd->forwardmove = 1;
			//prevent double jumping on crates
			VectorCopy( self->s.origin, v1 );
			v1[2] += self->r.mins[2];
			G_Trace( &trace, v1, tv( -12, -12, -8 ), tv( 12, 12, 0 ), v1, self, MASK_AISOLID );
			//trace = gi.trace( v1, tv(-12, -12, -8), tv(12, 12, 0), v1, self, MASK_AISOLID );
			if( trace.startsolid )
				ucmd->upmove = 1;
			return;
		}
	}

	// swimming
	if( self->is_swim )
	{
		// We need to be pointed up/down
		AI_ChangeAngle( self );

		//if( !(gi.pointcontents(nodes[self->ai.next_node].origin) & MASK_WATER) ) // Exit water
		if( !( G_PointContents( nodes[self->ai.next_node].origin ) & MASK_WATER ) )  // Exit water
			ucmd->upmove = 1;

		ucmd->forwardmove = 1;
		return;
	}

	// Check to see if stuck, and if so try to free us
	if( VectorCompare( self->s.origin, self->olds.origin ) )
	{
		// Keep a random factor just in case....
		if( random() > 0.1 && AI_SpecialMove( self, ucmd ) )  //jumps, crouches, turns...
			return;

		self->s.angles[YAW] += random() * 180 - 90;

		AI_ChangeAngle( self );

		ucmd->forwardmove = 1;

		return;
	}

	AI_ChangeAngle( self );

	// Otherwise move as fast as we can...
	ucmd->forwardmove = 1;
}

//==========================================
// M_default_CombatMovement
// movement while in state combat
//==========================================
static void M_default_CombatMovement( edict_t *self, usercmd_t *ucmd )
{
	BOT_DMclass_CombatMovement( self, ucmd );
}

//==========================================
// M_default_Wander
// movement when couldn't find a goal
//==========================================
static void M_default_Wander( edict_t *self, usercmd_t *ucmd )
{
	BOT_DMclass_Wander( self, ucmd );
}

//==========================================
// M_default_FindEnemy
//==========================================
static qboolean M_default_FindEnemy( edict_t *self )
{
	BOT_DMclass_FindEnemy( self );
	return ( self->enemy != NULL );
}

//==========================================
// M_default_ChooseWeapon
// Choose weapon based on range & weights
//==========================================
static void M_default_ChooseWeapon( edict_t *self )
{
}

//==========================================
// M_default_pain
//==========================================
static void M_default_pain( edict_t *self, edict_t *other, float kick, int damage )
{
}


//==========================================
// M_default_DeadFrame
// AI passes through here instead of M_default_RunFrame
// when self->deadflag is set up. The default monster
// explodes and frees itself, so it's not happening now.
//==========================================
static void M_default_DeadFrame( edict_t *self )
{
}

//==========================================
// M_default_die
//==========================================
static void M_default_die( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point )
{
	if( AIDevel.debugMode && bot_debugmonster->integer )
		G_PrintMsg( NULL, "monster: Die\n" );


	//throw gibs
	G_Sound( self, CHAN_BODY, trap_SoundIndex( "sounds/misc/udeath" ), 1, ATTN_NORM );
	ThrowSmallPileOfGibs( self, 4, damage );
	//ThrowHead( self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC );
	self->deadflag = DEAD_DEAD;

	if( self->item )
	{
		Drop_Item( self, self->item );
		self->item = NULL;
	}

	AI_EnemyRemoved( self );

	//explode
	barrel_delay( self, inflictor, attacker, damage, point );
}

//==========================================
// M_default_blockedTimeout
//==========================================
static void M_default_blockedTimeout( edict_t *self )
{
	M_default_die( self, self, self, 10000, self->s.origin );
}

//==========================================
// M_default_CheckShot
// Checks if shot is blocked or if too far to shoot
//==========================================
static qboolean M_default_CheckShot( edict_t *self, vec3_t point )
{
	trace_t tr;

	//blocked, don't shoot
	//tr = gi.trace( self->s.origin, vec3_origin, vec3_origin, point, self, MASK_AISOLID);
	G_Trace( &tr, self->s.origin, vec3_origin, vec3_origin, point, self, MASK_AISOLID );
	if( tr.fraction < 0.3 )  //just enough to prevent self damage (by now)
		return qfalse;

	return qtrue;
}

//==========================================
// M_default_FireWeapon
// Fire if needed
//==========================================
static void M_default_FireWeapon( edict_t *self )
{
	float firedelay;
	vec3_t target;
	vec3_t angles;


	if( !self->enemy )
		return;

	// Aim
	VectorCopy( self->enemy->s.origin, target );


	// modify attack angles based on accuracy
	target[0] += ( random()-0.5f ) * 48 * ( 1.0f - self->ai.pers.skillLevel*0.5f );
	target[1] += ( random()-0.5f ) * 48 * ( 1.0f - self->ai.pers.skillLevel*0.5f );

	// Set direction
	VectorSubtract( target, self->s.origin, self->ai.move_vector );
	VecToAngles( self->ai.move_vector, angles );
	VectorCopy( angles, self->s.angles );

	// Set the attack (fix this ramdomness)
	firedelay = ( 100.0f * ( random()-0.25f ) ) + ( 10.0f * self->ai.pers.skillLevel );
	if( firedelay > 0.0f && M_default_CheckShot( self, target ) )
	{
		vec3_t start, forward, right;
		AngleVectors( self->s.angles, forward, right, NULL );
		G_ProjectSource( self->s.origin, tv( 15, 15, 0 ), forward, right, start );

		W_Fire_Gunblade_Bullet( self, start, forward, 4, 4, MOD_UNKNOWN, 0 );
		//monster_fire_bullet(self, start, forward, 4, 4, MZ2_INFANTRY_MACHINEGUN_2);
		//monster_fire_bullet(self, start, forward, 4, 4, rand(), rand(), MZ2_INFANTRY_MACHINEGUN_2);
	}

	if( AIDevel.debugMode && bot_debugmonster->integer )
		G_PrintMsg( NULL, "monster: attacking\n" );
}


//==========================================
// M_default_WeightPlayers
// weight players based on game state
//==========================================
static void M_default_WeightPlayers( edict_t *self )
{
	int i;

	//clear
	memset( self->ai.status.playersWeights, 0, sizeof( self->ai.status.playersWeights ) );

	for( i = 0; i < num_AIEnemies; i++ )
	{
		if( AIEnemies[i] == NULL )
			continue;

		if( AIEnemies[i] == self )
			continue;

		if( !strcmp( AIEnemies[i]->classname, "monster" ) )
		{
			self->ai.status.playersWeights[i] = 0.0f;
			continue;
		}

		//ignore spectators and dead players
		if( AIEnemies[i]->r.svflags & SVF_NOCLIENT || AIEnemies[i]->deadflag )
		{
			self->ai.status.playersWeights[i] = 0.0f;
			continue;
		}

		//every player has some value
		self->ai.status.playersWeights[i] = 0.3f;
	}
}


//==========================================
// M_default_WeightInventory
// monsters can't pick up items, so zero all
//==========================================
static void M_default_WeightInventory( edict_t *self )
{
	//reset with persistant values
	memcpy( self->ai.status.inventoryWeights, self->ai.pers.inventoryWeights, sizeof( self->ai.pers.inventoryWeights ) );
}

//==========================================
// M_default_UpdateStatus
// Set up status for ai. Happens before M_default_RunFrame
//==========================================
static void M_default_UpdateStatus( edict_t *self )
{
	self->enemy = NULL;
	self->movetarget = NULL;

	//set up AI status for the upcoming AI_frame
	M_default_WeightInventory( self );
	M_default_WeightPlayers( self );
}

static qboolean M_default_movestep( edict_t *self, usercmd_t *ucmd )
{
	int move, i;
	float forwardaccel = 0;
	float sideaccel = 0;
	float upaccel = 0;
	vec3_t gravity_vector, gravity_dir = { 0.0f, 0.0f, -1.0f };
	vec3_t forward, right, up;
	vec3_t vaccel;
	vec3_t xzvelocity;
	float speed;
	qboolean GroundEntity = qfalse;
	trace_t	trace;
	vec3_t v1;
	//jalmoveme to defines or something
	float RUNaccel = 90; //26
	float CLASS_MAX_velocity = 90.0f; //32
	float CLASS_TRACTION = 1.0f;
	float CLASS_FRICTION = 3.0f;
	float stopspeed, CLASS_STOPDECCEL = 8;
	//float	CLASS_BOUNCESCALE = 1.0;

	forwardaccel = 0.0f;
	sideaccel = 0.0f;

	//figure out what move do we want to do (forward)
	if( ucmd->forwardmove > 0 )
		forwardaccel = RUNaccel;
	else if( ucmd->forwardmove < 0 )
		forwardaccel = -RUNaccel;

	//figure out what move do we want to do (side)
	if( ucmd->sidemove > 0 )
		sideaccel = RUNaccel;
	else if( ucmd->sidemove < 0 )
		sideaccel = -RUNaccel;

	upaccel = 0.0f;

	VectorCopy( self->s.origin, self->olds.origin );
	//see if we are on floor (I don't trust ent->groundentity right now)
	VectorCopy( self->s.origin, v1 ); v1[2] -= 0.25f; //inside floor
	//trace = gi.trace( self->s.origin, self->r.mins, self->r.maxs, v1, self, MASK_AISOLID );
	G_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, v1, self, MASK_AISOLID );
	if( trace.fraction < 1.0f && trace.plane.normal[2] >= 0.7 ) // We have ground entity
	{
		GroundEntity = qtrue;
	}

	stopspeed = 0.0f;
	if( GroundEntity == qtrue ) //don't accelerate if not at floor
	{
		//intended accel
		forwardaccel *= CLASS_TRACTION;
		sideaccel *= CLASS_TRACTION;

		// if the client is NOT pushing movement keys, it's assumed that the
		// player will stop moving, not only by friction, but by using their legs to stop
		if( !forwardaccel && !sideaccel )
		{
			if( self->velocity[0] || self->velocity[1] || self->velocity[2] )
			{
				stopspeed = CLASS_STOPDECCEL * CLASS_TRACTION;
			}

			//M_Phys_Momentum_AddFriction( CLASS_FRICTION, stopspeed, self->s.origin, self->velocity, FRAMETIME, self->r.mins, self->r.maxs, self->mass, self, MASK_AISOLID );
		}

	}
	else
	{

		//air accel
		forwardaccel *= 0.05f;
		sideaccel *= 0.05f;
	}

	M_Phys_Momentum_AddFriction( CLASS_FRICTION, stopspeed, self->s.origin, self->velocity, FRAMETIME, self->r.mins, self->r.maxs, self, MASK_AISOLID );

	//calculate accel
	VectorClear( vaccel );
	AngleVectors( tv( 0, self->s.angles[YAW], 0 ), forward, right, up );
	M_Phys_Momentum_AddPush( vaccel, forward, forwardaccel, self->mass, FRAMETIME );
	M_Phys_Momentum_AddPush( vaccel, right, sideaccel, self->mass, FRAMETIME );
	//gravity
	if( GroundEntity == qtrue )
	{
		if( self->velocity[2] < 0.0f )
			self->velocity[2] = 0.0f;
	}
	else
	{
		VectorScale( gravity_dir, g_gravity->value * self->gravity * FRAMETIME, gravity_vector );
		VectorAdd( vaccel, gravity_vector, vaccel );
	}

	//add momentum to velocity
	VectorAdd( self->velocity, vaccel, self->velocity );

	// clamp velocity on each axis to g_maxvelocity
	for( i = 0; i < 3; i++ )
	{
		clamp( self->velocity[i], -g_maxvelocity->value, g_maxvelocity->value );
	}

	//class speed limit
	xzvelocity[0] = self->velocity[0];
	xzvelocity[1] = self->velocity[1];
	xzvelocity[2] = 0.0f;
	speed = VectorLengthFast( xzvelocity );
	if( speed > CLASS_MAX_velocity )
	{
		speed = CLASS_MAX_velocity / speed;
		self->velocity[0] *= speed;
		self->velocity[1] *= speed;
	}


	//move
	move = G_BoxSlideMove( self, FRAMETIME, MASK_AISOLID, 1.01f );
	if( move == SLIDEMOVEFLAG_TRAPPED )
	{
		VectorClear( self->velocity );
		GClip_LinkEntity( self );
		return qfalse;
	}

	//relink
	GClip_LinkEntity( self );
	GClip_TouchTriggers( self );
	return qtrue;
}

//==========================================
// M_default_RunFrame
//
// States Machine & call client movement
//==========================================
static void M_default_RunFrame( edict_t *self )
{
	usercmd_t ucmd;
	memset( &ucmd, 0, sizeof( ucmd ) );

	// Look for enemies
	if( M_default_FindEnemy( self ) )
	{
		M_default_ChooseWeapon( self );
		M_default_FireWeapon( self );
		self->ai.state = BOT_STATE_ATTACK;
		self->ai.state_combat_timeout = level.time + 1000;

	}
	else if( self->ai.state == BOT_STATE_ATTACK &&
	         level.time > self->ai.state_combat_timeout )
	{
		//Jalfixme: change to: AI_SetUpStateMove(self);
		self->ai.state = BOT_STATE_MOVE;
	}

	// Execute the move, or wander
	if( self->ai.state == BOT_STATE_MOVE )
		M_default_Move( self, &ucmd );

	else if( self->ai.state == BOT_STATE_ATTACK )
		M_default_CombatMovement( self, &ucmd );

	else if( self->ai.state == BOT_STATE_WANDER )
		M_default_Wander( self, &ucmd );


	//move a step
	if( M_default_movestep( self, &ucmd ) )
		self->ai.blocked_timeout = level.time + 10000;

	self->nextthink = level.time + 1;
}


//==========================================
// M_default_InitPersistant
// Persistant after respawns.
//==========================================
static void M_default_InitPersistant( edict_t *self )
{
	float sv_skill;

	//jalfixme: this bools aren't the right way to go: ai.type might be better?
	self->ai.type = AI_ISMONSTER;

	//set 'class' functions
	self->ai.pers.RunFrame = M_default_RunFrame;
	self->ai.pers.UpdateStatus = M_default_UpdateStatus;
	self->ai.pers.blockedTimeout = M_default_blockedTimeout;
	self->ai.pers.deadFrame = M_default_DeadFrame;

	// set skill based on sv_skilllevel cvar
	sv_skill = trap_Cvar_Value( "sv_skilllevel" ); // 0 = easy, 2 = hard
	sv_skill += ( random() + 0.00001f ); // so we have a float between 0 and 3 meaning the server skill
	self->ai.pers.skillLevel = 1.0f - ( 3.0f / sv_skill ); // the same being a fraction of 1.
	if( self->ai.pers.skillLevel < 0.1f ) self->ai.pers.skillLevel = 0.1f;

	self->yaw_speed -= 20 * ( 1.0f - self->ai.pers.skillLevel );

	//available moveTypes for this class
	self->ai.pers.moveTypesMask = ( LINK_MOVE|LINK_STAIRS|LINK_FALL /*|LINK_JUMP*/ );

	//Persistant Inventory Weights (0 = can not pick)
	memset( self->ai.pers.inventoryWeights, 0, sizeof( self->ai.pers.inventoryWeights ) );
}


//==========================================
// M_default_Spawn
//
//==========================================

static void M_default_Start( edict_t *self )
{
	self->health = 30;
	self->max_health = self->health;
	//self->item = GS_FindItemByClassname("ammo_bullets");

	self->think = AI_Think;
	self->nextthink = level.time + 1;
	self->yaw_speed = AI_DEFAULT_YAW_SPEED;
	M_default_InitPersistant( self );
	AI_ResetNavigation( self );

	//add as bot enemy
	AI_EnemyAdded( self );

	if( AIDevel.debugMode && bot_debugmonster->integer )
		G_PrintMsg( NULL, "monster: Spawn\n" );
}


void M_default_Spawn( void )
{
	edict_t	*ent, *spawnpoint;
	vec3_t spawn_origin, spawn_angles; //spawn at a spawnpoint

	ent = G_Spawn();
	G_SpawnAI( ent ); //jabot092(2)

	//spawn at a spawnpoint
	SelectSpawnPoint( ent, &spawnpoint, spawn_origin, spawn_angles, 256 );
	spawn_origin[2] += 8;

	//-------------------------------------------------

	// clear entity values
	ent->groundentity = NULL;
	ent->takedamage = DAMAGE_AIM;
	ent->movetype = MOVETYPE_PLAYER;
	ent->viewheight = 22;
	ent->r.inuse = qtrue;
	ent->classname = "monster";

	ent->mass = 200;
	ent->r.solid = SOLID_BBOX;
	ent->deadflag = DEAD_NO;
	ent->air_finished = level.time + 12000;
	ent->r.clipmask = MASK_MONSTERSOLID;
	//ent->model = "models/monsters/infantry/tris.md2";//jalfixme
	ent->waterlevel = 0;
	ent->watertype = 0;
	ent->flags &= ~FL_NO_KNOCKBACK;

	ent->pain = M_default_pain;
	ent->die = M_default_die;

	VectorCopy( playerbox_stand_mins, ent->r.mins );
	VectorCopy( playerbox_stand_maxs, ent->r.maxs );
	VectorClear( ent->velocity );

	ent->s.type = ET_PLAYER;
	ent->s.modelindex = trap_ModelIndex( "$models/players/monada" );
	ent->s.skinnum = trap_SkinIndex( "models/players/monada/default" );

	// clear entity state values
	ent->s.effects = 0;
	ent->s.frame = 0;
	ent->s.light = 0;
	VectorCopy( spawn_origin, ent->s.origin );
	VectorCopy( ent->s.origin, ent->s.old_origin );
	VectorCopy( ent->s.origin, ent->olds.origin );

	ent->s.angles[PITCH] = 0;
	ent->s.angles[YAW] = spawn_angles[YAW];
	ent->s.angles[ROLL] = 0;

	if( !KillBox( ent ) )
	{ // could't spawn in?
	}
	GClip_LinkEntity( ent );

	//finish
	M_default_Start( ent );

	G_UseTargets( spawnpoint, ent );
}
