/*
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
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);
}

void barrel_delay( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point )
{
	self->takedamage = DAMAGE_NO;
	self->nextthink = level.timemsec + 2 * game.framemsec;
	self->think = barrel_explode;
	self->activator = attacker;
}

//ACE

void SelectSpawnPoint( edict_t *ent, vec3_t origin, vec3_t angles);
qboolean BOT_DMclass_FindEnemy( edict_t *self);
void BOT_DMclass_CombatMovement( edict_t *self, usercmd_t *ucmd );
void BOT_DMclass_Wander( edict_t *self, usercmd_t *ucmd);
void BOT_DMclass_Move( edict_t *self, usercmd_t *ucmd);

//==========================================
// M_default_Move
// movement following paths code
//==========================================
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 = 100;
		} 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];
		trap_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 = 400;
			//prevent double jumping on crates
			VectorCopy( self->s.origin, v1 );
			v1[2] += self->r.mins[2];
			trap_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 = 400;
			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( !(trap_PointContents(nodes[self->ai.next_node].origin) & MASK_WATER) ) // Exit water
			ucmd->upmove = 400;
		
		ucmd->forwardmove = 300;
		return;
	}

	// Check to see if stuck, and if so try to free us
	if( VectorCompare(self->s.old_origin, self->s.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 = 400;

		return;
	}

	AI_ChangeAngle(self);

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

//==========================================
// M_default_CombatMovement
// movement while in state combat
//==========================================
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
//==========================================
void M_default_Wander( edict_t *self, usercmd_t *ucmd )
{
	BOT_DMclass_Wander( self, ucmd );
}

//==========================================
// M_default_FindEnemy
//==========================================
qboolean M_default_FindEnemy( edict_t *self )
{
	return BOT_DMclass_FindEnemy( self );
}

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

//==========================================
// M_default_pain
//==========================================
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.
//==========================================
void M_default_DeadFrame( edict_t *self )
{
}

//==========================================
// M_default_die
//==========================================
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.wav" ), 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_BloquedTimeout
//==========================================
void M_default_BloquedTimeout( 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
//==========================================
qboolean M_default_CheckShot( edict_t *self, vec3_t point )
{
	trace_t tr;

	//bloqued, don't shoot
	//tr = gi.trace( self->s.origin, vec3_origin, vec3_origin, point, self, MASK_AISOLID);
	trap_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
//==========================================
void W_Fire_Gunblade_Bullet( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int mod);
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 );
		//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
//==========================================
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
//==========================================
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
//==========================================
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);
}



void M_Phys_Momentum_AddPush( vec3_t accel, vec3_t pushdir, float push, float mass, float timestep );
void M_Phys_Momentum_AddFriction( float classfriction, float class_stopspeed, vec3_t origin, vec3_t vel, float timestep, vec3_t mins, vec3_t maxs, edict_t *passent, int solidmask );
int AI_NPCPhysMove( edict_t *ent, float time, int mask, qboolean step);


qboolean M_default_movestep( edict_t *self, usercmd_t *ucmd )
{
	int		move;
	float	forwardaccel = 0;
	float	sideaccel = 0;
	float	upaccel = 0;
	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 > 5 )
		forwardaccel = RUNaccel;
	else if( ucmd->forwardmove < -5 )
		forwardaccel = -RUNaccel;

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

	upaccel = 0.0f;

	VectorCopy( self->s.origin, self->s.old_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 );
	trap_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;
		if( ucmd->upmove > 10 && self->velocity[2] == 0.0f )	//jump
			M_Phys_Momentum_AddPush( vaccel, up, 60.0f, self->mass, 1/*FRAMETIME*/ );
	} else {
		if( self->velocity[2] > -g_maxvelocity->value )
			self->velocity[2] -= self->gravity * g_gravity->value * FRAMETIME;
	}

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

	//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 = AI_NPCPhysMove( self, FRAMETIME, MASK_AISOLID, qtrue );
	if( move == 3 )
	{
		//VectorCopy( oldorigin, self->s.old_origin );
		VectorClear( self->velocity );
		trap_LinkEntity(self);
		return qfalse;
	}
	
	//relink
	trap_LinkEntity(self);
	G_TouchTriggers(self);
	return qtrue;
}


void G_SetPModelFrame (edict_t *ent);
//==========================================
// M_default_RunFrame
//
// States Machine & call client movement
//==========================================
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 + 1.0;
	
	} 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.bloqued_timeout = level.time + 10.0;

	//update model animations
	AI_SetUpAnimMoveFlags( self, &ucmd );
	G_SetPModelFrame( self );

	self->nextthink = level.timemsec + game.framemsec;
}


//==========================================
// M_default_InitPersistant
// Persistant after respawns.
//==========================================
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.bloquedTimeout = M_default_BloquedTimeout;
	self->ai.pers.deadFrame = M_default_DeadFrame;

	// set skill based on sv_skilllevel cvar
	sv_skill = trap_Cvar_VariableValue( "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
// 
//==========================================

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.timemsec + game.framemsec;
	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;
	vec3_t	spawn_origin, spawn_angles;//spawn at a spawnpoint

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

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

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

	// clear entity values
	ent->groundentity = NULL;
	ent->takedamage = DAMAGE_AIM;
	ent->movetype = MOVETYPE_WALK;
	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 + 12;
	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 );

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

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

	//finish
	M_default_Start(ent);
}


