
 #include <amxmodx>
 #include <cstrike>
 #include <fakemeta>

 #define VERSION "0.12"

 // kills for mp_hostagepenalty
 #define OFFSET_HOSTAGE_KILLS	217

 #define PLAYER_MAX_SAFE_FALL_SPEED	500.0 // exact (OLD: 488.5)
 #define PLAYER_FATAL_FALL_SPEED	960.0 // not exact (OLD: 988.5)
 #define DAMAGE_FOR_FALL_SPEED		100.0 / (PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED)

 // animation defines
 #define ANIM_KNEEL_HOS	126
 #define ANIM_KNEEL_SCI	127
 #define FRAME_RATE	3.0

 // storage containers
 #define WAS_DUCKING	pev_iuser1
 #define ON_LADDER	pev_iuser2
 #define ANIMATING	pev_iuser3
 #define LAST_TOUCHER	pev_iuser4
 #define ANIM_FRAME	pev_fuser1
 #define FALL_VELOCITY	pev_fuser2
 #define CUSTOM_WALL	pev_iuser1

 // ladder defines
 #define MAX_LADDERS	16
 #define DIR_NONE	-1
 #define DIR_EAST	0
 #define DIR_NORTH	1
 #define DIR_WEST	2
 #define DIR_SOUTH	3

 // ladder tracking
 new ladder_index[MAX_LADDERS];
 new ladder_direction[MAX_LADDERS];
 new Float:ladder_mins[MAX_LADDERS][3];
 new Float:ladder_maxs[MAX_LADDERS][3];
 new Float:ladder_origin[MAX_LADDERS][3];

 // cvars
 new cvClimbSpeed, cvCanDuck, cvDoorDist, cvFallDmg,
 cvDirLadders, cvFallPenalty, mpHostagePenalty;

 // often used variables
 new gmsgTextMsg, ladderNum, classname[10], trigger_hurt;

 // you know, you know...
 public plugin_init()
 {
	register_plugin("Prometheus",VERSION,"Avalanche");
	register_cvar("prometheus",VERSION,FCVAR_SERVER);
	set_cvar_string("prometheus",VERSION);

	cvCanDuck = register_cvar("hostage_can_duck","1");
	cvClimbSpeed = register_cvar("hostage_climb_speed","256.0");
	cvDirLadders = register_cvar("hostage_directional_ladders","1");
	cvFallDmg = register_cvar("hostage_fall_damage","1.0");
	cvFallPenalty = register_cvar("hostage_fall_penalty","1");
	cvDoorDist = register_cvar("hostage_use_door_distance","64.0");
	mpHostagePenalty = get_cvar_pointer("mp_hostagepenalty");

	new mod[16];
	get_modname(mod,15);

	// no condition zero
	if(!equal(mod,"cstrike"))
	{
		log_amx("Prometheus cannot run in %s!",mod);
		pause("ad");
		return;
	}

	// no hostages, nothing else to do
	if(!engfunc(EngFunc_FindEntityByString,0,"classname","hostage_entity"))
		return;

	gmsgTextMsg = get_user_msgid("TextMsg");
	trigger_hurt = engfunc(EngFunc_AllocString,"trigger_hurt");

	register_forward(FM_Think,"fw_think",0);
	register_forward(FM_Touch,"fw_touch",1);
	register_forward(FM_AddToFullPack,"fw_addtofullpack",1);

	// grab all ladder data at the start of map
	new ent, i;
	while((ent = engfunc(EngFunc_FindEntityByString,ent,"classname","func_ladder")) != 0)
	{
		ladder_index[ladderNum] = ent;
		pev(ent,pev_absmin,ladder_mins[ladderNum]);
		pev(ent,pev_absmax,ladder_maxs[ladderNum]);

		// calculate center based on abs boxes
		for(i=0;i<3;i++)
			ladder_origin[ladderNum][i] = (ladder_mins[ladderNum][i] + ladder_maxs[ladderNum][i]) * 0.5;

		// get direction that this ladder is facing
		ladder_direction[ladderNum] = get_ladder_direction(ladderNum);

		// increment and check for end
		if(++ladderNum >= MAX_LADDERS) break;
	}
 }

 // figure out which way a ladder faces based on which direction
 // has the least space between the func_ladder and a wall (which is
 // probably the ladder representation).
 public get_ladder_direction(ladder)
 {
	new illus, wall, Float:absmin[3], Float:absmax[3];
	new wall_classname = engfunc(EngFunc_AllocString,"func_wall");

	// create a solid func_wall over all func_illusionary entities,
	// so that essentially, func_illusionary entities are counted
	// in our traces below. this is because most of the visible ladders
	// that you see are a func_illusionary, and we need them included in
	// our calculations.
	while((illus = engfunc(EngFunc_FindEntityByString,illus,"classname","func_illusionary")) != 0)
	{
		wall = engfunc(EngFunc_CreateNamedEntity,wall_classname);

		// make me solid, so tracelines hit me
		set_pev(wall,pev_solid,SOLID_BSP);
		set_pev(wall,pev_movetype,MOVETYPE_PUSH);

		// make me the same size and position as func_illusionary
		set_pev(wall,pev_modelindex,pev(illus,pev_modelindex));
		pev(illus,pev_absmin,absmin);
		pev(illus,pev_absmax,absmax);
		engfunc(EngFunc_SetSize,wall,absmin,absmax);

		// remember for later
		set_pev(wall,CUSTOM_WALL,1);
	}

	new tr, Float:flFraction, Float:start[3], Float:goal[3], Float:end[3], Float:distances[4];

	////////////////
	// TRACE EAST (only comments are for this direction)
	////////////////
	start = ladder_origin[ladder];
	start[0] = ladder_mins[ladder][0]; // start at edge of ladder, ie go west->east, not center->east

	goal = ladder_origin[ladder];
	goal[0] += (ladder_maxs[ladder][0]-ladder_mins[ladder][0]) * 2.0; // check up to twice the distance of the length of this side

	// do our trace and get results
	engfunc(EngFunc_TraceLine,start,goal,0,ladder,tr);
	get_tr2(tr,TR_flFraction,flFraction);
	get_tr2(tr,TR_vecEndPos,end);

	// we didn't hit anything, or we started in a wall,
	// set it so far away that it will be ignored later
	if(flFraction == 1.0 || get_tr2(tr,TR_StartSolid))
		distances[DIR_EAST] = 4096.0;

	// otherwise, calculate distance
	else distances[DIR_EAST] = vector_distance(ladder_origin[ladder],end);

	////////////////
	// TRACE NORTH
	////////////////
	start = ladder_origin[ladder];
	start[1] = ladder_mins[ladder][1];

	goal = ladder_origin[ladder];
	goal[1] += (ladder_maxs[ladder][1]-ladder_mins[ladder][1]) * 2.0;

	engfunc(EngFunc_TraceLine,start,goal,0,ladder,tr);
	get_tr2(tr,TR_flFraction,flFraction);
	get_tr2(tr,TR_vecEndPos,end);

	if(flFraction == 1.0 || get_tr2(tr,TR_StartSolid))
		distances[DIR_NORTH] = 4096.0;
	else
		distances[DIR_NORTH] = vector_distance(ladder_origin[ladder],end);

	////////////////
	// TRACE WEST
	////////////////
	start = ladder_origin[ladder];
	start[0] = ladder_maxs[ladder][0];

	goal = ladder_origin[ladder];
	goal[0] -= (ladder_maxs[ladder][0]-ladder_mins[ladder][0]) * 2.0;

	engfunc(EngFunc_TraceLine,start,goal,0,ladder,tr);
	get_tr2(tr,TR_flFraction,flFraction);
	get_tr2(tr,TR_vecEndPos,end);

	if(flFraction == 1.0 || get_tr2(tr,TR_StartSolid))
		distances[DIR_WEST] = 4096.0;
	else
		distances[DIR_WEST] = vector_distance(ladder_origin[ladder],end);

	////////////////
	// TRACE SOUTH
	////////////////
	start = ladder_origin[ladder];
	start[1] = ladder_maxs[ladder][1];

	goal = ladder_origin[ladder];
	goal[1] -= (ladder_maxs[ladder][1]-ladder_mins[ladder][1]) * 2.0;

	engfunc(EngFunc_TraceLine,start,goal,0,ladder,tr);
	get_tr2(tr,TR_flFraction,flFraction);
	get_tr2(tr,TR_vecEndPos,end);

	if(flFraction == 1.0 || get_tr2(tr,TR_StartSolid))
		distances[DIR_SOUTH] = 4096.0;
	else
		distances[DIR_SOUTH] = vector_distance(ladder_origin[ladder],end);

	////////////////
	// END TRACES
	////////////////

	// find smallest distance
	new i, closestDir = DIR_NONE;
	for(i=0;i<4;i++)
	{
		// too far away to be considered, pretty much means that
		// one of our above checks failed: (flFraction == 1.0 || StartSolid)
		if(distances[i] >= 4096.0)
			continue;

		// this is a new record for closest
		if(closestDir == DIR_NONE || distances[i] <= distances[closestDir])
			closestDir = i;
	}

	// remove our func_wall entities that we created for the traces
	wall = 0;
	while((wall = engfunc(EngFunc_FindEntityByString,wall,"classname","func_wall")) != 0)
	{
		// this was ours, remove it
		if(pev(wall,CUSTOM_WALL)) engfunc(EngFunc_RemoveEntity,wall);
	}

	return closestDir;
 }

 // hook outgoing entity data, to set our custom hostage animation
 public fw_addtofullpack(es_handle,e,ent,host,hostflags,player,pSet)
 {
	static model[9];

	// definitely not what we're looking for
	if(player || !pev_valid(ent))
		return FMRES_IGNORED;

	pev(ent,pev_classname,classname,4);

	// not a hostage. NOTE: hostage_entity is the only entity in the game
	// that starts with an "h", but we have to leave support for some
	// custom entities. but just how much support is needed?
	if(classname[0] != 'h' || classname[1] != 'o' || classname[2] != 's' || classname[3] != 't')
		return FMRES_IGNORED;

	// animating, force animation and frame
	if(pev(ent,ANIMATING) && !pev(ent,pev_deadflag))
	{
		new Float:frame;
		pev(ent,ANIM_FRAME,frame);

		// force our current frame
		set_es(es_handle,ES_Frame,frame);
		set_es(es_handle,ES_FrameRate,0.0);

		// scientist hostage and regular hostage have different animation indexes
		pev(ent,pev_model,model,8);
		set_es(es_handle,ES_Sequence,(model[7] == 's') ? ANIM_KNEEL_SCI : ANIM_KNEEL_HOS);

		return FMRES_HANDLED;
	}

	return FMRES_IGNORED;
 }

 // store who touched us last, for fall damage penalties
 public fw_touch(touched,toucher)
 {
	// world is involved...
	// check for zero first to avoid native call if we can.
	if(touched <= 0 || toucher <= 0
	|| !pev_valid(touched) || !pev_valid(toucher))
		return FMRES_IGNORED;

	pev(touched,pev_classname,classname,4);

	// not a hostage
	if(classname[0] != 'h' || classname[1] != 'o' || classname[2] != 's' || classname[3] != 't')
		return FMRES_IGNORED;

	// check for a valid toucher
	if(!is_user_alive(toucher))
	{
		new owner = pev(toucher,pev_owner);

		if(is_user_alive(owner))
			toucher = owner;
		else
			toucher = 0;
	}

	// remember
	if(toucher) set_pev(touched,LAST_TOUCHER,toucher);

	return FMRES_IGNORED;
 }

 // hook thinking, to do hostage calculations
 public fw_think(ent)
 {
	static Float:hOrigin[3];

	pev(ent,pev_classname,classname,4);

	// not a hostage
	if(classname[0] != 'h' || classname[1] != 'o' || classname[2] != 's' || classname[3] != 't')
		return FMRES_IGNORED;

	// dead
	if(pev(ent,pev_deadflag))
	{
		reset_hostage(ent);
		return FMRES_IGNORED;
	}

	new leader = cs_get_hostage_foll(ent);
	pev(ent,pev_origin,hOrigin);

	// check stuff above me
	hOrigin[2] += 36.0; // match expectations that origin is center of abs boxes
	new bool:headSpace = is_monster_hull_vacant(ent,hOrigin);
	hOrigin[2] -= 36.0; // reset

	// check for fall damage first
	new fallDmgDeath = hostage_fall_ai(ent,leader,get_pcvar_float(cvFallDmg));

	// not following anyone
	if(!leader)
	{
		// reset ladders
		set_pev(ent,pev_movetype,MOVETYPE_STEP);
		set_pev(ent,ON_LADDER,0);

		// no room to stand up, nothing else to do
		if(pev(ent,WAS_DUCKING) && !headSpace)
			return FMRES_IGNORED;

		set_pev(ent,WAS_DUCKING,0);

		// reserve space required
		set_pev(ent,pev_mins,Float:{-10.0,-10.0,0.0});
		set_pev(ent,pev_maxs,Float:{10.0,10.0,62.0});

		// stand back up
		if(pev(ent,ANIMATING))
		{
			new Float:frame;
			pev(ent,ANIM_FRAME,frame);

			// we have room left for an entire frame
			if(frame - FRAME_RATE > 0.0)
			{
				set_pev(ent,ANIM_FRAME,frame-FRAME_RATE);
			}

			// can't move an entire frame, go to the end
			else
			{
				set_pev(ent,ANIM_FRAME,0.0);
				set_pev(ent,ANIMATING,0);
			}
		}

		return FMRES_IGNORED;
	}

	// if we died from falling, don't run other checks
	if(fallDmgDeath) return FMRES_IGNORED;

	new Float:doorDist = get_pcvar_float(cvDoorDist);
	new Float:climbSpeed = get_pcvar_float(cvClimbSpeed);

	// duck and climb, baby, duck and climb!
	if(doorDist > 0.0) hostage_door_ai(ent,leader,hOrigin,doorDist);
	if(get_pcvar_num(cvCanDuck)) hostage_duck_ai(ent,leader,headSpace);
	if(climbSpeed > 0.0) hostage_ladder_ai(ent,leader,hOrigin,climbSpeed);

	return FMRES_IGNORED;
 }

 //////////////////////////////
 // HOSTAGE DOOR CODE START
 //////////////////////////////
 public hostage_door_ai(ent,leader,Float:hOrigin[3],Float:maxDist)
 {
	static Float:lOrigin[3], Float:vecEndPos[3];
	pev(leader,pev_origin,lOrigin);

	new ptr;
	engfunc(EngFunc_TraceLine,hOrigin,lOrigin,DONT_IGNORE_MONSTERS,ent,ptr);

	new hit = get_tr2(ptr,TR_pHit);

	// nothing between hostage and his leader
	if(hit <= 0 || hit == leader || !pev_valid(hit))
		return;

	get_tr2(ptr,TR_vecEndPos,vecEndPos);

	// too far away to consider
	if(vector_distance(hOrigin,vecEndPos) > maxDist)
		return;

	pev(hit,pev_classname,classname,9);

	// not a door between hostage and his leader
	if(!equal(classname,"func_door"))
		return;

	pev(hit,pev_targetname,classname,1);

	// this door has a target (locked)
	if(classname[0]) return;

	// touch this door. this is not a force use because the problem isn't
	// that hostages can't use doors, it's that they don't bother trying
	// to because they see an object between them and their leader that
	// they can't get around.

	dllfunc(DLLFunc_Touch,hit,ent);
	return;
 }
 //////////////////////////////
 // HOSTAGE DOOR CODE END
 //////////////////////////////

 //////////////////////////////
 // HOSTAGE FALL DMG CODE START
 //////////////////////////////
 public hostage_fall_ai(ent,leader,Float:dmgModifier)
 {
	static Float:velocity[3];

	// fall damage is disabled
	if(dmgModifier <= 0.0)
		return 0;

	// no current leader, see who touched us last
	// (ie, whoever pushed us off of the ledge)
	if(!is_user_alive(leader))
	{
		new lastToucher = pev(ent,LAST_TOUCHER);

		if(is_user_alive(lastToucher))
			leader = lastToucher;
		else
			leader = 0;
	}

	new Float:fallVelocity;
	pev(ent,FALL_VELOCITY,fallVelocity);

	// we hit the ground with enough velocity to do damage
	if(fallVelocity > PLAYER_MAX_SAFE_FALL_SPEED && (pev(ent,pev_flags) & FL_ONGROUND))
	{
		new Float:fallDamage, Float:health, fallPenalty = get_pcvar_num(cvFallPenalty);
		pev(ent,pev_health,health);

		// HL calculates it the same way, sans the dmgModifier
		fallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED;
		fallDamage = float(floatround(fallVelocity * DAMAGE_FOR_FALL_SPEED * dmgModifier));

		// charge money for the damages
		if(fallPenalty && leader)
		{
			// calculate damage to price, based
			// loosely on the actual formula
			new Float:price;

			// if hostage has 1 HP and he takes 20 fall damage, only penalize 1 HP
			if(fallDamage > health) price = health / 0.055;
			else price = fallDamage / 0.055;

			// death cost twice as much... sounds good to me
			if(health - fallDamage <= 0.0) price *= 2.0;

			// round to nearest tenth dollar
			price = float(floatround(price / 10.0) * 10);

			// bwahahaha!
			cs_set_user_money(leader,clamp(cs_get_user_money(leader) - floatround(price),0,16000));
		}

		// this will kill the hostage
		if(health - float(floatround(fallDamage)) <= 0.0)
		{
			// display message and log it
			if(fallPenalty && leader)
			{
				static name[32], authid[32], team[10];
				get_user_name(leader,name,31);
				get_user_authid(leader,authid,31);
				get_user_team(leader,team,9);

				log_message("^"%s<%i><%s><%s>^" triggered ^"Killed_A_Hostage^"",name,get_user_userid(leader),authid,team);

				// use TextMsg message so that it appears in player's language
				emessage_begin(MSG_ONE,gmsgTextMsg,{0,0,0},leader);
				ewrite_byte(4);
				ewrite_string("#Killed_Hostage");
				emessage_end();

				// kick players after X amount of hostage kills
				new hostagePenalty = get_pcvar_num(mpHostagePenalty);
				if(hostagePenalty)
				{
					new hostageKills = get_pdata_int(leader,OFFSET_HOSTAGE_KILLS,5);

					// reached limit, kick
					if(hostageKills+1 >= hostagePenalty)
						server_cmd("kick #%i",get_user_userid(leader));

					// otherwise, add to it so that the game knows
					else
						set_pdata_int(leader,OFFSET_HOSTAGE_KILLS,hostageKills+1,5);
				}
			}

			// fake damage with a trigger_hurt to force a death
			new hurt = engfunc(EngFunc_CreateNamedEntity,trigger_hurt);
			if(hurt)
			{
				set_kvd(0,KV_ClassName,"trigger_hurt");
				set_kvd(0,KV_KeyName,"dmg");
				set_kvd(0,KV_Value,"4096.0");
				set_kvd(0,KV_fHandled,0);
				dllfunc(DLLFunc_KeyValue,hurt,0);

				set_kvd(0,KV_ClassName,"trigger_hurt");
				set_kvd(0,KV_KeyName,"damagetype");
				set_kvd(0,KV_Value,"0");
				set_kvd(0,KV_fHandled,0);
				dllfunc(DLLFunc_KeyValue,hurt,0);

				set_kvd(0,KV_ClassName,"trigger_hurt");
				set_kvd(0,KV_KeyName,"origin");
				set_kvd(0,KV_Value,"8192 8192 8192");
				set_kvd(0,KV_fHandled,0);
				dllfunc(DLLFunc_KeyValue,hurt,0);

				dllfunc(DLLFunc_Spawn,hurt);

				set_pev(hurt,pev_classname,"worldspawn");
				dllfunc(DLLFunc_Touch,hurt,ent);
				engfunc(EngFunc_RemoveEntity,hurt);
			}

			set_pev(ent,pev_deadflag,DEAD_DEAD);
			reset_hostage(ent);

			return 1;
		}
		else
		{
			// display message
			if(fallPenalty && leader)
			{
				// use TextMsg message so that it appears in player's language
				emessage_begin(MSG_ONE,gmsgTextMsg,{0,0,0},leader);
				ewrite_byte(4);
				ewrite_string("#Injured_Hostage");
				emessage_end();
			}

			set_pev(ent,pev_health,health - fallDamage);
		}

		set_pev(ent,FALL_VELOCITY,fallVelocity);
	}
	else
	{
		// remember fall velocity for next time
		pev(ent,pev_velocity,velocity);
		set_pev(ent,FALL_VELOCITY,velocity[2] * -1.0);
	}

	return 0;
 }
 //////////////////////////////
 // HOSTAGE FALL DMG CODE END
 //////////////////////////////

 //////////////////////////////
 // HOSTAGE DUCKING CODE START
 //////////////////////////////
 public hostage_duck_ai(ent,leader,bool:headSpace)
 {
	// leader is ducking 
	if(pev(leader,pev_flags) & FL_DUCKING)
	{
		// we weren't already ducking, start ducking animation
		if(!pev(ent,WAS_DUCKING))
		{
			set_pev(ent,WAS_DUCKING,1);
			set_pev(ent,ANIMATING,1);
			set_pev(ent,ANIM_FRAME,0.0);
		}

		new Float:frame;
		pev(ent,ANIM_FRAME,frame);

		// enough space to walk an entire frame
		if(frame + FRAME_RATE < 35.0)
		{
			set_pev(ent,ANIM_FRAME,frame+FRAME_RATE);
		}

		// not enough, go to last frame and finish
		else
		{
			set_pev(ent,ANIM_FRAME,35.0);

			// change bounding box at end of animation
			set_pev(ent,pev_mins,Float:{-10.0,-10.0,0.0});
			set_pev(ent,pev_maxs,Float:{10.0,10.0,32.0});
		}
	}
	else
	{
		// make sure we have room to stand up
		if(!pev(ent,WAS_DUCKING) || headSpace)
		{
			// reserve space required at start of animation
			set_pev(ent,pev_mins,Float:{-10.0,-10.0,0.0});
			set_pev(ent,pev_maxs,Float:{10.0,10.0,62.0});

			// start unducking animation
			if(pev(ent,WAS_DUCKING))
			{
				set_pev(ent,ANIMATING,1);
				set_pev(ent,WAS_DUCKING,0);
			}

			// animate in reverse
			if(pev(ent,ANIMATING))
			{
				new Float:frame;
				pev(ent,ANIM_FRAME,frame);

				// move an entire frame
				if(frame - FRAME_RATE > 0.0)
				{
					set_pev(ent,ANIM_FRAME,frame-FRAME_RATE);
				}

				// move a partial frame
				else
				{
					set_pev(ent,ANIM_FRAME,0.0);
					set_pev(ent,ANIMATING,0);
				}
			}
		}
	}
 }
 //////////////////////////////
 // HOSTAGE DUCKING CODE END
 //////////////////////////////

 //////////////////////////////
 // HOSTAGE LADDER CODE START
 //////////////////////////////
 public hostage_ladder_ai(ent,leader,Float:hOrigin[3],Float:climbSpeed)
 {
	// we'll be needing all of this
	static Float:lOrigin[3], Float:lMins[3], Float:goal[3], Float:absmin[3],
	Float:absmax[3], Float:newVelocity[3], Float:comparison[3];

	new dirLadders = get_pcvar_num(cvDirLadders);

	// get leader origin
	pev(leader,pev_origin,lOrigin);
	pev(leader,pev_mins,lMins);

	// we were climbing a ladder
	new onLadder = pev(ent,ON_LADDER);
	if(onLadder)
	{
		// we reached the top of the ladder, now
		// "step up" towards the player
		if(hOrigin[2] > ladder_maxs[onLadder-1][2])
		{
			// thanks GHW_Chronic
			get_speed_vector(hOrigin,lOrigin,256.0,newVelocity);

			set_pev(ent,pev_movetype,MOVETYPE_FLY);
			set_pev(ent,pev_velocity,newVelocity);

			set_pev(ent,ON_LADDER,0);
			return;
		}
			
	}

	// not below my leader (some leeway for leader)
	if(hOrigin[2] >= lOrigin[2]+lMins[2]-4.0) // hostage has to be 4.0 units below leader
	{
		set_pev(ent,pev_movetype,MOVETYPE_STEP);
		return;
	}

	// get absolute boxes
	pev(ent,pev_absmin,absmin);
	pev(ent,pev_absmax,absmax);

	// check to see if we are touching ladders
	new i, Float:dist, touchingLadder = -1;
	for(i=0;i<ladderNum;i++)
	{
		dist = get_box_distance(absmin,absmax,ladder_mins[i],ladder_maxs[i]);

		// too far away
		if(dist > 24.0) continue;

		// our leader isn't even halfway up this ladder
		if(ladder_origin[i][2] > lOrigin[2])
			continue;

		// don't bother checking direction, accept it
		if(!dirLadders || ladder_direction[i] == DIR_NONE)
		{
			touchingLadder = i;
			break;
		}		

		// find edge of side we should be on.
		// note that ladder_direction[i] will be the
		// direction that we should be facing to climb
		// this ladder.
		switch(ladder_direction[i])
		{
			case DIR_EAST:
			{
				// we are east of the west side, ignore
				if(hOrigin[0] > ladder_mins[i][0])
					continue;
			}
			case DIR_NORTH:
			{
				// we are north of the south side, ignore
				if(hOrigin[1] > ladder_mins[i][1])
					continue;
			}
			case DIR_WEST:
			{
				// we are west of the east side, ignore
				if(hOrigin[0] < ladder_maxs[i][0])
					continue;
			}
			case DIR_SOUTH:
			{
				// we are south of the north side, ignore
				if(hOrigin[1] < ladder_maxs[i][1])
					continue;
			}
		}

		// otherwise, we should be alright
		touchingLadder = i;
		break;
	}

	// store our status
	set_pev(ent,ON_LADDER,touchingLadder+1);

	// not touching any ladders
	if(touchingLadder == -1)
	{
		set_pev(ent,pev_movetype,MOVETYPE_STEP);
		return;
	}

	goal = ladder_origin[touchingLadder];

	// if we have direction data, make the goal origin
	// a little bit away from ledge, so hopefully our
	// goal will be straight up, and not clip into the
	// ledge. this makes climbing much smoother.
	if(dirLadders && ladder_direction[i] != DIR_NONE)
	{
		// note that ladder_direction[i] will be the
		// direction that we should be facing to climb
		// this ladder.
		switch(ladder_direction[i])
		{
			case DIR_EAST:
			{
				// move it west
				goal[0] = ladder_mins[touchingLadder][0] - 16.0;
			}
			case DIR_NORTH:
			{
				// move it south
				goal[1] = ladder_mins[touchingLadder][1] - 16.0;
			}
			case DIR_WEST:
			{
				// move it east
				goal[0] = ladder_maxs[touchingLadder][0] + 16.0;

			}
			case DIR_SOUTH:
			{
				// move it north
				goal[1] = ladder_maxs[touchingLadder][1] + 16.0;
			}
		}
	}

	// compare X and Y distances
	comparison = goal;
	comparison[2] = hOrigin[2];

	// too far from center of ladder, center ourselves on the ground.
	// this stops hostages from coming up the ladder at an angle and
	// getting stuck. ie: in militia sewers.
	if(vector_distance(hOrigin,comparison) > 12.0)
		goal[2] = ladder_mins[touchingLadder][2] + 8.0; // arbitrary

	// shoot for a little ABOVE the top of the ladder,
	// so that we will fully get over it (hostage origin is at feet).
	else
	{
		// if we have direction data, we only need to aim a little
		// bit above the ladder, since we are going straight up.
		if(dirLadders && ladder_direction[i] != DIR_NONE)
			goal[2] = ladder_maxs[touchingLadder][2] + 32.0;

		// otherwise, we need to aim way above the ladder (512.0
		// seems to be the magic number), to reduce the amount that
		// our path clips into the edge of the ladder.
		else
			goal[2] = ladder_maxs[touchingLadder][2] + 512.0;
	}

	// get new direction (thanks GHW_Chronic)
	get_speed_vector(hOrigin,goal,climbSpeed,newVelocity);

	// fire!!
	set_pev(ent,pev_movetype,MOVETYPE_FLY);
	set_pev(ent,pev_velocity,newVelocity);
 }
 //////////////////////////////
 // HOSTAGE LADDER CODE END
 //////////////////////////////

 // reset all changes
 reset_hostage(ent)
 {
	set_pev(ent,WAS_DUCKING,0);
	set_pev(ent,ON_LADDER,0);
	set_pev(ent,LAST_TOUCHER,0);
	set_pev(ent,ANIMATING,0);
	set_pev(ent,ANIM_FRAME,0.0);
	set_pev(ent,FALL_VELOCITY,0.0);
	set_pev(ent,pev_movetype,MOVETYPE_STEP);
	set_pev(ent,pev_mins,Float:{-10.0,-10.0,0.0});
	set_pev(ent,pev_maxs,Float:{10.0,10.0,62.0});
 }

 /*
 *
 *  Determines velocity (new_velocity) that
 *  you would set an entity to in order for
 *  it to go at "speed" from "origin1" to
 *  "origin2".
 *
 *  By GHW_Chronic
 *
 */
 stock get_speed_vector(const Float:origin1[3],const Float:origin2[3],Float:speed, Float:new_velocity[3])
 {
	new_velocity[0] = origin2[0] - origin1[0];
	new_velocity[1] = origin2[1] - origin1[1];
	new_velocity[2] = origin2[2] - origin1[2];
	new Float:num = floatsqroot(speed*speed / (new_velocity[0]*new_velocity[0] + new_velocity[1]*new_velocity[1] + new_velocity[2]*new_velocity[2]));
	new_velocity[0] *= num;
	new_velocity[1] *= num;
	new_velocity[2] *= num;

	return 1;
 }

 // original is_hull_vacant by VEN. modified to use TraceMonsterHull,
 // so that it uses the hostage's hull and not the player hull.
 stock bool:is_monster_hull_vacant(ent,const Float:origin[3])
 {
	new tr = 0;
	engfunc(EngFunc_TraceMonsterHull,ent,origin,origin,0,ent,tr);

	if(!get_tr2(tr,TR_StartSolid) && !get_tr2(tr,TR_AllSolid) && get_tr2(tr,TR_InOpen))
		return true;
	
	return false;
 }

 // gets distance between two boxes, by VEN
 stock Float:get_box_distance(Float:mins1[3],Float:maxs1[3],Float:mins2[3],Float:maxs2[3])
 {
	new Float:distance[3], i, Float:max, Float:min;

	for(i=0;i<3;i++)
	{
		min = mins1[i];
		max = maxs2[i];

		if(min > max)
		{
			distance[i] = min - max;
			continue;
		}

		min = mins2[i];
		max = maxs1[i];

		if(min > max)
			distance[i] = min - max;
	}

	return floatsqroot(distance[0] * distance[0] + distance[1] * distance[1] + distance[2] * distance[2]);
 }
