Convert Plugin ZP / ZE

Unpaid Requests, Public Plugins
Post Reply
User avatar
alexnovac18
Member
Member
Romania
Posts: 28
Joined: 5 years ago
Location: Romania
Contact:

Convert Plugin ZP / ZE

#1

Post by alexnovac18 » 5 years ago

Hello Team Escape-Zone, can you convert this plugin ZP to ZE please, thank you very much, and to make Knockback zm

Sma:

Code: Select all

//#define DEBUG

#include <amxmodx>
#include <amxmisc>
#include <engine>
#include <fun>
#include <cstrike>
#include <fakemeta>
#include <zombieplague>
#if defined DEBUG
#include <amxmisc>
#endif

#define MAXSENTRIESTOTAL 20
//#define MAXPLAYERSENTRIES		3				// how many sentries each player can build
#define MAXPLAYERSENTRIES		get_pcvar_num(sentry_max)	// how many sentries each player can build
#define DMG_EXPLOSION_TAKE		90				// how much HP at most an exploding sentry takes from a player - the further away the less dmg is dealt to player
#define SENTRYEXPLODERADIUS		250.0				// how far away it is safe to be from an exploding sentry without getting kicked back and hurt
#define THINKFIREFREQUENCY		0.1				// the rate in seconds between each bullet when firing at a locked target
#define SENTRYTILTRADIUS		830.0				// likely you won't need to touch this. it's how accurate the cannon will aim at the target vertically (up/down, just for looks, aim is calculated differently)
#if !defined DEBUG
#define DISALLOW_OWN_UPGRADES						// you cannot upgrade your own sentry to level 2 (only to level 3 if someone else upgraded it already) (only have this commented in debug mode)
#define DISALLOW_TWO_UPGRADES						// the upgrader cannot upgrade again, builder must upgrade to level 3 (only have this commented in debug mode)
#endif
//#define SENTRIES_SURVIVE_ROUNDS					// comment this define to have sentries removed between rounds, else they will stay where they are.
//#define RANDOM_TOPCOLOR						// sentries have two colors, one top and one bottom. The top one will be random if you leave this define be, else always red for T and blue for CT.
//#define RANDOM_BOTTOMCOLOR						// sentries have two colors, one top and one bottom. The bottom one will be random if you leave this define be, else always red for T and blue for CT.
#define EXPLODINGSENTRIES						// comment this out if you don't want the sentries to explode, push people away and hurt them (should now be stable!)
						
// Bots will build sentries at objective critical locations (around dropped bombs, at bomb targets, near hostages etc)
// They can also build randomly around maps using these values:
#define BOT_WAITTIME_MIN		0.0				// waittime = the time a bot will wait after he's decided to build a sentry, before actually building (seconds)
#define BOT_WAITTIME_MAX		15.0
#define BOT_NEXT_MIN			0.0				// next = after building a sentry, this specifies the time a bot will wait until considering about waittime again (seconds)
#define BOT_NEXT_MAX			120.0

// These are per sentry level, 1-3
new const g_SENTRYFRAGREWARDS[3] = {28, 15, 15}			// how many ammo packs you get if your sentry frags someone.
new const g_DMG[3] = {50, 100, 170}				// how much damage a bullet from a sentry does per hit
new const Float:g_THINKFREQUENCIES[3] = {3.0, 1.5, 0.3}		// how often, in seconds, a sentry searches for targets when not locked at a target, a lower value means a sentry will lock on targets faster
new const Float:g_HITRATIOS[3] = {0.5, 0.65, 0.75}		// how good a sentry is at hitting its target. 1.0 = always hit, 0.0 = never hit
new const Float:g_HEALTHS[3] = {550.0, 1100.0, 2200.0}		// how many HP a sentry has. Increase to make sentry sturdier
//new const g_COST[3] = {22, 17, 17}				// fun has a price, first is build cost, the next two upgrade costs

#define COST_INIT get_pcvar_num(sentry_cost1)
#define COST_UP get_pcvar_num(sentry_cost2)
#define COST_UPTWO get_pcvar_num(sentry_cost3)


#if !defined PI
#define PI	 	3.141592654 				// feel free to find a PI more exact than this
#endif

#define MENUBUTTON1			(1<<0)
#define MENUBUTTON2			(1<<1)
#define MENUBUTTON3			(1<<2)
#define MENUBUTTON4			(1<<3)
#define MENUBUTTON5			(1<<4)
#define MENUBUTTON6			(1<<5)
#define MENUBUTTON7			(1<<6)
#define MENUBUTTON8			(1<<7)
#define MENUBUTTON9			(1<<8)
#define MENUBUTTON0			(1<<9)
#define MENUSELECT1			0
#define MENUSELECT2			1
#define MENUSELECT3			2
#define MENUSELECT4			3
#define MENUSELECT5			4
#define MENUSELECT6			5
#define MENUSELECT7			6
#define MENUSELECT8			7
#define MENUSELECT9			8
#define MENUSELECT0			9
#define MAXHTMLSIZE			1536

#define MAXSENTRIES			32 * MAXSENTRIESTOTAL

#define SENTRY_VEC_PEOPLE		EV_VEC_vuser1
#define OWNER				0
#define UPGRADER_1			1
#define UPGRADER_2			2
//#define SENTRY_VECENT_OWNER		SENTRY_VEC_PEOPLE + OWNER
//#define SENTRY_VECENT_UPGRADER_1	SENTRY_VEC_PEOPLE + UPGRADER_1
//#define SENTRY_VECENT_UPGRADER_2	SENTRY_VEC_PEOPLE + UPGRADER_2

GetSentryPeople(sentry, who) 
{
	new Float:people[3]
	entity_get_vector(sentry, SENTRY_VEC_PEOPLE, people)
	return floatround(people[who])
}

SetSentryPeople(sentry, who, is) 
{
	new Float:people[3]
	entity_get_vector(sentry, SENTRY_VEC_PEOPLE, people)
	people[who] = is + 0.0
	entity_set_vector(sentry, SENTRY_VEC_PEOPLE, people)
}

#define SENTRY_ENT_TARGET		EV_ENT_euser1
#define SENTRY_ENT_BASE			EV_ENT_euser2
#define SENTRY_ENT_SPYCAM		EV_ENT_euser3
#define SENTRY_INT_FIRE			EV_INT_iuser1
#define SENTRY_INT_TEAM			EV_INT_iuser2
#define SENTRY_INT_LEVEL		EV_INT_iuser3
#define SENTRY_INT_PENDDIR		EV_INT_iuser4 			// 1st bit: sentry cannon, 2nd bit: radar
#define SENTRY_FL_ANGLE			EV_FL_fuser1
#define SENTRY_FL_SPINSPEED		EV_FL_fuser2
#define SENTRY_FL_MAXSPIN		EV_FL_fuser3
#define SENTRY_FL_RADARANGLE		EV_FL_fuser4

// These are bits used in SENTRY_INT_PENDDIR
#define SENTRY_DIR_CANNON		0
#define SENTRY_DIR_RADAR		1

#define BASE_ENT_SENTRY			EV_ENT_euser1
#define BASE_INT_TEAM			EV_INT_iuser1

#define SENTRY_LEVEL_1			0
#define SENTRY_LEVEL_2			1
#define SENTRY_LEVEL_3			2
#define SENTRY_FIREMODE_NO		0
#define SENTRY_FIREMODE_YES		1
#define SENTRY_FIREMODE_NUTS		2
#define TASKID_SENTRYFIRE		1000
#define TASKID_BOTBUILDRANDOMLY		2000
#define TASKID_SENTRYSTATUS		3000
#define TASKID_THINK			4000
#define TASKID_THINKPENDULUM		5000
#define TASKID_SENTRYONRADAR		6000
#define TASKID_SPYCAM			7000
#define DUCKINGPLAYERDIFFERENCE		18.0
#define TARGETUPMODIFIER		DUCKINGPLAYERDIFFERENCE 	// if player ducks on ground, traces don't hit...
#define DMG_BULLET			(1<<1)				// shot
#define DMG_BLAST			(1<<6)				// explosive blast damage
#define TE_EXPLFLAG_NONE		0
#define TE_EXPLOSION			3
#define	TE_TRACER			6
#define TE_BREAKMODEL			108
#define BASESENTRYDELAY			2.0 				// seconds from base is built until sentry top appears
#define PENDULUM_MAX			45.0 				// how far sentry turret turns in each direction when idle, before turning back
#define PENDULUM_INCREMENT		10.0 				// speed of turret turning...
#define RADAR_INCREMENT			2.0 				// speed of small radar turning on top of sentry level 3...
#define MAXUPGRADERANGE			75.0 				// farthest distance to sentry you can upgrade using upgrade command
#define COLOR_BOTTOM_CT			160 				// default bottom colour of CT:s sentries
#define COLOR_TOP_CT			150 				// default top colour of CT:s sentries
#define COLOR_BOTTOM_T			0 				// default bottom colour of T:s sentries
#define COLOR_TOP_T			0 				// default top colour of T:s sentries
#define SENTRYSHOCKPOWER		3.0 				// multiplier, increase to make exploding sentries throw stuff further away
#define CANNONHEIGHTFROMFEET		20.0 				// tweakable to make tracer originate from the same height as the sentry's cannon. Also traces rely on this Y-wise offset.
#define PLAYERORIGINHEIGHT		36.0 				// this is the distance from a player's EV_VEC_origin to ground, if standing up
#define HEIGHTDIFFERENCEALLOWED		20.0 				// increase value to allow building in slopes with higher angles. You can set to 0.0 and you will only be able to build on exact flat ground. note: mostly applies to downhill building, uphill is still likely to "collide" with ground...

// This cannot account for sentries which are still under construction (only base, no sentry head yet):
// How many (or more) sentries:
#define BOT_MAXSENTRIESNEAR		1

// cannot be in the vicinity of this radius:
#define BOT_MAXSENTRIESDISTANCE		1500.0

// for a bot to build at a location. Use higher values and bots will build sentries less close to other sentries on same team.
#define BOT_OBJECTIVEWAIT		10 				// nr of seconds that must pass after a bot has built an objective related sentry until he can build such a sentry again.

#define SENTRY_TILT_TURRET		EV_BYTE_controller2
#define SENTRY_TILT_LAUNCHER		EV_BYTE_controller3
#define SENTRY_TILT_RADAR		EV_BYTE_controller4
#define PEV_SENTRY_TILT_TURRET		pev_controller_1
#define PEV_SENTRY_TILT_LAUNCHER	pev_controller_2
#define PEV_SENTRY_TILT_RADAR		pev_controller_3

#define STATUSINFOTIME			0.5 				// the frequency of hud message updates when spectating a sentry, don't set too low or it could overflow clients. Data should now always send again as soon as it updates though.
#define SENTRY_RADAR			20 				// use as high as possible but should still be working (ie be able to see sentries plotted on radar while in menu, too high values doesn't seem to work)
#define SENTRY_RADAR_TEAMBUILT		21 				// same as above
#define SPYCAMTIME			5.0 				// nr of seconds the spycam is active

enum OBJECTTYPE 
{
	OBJECT_GENERIC,
	OBJECT_GRENADE,
	OBJECT_PLAYER,
	OBJECT_ARMOURY
}

// Global vars 
new g_sentriesNum = 0
new g_sentries[MAXSENTRIES]
new g_playerSentries[32] = {0, ...}
new g_playerSentriesEdicts[32][MAXSENTRIESTOTAL]
new g_sModelIndexFireball, g_msgDamage, g_msgDeathMsg, g_msgScoreInfo, g_msgHostagePos,
g_msgHostageK, g_MAXPLAYERS
new sentry_max, sentry_cost1, sentry_cost2, sentry_cost3, sentry_team

//new g_MAXENTITIES
//new g_hasSentries = 0
new Float:g_sentryOrigins[32][3]
new g_aimSentry[32]
new bool:g_inBuilding[32]
new bool:g_resetArmouryThisRound = true
new bool:g_hasArmouries = false
new Float:g_lastGameTime = 1.0 						// dunno, looks like get_systime() is always 1.0 first time...
new Float:g_ONEEIGHTYTHROUGHPI, Float:g_gameTime, Float:g_deltaTime
new g_sentryStatusBuffer[32][256]
new g_sentryStatusTrigger
new g_selectedSentry[32] = {-1, ...}
new g_menuId 								// used to store index of menu
new g_lastObjectiveBuild[32], g_inSpyCam[32]
new Float:g_spyCamOffset[3] = {26.0, 29.0, 26.0}				// for the three levels, just what looks good...

//Shaman: For disabling building until some time passes after the new round
new bool:g_allowBuild 							//Building, upgrading and reparing is not allowed if this is false
new sentry_wait 							//The cvar

new const PLUGINNAME[] = "[ZP] Extra : Sentry Guns"
new const VERSION[] = "0.1.2a"
new const AUTHOR[] = "The_Thing"

public plugin_init() 
{
	register_plugin(PLUGINNAME, VERSION, AUTHOR)

	register_clcmd("sentry_build", "createsentryhere", 0, "- build a sentry gun where you are")
	register_clcmd("sentry_menu", "menumain", 0, "- displays Sentry gun menu")
	register_clcmd("say", "check_say")
	register_clcmd("say_team", "check_say")

	#if defined DEBUG
	register_concmd("0botbuild", "botbuild_fn", ADMIN_CFG, "- force bots to build right where they are (debug)")
	#endif

	//register_cvar("pend_inc", "30")
	//register_cvar("radar_increment", "4.56")
	//register_cvar("spycamoffset", "24.0")

	sentry_max = register_cvar("zp_sentry_max", "9");
	sentry_cost1 = register_cvar("zp_sentry_cost1", "27");
	sentry_cost2 = register_cvar("zp_sentry_cost2", "15");
	sentry_cost3 = register_cvar("zp_sentry_cost3", "20");
	sentry_team = register_cvar("zp_sentry_team", "2");
	sentry_wait = register_cvar("zp_sentry_wait", "15");

	register_event("ResetHUD", "newround_event", "b")
	register_event("SendAudio", "endround_event", "a", "2&%!MRAD_terwin", "2&%!MRAD_ctwin", "2&%!MRAD_rounddraw")
	register_event("TextMsg", "endround_event", "a", "2&#Game_C", "2&#Game_w")
	register_event("TextMsg", "endround_event", "a", "2&#Game_will_restart_in")
	
	//Shaman: For destroying sentries after team change
	register_event( "TeamInfo", "jointeam_event", "a")

	register_forward(FM_TraceLine, "forward_traceline_post", 1)

	//new bool:foundSomething = false
	if (find_ent_by_class(0, "func_bomb_target")) 
	{
		register_touch("func_bomb_target", "player", "playerreachedtarget")
		register_touch("weaponbox", "player", "playertouchedweaponbox")
		//foundSomething = true
	}
	
	if (find_ent_by_class(0, "func_hostage_rescue")) 
	{
		register_touch("func_hostage_rescue", "player", "playerreachedhostagerescue")
		//foundSomething = true
	}
	if (find_ent_by_class(0, "func_vip_safetyzone")) 
	{
		register_touch("func_vip_safetyzone", "player", "playerreachedtarget")
		//foundSomething = true
	}
	if (find_ent_by_class(0, "hostage_entity")) 
	{
		register_touch("hostage_entity", "player", "playertouchedhostage")
		//foundSomething = true
	}

	register_touch("sentry", "player", "playertouchedsentry")

	g_menuId = register_menuid("\ySentry gun menu")
	register_menucmd(g_menuId, 1023, "menumain_handle")

	register_message(23, "message_tempentity") // <-- works for 0.16 as well
	//register_think("sentry", "think_sentry") // <-- only 0.20+ can do this
	register_think("sentrybase", "think_sentrybase")

	g_msgDamage = get_user_msgid("Damage")
	g_msgDeathMsg = get_user_msgid("DeathMsg")
	g_msgScoreInfo = get_user_msgid("ScoreInfo")
	g_msgHostagePos = get_user_msgid("HostagePos")
	g_msgHostageK = get_user_msgid("HostageK")

	g_MAXPLAYERS = get_global_int(GL_maxClients)
	//g_MAXENTITIES = get_global_int(GL_maxEntities)
	g_ONEEIGHTYTHROUGHPI = 180.0 / PI

	// Add menu item to menufront
	#if defined AddMenuItem
	AddMenuItem("Sentry guns", "sentry_menu", ADMIN_CFG, PLUGINNAME)
	#endif

	// InitArmoury saves the location of all onground weapons. Later we restore them to these origins when a newround begin.
	set_task(5.0, "InitArmoury")
}

//Shaman: Event function that removes sentries of a player after team change
public jointeam_event()
{
	//Get the id from the event data
	new id = read_data(1)	
	
	//Remove sentries
	while(GetSentryCount(id)>0)
		sentry_detonate_by_owner(id, true)
	
	return PLUGIN_CONTINUE
}

//Shaman: The function that enables building
public enablesentrybur()
{
	g_allowBuild= true;
}

public createsentryhere(id) 
{
	//Shaman: Check if the player is allowed to build
	if(!g_allowBuild)
	{
		client_print(id, print_center, "You must wait until you can build, upgrade or repair sentries!")
		return PLUGIN_HANDLED
	}

	new sentry = AimingAtSentry(id, true)
	// if a valid sentry
	// if within range
	// we can try to upgrade/repair...
	if (sentry && entity_range(sentry, id) <= MAXUPGRADERANGE)
	{
		//client_print(id, print_chat, "Sentry level: %d, last upgrader: %d", entity_get_int(sentry, SENTRY_INT_LEVEL) + 1, entity_get_edict(sentry, SENTRY_ENT_LASTUPGRADER))
		#if defined DISALLOW_OWN_UPGRADES
		// Don't allow builder to upgrade his own sentry first time.
		if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_1 && id == GetSentryPeople(sentry, OWNER)) 
		{
			client_print(id, print_center, "You cannot upgrade your own sentry gun to level 2, a team mate must do this!")
			return PLUGIN_HANDLED
		}
		#endif
		#if defined DISALLOW_TWO_UPGRADES
		// Don't allow upgrader to upgrade again.
		if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_2 && id == GetSentryPeople(sentry, UPGRADER_1)) 
		{
			client_print(id, print_center, "You cannot upgrade this sentry gun another time to level 3, a team mate must do this!")
			return PLUGIN_HANDLED
		}
		#endif
		g_aimSentry[id - 1] = sentry
		//if (entity_
		sentry_upgrade(id, sentry)
	}
	
	else 
	{
		sentry_build(id)
	}
	return PLUGIN_HANDLED
}

public sentry_build(id) 
{
	//Shaman: Check if the player is allowed to build
	if(!g_allowBuild)
	{
		client_print(id, print_center, "You must wait until you can build, upgrade or repair sentries!")
		return
	}

	if (GetSentryCount(id) >= MAXPLAYERSENTRIES) 
	{
		new wordnumbers[128]
		getnumbers(MAXPLAYERSENTRIES, wordnumbers, 127)
		new maxsentries = MAXPLAYERSENTRIES 				// stupid, but compiler gives warning on next line if a defined constant is used
		client_print(id, print_center, "You can only build %s sentry gun%s!", wordnumbers, maxsentries != 1 ? "s" : "")
		return
	}
	
	else if (g_inBuilding[id - 1]) 
	{
		client_print(id, print_center, "Wow, you're a fast builder...")
		return
	}
	
	else if (!is_user_alive(id) || zp_get_user_zombie(id)) 
	{
		return
	}
	
	else if (zp_get_user_ammo_packs(id) < g_COST(0)) 
	{
		client_print(id, print_center, "You don't have enough money to build a sentry gun! (%d needed)", g_COST(0))
		return
	}
	
	else if (!entity_is_on_ground(id)) 
	{
		client_print(id, print_center, "You must stand on the ground to build a sentry gun!")
		return 
	}
	//else if (entity_get_int(id, EV_INT_flags) & FL_DUCKING) 
	//{
	
	else if (entity_get_int(id, EV_INT_bInDuck)) 
	{
		client_print(id, print_center, "Yeah, right, try building a sentry gun sitting on your ***!")
		return
	}
	
	else if ( get_pcvar_num( sentry_team ) ) 
	{
		if( get_pcvar_num( sentry_team ) < 0 && !is_user_admin(id) )
		{
			client_print(id, print_center, "Only Admins can build!")
			return
		}
	}
	
	else if ( !(abs(get_pcvar_num( sentry_team )) & _:cs_get_user_team(id)) )
	{
		client_print(id, print_center, "Your team cannot build!")
	}


	new Float:playerOrigin[3]
	entity_get_vector(id, EV_VEC_origin, playerOrigin)

	new Float:vNewOrigin[3]
	new Float:vTraceDirection[3]
	new Float:vTraceEnd[3]
	new Float:vTraceResult[3]
	velocity_by_aim(id, 64, vTraceDirection) 			// get a velocity in the directino player is aiming, with a multiplier of 64...
	vTraceEnd[0] = vTraceDirection[0] + playerOrigin[0] 		// find the new max end position
	vTraceEnd[1] = vTraceDirection[1] + playerOrigin[1]
	vTraceEnd[2] = vTraceDirection[2] + playerOrigin[2]
	trace_line(id, playerOrigin, vTraceEnd, vTraceResult) 		// trace, something can be in the way, use hitpoint from vTraceResult as new origin, if nothing's in the way it should be same as vTraceEnd
	vNewOrigin[0] = vTraceResult[0]					// just copy the new result position to new origin
	vNewOrigin[1] = vTraceResult[1]					// just copy the new result position to new origin
	vNewOrigin[2] = playerOrigin[2]					// always build in the same height as player.

	if (CreateSentryBase(vNewOrigin, id)) 
	{
		zp_set_user_ammo_packs(id, zp_get_user_ammo_packs(id) - g_COST(0))
		//SetHasSentry(id, true)
		//client_print(id, print_chat, "Done creating a sentry at %f %f %f!", vNewOrigin[0], vNewOrigin[1], vNewOrigin[2])
	}

	else 
	{
		//SetHasSentry(id, false)
		client_print(id, print_center, "Cannot build sentry here")
	}
}

GetSentryCount(id) 
{
	return g_playerSentries[id - 1]

//else

/*
if (id < 1 || id > get_maxplayers())
	return false

//spambits(id, g_hasSentries)

return g_hasSentries & (1<<(id - 1)) ? true : false // g_hasSentries[id - 1] //
*/
}

bool:GetStatusTrigger(player) 
{
	if (!is_user_alive(player))
	return false

	return g_sentryStatusTrigger & (1<<(player-1)) ? true : false
}

SetStatusTrigger(player, bool:onOrOff) 
{
	if (onOrOff)
	g_sentryStatusTrigger |= (1<<(player - 1))
	else
	g_sentryStatusTrigger &= ~(1<<(player - 1))
}

IncreaseSentryCount(id, sentryEntity) 
{
	g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1]] = sentryEntity
	g_playerSentries[id - 1] = g_playerSentries[id - 1] + 1
	new Float:sentryOrigin[3], iSentryOrigin[3]
	entity_get_vector(sentryEntity, EV_VEC_origin, sentryOrigin)
	FVecIVec(sentryOrigin, iSentryOrigin)

	new name[32]
	get_user_name(id, name, 31)
	new CsTeams:builderTeam = cs_get_user_team(id)
	for (new i = 1; i <= g_MAXPLAYERS; i++) 
	{
		if (!is_user_connected(i) || !is_user_alive(i) || zp_get_user_zombie(id) || cs_get_user_team(i) != builderTeam || id == i)
			continue
		client_print(i, print_center, "%s has built a sentry gun %d units away from you", name, floatround(entity_range(i, sentryEntity)))

		//client_print(parm[0], print_chat, "Plotting closest sentry %d on radar: %f %f %f", parm[1], sentryOrigin[0], sentryOrigin[1], sentryOrigin[2])
		message_begin(MSG_ONE, g_msgHostagePos, {0,0,0}, i)
		write_byte(i)
		write_byte(SENTRY_RADAR_TEAMBUILT)
		write_coord(iSentryOrigin[0])
		write_coord(iSentryOrigin[1])
		write_coord(iSentryOrigin[2])
		message_end()

		message_begin(MSG_ONE, g_msgHostageK, {0,0,0}, i)
		write_byte(SENTRY_RADAR_TEAMBUILT)
		message_end()
	}
	//client_print(0, print_chat, "%s has built a sentry gun", name)
}

DecreaseSentryCount(id, sentry) 
{
	// Note that sentry does not exist at this moment, it's just an old index that should get zeroed where it occurs in g_playerSentriesEdicts[id - 1][]
	g_selectedSentry[id - 1] = -1

	for (new i = 0; i < g_playerSentries[id - 1]; i++) 
	{
		if (g_playerSentriesEdicts[id - 1][i] == sentry) 
		{
			// Copy last sentry edict index to this one
			g_playerSentriesEdicts[id - 1][i] = g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1] - 1]
			// Zero out last sentry index
			g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1] - 1] = 0
			break
		}
	}
	g_playerSentries[id - 1] = g_playerSentries[id - 1] - 1
}

/*
SetHasSentry(id, bool:trueOrFalse) 
{
	//g_hasSentries[id - 1] = trueOrFalse
	//spambits(id, g_hasSentries)
	if (trueOrFalse) 
	{
		g_hasSentries |= (1<<(id - 1))
		new name[32]
		get_user_name(id, name, 31)
		new CsTeams:builderTeam = cs_get_user_team(id)
		for (new i = 0; i < g_MAXPLAYERS; i++) 
		{
			if (!is_user_connected(i) || !is_user_alive(i) || zp_get_user_zombie(i) || cs_get_user_team(i) != builderTeam || id == i)
				continue
			client_print(i, print_center, "%s has built a sentry gun", name)
		}
	}
	
	else
		g_hasSentries &= ~(1<<(id - 1))

	//spambits(id, g_hasSentries)
}
*/

stock bool:CreateSentryBase(Float:origin[3], creator) 
{
	// Check contents of point, also trace lines from center to each of the eight ends
	if (point_contents(origin) != CONTENTS_EMPTY || TraceCheckCollides(origin, 24.0)) 
	{
		return false
	}

	// Check that a trace from origin straight down to ground results in a distance which is the same as player height over ground?
	new Float:hitPoint[3], Float:originDown[3]
	originDown = origin
	originDown[2] = -5000.0 					// dunno the lowest possible height...
	trace_line(0, origin, originDown, hitPoint)
	new Float:baDistanceFromGround = vector_distance(origin, hitPoint)
	//client_print(creator, print_chat, "Base distance from ground: %f", baDistanceFromGround)

	new Float:difference = PLAYERORIGINHEIGHT - baDistanceFromGround
	if (difference < -1 * HEIGHTDIFFERENCEALLOWED || difference > HEIGHTDIFFERENCEALLOWED) 
	{
		//client_print(creator, print_chat, "You can't build here! %f", difference)
		return false
	}

	new entbase = create_entity("func_breakable") 		// func_wall
	if (!entbase)
		return false

	// Set sentrybase health
	new healthstring[16]
	num_to_str(floatround(g_HEALTHS[0]), healthstring, 15)
	DispatchKeyValue(entbase, "health", healthstring)
	DispatchKeyValue(entbase, "material", "6")

	DispatchSpawn(entbase)
	// Change classname
	entity_set_string(entbase, EV_SZ_classname, "sentrybase")
	// Set model
	entity_set_model(entbase, "models/sentries/base.mdl")	// later set according to level
	// Set size
	new Float:mins[3], Float:maxs[3]
	mins[0] = -16.0
	mins[1] = -16.0
	mins[2] = 0.0
	maxs[0] = 16.0
	maxs[1] = 16.0
	maxs[2] = 1000.0 					// Set to 16.0 later.
	entity_set_size(entbase, mins, maxs)
	//client_print(creator, print_chat, "Creating sentry %d with bounds %f", ent, BOUNDS)
	// Set origin
	entity_set_origin(entbase, origin)
	// Set starting angle
	//entity_get_vector(creator, EV_VEC_angles, origin)
	//origin[0] = 0.0
	//origin[1] += 180.0
	//origin[2] = 0.0
	//entity_set_vector(ent, EV_VEC_angles, origin)
	// Set solidness
	entity_set_int(entbase, EV_INT_solid, SOLID_SLIDEBOX) // SOLID_SLIDEBOX
	// Set movetype
	entity_set_int(entbase, EV_INT_movetype, MOVETYPE_TOSS) // head flies, base falls

	// Set team
	entity_set_int(entbase, BASE_INT_TEAM, get_user_team(creator))

	new parms[2]
	parms[0] = entbase
	parms[1] = creator

	g_sentryOrigins[creator - 1] = origin

	emit_sound(creator, CHAN_AUTO, "sentries/building.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)

	set_task(BASESENTRYDELAY, "createsentryhead", 0, parms, 2)
	g_inBuilding[creator - 1] = true

	return true
}

public createsentryhead(parms[2]) 
{
	new entbase = parms[0]

	new creator = parms[1]
	
	if (!g_inBuilding[creator - 1]) 
	{
		// g_inBuilding is reset upon new round, then don't continue with building sentry head. Remove base and return.
		if (is_valid_ent(entbase))
			remove_entity(entbase)
		return
	}

	new Float:origin[3]
	origin = g_sentryOrigins[creator - 1]

	new ent = create_entity("func_breakable")
	
	if (!ent) 
	{
		if (is_valid_ent(entbase))
			remove_entity(entbase)
		return
	}

	new Float:mins[3], Float:maxs[3]
	// Set true	size of base... if it exists!
	// Also set sentry <-> base connections, if base still exists
	if (is_valid_ent(entbase)) 
	{
		mins[0] = -16.0
		mins[1] = -16.0
		mins[2] = 0.0
		maxs[0] = 16.0
		maxs[1] = 16.0
		maxs[2] = 16.0
		entity_set_size(entbase, mins, maxs)

		entity_set_edict(ent, SENTRY_ENT_BASE, entbase)
		entity_set_edict(entbase, BASE_ENT_SENTRY, ent)
	}

	// Store our sentry in array
	g_sentries[g_sentriesNum] = ent

	new healthstring[16]
	num_to_str(floatround(g_HEALTHS[0]), healthstring, 15)
	DispatchKeyValue(ent, "health", healthstring)
	DispatchKeyValue(ent, "material", "6")

	DispatchSpawn(ent)
	// Change classname
	entity_set_string(ent, EV_SZ_classname, "sentry")
	// Set model
	entity_set_model(ent, "models/sentries/sentry1.mdl") // later set according to level
	// Set size
	mins[0] = -16.0
	mins[1] = -16.0
	mins[2] = 0.0
	maxs[0] = 16.0
	maxs[1] = 16.0
	maxs[2] = 48.0
	entity_set_size(ent, mins, maxs)
	//client_print(creator, print_chat, "Creating sentry %d with bounds %f", ent, BOUNDS)
	// Set origin
	entity_set_origin(ent, origin)
	// Set starting angle
	entity_get_vector(creator, EV_VEC_angles, origin)
	origin[0] = 0.0
	origin[1] += 180.0
	entity_set_float(ent, SENTRY_FL_ANGLE, origin[1])
	origin[2] = 0.0
	entity_set_vector(ent, EV_VEC_angles, origin)
	// Set solidness
	entity_set_int(ent, EV_INT_solid, SOLID_SLIDEBOX) 			// SOLID_SLIDEBOX
	// Set movetype
	entity_set_int(ent, EV_INT_movetype, MOVETYPE_TOSS) 			// head flies, base doesn't
	// Set tilt of cannon
	set_pev(ent, PEV_SENTRY_TILT_TURRET, 127) 				//entity_set_byte(ent, SENTRY_TILT_TURRET, 127) // 127 is horisontal
	// Tilt of rocket launcher barrels at level 3
	set_pev(ent, PEV_SENTRY_TILT_LAUNCHER, 127) 				//entity_set_byte(ent, SENTRY_TILT_LAUNCHER, 127) // 127 is horisontal
	// Angle of small radar at level 3
	entity_set_float(ent, SENTRY_FL_RADARANGLE, 127.0)
	set_pev(ent, PEV_SENTRY_TILT_RADAR, 127) 				//entity_set_byte(ent, SENTRY_TILT_RADAR, 127) // 127 is middle


	// Set owner
	//entity_set_edict(ent, SENTRY_ENT_OWNER, creator)
	SetSentryPeople(ent, OWNER, creator)

	// Set team
	entity_set_int(ent, SENTRY_INT_TEAM, get_user_team(creator))

	// Set level (not really necessary, but for looks)
	entity_set_int(ent, SENTRY_INT_LEVEL, SENTRY_LEVEL_1)

	// Top color
	#if defined RANDOM_TOPCOLOR
	new topColor = random_num(0, 255)
	#else
	new topColor = cs_get_user_team(creator) == CS_TEAM_CT ? COLOR_TOP_CT : COLOR_TOP_T
	#endif
	// Bottom color
	#if defined RANDOM_BOTTOMCOLOR
	new bottomColor = random_num(0, 255)
	#else
	new bottomColor = cs_get_user_team(creator) == CS_TEAM_CT ? COLOR_BOTTOM_CT : COLOR_BOTTOM_T
	#endif

	// Set color
	new map = topColor | (bottomColor<<8)
	//spambits(creator, topColor)
	//spambits(creator, bottomColor)
	//spambits(creator, map)
	entity_set_int(ent, EV_INT_colormap, map)
	
	g_sentriesNum++

	emit_sound(ent, CHAN_AUTO, "sentries/turrset.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)

	IncreaseSentryCount(creator, ent)

	new parm[4]
	parm[0] = ent
	set_task(g_THINKFREQUENCIES[0], "sentry_think", TASKID_THINK + parm[0], parm, 1)

	parm[1] = random_num(0, 1)
	parm[2] = 0
	parm[3] = 0
	new directions = (random_num(0, 1)<<SENTRY_DIR_CANNON) | (random_num(0, 1)<<SENTRY_DIR_RADAR)
	entity_set_int(ent, SENTRY_INT_PENDDIR, directions)

	//entity_set_float(ent, SENTRY_FL_RADARDIR, random_num(0, 1) + 0.0)

	g_inBuilding[creator - 1] = false

	if (!is_valid_ent(entbase)) 
	{
		// Someone probably destroyed the base before head was built!
		// Sentry should go nuts. :-P
		entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NUTS)
	}
}

public server_frame() 
{
	g_gameTime = get_gametime()
	g_deltaTime = g_gameTime - g_lastGameTime
	//server_print("Gametime: %f, Last gametime: %f", g_gameTime, g_lastGameTime)

	new tempSentries[MAXSENTRIES], Float:angles[3]

	new tempSentriesNum = 0
	
	for (new i = 0; i < g_sentriesNum; i++) 
	{
		//sentry_pendulum(g_sentries[i], g_deltaTime)
		tempSentries[i] = g_sentries[i]
		tempSentriesNum++
	}

	for (new i = 0; i < tempSentriesNum; i++) 
	{
		//if (entity_get_float(tempSentries[i], EV_FL_nextthink) < g_game
		sentry_pendulum(tempSentries[i], g_deltaTime)
		
		if (entity_get_edict(tempSentries[i], SENTRY_ENT_SPYCAM) != 0) 
		{
			entity_get_vector(tempSentries[i], EV_VEC_angles, angles)
			entity_set_vector(entity_get_edict(tempSentries[i], SENTRY_ENT_SPYCAM), EV_VEC_angles, angles)
		}
	}

	g_lastGameTime = g_gameTime
	return PLUGIN_CONTINUE
}

sentry_pendulum(sentry, Float:deltaTime) 
{
	switch (entity_get_int(sentry, SENTRY_INT_FIRE)) 
	{
		case SENTRY_FIREMODE_NO: 
		{
			new Float:angles[3]
			entity_get_vector(sentry, EV_VEC_angles, angles)
			new Float:baseAngle = entity_get_float(sentry, SENTRY_FL_ANGLE)
			new directions = entity_get_int(sentry, SENTRY_INT_PENDDIR)
			
			if (directions & (1<<SENTRY_DIR_CANNON)) 
			{
				angles[1] -= (PENDULUM_INCREMENT * deltaTime) 			// PENDULUM_INCREMENT get_cvar_float("pend_inc")
				
				if (angles[1] < baseAngle - PENDULUM_MAX) 
				{
					angles[1] = baseAngle - PENDULUM_MAX
					directions &= ~(1<<SENTRY_DIR_CANNON)
					entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
				}
			}
			
			else 
			{
				angles[1] += (PENDULUM_INCREMENT * deltaTime) 			// PENDULUM_INCREMENT get_cvar_float("pend_inc")
				
				if (angles[1] > baseAngle + PENDULUM_MAX) 
				{
					angles[1] = baseAngle + PENDULUM_MAX
					directions |= (1<<SENTRY_DIR_CANNON)
					entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
				}
			}
		
			entity_set_vector(sentry, EV_VEC_angles, angles);

			if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_3) 
			{
					//new radarAngle = entity_get_byte(sentry, SENTRY_TILT_RADAR)
					//SENTRY_FL_RADARANGLE
					new Float:radarAngle = entity_get_float(sentry, SENTRY_FL_RADARANGLE)

					if (directions & (1<<SENTRY_DIR_RADAR)) 
					{
						radarAngle = radarAngle - RADAR_INCREMENT 			// get_cvar_float("radar_increment")
				
						if (radarAngle < 0.0) 
						{
							radarAngle = 0.0
							directions &= ~(1<<SENTRY_DIR_RADAR)
							entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
						}
					}
			
					else 
					{	
						radarAngle = radarAngle + RADAR_INCREMENT 			// get_cvar_float("radar_increment")
					
						if (radarAngle > 255.0) 
						{
							radarAngle = 255.0
							directions |= (1<<SENTRY_DIR_RADAR)
							entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
						}
					}
					entity_set_float(sentry, SENTRY_FL_RADARANGLE, radarAngle)
					set_pev(sentry, PEV_SENTRY_TILT_RADAR, floatround(radarAngle)) 		//entity_set_byte(sentry, SENTRY_TILT_RADAR, floatround(radarAngle))
			}
			return
	}
	case SENTRY_FIREMODE_NUTS: 
	{
		new Float:angles[3]
		entity_get_vector(sentry, EV_VEC_angles, angles)

		new Float:spinSpeed = entity_get_float(sentry, SENTRY_FL_SPINSPEED)
		
		if (entity_get_int(sentry, SENTRY_INT_PENDDIR) & (1<<SENTRY_DIR_CANNON)) 
		{
			angles[1] -= (spinSpeed * deltaTime)
			
			if (angles[1] < 0.0) 
			{
				angles[1] = 360.0 + angles[1]
			}
		}
		
		else 
		{
			angles[1] += (spinSpeed * deltaTime)
			
			if (angles[1] > 360.0) 
			{
				angles[1] = angles[1] - 360.0
			}
		}
		// Increment speed raise
		entity_set_float(sentry, SENTRY_FL_SPINSPEED, (spinSpeed += random_float(1.0, 2.0)))

		new Float:maxSpin = entity_get_float(sentry, SENTRY_FL_MAXSPIN)
		
		if (maxSpin == 0.0) 
		{
			// Set rotation speed to explode at
			entity_set_float(sentry, SENTRY_FL_MAXSPIN, maxSpin = random_float(500.0, 750.0))
			//client_print(0, print_chat, "parm3 set to %d", parm[3])
		}
		
		else if (spinSpeed >= maxSpin) 
		{
			//client_print(0, print_chat, "Detonating!")
			sentry_detonate(sentry, false, false)
			//remove_entity(parm[0])
			return
		}
		entity_set_vector(sentry, EV_VEC_angles, angles);

		return
		}
	}
}


// Checks the contents of eight points corresponding to the bbox around ent origin. Also does a trace from origin to each point. If anything goes wrong, report a hit.
// TODO: high bounds should get higher, so that building in tight places not gets sentries stuck in roof... TraceCheckCollides
bool:TraceCheckCollides(Float:origin[3], const Float:BOUNDS) 
{
	new Float:traceEnds[8][3], Float:traceHit[3], hitEnt

	// x, z, y
	traceEnds[0][0] = origin[0] - BOUNDS
	traceEnds[0][1] = origin[1] - BOUNDS
	traceEnds[0][2] = origin[2] - BOUNDS

	traceEnds[1][0] = origin[0] - BOUNDS
	traceEnds[1][1] = origin[1] - BOUNDS
	traceEnds[1][2] = origin[2] + BOUNDS

	traceEnds[2][0] = origin[0] + BOUNDS
	traceEnds[2][1] = origin[1] - BOUNDS
	traceEnds[2][2] = origin[2] + BOUNDS

	traceEnds[3][0] = origin[0] + BOUNDS
	traceEnds[3][1] = origin[1] - BOUNDS
	traceEnds[3][2] = origin[2] - BOUNDS
	//
	traceEnds[4][0] = origin[0] - BOUNDS
	traceEnds[4][1] = origin[1] + BOUNDS
	traceEnds[4][2] = origin[2] - BOUNDS

	traceEnds[5][0] = origin[0] - BOUNDS
	traceEnds[5][1] = origin[1] + BOUNDS
	traceEnds[5][2] = origin[2] + BOUNDS

	traceEnds[6][0] = origin[0] + BOUNDS
	traceEnds[6][1] = origin[1] + BOUNDS
	traceEnds[6][2] = origin[2] + BOUNDS

	traceEnds[7][0] = origin[0] + BOUNDS
	traceEnds[7][1] = origin[1] + BOUNDS
	traceEnds[7][2] = origin[2] - BOUNDS

	for (new i = 0; i < 8; i++) 
	{
		if (point_contents(traceEnds[i]) != CONTENTS_EMPTY)
			return true

		hitEnt = trace_line(0, origin, traceEnds[i], traceHit)
		
		if (hitEnt != 0)
			return true
			
		for (new j = 0; j < 3; j++) 
		{
			if (traceEnds[i][j] != traceHit[j])
				return true
		}
	}

	return false
}


//#define TE_TRACER				6		// tracer effect from point to point
// coord, coord, coord (start)
// coord, coord, coord (end)

tracer(Float:start[3], Float:end[3]) 
{
	//new start_[3]
	new start_[3], end_[3]
	FVecIVec(start, start_)
	FVecIVec(end, end_)
	message_begin(MSG_BROADCAST, SVC_TEMPENTITY) 		//  MSG_PAS MSG_BROADCAST
	write_byte(TE_TRACER)
	write_coord(start_[0])
	write_coord(start_[1])
	write_coord(start_[2])
	write_coord(end_[0])
	write_coord(end_[1])
	write_coord(end_[2])
	message_end()
}

/*
#define TE_BREAKMODEL				108		// box of models or sprites
// coord, coord, coord (position)
// coord, coord, coord (size)
// coord, coord, coord (velocity)
// byte (random velocity in 10's)
// short (sprite or model index)
// byte (count)
// byte (life in 0.1 secs)
// byte (flags)
*/

stock create_explosion(Float:origin_[3]) 
{
	new origin[3]
	FVecIVec(origin_, origin)
	//client_print(0, print_chat, "Creating explosion at %d %d %d", origin[0], origin[1], origin[2])

	message_begin(MSG_BROADCAST, SVC_TEMPENTITY, origin) // MSG_PAS not really good here
	write_byte(TE_EXPLOSION)
	write_coord(origin[0])
	write_coord(origin[1])
	write_coord(origin[2])
	write_short(g_sModelIndexFireball)
	write_byte(random_num(0, 20) + 50) // scale * 10 // random_num(0, 20) + 20
	write_byte(12) // framerate
	write_byte(TE_EXPLFLAG_NONE)
	message_end()

	// Blast stuff away
	genericShock(origin_, SENTRYEXPLODERADIUS, "weaponbox", 32, SENTRYSHOCKPOWER, OBJECT_GENERIC)
	genericShock(origin_, SENTRYEXPLODERADIUS, "armoury_entity", 32, SENTRYSHOCKPOWER, OBJECT_ARMOURY)
	genericShock(origin_, SENTRYEXPLODERADIUS, "player", 32, SENTRYSHOCKPOWER, OBJECT_PLAYER)
	genericShock(origin_, SENTRYEXPLODERADIUS, "grenade", 32, SENTRYSHOCKPOWER, OBJECT_GRENADE)
	genericShock(origin_, SENTRYEXPLODERADIUS, "hostage_entity", 32, SENTRYSHOCKPOWER, OBJECT_GENERIC)

	// Hurt ppl in vicinity

	new Float:playerOrigin[3], Float:distance, Float:flDmgToDo, Float:dmgbase = DMG_EXPLOSION_TAKE + 0.0, newHealth
	for (new i = 1; i <= g_MAXPLAYERS; i++) 
	{
		if (!is_user_alive(i) || !zp_get_user_zombie(i) || get_user_godmode(i))
			continue

		entity_get_vector(i, EV_VEC_origin, playerOrigin)
		distance = vector_distance(playerOrigin, origin_)
	
		if (distance <= SENTRYEXPLODERADIUS) 
		{
			flDmgToDo = dmgbase - (dmgbase * (distance / SENTRYEXPLODERADIUS))
			//client_print(i, print_chat, "flDmgToDo = %f, dmgbase = %f, distance = %f, SENTRYEXPLODERADIUS = %f", flDmgToDo, dmgbase, distance, SENTRYEXPLODERADIUS)
			newHealth = get_user_health(i) - floatround(flDmgToDo)
		
			if (newHealth <= 0) 
			{
				// Somehow if player is killed here server crashes out saying some message (Damage or Death) has not been sent yet when trying to send another message.
				// By delaying death with 0.0 (huuh?) seconds this seems to be fixed.
				set_task(0.0, "TicketToHell", i)
				continue
			}

			set_user_health(i, newHealth)

			message_begin(MSG_ONE_UNRELIABLE, g_msgDamage, {0,0,0}, i)
			write_byte(floatround(flDmgToDo))
			write_byte(floatround(flDmgToDo))
			write_long(DMG_BLAST)
			write_coord(origin[0])
			write_coord(origin[1])
			write_coord(origin[2])
			message_end()
		}
	}
}

// Hacks, damn you!
public TicketToHell(player) 
{
	if (!is_user_connected(player))
		return
		
	new frags = get_user_frags(player)
	user_kill(player, 1) 					// don't decrease frags
	new parms[4]
	parms[0] = player
	parms[1] = frags
	parms[2] = cs_get_user_deaths(player)
	parms[3] = int:cs_get_user_team(player)
	set_task(0.0, "DelayedScoreInfoUpdate", 0, parms, 4)
}

public DelayedScoreInfoUpdate(parms[4]) 
{
	scoreinfo_update(parms[0], parms[1], parms[2], parms[3])
}

stock genericShock(Float:hitPointOrigin[3], Float:radius, classString[], maxEntsToFind, Float:power, OBJECTTYPE:objecttype) 
{ // bool:isthisplayer, bool:isthisarmouryentity, bool:isthisgrenade/*, Float:pullup*/) {
	new entList[32]
	if (maxEntsToFind > 32)
		maxEntsToFind = 32

	new entsFound = find_sphere_class(0, classString, radius, entList, maxEntsToFind, hitPointOrigin)

	new Float:entOrigin[3]
	new Float:velocity[3]
	new Float:cOrigin[3]

	for (new j = 0; j < entsFound; j++) 
	{
		switch (objecttype) 
		{
			case OBJECT_PLAYER: 
			{
				if (!is_user_alive(entList[j])) 		// Don't move dead players
					continue
			}
			
			case OBJECT_GRENADE: 
			{
				new l_model[16]
				entity_get_string(entList[j], EV_SZ_model, l_model, 15)
				
				if (equal(l_model, "models/w_c4.mdl")) 						// don't move planted c4s :-P
					continue
			}
		}
		entity_get_vector(entList[j], EV_VEC_origin, entOrigin) // get_entity_origin(entList[j],entOrigin)

		new Float:distanceNadePl = vector_distance(entOrigin, hitPointOrigin)

		// Stuff on ground AND below explosion are "placed" a distance above explosion Y-wise ([2]), so that they fly off ground etc.
		if (entity_is_on_ground(entList[j]) && entOrigin[2] < hitPointOrigin[2])
		entOrigin[2] = hitPointOrigin[2] + distanceNadePl

		entity_get_vector(entList[j], EV_VEC_velocity, velocity)

		cOrigin[0] = (entOrigin[0] - hitPointOrigin[0]) * radius / distanceNadePl + hitPointOrigin[0]
		cOrigin[1] = (entOrigin[1] - hitPointOrigin[1]) * radius / distanceNadePl + hitPointOrigin[1]
		cOrigin[2] = (entOrigin[2] - hitPointOrigin[2]) * radius / distanceNadePl + hitPointOrigin[2]

		velocity[0] += (cOrigin[0] - entOrigin[0]) * power
		velocity[1] += (cOrigin[1] - entOrigin[1]) * power
		velocity[2] += (cOrigin[2] - entOrigin[2]) * power

		entity_set_vector(entList[j], EV_VEC_velocity, velocity)

	}
}

stock entity_is_on_ground(entity) 
{
	return entity_get_int(entity, EV_INT_flags) & FL_ONGROUND
}


public message_tempentity() 
{
	if (get_msg_args() != 15 && get_msg_arg_int(1) != TE_BREAKMODEL)
		return PLUGIN_CONTINUE

	// Something broke, maybe it was one of our sentries. Loop through all sentries to see if any of them has health <=0.
	for (new i = 0; i < g_sentriesNum; i++) 
	{
		if (entity_get_float(g_sentries[i], EV_FL_health) <= 0.0) 
		{
			//server_cmd("amx_box %d", g_sentries[i])
			sentry_detonate(i, false, true)

			//origin[0] = get_msg_arg_float(2)
			//origin[1] = get_msg_arg_float(3)
			//origin[2] = get_msg_arg_float(4)

			// Rewind iteration loop; the last sentry may have been destroyed also
			i--
		}
	}

	return PLUGIN_CONTINUE
}

/*
public think_sentry(ent) 
{
	// Hmm this place can be used to tell when a sentry breaks...

	sentry_detonate(ent, false)
	// All of these always give 0 values :-(
	//client_print(0, print_chat, "%d thinks: inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))

	return PLUGIN_CONTINUE
}
*/
public think_sentrybase(sentrybase) 
{
	// Hmm this place can be used to tell when a sentrybase breaks...

	sentrybase_broke(sentrybase)
	//sentry_detonate(ent, false)

	// All of these always give 0 values :-(
	//client_print(0, print_chat, "%d thinks: inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))

	return PLUGIN_CONTINUE
}

sentrybase_broke(sentrybase) 
{
	new sentry = entity_get_edict(sentrybase, BASE_ENT_SENTRY)
	if (is_valid_ent(sentrybase))
		remove_entity(sentrybase)

	// Sentry could be 0 which should mean it has not been built yet. No need to do anything in that case.
	if (sentry == 0)
		return

	entity_set_int(sentry, SENTRY_INT_FIRE, SENTRY_FIREMODE_NUTS)
	// Set cannon tower straight, calculate tower tilt offset to angles later... entityviewhitpoint fn needs changing for this to use a custom angle vector
	set_pev(sentry, PEV_SENTRY_TILT_TURRET, 127) 						//entity_set_byte(sentry, SENTRY_TILT_TURRET, 127)
}

sentry_detonate(sentry, bool:quiet, bool:isIndex) 
{
	// Explode!
	new i
	
	if (isIndex) 
	{
		i = sentry
		sentry = g_sentries[sentry]
		if (!is_valid_ent(sentry))
			return
	}
	
	else 
	{
		if (!is_valid_ent(sentry))
			return
		// Find index of this sentry
		for (new j = 0; j < g_sentriesNum; j++) 
		{
			if (g_sentries[j] == sentry) 
			{
				i = j
				break
			}
		}
	}

	// Kill tasks
	remove_task(TASKID_THINK + sentry) 					// removes think
	remove_task(TASKID_THINKPENDULUM + sentry) 				// removes think
	remove_task(TASKID_SENTRYONRADAR + sentry) 				// in case someone's displaying this on radar

	new owner = GetSentryPeople(sentry, OWNER)

	// If sentry has a spycam, call the stuff to remove it now
	if (entity_get_edict(sentry, SENTRY_ENT_SPYCAM) != 0) 
	{
		remove_task(TASKID_SPYCAM + owner) 				// remove the ongoing task...
		// And call this now on our own...
		new parms[3]
		parms[0] = owner
		parms[1] = entity_get_edict(sentry, SENTRY_ENT_SPYCAM)
		parms[2] = sentry
		DestroySpyCam(parms)
	}

	if (!quiet) 
	{
		#if defined EXPLODINGSENTRIES
		new Float:origin[3]
		entity_get_vector(sentry, EV_VEC_origin, origin)
		create_explosion(origin)
		#endif

		// Report to owner that it broke
		client_print(owner, print_center, "Your sentry detonated!")
	}
	DecreaseSentryCount(owner, sentry)
	//SetHasSentry(GetSentryPeople(sentry, OWNER), false)

	// Remove base first
	//server_cmd("amx_entinfo %d", ent)
	if (entity_get_int(sentry, SENTRY_INT_FIRE) != SENTRY_FIREMODE_NUTS)
	set_task(0.0, "delayedremovalofentity", entity_get_edict(sentry, SENTRY_ENT_BASE))
	//remove_entity(entity_get_edict(g_sentries[i], SENTRY_ENT_BASE))
	// Remove this entity
	//server_cmd("amx_entinfo %d", ent)
	set_task(0.0, "delayedremovalofentity", sentry)
	//remove_entity(g_sentries[i])
	// Put the last sentry in the deleted entity's place
	g_sentries[i] = g_sentries[g_sentriesNum - 1]
	// Lower nr of sentries
	g_sentriesNum--
}

public delayedremovalofentity(entity) 
{
	if (!is_valid_ent(entity)) 
	{
		//client_print(0, print_chat, "Was gonna remove %d, but it's not valid", entity)
		return
	}
	//client_print(0, print_chat, "removing %d", entity)
	remove_entity(entity)
}

sentry_detonate_by_owner(owner, bool:quiet = false) 
{
/*
for (new i = g_MAXPLAYERS + 1, classname[7]; i < g_MAXENTITIES; i++) 
{
	if (!is_valid_ent(i))
		continue
	entity_get_string(i, EV_SZ_classname, classname, 6)
	if (!equal(classname, "sentry"))
		continue

	if (entity_get_edict(i, SENTRY_ENT_OWNER) == owner) 
	{
		sentry_detonate(i, quiet)
		return
	}
}
*/

	for(new i = 0; i < g_sentriesNum; i++) 
	{
		if (GetSentryPeople(g_sentries[i], OWNER) == owner) 
		{
			sentry_detonate(i, quiet, true)
			break
		}
	}
}

public client_disconnect(id) 
{
	while (GetSentryCount(id) > 0)
		sentry_detonate_by_owner(id)
}

public sentry_think(parm[1]) 
{
	if (!is_valid_ent(parm[0])) 
	{
		client_print(0, print_chat, "%d is not a valid ent, ending sentry_think!", parm[0])
		return
	}

	new ent = parm[0]

	new Float:sentryOrigin[3], Float:hitOrigin[3], hitent
	entity_get_vector(ent, EV_VEC_origin, sentryOrigin)
	sentryOrigin[2] += CANNONHEIGHTFROMFEET 					// Move up some, this should be the Y origin of the cannon

	// If fire, do a trace and fire
	new firemode = entity_get_int(ent, SENTRY_INT_FIRE)
	
	new target = entity_get_edict(ent, SENTRY_ENT_TARGET)
	if (firemode == SENTRY_FIREMODE_YES && is_valid_ent(target) && zp_get_user_zombie(target)   != entity_get_int(ent, SENTRY_INT_TEAM)) 
	{ 	// temp removed team check:  && get_user_team(target) != entity_get_int(ent, SENTRY_INT_TEAM)
		new sentryLevel = entity_get_int(ent, SENTRY_INT_LEVEL)

		// Is target still visible?
		new Float:targetOrigin[3]
		entity_get_vector(target, EV_VEC_origin, targetOrigin)

		// Adjust for ducking. This is still not 100%. :-(
		if (entity_get_int(target, EV_INT_flags) & FL_DUCKING) 
		{
			//client_print(0, print_chat, "%d: Target %d is ducking, moving its origin up by %f", ent, target, TARGETUPMODIFIER)
			targetOrigin[2] += TARGETUPMODIFIER
		}

		hitent = trace_line(ent, sentryOrigin, targetOrigin, hitOrigin)
		if (hitent == entity_get_edict(ent, SENTRY_ENT_BASE)) 
		{
			// We traced into our base, do another trace from there
			hitent = trace_line(hitent, hitOrigin, targetOrigin, hitOrigin)
			//client_print(0, print_chat, "%d: I first hit my own base, and after doing another trace I hit %d, target: %d", ent, hitent, target)
		}

		if (hitent != target && is_user_alive(hitent) && zp_get_user_zombie(hitent) || !zp_get_user_zombie(hitent) && zp_get_user_zombie(hitent) && entity_get_int(ent, SENTRY_INT_TEAM) != zp_get_user_zombie(hitent)) 
		{
			// Another new enemy target got into scope, pick this new enemy as a new target...
			target = hitent
			entity_set_edict(ent, SENTRY_ENT_TARGET, hitent)
		}
		
		if (hitent == target) 
		{
			// Fire here
			//client_print(0, print_chat, "%d: I see %d, will fire. Dist: %f, Hitorigin: %f %f %f", ent, hitent, dist, hitOrigin[0], hitOrigin[1], hitOrigin[2])
			sentry_turntotarget(ent, sentryOrigin, target, targetOrigin)
			// Firing sound
			emit_sound(ent, CHAN_WEAPON, "weapons/m249-1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)

			new Float:hitRatio = random_float(0.0, 1.0) - g_HITRATIOS[sentryLevel]		// ie 0.5 - 0.7 = -0.2, a hit and 0.8 - 0.7 = a miss by 0.1

			if (!get_user_godmode(target) && hitRatio <= 0.0) 
			{
				// Do damage to player
				sentry_damagetoplayer(ent, sentryLevel, sentryOrigin, target)
				// Tracer effect to target
			}
			
			else 
			{
				// Tracer hitOrigin adjusted for miss...
				/*
				MAKE_VECTORS(pEnt->v.v_angle);
				vVector = gpGlobals->v_forward * iVelocity;

				vRet[0] = amx_ftoc(vVector.x);
				vRet[1] = amx_ftoc(vVector.y);
				vRet[2] = amx_ftoc(vVector.z);
				*/
				new Float:sentryAngle[3] = {0.0, 0.0, 0.0}

				new Float:x = hitOrigin[0] - sentryOrigin[0]
				new Float:z = hitOrigin[1] - sentryOrigin[1]
				new Float:radians = floatatan(z/x, radian)
				sentryAngle[1] = radians * g_ONEEIGHTYTHROUGHPI
				
				if (hitOrigin[0] < sentryOrigin[0])
					sentryAngle[1] -= 180.0

				new Float:h = hitOrigin[2] - sentryOrigin[2]
				new Float:b = vector_distance(sentryOrigin, hitOrigin)
				radians = floatatan(h/b, radian)
				sentryAngle[0] = radians * g_ONEEIGHTYTHROUGHPI;

				sentryAngle[0] += random_float(-10.0 * hitRatio, 10.0 * hitRatio) 		// aim is a little off here :-)
				sentryAngle[1] += random_float(-10.0 * hitRatio, 10.0 * hitRatio) 		// aim is a little off here :-)
				engfunc(EngFunc_MakeVectors, sentryAngle)
				new Float:vector[3]
				get_global_vector(GL_v_forward, vector)
				for (new i = 0; i < 3; i++)
					vector[i] *= 1000;

				new Float:traceEnd[3]
				for (new i = 0; i < 3; i++)
					traceEnd[i] = vector[i] + sentryOrigin[i]

				new hitEnt = ent
				while((hitEnt = trace_line(hitEnt, hitOrigin, traceEnd, hitOrigin))) 
				{
					// continue tracing until hit nothing...
				}

				//for (new i = 0; i < 3; i++)
					//hitOrigin[i] += random_float(-5.0, 5.0)
			}
			tracer(sentryOrigin, hitOrigin)

			// Don't do any more here
			//set_task(THINKFIREFREQUENCY, "sentry_think", ent)
			set_task(THINKFIREFREQUENCY, "sentry_think", TASKID_THINK + parm[0], parm, 1)
			return
		}
		else 
		{
			//client_print(target, print_chat, "%d: Lost track of you! Hit: %d", ent, target, hitent)
			//client_print(0, print_chat, "%d: I can't see %d, i see %d... will not fire. Dist: %f, Hitorigin: %f %f %f", ent, entity_get_edict(ent, SENTRY_ENT_TARGET), hitent, dist, hitOrigin[0], hitOrigin[1], hitOrigin[2])
			// Else target isn't still visible, unset fire state.
			entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NO)
			// vvvv - Not really necessary but it's cleaner. Leave it out for now and be sure to set a fresh target each time SENTRY_INT_FIRE is set to 1!!!
			// vvvv - Else this is breaking this think altogether! :-(
			//entity_set_edict(ent, SENTRY_ENT_TARGET, 0)

			// Don't return here, continue with searching for targets below...
		}
	}
	
	else if (firemode == SENTRY_FIREMODE_NUTS) 
	{
		//client_print(0, print_chat, "Gone nuts firing... spin speed: %f", entity_get_float(ent, SENTRY_FL_SPINSPEED))
		new hitEnt = entityviewhitpoint(ent, sentryOrigin, hitOrigin)
		// Firing sound
		emit_sound(ent, CHAN_WEAPON, "weapons/m249-1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
		// Tracer effect
		tracer(sentryOrigin, hitOrigin)

		if (is_user_connected(hitEnt) && is_user_alive(hitEnt) || !zp_get_user_zombie(hitEnt) && zp_get_user_zombie(hitEnt) && !get_user_godmode(hitEnt)) 
		{
			// Do damage to player
			sentry_damagetoplayer(ent, entity_get_int(ent, SENTRY_INT_LEVEL), sentryOrigin, hitEnt)
		}

		// Don't do any more here
		set_task(THINKFIREFREQUENCY, "sentry_think", TASKID_THINK + parm[0], parm, 1)
		return
	}
	
	else 
	{
		//client_print(0, print_chat, "My firemode: %d", firemode)
		// Either wasn't meant to fire or target was not a valid entity or dead, set both to 0.
		//client_print(target, print_chat, "%d: Fire: %d Target: %d (%s)", ent, entity_get_int(ent, SENTRY_INT_FIRE), target, is_valid_ent(target) ? (is_user_alive(target) ? "alive" : "dead") : "invalid")

		//entity_set_int(ent, SENTRY_INT_FIRE, 0)
		//entity_set_edict(ent, SENTRY_ENT_TARGET, 0)
	}

	// Tell what players you see
	if (random_num(0, 99) < 10)
		emit_sound(ent, CHAN_AUTO, "sentries/turridle.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)

	new closestTarget = 0, Float:closestDistance, Float:distance, Float:closestOrigin[3], Float:playerOrigin[3], sentryTeam = entity_get_int(ent, SENTRY_INT_TEAM)
	for (new i = 1; i <= g_MAXPLAYERS; i++) 
	{
		if (!is_user_connected(i) || !is_user_alive(i) || !zp_get_user_zombie(i) || get_user_team(i) == sentryTeam) // temporarily dont check team:  || get_user_team(i) == sentryTeam
			continue

		entity_get_vector(i, EV_VEC_origin, playerOrigin)

		// Adjust for ducking. This is still not 100%. :-(
		if (entity_get_int(i, EV_INT_flags) & FL_DUCKING) 
		{
			//client_print(0, print_chat, "%d: Target %d is ducking, moving its origin up by %f", ent, target, TARGETUPMODIFIER)
			playerOrigin[2] += TARGETUPMODIFIER
		}

		//playerOrigin[2] += TARGETUPMODIFIER

		hitent = trace_line(ent, sentryOrigin, playerOrigin, hitOrigin)
		
		if (hitent == entity_get_edict(ent, SENTRY_ENT_BASE)) 
		{
			// We traced into our base, do another trace from there
			hitent = trace_line(hitent, hitOrigin, playerOrigin, hitOrigin)
			//client_print(0, print_chat, "%d (scanning): I first hit my own base, and after doing another trace I hit %d, target: %d", ent, hitent, i)
		}
		//client_print(0, print_chat, "%d: t: %f %f %f - %f %f %f - %f %f %f, i: %d hitent: %d", ent, sentryOrigin[0], sentryOrigin[1], sentryOrigin[2], playerOrigin[0], playerOrigin[1], playerOrigin[2], hitOrigin[0], hitOrigin[1], hitOrigin[2], i, hitent)
		if (hitent == i) 
		{
			//len += format(seethese[len], 63 - len, "%d,", hitent)

			distance = vector_distance(sentryOrigin, playerOrigin)
			closestOrigin = playerOrigin

			if (distance < closestDistance || closestTarget == 0) 
			{
				closestTarget = i
				closestDistance = distance
			}
		}
	}

	if (closestTarget) 
	{
		// We found a target, play sound and turn to target
		emit_sound(ent, CHAN_AUTO, "sentries/turrspot.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
		sentry_turntotarget(ent, sentryOrigin, closestTarget, closestOrigin)

		// Set to fire mode and set target (always set also a new target when setting fire mode 1!!!)
		entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_YES)
		entity_set_edict(ent, SENTRY_ENT_TARGET, closestTarget)
		// Set radar straight...
		entity_set_float(ent, SENTRY_FL_RADARANGLE, 127.0)
		set_pev(ent, PEV_SENTRY_TILT_RADAR, 127)
	}
	
	else
		entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NO)

	//set_task(g_THINKFREQUENCIES[entity_get_int(ent, SENTRY_INT_LEVEL)], "sentry_think", ent)
	//client_print(0, print_chat, "%d: my inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))
	set_task(g_THINKFREQUENCIES[entity_get_int(ent, SENTRY_INT_LEVEL)], "sentry_think", TASKID_THINK + parm[0], parm, 1)
}

stock sentry_damagetoplayer(sentry, sentryLevel, Float:sentryOrigin[3], target) 
{
	new newHealth = get_user_health(target) - g_DMG[sentryLevel]

	if (newHealth <= 0) 
	{
		new targetFrags = get_user_frags(target) + 1
		new owner = GetSentryPeople(sentry, OWNER)
		new ownerFrags = get_user_frags(owner) + 1
		set_user_frags(target, targetFrags) // otherwise frags are subtracted from victim for dying (!!)
		set_user_frags(owner, ownerFrags)
		// Give money to player here
		new contributors[3], moneyRewards[33] = {0, ...}
		contributors[0] = owner
		contributors[1] = GetSentryPeople(sentry, UPGRADER_1)
		contributors[2] = GetSentryPeople(sentry, UPGRADER_2)
		for (new i = SENTRY_LEVEL_1; i <= sentryLevel; i++) 
		{
			moneyRewards[contributors[i]] += g_SENTRYFRAGREWARDS[i]
		}
	
		for (new i = 1; i <= g_MAXPLAYERS; i++) 
		{
			if (moneyRewards[i] && is_user_connected(i) && !zp_get_user_zombie(i))
			{
				zp_set_user_ammo_packs(i, zp_get_user_ammo_packs(i) + moneyRewards[i])
			}
		}

		message_begin(MSG_ALL, g_msgDeathMsg, {0, 0, 0} ,0)
		write_byte(owner)
		write_byte(target)
		write_byte(0)
		write_string("sentry gun")
		message_end()

		scoreinfo_update(owner, ownerFrags, cs_get_user_deaths(owner), int:cs_get_user_team(owner))
		//scoreinfo_update(target, targetFrags, targetDeaths, targetTeam) // dont need to update frags of victim, because it's done after set_user_health

		set_msg_block(g_msgDeathMsg, BLOCK_ONCE)
	}

	set_user_health(target, newHealth)

	message_begin(MSG_ONE_UNRELIABLE, g_msgDamage, {0,0,0}, target) //
	write_byte(g_DMG[sentryLevel]) // write_byte(DMG_SAVE)
	write_byte(g_DMG[sentryLevel])
	write_long(DMG_BULLET)
	write_coord(floatround(sentryOrigin[0]))
	write_coord(floatround(sentryOrigin[1]))
	write_coord(floatround(sentryOrigin[2]))
	message_end()
}

scoreinfo_update(id, frags, deaths, team) 
{
	// Send msg to update ppls scoreboards.
	/*
	MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo );
		WRITE_BYTE( params[1] );
		WRITE_SHORT( pPlayer->v.frags );
		WRITE_SHORT( params[2] );
		WRITE_SHORT( 0 );
		WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 );
	MESSAGE_END();

*/
	message_begin(MSG_ALL, g_msgScoreInfo)
	write_byte(id)
	write_short(frags)
	write_short(deaths)
	write_short(0)
	write_short(team)
	message_end()
}

sentry_turntotarget(ent, Float:sentryOrigin[3], target, Float:closestOrigin[3]) 
{
	if (target) 
	{
		new name[32]
		get_user_name(target, name, 31)

		// Alter ent's angle
		new Float:newAngle[3]
		entity_get_vector(ent, EV_VEC_angles, newAngle)
		new Float:x = closestOrigin[0] - sentryOrigin[0]
		new Float:z = closestOrigin[1] - sentryOrigin[1]
		//new Float:y = closestOrigin[2] - sentryOrigin[2]
		/*
		//newAngle[0] = floatasin(x/floatsqroot(x*x+y*y), degrees)
		newAngle[1] = floatasin(z/floatsqroot(x*x+z*z), degrees)
		*/

		new Float:radians = floatatan(z/x, radian)
		newAngle[1] = radians * g_ONEEIGHTYTHROUGHPI
		if (closestOrigin[0] < sentryOrigin[0])

			newAngle[1] -= 180.0

		entity_set_float(ent, SENTRY_FL_ANGLE, newAngle[1])
		// Tilt is handled thorugh the EV_BYTE_controller1 member. 0-255 are the values, 127ish should be horisontal aim, 255 is furthest down (about 50 degrees off)
		// and 0 is also about 50 degrees up. Scope = ~100 degrees
	
		// Set tilt
		new Float:h = closestOrigin[2] - sentryOrigin[2]
		new Float:b = vector_distance(sentryOrigin, closestOrigin)
		radians = floatatan(h/b, radian)
		new Float:degs = radians * g_ONEEIGHTYTHROUGHPI;
		// Now adjust EV_BYTE_controller1
		// Each degree corresponds to about 100/256 "bytes", = ~0,39 byte / degree (ok this is not entirely true, just tweaked for now with SENTRYTILTRADIUS)
		new Float:RADIUS = SENTRYTILTRADIUS 					// get_cvar_float("sentry_tiltradius");
		new Float:degreeByte = RADIUS/256.0; 					// tweak radius later
		new Float:tilt = 127.0 - degreeByte * degs; 				// 127 is center of 256... well, almost
		//client_print(GetSentryPeople(ent, OWNER), print_chat, "%d: Setting tilt to %d", ent, floatround(tilt))
		set_pev(ent, PEV_SENTRY_TILT_TURRET, floatround(tilt)) 			//entity_set_byte(ent, SENTRY_TILT_TURRET, floatround(tilt))
		entity_set_vector(ent, EV_VEC_angles, newAngle)
	}
	
	else 
	{
		//entity_set_int(ent, SENTRY_INT_FIRE, 0)
		//entity_set_edict(ent, SENTRY_ENT_TARGET, 0)
		//client_print(0, print_chat, "%d: I don't see anyone.", ent)
	}
}

public menumain(id) 
{
	if (!is_user_alive(id) && !zp_get_user_zombie(id))
		return PLUGIN_HANDLED

	menumain_starter(id)

	return PLUGIN_HANDLED
}

AimingAtSentry(id, bool:alwaysReturn = false) 
{
	//new Float:hitOrigin[3]
	//new hitEnt = userviewhitpoint(id, hitOrigin)
	new hitEnt, bodyPart
	//
	if (get_user_aiming(id, hitEnt, bodyPart) == 0.0)
		return 0

	//if (get_user_aiming_func(id, hitEnt, bodyPart) == 0.0)
		//return 0

	new sentry = 0
	while (hitEnt) 
	{
		new classname[32], l_sentry
		entity_get_string(hitEnt, EV_SZ_classname, classname, 31)
		if (equal(classname, "sentry_base"))
			l_sentry = entity_get_edict(hitEnt, BASE_ENT_SENTRY)
	
		else if (equal(classname, "sentry"))
			l_sentry = hitEnt
		else
			break

		if (alwaysReturn)
			return l_sentry

		new sentryLevel = entity_get_int(l_sentry, SENTRY_INT_LEVEL)
		new owner = GetSentryPeople(l_sentry, OWNER)
	
		if (cs_get_user_team(owner) == cs_get_user_team(id) && sentryLevel < 2) 
		{
		
			#if defined DISALLOW_OWN_UPGRADES
			// Don't allow builder to upgrade his own sentry first time.
			if (sentryLevel == SENTRY_LEVEL_1 && id == owner)
				break
			#endif
			#if defined DISALLOW_TWO_UPGRADES
			// Don't allow upgrader to upgrade again.
			if (sentryLevel == SENTRY_LEVEL_2 && id == GetSentryPeople(l_sentry, UPGRADER_1))
				break
			#endif
			sentry = l_sentry
		}
		break
	}

	return sentry
}

menumain_starter(id) 
{
	if (g_inSpyCam[id - 1])
		return

	g_aimSentry[id - 1] = 0
	new menuBuffer[256], len = 0, flags = MENUBUTTON0
	len += format(menuBuffer[len], 255 - len, "\ySentry gun menu^n^n")
	
	len += format(menuBuffer[len], 255 - len, "%s1. Build sentry, %d^n ammo packs", GetSentryCount(id) < MAXPLAYERSENTRIES && zp_get_user_ammo_packs(id) >= g_COST(0) ? "\w" : "\d", g_COST(0))

	//if (GetSentryCount(id) == 1)
		//g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][0]
	//g_selectedSentry[id - 1] = GetClosestSentry(id)
	if (GetSentryCount(id) > 0 && g_selectedSentry[id - 1] == -1)
		g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][0]
	// g_playerSentriesEdicts[id - 1]

	if (g_selectedSentry[id - 1]) 
	{
		new parm[2]
		parm[0] = id
		parm[1] = g_selectedSentry[id - 1]
		set_task(0.0, "SentryRadarBlink", TASKID_SENTRYONRADAR + g_selectedSentry[id - 1], parm, 2)
	}

	//len += format(menuBuffer[len], 255 - len, "%s2. Detonate %ssentry^n", GetSentryCount(id) > 0 ? "\w" : "\d", GetSentryCount(id) > 1 ? "closest " : "")
	len += format(menuBuffer[len], 255 - len, "%s2. Detonate sentry flashing on radar^n", GetSentryCount(id) > 0 ? "\w" : "\d")

	while (len) 
	{
		new sentry = AimingAtSentry(id)
		
		if (!sentry)
			break
	
		new sentryLevel = entity_get_int(sentry, SENTRY_INT_LEVEL)

		if (entity_range(sentry, id) <= MAXUPGRADERANGE) 
		{
			if (zp_get_user_ammo_packs(id) >= g_COST(sentryLevel + 1)) 
			{
				len += format(menuBuffer[len], 255 - len, "\w3. Upgrade this sentry, %d^n ammo packs", g_COST(sentryLevel + 1))
				flags |= MENUBUTTON3
				g_aimSentry[id - 1] = sentry
			}
			else
				len += format(menuBuffer[len], 255 - len, "\d3. Upgrade this sentry (needs %d)^n ammo packs", g_COST(sentryLevel + 1))
		}
		
		else
			len += format(menuBuffer[len], 255 - len, "\d3. Upgrade this sentry, %d (out of range)^n ammo packs", g_COST(sentryLevel + 1))
	//}

		break
	}
	
	if (GetSentryCount(id) > 1) 
	{
		len += format(menuBuffer[len], 255 - len, "\w4. Detonate all sentries^n")
		len += format(menuBuffer[len], 255 - len, "^n\w5. Select previous sentry^n")
		len += format(menuBuffer[len], 255 - len, "\w6. Select next sentry^n")
		flags |= MENUBUTTON4 | MENUBUTTON5 | MENUBUTTON6
	}

	len += format(menuBuffer[len], 255 - len, "%s7. View from sentry flashing on radar^n", g_selectedSentry[id - 1] != -1 ? "\w" : "\d")
	if (g_selectedSentry[id - 1] != -1)
		flags |= MENUBUTTON7

	//len += format(menuBuffer[len], 255 - len, "%s4. View from sentry^n", HasSentry(id) ? "\w" : "\d")

	len += format(menuBuffer[len], 255 - len, "^n\w0. Exit")

	if (GetSentryCount(id) > 0) 
	{
		flags |= MENUBUTTON2
	}

	if (GetSentryCount(id) < MAXPLAYERSENTRIES && zp_get_user_ammo_packs(id) >= g_COST(SENTRY_LEVEL_1))
		flags |= MENUBUTTON1

	show_menu(id, flags, menuBuffer)
}

public SentryRadarBlink(parm[2]) 
{
	// 0 = player
	// 1 = sentry
	if (!is_user_connected(parm[0]) || !is_valid_ent(parm[1]))
		return

	new Float:sentryOrigin[3]
	entity_get_vector(parm[1], EV_VEC_origin, sentryOrigin)
	//client_print(parm[0], print_chat, "Plotting closest sentry %d on radar: %f %f %f", parm[1], sentryOrigin[0], sentryOrigin[1], sentryOrigin[2])
	message_begin(MSG_ONE, g_msgHostagePos, {0,0,0}, parm[0])
	write_byte(parm[0])
	write_byte(SENTRY_RADAR)
	write_coord(floatround(sentryOrigin[0]))
	write_coord(floatround(sentryOrigin[1]))
	write_coord(floatround(sentryOrigin[2]))
	message_end()

	message_begin(MSG_ONE, g_msgHostageK, {0,0,0}, parm[0])
	write_byte(SENTRY_RADAR)
	message_end()

	new usermenuid, keys
	get_user_menu(parm[0], usermenuid, keys)
	if (g_menuId == usermenuid)
		set_task(1.5, "SentryRadarBlink", TASKID_SENTRYONRADAR + parm[1], parm, 2)
}

stock GetClosestSentry(id) 
{
	// Find closest sentry
	new sentry = 0, closestSentry = 0, Float:closestDistance, Float:distance
	while ((sentry = find_ent_by_class(sentry, "sentry"))) 
	{
		if (GetSentryPeople(sentry, OWNER) != id)
			continue

		distance = entity_range(id, sentry)
		if (distance < closestDistance || closestSentry == 0) 
		{
			closestSentry = sentry
			closestDistance = distance
		}
	}

	return closestSentry
}

public menumain_handle(id, key) 
{
	new bool:stayInMenu = false
	switch (key) 
	{
		case MENUSELECT1: 
		{
			// Build if still not has
			if (GetSentryCount(id) < MAXPLAYERSENTRIES) 
			{
				sentry_build(id)
			}
			/*
			else 
			{
				new numwords[128]
				getnumbers(MAXPLAYERSENTRIES, numwords, MAXPLAYERSENTRIES)
				client_print(id, print_center, "You can only build %s sentry gun!", numwords)
			}
			*/
		}
		case MENUSELECT2: 
		{
			// Detonate if still has
			new sentryCount = GetSentryCount(id)
	
			if (sentryCount == 1)
				sentry_detonate_by_owner(id)
			else if (sentryCount > 1) 
			{
				sentry_detonate(g_selectedSentry[id - 1], false, false)
			}
		}
	case MENUSELECT3: 
	{
		// Upgrade sentry
		new sentry = g_aimSentry[id - 1]
		if (is_valid_ent(sentry) && entity_range(sentry, id) <= MAXUPGRADERANGE) 
		{
			sentry_upgrade(id, sentry)
		}
	}
	case MENUSELECT4: {
		while(GetSentryCount(id) > 0)
			sentry_detonate_by_owner(id, true)
	}
	case MENUSELECT5: 
	{
		// one back
		CycleSelectedSentry(id, -1)
		stayInMenu = true
	}
	case MENUSELECT6: 
	{
		// one forward
		CycleSelectedSentry(id, 1)
		stayInMenu = true
	}
	case MENUSELECT7: 
	{
		if (g_selectedSentry[id - 1] != -1) 
		{
			new spycam = CreateSpyCam(id, g_selectedSentry[id - 1])
			if (!spycam)
				return PLUGIN_HANDLED

			new parms[3]
			parms[0] = id
			parms[1] = spycam
			parms[2] = g_selectedSentry[id - 1]
			set_task(SPYCAMTIME, "DestroySpyCam", TASKID_SPYCAM + id, parms, 3)
		}
	}
	case MENUSELECT0: 
	{
		// nothing
		//stayInMenu = false
	}
}

	if (stayInMenu)
		menumain_starter(id)

	return PLUGIN_HANDLED
}

CreateSpyCam(id, sentry) 
{
	new spycam = create_entity("info_target")
	if (!spycam)
		return 0

	// Set connection from sentry to this spycam
	entity_set_edict(sentry, SENTRY_ENT_SPYCAM, spycam)

	// Set classname
	entity_set_string(spycam, EV_SZ_classname, "spycam")

	// Set origin, pull up some
	new Float:origin[3]
	entity_get_vector(sentry, EV_VEC_origin, origin)
	origin[2] += g_spyCamOffset[entity_get_int(sentry, SENTRY_INT_LEVEL)]
	entity_set_vector(spycam, EV_VEC_origin, origin)

	// Set model, has to have one... but make it invisible with the stuff below
	entity_set_model(spycam, "models/sentries/base.mdl")
	entity_set_int(spycam, EV_INT_rendermode, kRenderTransColor)
	entity_set_float(spycam, EV_FL_renderamt, 0.0)
	entity_set_int(spycam, EV_INT_renderfx, kRenderFxNone)

	// Set initial angle, this is also done in server_frame
	new Float:angles[3]
	entity_get_vector(sentry, EV_VEC_angles, angles)
	entity_set_vector(spycam, EV_VEC_angles, angles)

	// Set view of player
	engfunc(EngFunc_SetView, id, spycam)
	g_inSpyCam[id - 1] = true

	return spycam
}

public DestroySpyCam(parms[3]) 
{
	new id = parms[0]
	new spycam = parms[1]
	new sentry = parms[2]
	g_inSpyCam[id - 1] = false

	// If user is still around, set his view back
	if (is_user_connected(id))
		engfunc(EngFunc_SetView, id, id)

	// Remove connection from sentry (this sentry could've been removed because of a newround, or it was destroyed...)
	if (is_valid_ent(sentry) && entity_get_edict(sentry, SENTRY_ENT_SPYCAM) == spycam)
		entity_set_edict(sentry, SENTRY_ENT_SPYCAM, 0)

	remove_entity(spycam)
}

CycleSelectedSentry(id, steps) 
{
	// Find current index
	new index = -1
	for (new i = 0; i < g_playerSentries[id - 1]; i++) 
	{
		if (g_playerSentriesEdicts[id - 1][i] == g_selectedSentry[id - 1]) 
		{
			index = i
			break
		}
	}
	if (index == -1)
		return // error :-P

	remove_task(TASKID_SENTRYONRADAR + g_selectedSentry[id - 1])

	if (steps > 0) 
	{
		do 
		{
			index++
			steps--
			
			if (index == g_playerSentries[id - 1])
				index = 0
		}
		while(steps > 0)
	}
	else if (steps < 0) 
	{
		do 
		{
			index--
			steps++
			if (index == -1)
				index = g_playerSentries[id - 1] - 1
		}
		while(steps < 0)
	}

	g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][index]
}

sentry_upgrade(id, sentry) 
{
	new sentryLevel = entity_get_int(sentry, SENTRY_INT_LEVEL)
	if (entity_get_int(sentry, SENTRY_INT_FIRE) == SENTRY_FIREMODE_NUTS) 
	{
		client_print(id, print_center, "This sentry cannot be upgraded.")
		return
	}
	else if (get_user_team(id) != entity_get_int(sentry, SENTRY_INT_TEAM)) 
	{
		client_print(id, print_center, "You can only upgrade your own team's sentries.")
		return
	}
	#if defined DISALLOW_OWN_UPGRADES
	else if (sentryLevel == SENTRY_LEVEL_1 && GetSentryPeople(sentry, OWNER) == id) 
	{
		// Don't print anything here, it could get spammy
		//client_print(id, print_center, "")
		return
	}
	#endif
	#if defined DISALLOW_TWO_UPGRADES
	else if (sentryLevel == SENTRY_LEVEL_2 && GetSentryPeople(sentry, UPGRADER_1) == id) 
	{
		// Don't print anything here, it could get spammy
		//client_print(id, print_center, "")
		return
	}

	#endif
	sentryLevel++
	new bool:newLevelIsOK = true, upgraderField
	switch (sentryLevel) 
	{
		case SENTRY_LEVEL_2: 
		{
			entity_set_model(sentry, "models/sentries/sentry2.mdl")
			upgraderField = UPGRADER_1
		}
		case SENTRY_LEVEL_3: 
		{
			entity_set_model(sentry, "models/sentries/sentry3.mdl")
			upgraderField = UPGRADER_2
		}
		default: 
		{
			// Error... can only upgrade to level 2 and 3... so far! ;-)
			newLevelIsOK = false
		}
	}

	if (newLevelIsOK) 
	{
		if (zp_get_user_ammo_packs(id) - g_COST(sentryLevel) < 0) 
		{
			client_print(id, print_center, "You don't have enough money to upgrade this sentry gun! (needed %d) ammo packs", g_COST(sentryLevel))
			return
		}

		zp_set_user_ammo_packs(id, zp_get_user_ammo_packs(id) - g_COST(sentryLevel))

		new Float:mins[3], Float:maxs[3]
		mins[0] = -16.0
		mins[1] = -16.0
		mins[2] = 0.0
		maxs[0] = 16.0
		maxs[1] = 16.0
		maxs[2] = 48.0 // 4.0
		entity_set_size(sentry, mins, maxs)
		emit_sound(sentry, CHAN_AUTO, "sentries/turrset.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
		entity_set_int(sentry, SENTRY_INT_LEVEL, sentryLevel)
		entity_set_float(sentry, EV_FL_health, g_HEALTHS[sentryLevel])
		entity_set_float(entity_get_edict(sentry, SENTRY_ENT_BASE), EV_FL_health, g_HEALTHS[0])
		SetSentryPeople(sentry, upgraderField, id)

		if (id != GetSentryPeople(sentry, OWNER)) 
		{
			new upgraderName[32]
			get_user_name(id, upgraderName, 31)
			client_print(GetSentryPeople(sentry, OWNER), print_center, "%s upgraded your sentry to level %d", upgraderName, sentryLevel + 1)
		}
	}
}

stock userviewhitpoint(index, Float:hitorigin[3]) 
{
	if (!is_user_connected(index)) 
	{
		// Error
		log_amx("ERROR in plugin - %d is not a valid player index", index)
		return 0
	}
	new Float:origin[3], Float:pos[3], Float:v_angle[3], Float:vec[3], Float:f_dest[3]

	entity_get_vector(index, EV_VEC_origin, origin)
	entity_get_vector(index, EV_VEC_view_ofs, pos)

	pos[0] += origin[0]
	pos[1] += origin[1]
	pos[2] += origin[2]

	entity_get_vector(index, EV_VEC_v_angle, v_angle)

	engfunc(EngFunc_AngleVectors, v_angle, vec, 0, 0)

	f_dest[0] = pos[0] + vec[0] * 9999
	f_dest[1] = pos[1] + vec[1] * 9999
	f_dest[2] = pos[2] + vec[2] * 9999

	return trace_line(index, pos, f_dest, hitorigin)
}

stock entityviewhitpoint(index, Float:origin[3], Float:hitorigin[3]) 
{
	if (!is_valid_ent(index)) 
	{
		// Error
		log_amx("ERROR in plugin - %d is not a valid entity index", index)
		return 0
	}
	new Float:angle[3], Float:vec[3], Float:f_dest[3]

	//entity_get_vector(index, EV_VEC_origin, origin)
	/*
	entity_get_vector(index, EV_VEC_view_ofs, pos)
	
	pos[0] += origin[0]
	pos[1] += origin[1]
	pos[2] += origin[2]
	*/

	entity_get_vector(index, EV_VEC_angles, angle)

	engfunc(EngFunc_AngleVectors, angle, vec, 0, 0)

	f_dest[0] = origin[0] + vec[0] * 9999
	f_dest[1] = origin[1] + vec[1] * 9999
	f_dest[2] = origin[2] + vec[2] * 9999

	return trace_line(index, origin, f_dest, hitorigin)
}

public newround_event(id) 
{
	//Shaman: Disallow building and enable it after some time
	g_allowBuild= false
	set_task(get_pcvar_float(sentry_wait), "enablesentrybur")

	g_inBuilding[id - 1] = false

	#if !defined SENTRIES_SURVIVE_ROUNDS
	while(GetSentryCount(id) > 0)
		sentry_detonate_by_owner(id, true)
	#endif

	if (!g_resetArmouryThisRound && g_hasArmouries) 
	{
		ResetArmoury()
		g_resetArmouryThisRound = true
	}

	return PLUGIN_CONTINUE
}

public endround_event() 
{
	if (!g_hasArmouries)
		return PLUGIN_CONTINUE
	
	set_task(4.0, "ResetArmouryFalse")

	return PLUGIN_CONTINUE
}

public ResetArmouryFalse() 
{
	//client_print(0, print_chat, "Setting g_resetArmouryThisRound to false!")
	g_resetArmouryThisRound = false
}

public client_putinserver(id) 
{
	if (is_user_bot(id) && !zp_get_user_zombie(id)) 
	{
		new parm[1]
		parm[0] = id
		botbuildsrandomly(parm)

	}
	
	else
		set_task(15.0, "dispInfo", id)

	return PLUGIN_CONTINUE
}

public dispInfo(id)
{
	client_print(id, print_chat, "[ZP] You can build sentries, for more help, say /sentryhelp")
}

public check_say(id)
{
	new said[32]
	read_args(said, 31)

	if (equali(said, "^"sentryhelp^"") || equali(said, "^"/sentryhelp^"")) 
	{
		const SIZE = MAXHTMLSIZE
		new msg[SIZE + 1], len = 0

		len += format(msg[len], SIZE - len, "<html><body>")
		len += format(msg[len], SIZE - len, "<p>Sentries in TFC were cool. Sentries in CS are cool.<br/>")
		len += format(msg[len], SIZE - len, "Sentry guns are stationary engineering wonders that fire bullets at and kill your enemies.<br/>")
		len += format(msg[len], SIZE - len, "Sentry guns can be upgraded twice, with the help of a team member, to be bigger and meaner.</p>")
		len += format(msg[len], SIZE - len, "<p>Open console and type ^"bind j sentry_menu^" to bind the menu button to J. You can also bind ^"sentry_build^".<br/>")
		len += format(msg[len], SIZE - len, "Note that you can bind to any button you choose. To bind the fast build/update command to a mouse button 4, write ^"bind mouse4 sentry_build^".</p>")
	#if defined DISALLOW_OWN_UPGRADES
		len += format(msg[len], SIZE - len, "<p>You <b>cannot</b> upgrade your own sentry from level 1 to level 2. A team mate must do this.</p>")
	#else
		len += format(msg[len], SIZE - len, "<p>You <b>can</b> upgrade your own sentry from level 1 to level 2.</p>")
	#endif
	#if defined DISALLOW_TWO_UPGRADES
		len += format(msg[len], SIZE - len, "<p>A sentry at level 2 <b>cannot</b> be upgraded to level 3 by the same player that performed the first upgrade. A team mate must do this (original builder is OK).</p>")
	#else
		len += format(msg[len], SIZE - len, "<p>A sentry at level 2 <b>can</b> be further upgraded to level 3 by the the same player that performed the first upgrade.</p>")
	#endif
		len += format(msg[len], SIZE - len, "<center>")
		len += format(msg[len], SIZE - len, "<table width=^"50%^" border=^"1^">")
		len += format(msg[len], SIZE - len, "<tr><td><b>Command</b></td><td><b>Description</b></td>")
		len += format(msg[len], SIZE - len, "<tr><td>sentry_menu</td><td>From this menu you can build, upgrade and detonate sentry guns. To upgrade a sentry, first point at it, then open menu.</td>")
		len += format(msg[len], SIZE - len, "<tr><td>sentry_build</td><td>Quick button to build and upgrade sentry guns. To upgrade a sentry, first point at it, then press this button.</td>")
		len += format(msg[len], SIZE - len, "</table>")
		len += format(msg[len], SIZE - len, "<table width=^"50%^" border=^"1^">")
		len += format(msg[len], SIZE - len, "<tr><td><b>Sentry gun level</b></td><td><b>Cost to build/upgrade to</b></td>")
		len += format(msg[len], SIZE - len, "<tr><td>1</td><td>%d</td>", g_COST(0))
		len += format(msg[len], SIZE - len, "<tr><td>2</td><td>%d</td>", g_COST(1))
		len += format(msg[len], SIZE - len, "<tr><td>3</td><td>%d</td>", g_COST(2))
		len += format(msg[len], SIZE - len, "</table>")
		len += format(msg[len], SIZE - len, "</center>")
		len += format(msg[len], SIZE - len, "</body></html>")
		show_motd(id, msg, "Sentry guns help")
	}
	
	else if (containi(said, "sentr") != -1) 
	{
		dispInfo(id)
	}

	return PLUGIN_CONTINUE
}

public plugin_modules() 
{
	require_module("engine")
	require_module("fun")
	require_module("cstrike")
	require_module("fakemeta")
}

public plugin_precache() 
{
	// Sentries below
	precache_model("models/sentries/base.mdl")
	precache_model("models/sentries/sentry1.mdl")
	precache_model("models/sentries/sentry2.mdl")
	precache_model("models/sentries/sentry3.mdl")

	g_sModelIndexFireball = precache_model("sprites/zerogxplode.spr") 		// explosion

	precache_sound("debris/bustmetal1.wav") 					// metal, computer breaking
	precache_sound("debris/bustmetal2.wav") 					// metal, computer breaking
	precache_sound("debris/metal1.wav") 						// metal breaking (needed for comp also?!)
	//precache_sound("debris/metal2.wav") 						// metal breaking
	precache_sound("debris/metal3.wav") 						// metal breaking (needed for comp also?!)
	//precache_model("models/metalplategibs.mdl") 					// metal breaking
	precache_model("models/computergibs.mdl") 					// computer breaking

	precache_sound("sentries/asscan1.wav")
	precache_sound("sentries/asscan2.wav")
	precache_sound("sentries/asscan3.wav")
	precache_sound("sentries/asscan4.wav")
	precache_sound("sentries/turridle.wav")
	precache_sound("sentries/turrset.wav")
	precache_sound("sentries/turrspot.wav")
	precache_sound("sentries/building.wav")

	precache_sound("weapons/m249-1.wav")
}


stock spambits(to, bits) 
{
	new buffer[512], len = 0
	for (new i = 31; i >= 0; i--) 
	{
		len += format(buffer[len], 511 - len, "%d", bits & (1<<i) ? 1 : 0)
	}
	client_print(to, print_chat, buffer)
	server_print(buffer)
}

public forward_traceline_post(Float:start[3], Float:end[3], noMonsters, player) 
{
	if (is_user_bot(player) || player < 1 || player > g_MAXPLAYERS)
	return FMRES_IGNORED

	if (!is_user_alive(player) || !zp_get_user_zombie(player))
		return FMRES_IGNORED

	SetStatusTrigger(player, false)

	new hitEnt = get_tr(TR_pHit)
	if (hitEnt <= g_MAXPLAYERS)
		return FMRES_IGNORED

	new classname[11], sentry = 0, base = 0
	entity_get_string(hitEnt, EV_SZ_classname, classname, 10)
	if (equal(classname, "sentrybase")) 
	{
		base = hitEnt
		sentry = entity_get_edict(hitEnt, BASE_ENT_SENTRY)
	}
	else if (equal(classname, "sentry")) 
	{
		sentry = hitEnt
		base = entity_get_edict(sentry, SENTRY_ENT_BASE)
	}
	if (!sentry || !base || entity_get_int(sentry, SENTRY_INT_FIRE) == SENTRY_FIREMODE_NUTS)
		return FMRES_IGNORED
	new Float:health = entity_get_float(sentry, EV_FL_health)

	if (health <= 0)
		return FMRES_IGNORED
	
	new Float:basehealth = entity_get_float(base, EV_FL_health)
	
	if (basehealth <= 0)
		return FMRES_IGNORED

	new team = entity_get_int(sentry, SENTRY_INT_TEAM)
	
	if (team != get_user_team(player))
		return FMRES_IGNORED

	// Display health
	new level = entity_get_int(sentry, SENTRY_INT_LEVEL)
	new upgradeInfo[128]
	if (PlayerCanUpgradeSentry(player, sentry))
		format(upgradeInfo, 127, "^n(Run into me to upgrade me to level %d for $%d)", level + 2, g_COST(level + 1))
	else if (level < SENTRY_LEVEL_3)
		format(upgradeInfo, 127, "^n(Upgrade cost: $%d)", g_COST(level + 1))
	else
		upgradeInfo = ""

	new tempStatusBuffer[256]

	format(tempStatusBuffer, 255, "Health: %d/%d^nBase health: %d/%d^nLevel: %d%s", floatround(health), floatround(g_HEALTHS[level]), floatround(basehealth), floatround(g_HEALTHS[0]), level + 1, upgradeInfo)
	SetStatusTrigger(player, true)
	if (!task_exists(TASKID_SENTRYSTATUS + player) || !equal(tempStatusBuffer, g_sentryStatusBuffer[player - 1])) 
	{
		// may still exist if !equal was true, so we remove previous task. This happens when sentry is being fired upon, player gets enough money to upgrade or sentry
		// suddenly is upgradeable because another teammate upgraded it or something. This should make for instant updates to message without risking sending a lot of messages
		// just in case data updated, now we only send more often if data changed often enough.
		//client_print(player, print_chat, "Starting to send: %s", tempStatusBuffer)
		remove_task(TASKID_SENTRYSTATUS + player)

		g_sentryStatusBuffer[player - 1] = tempStatusBuffer
		new parms[2]
		parms[0] = player
		parms[1] = team
		set_task(0.0, "displaysentrystatus", TASKID_SENTRYSTATUS + player, parms, 2)
	}

	return FMRES_IGNORED
}

	// Counting level, team, money and DEFINES
	bool:PlayerCanUpgradeSentry(player, sentry) 
	{
		new level = entity_get_int(sentry, SENTRY_INT_LEVEL)
		switch(level) 
		{
			case SENTRY_LEVEL_1: 
			{
				#if defined DISALLOW_OWN_UPGRADES
				if (player == GetSentryPeople(sentry, OWNER))
					return false
				#endif
				return get_user_team(player) == entity_get_int(sentry, SENTRY_INT_TEAM) && zp_get_user_ammo_packs(player) >= g_COST(level + 1)
			}	
			case SENTRY_LEVEL_2: 
			{
				#if defined DISALLOW_TWO_UPGRADES
				if (player == GetSentryPeople(sentry, UPGRADER_1))
					return false
				#endif
				return get_user_team(player) == entity_get_int(sentry, SENTRY_INT_TEAM) && zp_get_user_ammo_packs(player) >= g_COST(level + 1)
			}
		}
		return false
}

public displaysentrystatus(parms[2]) 
{
	// parm 0 = player
	// parm 1 = team
	if (!GetStatusTrigger(parms[0]))
		return

	set_hudmessage(parms[1] == 1 ? 150 : 0, 0, parms[1] == 2 ? 150 : 0, -1.0, 0.35, 0, 0.0, STATUSINFOTIME + 0.1, 0.0, 0.0, 2) // STATUSINFOTIME + 0.1 = overlapping a little..
	show_hudmessage(parms[0], g_sentryStatusBuffer[parms[0] - 1])

	set_task(STATUSINFOTIME, "displaysentrystatus", TASKID_SENTRYSTATUS + parms[0], parms, 2)
}


ResetArmoury() 
{
	// Find all armoury_entity:s, restore their initial origins
	new entity = 0, Float:NULLVELOCITY[3] = {0.0, 0.0, 0.0}, Float:origin[3]
	while ((entity = find_ent_by_class(entity, "armoury_entity"))) 
	{
		// Reset speed in case it's flying around...
		entity_set_vector(entity, EV_VEC_velocity, NULLVELOCITY)

		// Get origin and set it.
		entity_get_vector(entity, EV_VEC_vuser1, origin)
		entity_set_origin(entity, origin)
	}
}

public InitArmoury() 
{
	// Find all armoury_entity:s, store their initial origins
	new entity = 0, Float:origin[3], counter = 0
	while ((entity = find_ent_by_class(entity, "armoury_entity"))) 
	{
		entity_get_vector(entity, EV_VEC_origin, origin)
		entity_set_vector(entity, EV_VEC_vuser1, origin)
		counter++
	}
	if (counter > 0)
		g_hasArmouries = true
}

stock getnumbers(number, wordnumbers[], length) 
{
	if (number < 0) 
	{
		format(wordnumbers, length, "error")
		return
	}

	new numberstr[20]
	num_to_str(number, numberstr, 19)
	new stlen = strlen(numberstr), bool:getzero = false, bool:jumpnext = false
	if (stlen == 1)
		getzero = true

	do 
	{
		if (jumpnext)
			jumpnext = false
			
		else if (numberstr[0] != '0') 
		{
			switch (stlen) 
			{
				case 9: 
				{
					if (getsingledigit(numberstr[0], wordnumbers, length))
						format(wordnumbers, length, "%s hundred%s", wordnumbers, numberstr[1] == '0' && numberstr[2] == '0' ? " million" : "")
				}
				
				case 8: 
				{
					jumpnext = gettens(wordnumbers, length, numberstr)
					
					if (jumpnext)
						format(wordnumbers, length, "%s million", wordnumbers)
				}
			
				case 7: 
				{
					getsingledigit(numberstr[0], wordnumbers, length)
					format(wordnumbers, length, "%s million", wordnumbers)
				}
				
				case 6: 
				{
					if (getsingledigit(numberstr[0], wordnumbers, length))
						format(wordnumbers, length, "%s hundred%s", wordnumbers, numberstr[1] == '0' && numberstr[2] == '0' ? " thousand" : "")
				}
				
				case 5: 
				{
					jumpnext = gettens(wordnumbers, length, numberstr)
					if (numberstr[0] == '1' || numberstr[1] == '0')
						format(wordnumbers, length, "%s thousand", wordnumbers)
				}
				
				case 4: 
				{
					getsingledigit(numberstr[0], wordnumbers, length)
					format(wordnumbers, length, "%s thousand", wordnumbers)
				}
				
				case 3: 
				{
					getsingledigit(numberstr[0], wordnumbers, length)
					format(wordnumbers, length, "%s hundred", wordnumbers)
				}
				
				case 2: jumpnext = gettens(wordnumbers, length, numberstr)
				case 1: 
				{
					getsingledigit(numberstr[0], wordnumbers, length, getzero)
					break // could've trimmed, but of no use here
				}
				
				default: 
				{
					format(wordnumbers, length, "%s TOO LONG", wordnumbers)
					break
				}
			}
		}

		jghg_trim(numberstr, length, 1)
		stlen = strlen(numberstr)
	}
	
	while (stlen > 0)

	// Trim a char from left if first char is a space (very likely)
	if (wordnumbers[0] == ' ')
		jghg_trim(wordnumbers, length, 1)
}

// Returns true if next char should be jumped
stock bool:gettens(wordnumbers[], length, numberstr[]) 
{
	new digitstr[11], bool:dont = false, bool:jumpnext = false
	switch (numberstr[0]) 
	{
		case '1': 
		{
			jumpnext = true
			switch (numberstr[1]) 
			{
				case '0': digitstr = "ten"
				case '1': digitstr = "eleven"
				case '2': digitstr = "twelve"
				case '3': digitstr = "thirteen"
				case '4': digitstr = "fourteen"
				case '5': digitstr = "fifteen"
				case '6': digitstr = "sixteen"
				case '7': digitstr = "seventeen"
				case '8': digitstr = "eighteen"
				case '9': digitstr = "nineteen"
				default: digitstr = "TEENSERROR"
			}
		}
		
		case '2': digitstr = "twenty"
		case '3': digitstr = "thirty"
		case '4': digitstr = "fourty"
		case '5': digitstr = "fifty"
		case '6': digitstr = "sixty"
		case '7': digitstr = "seventy"
		case '8': digitstr = "eighty"
		case '9': digitstr = "ninety"
		case '0': dont = true // do nothing
		default : digitstr = "TENSERROR"
	}
	
	if (!dont)
		format(wordnumbers, length, "%s %s", wordnumbers, digitstr)

	return jumpnext
}

// Returns true when sets, else false
stock getsingledigit(digit[], numbers[], length, bool:getzero = false) 
{
	new digitstr[11]
	switch (digit[0]) 
	{
		case '1': digitstr = "one"
		case '2': digitstr = "two"
		case '3': digitstr = "three"
		case '4': digitstr = "four"
		case '5': digitstr = "five"
		case '6': digitstr = "six"
		case '7': digitstr = "seven"
		case '8': digitstr = "eight"
		case '9': digitstr = "nine"
		case '0': 
		{
			if (getzero)
				digitstr = "zero"
			else
				return false
		}
		default : digitstr = "digiterror"
	}
	format(numbers, length, "%s %s", numbers, digitstr)

	return true
}

stock jghg_trim(stringtotrim[], len, charstotrim, bool:fromleft = true) 
{
	if (charstotrim <= 0)
		return

	if (fromleft) 
	{
		new maxlen = strlen(stringtotrim)
		if (charstotrim > maxlen)
			charstotrim = maxlen

		format(stringtotrim, len, "%s", stringtotrim[charstotrim])
	}
	
	else 
	{
		new maxlen = strlen(stringtotrim) - charstotrim
		if (maxlen < 0)
			maxlen = 0

		format(stringtotrim, maxlen, "%s", stringtotrim)
	}
}

BotBuild(bot, Float:closestTime = 0.1, Float:longestTime = 5.0) 
{
	// This function should only be used to build sentries at objective related targets.
	// So as to not try to build all the time if recently started a build task when touched a objective related target
	if (task_exists(bot))
		return

	new teamSentriesNear = GetStuffInVicinity(bot, BOT_MAXSENTRIESDISTANCE, true, "sentry") + GetStuffInVicinity(bot, BOT_MAXSENTRIESDISTANCE, true, "sentrybase")
	if (teamSentriesNear >= BOT_MAXSENTRIESNEAR) 
	{
		new name[32]
		get_user_name(bot, name, 31)
		//client_print(0, print_chat, "There are already %d sentries near me, I won't build here, %s says. (objective)", teamSentriesNear, name)
		return
	}

	new Float:ltime = random_float(closestTime, longestTime)
	set_task(ltime, "sentry_build", bot)
	//server_print("Bot task %d set to %f seconds", bot, ltime)

	/*new tempname[32]
	get_user_name(bot, tempname, 31)
	client_print(0, print_chat, "Bot %s will build a sentry in %f seconds...", tempname, ltime)*/
}

public sentry_build_randomlybybot(taskid_and_id) 
{
	//Shaman: Check if the player is allowed to build
	if(!g_allowBuild)
		return

	if (!is_user_alive(taskid_and_id - TASKID_BOTBUILDRANDOMLY))
		return

	// Now finally do a short check if there already are enough (2-3 sentries) in this vicinity... then don't build.
	new teamSentriesNear = GetStuffInVicinity(taskid_and_id - TASKID_BOTBUILDRANDOMLY, BOT_MAXSENTRIESDISTANCE, true, "sentry") + GetStuffInVicinity(taskid_and_id - TASKID_BOTBUILDRANDOMLY, BOT_MAXSENTRIESDISTANCE, true, "sentrybase")
	if (teamSentriesNear >= BOT_MAXSENTRIESNEAR) 
	{
		//new name[32]
		//get_user_name(taskid_and_id - TASKID_BOTBUILDRANDOMLY, name, 31)
		//client_print(0, print_chat, "There are already %d sentries near me, I won't build here, %s says. (random)", teamSentriesNear, name)
		return
	}

	sentry_build(taskid_and_id - TASKID_BOTBUILDRANDOMLY)
}

GetStuffInVicinity(entity, const Float:RADIUS, bool:followTeam, STUFF[]) 
{
	new classname[32], sentryTeam, nrOfStuffNear = 0
	entity_get_string(entity, EV_SZ_classname, classname, 31)
	if (followTeam) 
	{
		if (equal(classname, "player"))
			sentryTeam = get_user_team(entity)
		else if (equal(classname, "sentry"))
			sentryTeam = entity_get_int(entity, SENTRY_INT_TEAM)
	}	

	if (followTeam) 
	{
		if (equal(STUFF, "sentry")) 
		{
			for (new i = 0; i < g_sentriesNum; i++) 
			{
				if (g_sentries[i] == entity || (followTeam && entity_get_int(g_sentries[i], SENTRY_INT_TEAM) != sentryTeam) || entity_range(g_sentries[i], entity) > RADIUS)
					continue

				nrOfStuffNear++
			}
		}
		else if (equal(STUFF, "sentrybase")) 
		{
			new ent = 0
			while ((ent = find_ent_by_class(ent, STUFF))) 
			{
				// Don't count if:
				// If follow team then if team is not same
				// If ent is the same as what we're searching from, which is entity
				// Don't count a base if it has a head, we consider sentry+base only as one item (a sentry)
				// Or if out of range
				if ((followTeam && entity_get_int(ent, BASE_INT_TEAM) != sentryTeam)
				|| ent == entity
				|| entity_get_edict(ent, BASE_ENT_SENTRY) != 0
				|| entity_range(ent, entity) > RADIUS)
					continue

				nrOfStuffNear++
			}
		}
	}
	//client_print(0, print_chat, "Found %d sentries within %f distance of entity %d...", nrOfSentriesNear, RADIUS, entity)
	return nrOfStuffNear
}

BotBuildRandomly(bot, Float:closestTime = 0.1, Float:longestTime = 5.0) 
{
	// This function is used to stark tasks that will build sentries randomly regardless of map objectives and its targets.
	new Float:ltime = random_float(closestTime, longestTime)
	set_task(ltime, "sentry_build_randomlybybot", TASKID_BOTBUILDRANDOMLY + bot)

	new tempname[32]
	get_user_name(bot, tempname, 31)
	//client_print(0, print_chat, "Bot %s will build a random sentry in %f seconds...", tempname, ltime)
	//server_print("Bot %s will build a random sentry in %f seconds...", tempname, ltime)
}

public playerreachedtarget(target, bot) 
{
	if (!is_user_bot(bot) && !zp_get_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || entity_get_int(bot, EV_INT_bInDuck) || cs_get_user_vip(bot) || get_systime() < g_lastObjectiveBuild[bot - 1] + BOT_OBJECTIVEWAIT)
		return PLUGIN_CONTINUE

	//client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)
	BotBuild(bot)
	g_lastObjectiveBuild[bot - 1] = get_systime()

	return PLUGIN_CONTINUE
}

public playertouchedweaponbox(weaponbox, bot) 
{
	if (!is_user_bot(bot) && !zp_get_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || cs_get_user_team(bot) != CS_TEAM_CT)
	return PLUGIN_CONTINUE

	new model[22]
	entity_get_string(weaponbox, EV_SZ_model, model, 21)
	if (!equal(model, "models/w_backpack.mdl"))
		return PLUGIN_CONTINUE

	// A ct will build near a dropped bomb
	BotBuild(bot, 0.0, 2.0)

	return PLUGIN_CONTINUE
}

public playerreachedhostagerescue(target, bot) 
{
	if (!is_user_bot(bot) && !zp_get_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES) //  || cs_get_user_team(bot) != CS_TEAM_T
	return PLUGIN_CONTINUE

	// ~5% chance that a ct will build a sentry here, a t always builds
	if (cs_get_user_team(bot) == CS_TEAM_CT) 
	{
		if (random_num(0, 99) < 95)
			return PLUGIN_CONTINUE
	}

	BotBuild(bot)

	//client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)

	return PLUGIN_CONTINUE
}

public playertouchedhostage(hostage, bot) 
{
	if (!is_user_bot(bot) && !zp_get_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || cs_get_user_team(bot) != CS_TEAM_T)
		return PLUGIN_CONTINUE

	// Build a sentry close to a hostage
	BotBuild(bot)

	//client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)

	return PLUGIN_CONTINUE
}


public playertouchedsentry(sentry, player) 
{
	if (PlayerCanUpgradeSentry(player, sentry))
		sentry_upgrade(player, sentry)
	
	//client_print(bot, print_chat, "You touched a sentry %d!", sentry)

	return PLUGIN_CONTINUE
}

public botbuildsrandomly(parm[1]) 
{
	if (!is_user_connected(parm[0])) 
	{
		//server_print("********* %d is no longer in server!", parm[0])
		return
	}

	new Float:ltime = random_float(BOT_WAITTIME_MIN, BOT_WAITTIME_MAX)
	new Float:ltime2 = ltime + random_float(BOT_NEXT_MIN, BOT_NEXT_MAX)
	BotBuildRandomly(parm[0], ltime, ltime2)

	set_task(ltime2, "botbuildsrandomly", 0, parm, 1)
}

#if defined DEBUG
public botbuild_fn(id, level, cid) 
{
	if (!cmd_access(id, level, cid, 1))
		return PLUGIN_HANDLED

	new asked = 0
	for(new i = 1; i <= g_MAXPLAYERS; i++) 
	{
		if (!is_user_connected(i) || !is_user_bot(i) || !is_user_alive(i))
			continue

		sentry_build(i)
		asked++
	}
	console_print(id, "Asked %d bots to build sentries (not counting money etc)", asked)

	return PLUGIN_HANDLED
}
	
#endif
g_COST(i)
{
	switch(i)
	{
		case 0: return COST_INIT
		case 1: return COST_UP
		case 2: return COST_UPTWO
	}
	return 0;
}

User avatar
Raheem
Mod Developer
Mod Developer
Posts: 2214
Joined: 7 years ago
Contact:

#2

Post by Raheem » 5 years ago

  1. //#define DEBUG
  2.  
  3. #include <zombie_escape>
  4. #include <engine>
  5. #include <fun>
  6. #include <cstrike>
  7.  
  8. #if defined DEBUG
  9. #include <amxmisc>
  10. #endif
  11.  
  12. #define MAXSENTRIESTOTAL 20
  13. //#define MAXPLAYERSENTRIES     3               // how many sentries each player can build
  14. #define MAXPLAYERSENTRIES       get_pcvar_num(sentry_max)   // how many sentries each player can build
  15. #define DMG_EXPLOSION_TAKE      90              // how much HP at most an exploding sentry takes from a player - the further away the less dmg is dealt to player
  16. #define SENTRYEXPLODERADIUS     250.0               // how far away it is safe to be from an exploding sentry without getting kicked back and hurt
  17. #define THINKFIREFREQUENCY      0.1             // the rate in seconds between each bullet when firing at a locked target
  18. #define SENTRYTILTRADIUS        830.0               // likely you won't need to touch this. it's how accurate the cannon will aim at the target vertically (up/down, just for looks, aim is calculated differently)
  19. #if !defined DEBUG
  20. #define DISALLOW_OWN_UPGRADES                       // you cannot upgrade your own sentry to level 2 (only to level 3 if someone else upgraded it already) (only have this commented in debug mode)
  21. #define DISALLOW_TWO_UPGRADES                       // the upgrader cannot upgrade again, builder must upgrade to level 3 (only have this commented in debug mode)
  22. #endif
  23. //#define SENTRIES_SURVIVE_ROUNDS                   // comment this define to have sentries removed between rounds, else they will stay where they are.
  24. //#define RANDOM_TOPCOLOR                       // sentries have two colors, one top and one bottom. The top one will be random if you leave this define be, else always red for T and blue for CT.
  25. //#define RANDOM_BOTTOMCOLOR                        // sentries have two colors, one top and one bottom. The bottom one will be random if you leave this define be, else always red for T and blue for CT.
  26. #define EXPLODINGSENTRIES                       // comment this out if you don't want the sentries to explode, push people away and hurt them (should now be stable!)
  27.                        
  28. // Bots will build sentries at objective critical locations (around dropped bombs, at bomb targets, near hostages etc)
  29. // They can also build randomly around maps using these values:
  30. #define BOT_WAITTIME_MIN        0.0             // waittime = the time a bot will wait after he's decided to build a sentry, before actually building (seconds)
  31. #define BOT_WAITTIME_MAX        15.0
  32. #define BOT_NEXT_MIN            0.0             // next = after building a sentry, this specifies the time a bot will wait until considering about waittime again (seconds)
  33. #define BOT_NEXT_MAX            120.0
  34.  
  35. // These are per sentry level, 1-3
  36. new const g_SENTRYFRAGREWARDS[3] = {28, 15, 15}         // how many ammo packs you get if your sentry frags someone.
  37. new const g_DMG[3] = {50, 100, 170}             // how much damage a bullet from a sentry does per hit
  38. new const Float:g_THINKFREQUENCIES[3] = {3.0, 1.5, 0.3}     // how often, in seconds, a sentry searches for targets when not locked at a target, a lower value means a sentry will lock on targets faster
  39. new const Float:g_HITRATIOS[3] = {0.5, 0.65, 0.75}      // how good a sentry is at hitting its target. 1.0 = always hit, 0.0 = never hit
  40. new const Float:g_HEALTHS[3] = {550.0, 1100.0, 2200.0}      // how many HP a sentry has. Increase to make sentry sturdier
  41. //new const g_COST[3] = {22, 17, 17}                // fun has a price, first is build cost, the next two upgrade costs
  42.  
  43. #define COST_INIT get_pcvar_num(sentry_cost1)
  44. #define COST_UP get_pcvar_num(sentry_cost2)
  45. #define COST_UPTWO get_pcvar_num(sentry_cost3)
  46.  
  47.  
  48. #if !defined PI
  49. #define PI      3.141592654                 // feel free to find a PI more exact than this
  50. #endif
  51.  
  52. #define MENUBUTTON1         (1<<0)
  53. #define MENUBUTTON2         (1<<1)
  54. #define MENUBUTTON3         (1<<2)
  55. #define MENUBUTTON4         (1<<3)
  56. #define MENUBUTTON5         (1<<4)
  57. #define MENUBUTTON6         (1<<5)
  58. #define MENUBUTTON7         (1<<6)
  59. #define MENUBUTTON8         (1<<7)
  60. #define MENUBUTTON9         (1<<8)
  61. #define MENUBUTTON0         (1<<9)
  62. #define MENUSELECT1         0
  63. #define MENUSELECT2         1
  64. #define MENUSELECT3         2
  65. #define MENUSELECT4         3
  66. #define MENUSELECT5         4
  67. #define MENUSELECT6         5
  68. #define MENUSELECT7         6
  69. #define MENUSELECT8         7
  70. #define MENUSELECT9         8
  71. #define MENUSELECT0         9
  72. #define MAXHTMLSIZE         1536
  73.  
  74. #define MAXSENTRIES         32 * MAXSENTRIESTOTAL
  75.  
  76. #define SENTRY_VEC_PEOPLE       EV_VEC_vuser1
  77. #define OWNER               0
  78. #define UPGRADER_1          1
  79. #define UPGRADER_2          2
  80. //#define SENTRY_VECENT_OWNER       SENTRY_VEC_PEOPLE + OWNER
  81. //#define SENTRY_VECENT_UPGRADER_1  SENTRY_VEC_PEOPLE + UPGRADER_1
  82. //#define SENTRY_VECENT_UPGRADER_2  SENTRY_VEC_PEOPLE + UPGRADER_2
  83.  
  84. GetSentryPeople(sentry, who)
  85. {
  86.     new Float:people[3]
  87.     entity_get_vector(sentry, SENTRY_VEC_PEOPLE, people)
  88.     return floatround(people[who])
  89. }
  90.  
  91. SetSentryPeople(sentry, who, is)
  92. {
  93.     new Float:people[3]
  94.     entity_get_vector(sentry, SENTRY_VEC_PEOPLE, people)
  95.     people[who] = is + 0.0
  96.     entity_set_vector(sentry, SENTRY_VEC_PEOPLE, people)
  97. }
  98.  
  99. #define SENTRY_ENT_TARGET       EV_ENT_euser1
  100. #define SENTRY_ENT_BASE         EV_ENT_euser2
  101. #define SENTRY_ENT_SPYCAM       EV_ENT_euser3
  102. #define SENTRY_INT_FIRE         EV_INT_iuser1
  103. #define SENTRY_INT_TEAM         EV_INT_iuser2
  104. #define SENTRY_INT_LEVEL        EV_INT_iuser3
  105. #define SENTRY_INT_PENDDIR      EV_INT_iuser4           // 1st bit: sentry cannon, 2nd bit: radar
  106. #define SENTRY_FL_ANGLE         EV_FL_fuser1
  107. #define SENTRY_FL_SPINSPEED     EV_FL_fuser2
  108. #define SENTRY_FL_MAXSPIN       EV_FL_fuser3
  109. #define SENTRY_FL_RADARANGLE        EV_FL_fuser4
  110.  
  111. // These are bits used in SENTRY_INT_PENDDIR
  112. #define SENTRY_DIR_CANNON       0
  113. #define SENTRY_DIR_RADAR        1
  114.  
  115. #define BASE_ENT_SENTRY         EV_ENT_euser1
  116. #define BASE_INT_TEAM           EV_INT_iuser1
  117.  
  118. #define SENTRY_LEVEL_1          0
  119. #define SENTRY_LEVEL_2          1
  120. #define SENTRY_LEVEL_3          2
  121. #define SENTRY_FIREMODE_NO      0
  122. #define SENTRY_FIREMODE_YES     1
  123. #define SENTRY_FIREMODE_NUTS        2
  124. #define TASKID_SENTRYFIRE       1000
  125. #define TASKID_BOTBUILDRANDOMLY     2000
  126. #define TASKID_SENTRYSTATUS     3000
  127. #define TASKID_THINK            4000
  128. #define TASKID_THINKPENDULUM        5000
  129. #define TASKID_SENTRYONRADAR        6000
  130. #define TASKID_SPYCAM           7000
  131. #define DUCKINGPLAYERDIFFERENCE     18.0
  132. #define TARGETUPMODIFIER        DUCKINGPLAYERDIFFERENCE     // if player ducks on ground, traces don't hit...
  133. #define DMG_BULLET          (1<<1)              // shot
  134. #define DMG_BLAST           (1<<6)              // explosive blast damage
  135. #define TE_EXPLFLAG_NONE        0
  136. #define TE_EXPLOSION            3
  137. #define TE_TRACER           6
  138. #define TE_BREAKMODEL           108
  139. #define BASESENTRYDELAY         2.0                 // seconds from base is built until sentry top appears
  140. #define PENDULUM_MAX            45.0                // how far sentry turret turns in each direction when idle, before turning back
  141. #define PENDULUM_INCREMENT      10.0                // speed of turret turning...
  142. #define RADAR_INCREMENT         2.0                 // speed of small radar turning on top of sentry level 3...
  143. #define MAXUPGRADERANGE         75.0                // farthest distance to sentry you can upgrade using upgrade command
  144. #define COLOR_BOTTOM_CT         160                 // default bottom colour of CT:s sentries
  145. #define COLOR_TOP_CT            150                 // default top colour of CT:s sentries
  146. #define COLOR_BOTTOM_T          0               // default bottom colour of T:s sentries
  147. #define COLOR_TOP_T         0               // default top colour of T:s sentries
  148. #define SENTRYSHOCKPOWER        3.0                 // multiplier, increase to make exploding sentries throw stuff further away
  149. #define CANNONHEIGHTFROMFEET        20.0                // tweakable to make tracer originate from the same height as the sentry's cannon. Also traces rely on this Y-wise offset.
  150. #define PLAYERORIGINHEIGHT      36.0                // this is the distance from a player's EV_VEC_origin to ground, if standing up
  151. #define HEIGHTDIFFERENCEALLOWED     20.0                // increase value to allow building in slopes with higher angles. You can set to 0.0 and you will only be able to build on exact flat ground. note: mostly applies to downhill building, uphill is still likely to "collide" with ground...
  152.  
  153. // This cannot account for sentries which are still under construction (only base, no sentry head yet):
  154. // How many (or more) sentries:
  155. #define BOT_MAXSENTRIESNEAR     1
  156.  
  157. // cannot be in the vicinity of this radius:
  158. #define BOT_MAXSENTRIESDISTANCE     1500.0
  159.  
  160. // for a bot to build at a location. Use higher values and bots will build sentries less close to other sentries on same team.
  161. #define BOT_OBJECTIVEWAIT       10              // nr of seconds that must pass after a bot has built an objective related sentry until he can build such a sentry again.
  162.  
  163. #define SENTRY_TILT_TURRET      EV_BYTE_controller2
  164. #define SENTRY_TILT_LAUNCHER        EV_BYTE_controller3
  165. #define SENTRY_TILT_RADAR       EV_BYTE_controller4
  166. #define PEV_SENTRY_TILT_TURRET      pev_controller_1
  167. #define PEV_SENTRY_TILT_LAUNCHER    pev_controller_2
  168. #define PEV_SENTRY_TILT_RADAR       pev_controller_3
  169.  
  170. #define STATUSINFOTIME          0.5                 // the frequency of hud message updates when spectating a sentry, don't set too low or it could overflow clients. Data should now always send again as soon as it updates though.
  171. #define SENTRY_RADAR            20              // use as high as possible but should still be working (ie be able to see sentries plotted on radar while in menu, too high values doesn't seem to work)
  172. #define SENTRY_RADAR_TEAMBUILT      21              // same as above
  173. #define SPYCAMTIME          5.0                 // nr of seconds the spycam is active
  174.  
  175. enum OBJECTTYPE
  176. {
  177.     OBJECT_GENERIC,
  178.     OBJECT_GRENADE,
  179.     OBJECT_PLAYER,
  180.     OBJECT_ARMOURY
  181. }
  182.  
  183. // Global vars
  184. new g_sentriesNum = 0
  185. new g_sentries[MAXSENTRIES]
  186. new g_playerSentries[32] = {0, ...}
  187. new g_playerSentriesEdicts[32][MAXSENTRIESTOTAL]
  188. new g_sModelIndexFireball, g_msgDamage, g_msgDeathMsg, g_msgScoreInfo, g_msgHostagePos,
  189. g_msgHostageK, g_MAXPLAYERS
  190. new sentry_max, sentry_cost1, sentry_cost2, sentry_cost3, sentry_team
  191.  
  192. //new g_MAXENTITIES
  193. //new g_hasSentries = 0
  194. new Float:g_sentryOrigins[32][3]
  195. new g_aimSentry[32]
  196. new bool:g_inBuilding[32]
  197. new bool:g_resetArmouryThisRound = true
  198. new bool:g_hasArmouries = false
  199. new Float:g_lastGameTime = 1.0                      // dunno, looks like get_systime() is always 1.0 first time...
  200. new Float:g_ONEEIGHTYTHROUGHPI, Float:g_gameTime, Float:g_deltaTime
  201. new g_sentryStatusBuffer[32][256]
  202. new g_sentryStatusTrigger
  203. new g_selectedSentry[32] = {-1, ...}
  204. new g_menuId                                // used to store index of menu
  205. new g_lastObjectiveBuild[32], g_inSpyCam[32]
  206. new Float:g_spyCamOffset[3] = {26.0, 29.0, 26.0}                // for the three levels, just what looks good...
  207.  
  208. //Shaman: For disabling building until some time passes after the new round
  209. new bool:g_allowBuild                           //Building, upgrading and reparing is not allowed if this is false
  210. new sentry_wait                             //The cvar
  211.  
  212. new const PLUGINNAME[] = "[ZE] Extra : Sentry Guns"
  213. new const VERSION[] = "0.1.2a"
  214. new const AUTHOR[] = "The_Thing"
  215.  
  216. public plugin_init()
  217. {
  218.     register_plugin(PLUGINNAME, VERSION, AUTHOR)
  219.  
  220.     register_clcmd("sentry_build", "createsentryhere", 0, "- build a sentry gun where you are")
  221.     register_clcmd("sentry_menu", "menumain", 0, "- displays Sentry gun menu")
  222.     register_clcmd("say", "check_say")
  223.     register_clcmd("say_team", "check_say")
  224.  
  225.     #if defined DEBUG
  226.     register_concmd("0botbuild", "botbuild_fn", ADMIN_CFG, "- force bots to build right where they are (debug)")
  227.     #endif
  228.  
  229.     //register_cvar("pend_inc", "30")
  230.     //register_cvar("radar_increment", "4.56")
  231.     //register_cvar("spycamoffset", "24.0")
  232.  
  233.     sentry_max = register_cvar("ze_sentry_max", "9");
  234.     sentry_cost1 = register_cvar("ze_sentry_cost1", "27");
  235.     sentry_cost2 = register_cvar("ze_sentry_cost2", "15");
  236.     sentry_cost3 = register_cvar("ze_sentry_cost3", "20");
  237.     sentry_team = register_cvar("ze_sentry_team", "2");
  238.     sentry_wait = register_cvar("ze_sentry_wait", "15");
  239.  
  240.     register_event("ResetHUD", "newround_event", "b")
  241.     register_event("SendAudio", "endround_event", "a", "2&%!MRAD_terwin", "2&%!MRAD_ctwin", "2&%!MRAD_rounddraw")
  242.     register_event("TextMsg", "endround_event", "a", "2&#Game_C", "2&#Game_w")
  243.     register_event("TextMsg", "endround_event", "a", "2&#Game_will_restart_in")
  244.    
  245.     //Shaman: For destroying sentries after team change
  246.     register_event( "TeamInfo", "jointeam_event", "a")
  247.  
  248.     register_forward(FM_TraceLine, "forward_traceline_post", 1)
  249.  
  250.     //new bool:foundSomething = false
  251.     if (find_ent_by_class(0, "func_bomb_target"))
  252.     {
  253.         register_touch("func_bomb_target", "player", "playerreachedtarget")
  254.         register_touch("weaponbox", "player", "playertouchedweaponbox")
  255.         //foundSomething = true
  256.     }
  257.    
  258.     if (find_ent_by_class(0, "func_hostage_rescue"))
  259.     {
  260.         register_touch("func_hostage_rescue", "player", "playerreachedhostagerescue")
  261.         //foundSomething = true
  262.     }
  263.     if (find_ent_by_class(0, "func_vip_safetyzone"))
  264.     {
  265.         register_touch("func_vip_safetyzone", "player", "playerreachedtarget")
  266.         //foundSomething = true
  267.     }
  268.     if (find_ent_by_class(0, "hostage_entity"))
  269.     {
  270.         register_touch("hostage_entity", "player", "playertouchedhostage")
  271.         //foundSomething = true
  272.     }
  273.  
  274.     register_touch("sentry", "player", "playertouchedsentry")
  275.  
  276.     g_menuId = register_menuid("\ySentry gun menu")
  277.     register_menucmd(g_menuId, 1023, "menumain_handle")
  278.  
  279.     register_message(23, "message_tempentity") // <-- works for 0.16 as well
  280.     //register_think("sentry", "think_sentry") // <-- only 0.20+ can do this
  281.     register_think("sentrybase", "think_sentrybase")
  282.  
  283.     g_msgDamage = get_user_msgid("Damage")
  284.     g_msgDeathMsg = get_user_msgid("DeathMsg")
  285.     g_msgScoreInfo = get_user_msgid("ScoreInfo")
  286.     g_msgHostagePos = get_user_msgid("HostagePos")
  287.     g_msgHostageK = get_user_msgid("HostageK")
  288.  
  289.     g_MAXPLAYERS = get_global_int(GL_maxClients)
  290.     //g_MAXENTITIES = get_global_int(GL_maxEntities)
  291.     g_ONEEIGHTYTHROUGHPI = 180.0 / PI
  292.  
  293.     // Add menu item to menufront
  294.     #if defined AddMenuItem
  295.     AddMenuItem("Sentry guns", "sentry_menu", ADMIN_CFG, PLUGINNAME)
  296.     #endif
  297.  
  298.     // InitArmoury saves the location of all onground weapons. Later we restore them to these origins when a newround begin.
  299.     set_task(5.0, "InitArmoury")
  300. }
  301.  
  302. //Shaman: Event function that removes sentries of a player after team change
  303. public jointeam_event()
  304. {
  305.     //Get the id from the event data
  306.     new id = read_data(1)  
  307.    
  308.     //Remove sentries
  309.     while(GetSentryCount(id)>0)
  310.         sentry_detonate_by_owner(id, true)
  311.    
  312.     return PLUGIN_CONTINUE
  313. }
  314.  
  315. //Shaman: The function that enables building
  316. public enablesentrybur()
  317. {
  318.     g_allowBuild= true;
  319. }
  320.  
  321. public createsentryhere(id)
  322. {
  323.     //Shaman: Check if the player is allowed to build
  324.     if(!g_allowBuild)
  325.     {
  326.         client_print(id, print_center, "You must wait until you can build, upgrade or repair sentries!")
  327.         return PLUGIN_HANDLED
  328.     }
  329.  
  330.     new sentry = AimingAtSentry(id, true)
  331.     // if a valid sentry
  332.     // if within range
  333.     // we can try to upgrade/repair...
  334.     if (sentry && entity_range(sentry, id) <= MAXUPGRADERANGE)
  335.     {
  336.         //client_print(id, print_chat, "Sentry level: %d, last upgrader: %d", entity_get_int(sentry, SENTRY_INT_LEVEL) + 1, entity_get_edict(sentry, SENTRY_ENT_LASTUPGRADER))
  337.         #if defined DISALLOW_OWN_UPGRADES
  338.         // Don't allow builder to upgrade his own sentry first time.
  339.         if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_1 && id == GetSentryPeople(sentry, OWNER))
  340.         {
  341.             client_print(id, print_center, "You cannot upgrade your own sentry gun to level 2, a team mate must do this!")
  342.             return PLUGIN_HANDLED
  343.         }
  344.         #endif
  345.         #if defined DISALLOW_TWO_UPGRADES
  346.         // Don't allow upgrader to upgrade again.
  347.         if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_2 && id == GetSentryPeople(sentry, UPGRADER_1))
  348.         {
  349.             client_print(id, print_center, "You cannot upgrade this sentry gun another time to level 3, a team mate must do this!")
  350.             return PLUGIN_HANDLED
  351.         }
  352.         #endif
  353.         g_aimSentry[id - 1] = sentry
  354.         //if (entity_
  355.         sentry_upgrade(id, sentry)
  356.     }
  357.    
  358.     else
  359.     {
  360.         sentry_build(id)
  361.     }
  362.     return PLUGIN_HANDLED
  363. }
  364.  
  365. public sentry_build(id)
  366. {
  367.     //Shaman: Check if the player is allowed to build
  368.     if(!g_allowBuild)
  369.     {
  370.         client_print(id, print_center, "You must wait until you can build, upgrade or repair sentries!")
  371.         return
  372.     }
  373.  
  374.     if (GetSentryCount(id) >= MAXPLAYERSENTRIES)
  375.     {
  376.         new wordnumbers[128]
  377.         getnumbers(MAXPLAYERSENTRIES, wordnumbers, 127)
  378.         new maxsentries = MAXPLAYERSENTRIES                 // stupid, but compiler gives warning on next line if a defined constant is used
  379.         client_print(id, print_center, "You can only build %s sentry gun%s!", wordnumbers, maxsentries != 1 ? "s" : "")
  380.         return
  381.     }
  382.    
  383.     else if (g_inBuilding[id - 1])
  384.     {
  385.         client_print(id, print_center, "Wow, you're a fast builder...")
  386.         return
  387.     }
  388.    
  389.     else if (!is_user_alive(id) || ze_is_user_zombie(id))
  390.     {
  391.         return
  392.     }
  393.    
  394.     else if (ze_get_escape_coins(id) < g_COST(0))
  395.     {
  396.         client_print(id, print_center, "You don't have enough money to build a sentry gun! (%d needed)", g_COST(0))
  397.         return
  398.     }
  399.    
  400.     else if (!entity_is_on_ground(id))
  401.     {
  402.         client_print(id, print_center, "You must stand on the ground to build a sentry gun!")
  403.         return
  404.     }
  405.     //else if (entity_get_int(id, EV_INT_flags) & FL_DUCKING)
  406.     //{
  407.    
  408.     else if (entity_get_int(id, EV_INT_bInDuck))
  409.     {
  410.         client_print(id, print_center, "Yeah, right, try building a sentry gun sitting on your ***!")
  411.         return
  412.     }
  413.    
  414.     else if ( get_pcvar_num( sentry_team ) )
  415.     {
  416.         if( get_pcvar_num( sentry_team ) < 0 && !is_user_admin(id) )
  417.         {
  418.             client_print(id, print_center, "Only Admins can build!")
  419.             return
  420.         }
  421.     }
  422.    
  423.     else if ( !(abs(get_pcvar_num( sentry_team )) & _:cs_get_user_team(id)) )
  424.     {
  425.         client_print(id, print_center, "Your team cannot build!")
  426.     }
  427.  
  428.  
  429.     new Float:playerOrigin[3]
  430.     entity_get_vector(id, EV_VEC_origin, playerOrigin)
  431.  
  432.     new Float:vNewOrigin[3]
  433.     new Float:vTraceDirection[3]
  434.     new Float:vTraceEnd[3]
  435.     new Float:vTraceResult[3]
  436.     velocity_by_aim(id, 64, vTraceDirection)            // get a velocity in the directino player is aiming, with a multiplier of 64...
  437.     vTraceEnd[0] = vTraceDirection[0] + playerOrigin[0]         // find the new max end position
  438.     vTraceEnd[1] = vTraceDirection[1] + playerOrigin[1]
  439.     vTraceEnd[2] = vTraceDirection[2] + playerOrigin[2]
  440.     trace_line(id, playerOrigin, vTraceEnd, vTraceResult)       // trace, something can be in the way, use hitpoint from vTraceResult as new origin, if nothing's in the way it should be same as vTraceEnd
  441.     vNewOrigin[0] = vTraceResult[0]                 // just copy the new result position to new origin
  442.     vNewOrigin[1] = vTraceResult[1]                 // just copy the new result position to new origin
  443.     vNewOrigin[2] = playerOrigin[2]                 // always build in the same height as player.
  444.  
  445.     if (CreateSentryBase(vNewOrigin, id))
  446.     {
  447.         ze_set_escape_coins(id, ze_get_escape_coins(id) - g_COST(0))
  448.         //SetHasSentry(id, true)
  449.         //client_print(id, print_chat, "Done creating a sentry at %f %f %f!", vNewOrigin[0], vNewOrigin[1], vNewOrigin[2])
  450.     }
  451.  
  452.     else
  453.     {
  454.         //SetHasSentry(id, false)
  455.         client_print(id, print_center, "Cannot build sentry here")
  456.     }
  457. }
  458.  
  459. GetSentryCount(id)
  460. {
  461.     return g_playerSentries[id - 1]
  462.  
  463. //else
  464.  
  465. /*
  466. if (id < 1 || id > get_maxplayers())
  467.     return false
  468.  
  469. //spambits(id, g_hasSentries)
  470.  
  471. return g_hasSentries & (1<<(id - 1)) ? true : false // g_hasSentries[id - 1] //
  472. */
  473. }
  474.  
  475. bool:GetStatusTrigger(player)
  476. {
  477.     if (!is_user_alive(player))
  478.     return false
  479.  
  480.     return g_sentryStatusTrigger & (1<<(player-1)) ? true : false
  481. }
  482.  
  483. SetStatusTrigger(player, bool:onOrOff)
  484. {
  485.     if (onOrOff)
  486.     g_sentryStatusTrigger |= (1<<(player - 1))
  487.     else
  488.     g_sentryStatusTrigger &= ~(1<<(player - 1))
  489. }
  490.  
  491. IncreaseSentryCount(id, sentryEntity)
  492. {
  493.     g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1]] = sentryEntity
  494.     g_playerSentries[id - 1] = g_playerSentries[id - 1] + 1
  495.     new Float:sentryOrigin[3], iSentryOrigin[3]
  496.     entity_get_vector(sentryEntity, EV_VEC_origin, sentryOrigin)
  497.     FVecIVec(sentryOrigin, iSentryOrigin)
  498.  
  499.     new name[32]
  500.     get_user_name(id, name, 31)
  501.     new CsTeams:builderTeam = cs_get_user_team(id)
  502.     for (new i = 1; i <= g_MAXPLAYERS; i++)
  503.     {
  504.         if (!is_user_connected(i) || !is_user_alive(i) || ze_is_user_zombie(id) || cs_get_user_team(i) != builderTeam || id == i)
  505.             continue
  506.         client_print(i, print_center, "%s has built a sentry gun %d units away from you", name, floatround(entity_range(i, sentryEntity)))
  507.  
  508.         //client_print(parm[0], print_chat, "Plotting closest sentry %d on radar: %f %f %f", parm[1], sentryOrigin[0], sentryOrigin[1], sentryOrigin[2])
  509.         message_begin(MSG_ONE, g_msgHostagePos, {0,0,0}, i)
  510.         write_byte(i)
  511.         write_byte(SENTRY_RADAR_TEAMBUILT)
  512.         write_coord(iSentryOrigin[0])
  513.         write_coord(iSentryOrigin[1])
  514.         write_coord(iSentryOrigin[2])
  515.         message_end()
  516.  
  517.         message_begin(MSG_ONE, g_msgHostageK, {0,0,0}, i)
  518.         write_byte(SENTRY_RADAR_TEAMBUILT)
  519.         message_end()
  520.     }
  521.     //client_print(0, print_chat, "%s has built a sentry gun", name)
  522. }
  523.  
  524. DecreaseSentryCount(id, sentry)
  525. {
  526.     // Note that sentry does not exist at this moment, it's just an old index that should get zeroed where it occurs in g_playerSentriesEdicts[id - 1][]
  527.     g_selectedSentry[id - 1] = -1
  528.  
  529.     for (new i = 0; i < g_playerSentries[id - 1]; i++)
  530.     {
  531.         if (g_playerSentriesEdicts[id - 1][i] == sentry)
  532.         {
  533.             // Copy last sentry edict index to this one
  534.             g_playerSentriesEdicts[id - 1][i] = g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1] - 1]
  535.             // Zero out last sentry index
  536.             g_playerSentriesEdicts[id - 1][g_playerSentries[id - 1] - 1] = 0
  537.             break
  538.         }
  539.     }
  540.     g_playerSentries[id - 1] = g_playerSentries[id - 1] - 1
  541. }
  542.  
  543. /*
  544. SetHasSentry(id, bool:trueOrFalse)
  545. {
  546.     //g_hasSentries[id - 1] = trueOrFalse
  547.     //spambits(id, g_hasSentries)
  548.     if (trueOrFalse)
  549.     {
  550.         g_hasSentries |= (1<<(id - 1))
  551.         new name[32]
  552.         get_user_name(id, name, 31)
  553.         new CsTeams:builderTeam = cs_get_user_team(id)
  554.         for (new i = 0; i < g_MAXPLAYERS; i++)
  555.         {
  556.             if (!is_user_connected(i) || !is_user_alive(i) || ze_is_user_zombie(i) || cs_get_user_team(i) != builderTeam || id == i)
  557.                 continue
  558.             client_print(i, print_center, "%s has built a sentry gun", name)
  559.         }
  560.     }
  561.    
  562.     else
  563.         g_hasSentries &= ~(1<<(id - 1))
  564.  
  565.     //spambits(id, g_hasSentries)
  566. }
  567. */
  568.  
  569. stock bool:CreateSentryBase(Float:origin[3], creator)
  570. {
  571.     // Check contents of point, also trace lines from center to each of the eight ends
  572.     if (point_contents(origin) != CONTENTS_EMPTY || TraceCheckCollides(origin, 24.0))
  573.     {
  574.         return false
  575.     }
  576.  
  577.     // Check that a trace from origin straight down to ground results in a distance which is the same as player height over ground?
  578.     new Float:hitPoint[3], Float:originDown[3]
  579.     originDown = origin
  580.     originDown[2] = -5000.0                     // dunno the lowest possible height...
  581.     trace_line(0, origin, originDown, hitPoint)
  582.     new Float:baDistanceFromGround = vector_distance(origin, hitPoint)
  583.     //client_print(creator, print_chat, "Base distance from ground: %f", baDistanceFromGround)
  584.  
  585.     new Float:difference = PLAYERORIGINHEIGHT - baDistanceFromGround
  586.     if (difference < -1 * HEIGHTDIFFERENCEALLOWED || difference > HEIGHTDIFFERENCEALLOWED)
  587.     {
  588.         //client_print(creator, print_chat, "You can't build here! %f", difference)
  589.         return false
  590.     }
  591.  
  592.     new entbase = create_entity("func_breakable")       // func_wall
  593.     if (!entbase)
  594.         return false
  595.  
  596.     // Set sentrybase health
  597.     new healthstring[16]
  598.     num_to_str(floatround(g_HEALTHS[0]), healthstring, 15)
  599.     DispatchKeyValue(entbase, "health", healthstring)
  600.     DispatchKeyValue(entbase, "material", "6")
  601.  
  602.     DispatchSpawn(entbase)
  603.     // Change classname
  604.     entity_set_string(entbase, EV_SZ_classname, "sentrybase")
  605.     // Set model
  606.     entity_set_model(entbase, "models/sentries/base.mdl")   // later set according to level
  607.     // Set size
  608.     new Float:mins[3], Float:maxs[3]
  609.     mins[0] = -16.0
  610.     mins[1] = -16.0
  611.     mins[2] = 0.0
  612.     maxs[0] = 16.0
  613.     maxs[1] = 16.0
  614.     maxs[2] = 1000.0                    // Set to 16.0 later.
  615.     entity_set_size(entbase, mins, maxs)
  616.     //client_print(creator, print_chat, "Creating sentry %d with bounds %f", ent, BOUNDS)
  617.     // Set origin
  618.     entity_set_origin(entbase, origin)
  619.     // Set starting angle
  620.     //entity_get_vector(creator, EV_VEC_angles, origin)
  621.     //origin[0] = 0.0
  622.     //origin[1] += 180.0
  623.     //origin[2] = 0.0
  624.     //entity_set_vector(ent, EV_VEC_angles, origin)
  625.     // Set solidness
  626.     entity_set_int(entbase, EV_INT_solid, SOLID_SLIDEBOX) // SOLID_SLIDEBOX
  627.     // Set movetype
  628.     entity_set_int(entbase, EV_INT_movetype, MOVETYPE_TOSS) // head flies, base falls
  629.  
  630.     // Set team
  631.     entity_set_int(entbase, BASE_INT_TEAM, get_user_team(creator))
  632.  
  633.     new parms[2]
  634.     parms[0] = entbase
  635.     parms[1] = creator
  636.  
  637.     g_sentryOrigins[creator - 1] = origin
  638.  
  639.     emit_sound(creator, CHAN_AUTO, "sentries/building.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  640.  
  641.     set_task(BASESENTRYDELAY, "createsentryhead", 0, parms, 2)
  642.     g_inBuilding[creator - 1] = true
  643.  
  644.     return true
  645. }
  646.  
  647. public createsentryhead(parms[2])
  648. {
  649.     new entbase = parms[0]
  650.  
  651.     new creator = parms[1]
  652.    
  653.     if (!g_inBuilding[creator - 1])
  654.     {
  655.         // g_inBuilding is reset upon new round, then don't continue with building sentry head. Remove base and return.
  656.         if (is_valid_ent(entbase))
  657.             remove_entity(entbase)
  658.         return
  659.     }
  660.  
  661.     new Float:origin[3]
  662.     origin = g_sentryOrigins[creator - 1]
  663.  
  664.     new ent = create_entity("func_breakable")
  665.    
  666.     if (!ent)
  667.     {
  668.         if (is_valid_ent(entbase))
  669.             remove_entity(entbase)
  670.         return
  671.     }
  672.  
  673.     new Float:mins[3], Float:maxs[3]
  674.     // Set true size of base... if it exists!
  675.     // Also set sentry <-> base connections, if base still exists
  676.     if (is_valid_ent(entbase))
  677.     {
  678.         mins[0] = -16.0
  679.         mins[1] = -16.0
  680.         mins[2] = 0.0
  681.         maxs[0] = 16.0
  682.         maxs[1] = 16.0
  683.         maxs[2] = 16.0
  684.         entity_set_size(entbase, mins, maxs)
  685.  
  686.         entity_set_edict(ent, SENTRY_ENT_BASE, entbase)
  687.         entity_set_edict(entbase, BASE_ENT_SENTRY, ent)
  688.     }
  689.  
  690.     // Store our sentry in array
  691.     g_sentries[g_sentriesNum] = ent
  692.  
  693.     new healthstring[16]
  694.     num_to_str(floatround(g_HEALTHS[0]), healthstring, 15)
  695.     DispatchKeyValue(ent, "health", healthstring)
  696.     DispatchKeyValue(ent, "material", "6")
  697.  
  698.     DispatchSpawn(ent)
  699.     // Change classname
  700.     entity_set_string(ent, EV_SZ_classname, "sentry")
  701.     // Set model
  702.     entity_set_model(ent, "models/sentries/sentry1.mdl") // later set according to level
  703.     // Set size
  704.     mins[0] = -16.0
  705.     mins[1] = -16.0
  706.     mins[2] = 0.0
  707.     maxs[0] = 16.0
  708.     maxs[1] = 16.0
  709.     maxs[2] = 48.0
  710.     entity_set_size(ent, mins, maxs)
  711.     //client_print(creator, print_chat, "Creating sentry %d with bounds %f", ent, BOUNDS)
  712.     // Set origin
  713.     entity_set_origin(ent, origin)
  714.     // Set starting angle
  715.     entity_get_vector(creator, EV_VEC_angles, origin)
  716.     origin[0] = 0.0
  717.     origin[1] += 180.0
  718.     entity_set_float(ent, SENTRY_FL_ANGLE, origin[1])
  719.     origin[2] = 0.0
  720.     entity_set_vector(ent, EV_VEC_angles, origin)
  721.     // Set solidness
  722.     entity_set_int(ent, EV_INT_solid, SOLID_SLIDEBOX)           // SOLID_SLIDEBOX
  723.     // Set movetype
  724.     entity_set_int(ent, EV_INT_movetype, MOVETYPE_TOSS)             // head flies, base doesn't
  725.     // Set tilt of cannon
  726.     set_pev(ent, PEV_SENTRY_TILT_TURRET, 127)               //entity_set_byte(ent, SENTRY_TILT_TURRET, 127) // 127 is horisontal
  727.     // Tilt of rocket launcher barrels at level 3
  728.     set_pev(ent, PEV_SENTRY_TILT_LAUNCHER, 127)                 //entity_set_byte(ent, SENTRY_TILT_LAUNCHER, 127) // 127 is horisontal
  729.     // Angle of small radar at level 3
  730.     entity_set_float(ent, SENTRY_FL_RADARANGLE, 127.0)
  731.     set_pev(ent, PEV_SENTRY_TILT_RADAR, 127)                //entity_set_byte(ent, SENTRY_TILT_RADAR, 127) // 127 is middle
  732.  
  733.  
  734.     // Set owner
  735.     //entity_set_edict(ent, SENTRY_ENT_OWNER, creator)
  736.     SetSentryPeople(ent, OWNER, creator)
  737.  
  738.     // Set team
  739.     entity_set_int(ent, SENTRY_INT_TEAM, get_user_team(creator))
  740.  
  741.     // Set level (not really necessary, but for looks)
  742.     entity_set_int(ent, SENTRY_INT_LEVEL, SENTRY_LEVEL_1)
  743.  
  744.     // Top color
  745.     #if defined RANDOM_TOPCOLOR
  746.     new topColor = random_num(0, 255)
  747.     #else
  748.     new topColor = cs_get_user_team(creator) == CS_TEAM_CT ? COLOR_TOP_CT : COLOR_TOP_T
  749.     #endif
  750.     // Bottom color
  751.     #if defined RANDOM_BOTTOMCOLOR
  752.     new bottomColor = random_num(0, 255)
  753.     #else
  754.     new bottomColor = cs_get_user_team(creator) == CS_TEAM_CT ? COLOR_BOTTOM_CT : COLOR_BOTTOM_T
  755.     #endif
  756.  
  757.     // Set color
  758.     new map = topColor | (bottomColor<<8)
  759.     //spambits(creator, topColor)
  760.     //spambits(creator, bottomColor)
  761.     //spambits(creator, map)
  762.     entity_set_int(ent, EV_INT_colormap, map)
  763.    
  764.     g_sentriesNum++
  765.  
  766.     emit_sound(ent, CHAN_AUTO, "sentries/turrset.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  767.  
  768.     IncreaseSentryCount(creator, ent)
  769.  
  770.     new parm[4]
  771.     parm[0] = ent
  772.     set_task(g_THINKFREQUENCIES[0], "sentry_think", TASKID_THINK + parm[0], parm, 1)
  773.  
  774.     parm[1] = random_num(0, 1)
  775.     parm[2] = 0
  776.     parm[3] = 0
  777.     new directions = (random_num(0, 1)<<SENTRY_DIR_CANNON) | (random_num(0, 1)<<SENTRY_DIR_RADAR)
  778.     entity_set_int(ent, SENTRY_INT_PENDDIR, directions)
  779.  
  780.     //entity_set_float(ent, SENTRY_FL_RADARDIR, random_num(0, 1) + 0.0)
  781.  
  782.     g_inBuilding[creator - 1] = false
  783.  
  784.     if (!is_valid_ent(entbase))
  785.     {
  786.         // Someone probably destroyed the base before head was built!
  787.         // Sentry should go nuts. :-P
  788.         entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NUTS)
  789.     }
  790. }
  791.  
  792. public server_frame()
  793. {
  794.     g_gameTime = get_gametime()
  795.     g_deltaTime = g_gameTime - g_lastGameTime
  796.     //server_print("Gametime: %f, Last gametime: %f", g_gameTime, g_lastGameTime)
  797.  
  798.     new tempSentries[MAXSENTRIES], Float:angles[3]
  799.  
  800.     new tempSentriesNum = 0
  801.    
  802.     for (new i = 0; i < g_sentriesNum; i++)
  803.     {
  804.         //sentry_pendulum(g_sentries[i], g_deltaTime)
  805.         tempSentries[i] = g_sentries[i]
  806.         tempSentriesNum++
  807.     }
  808.  
  809.     for (new i = 0; i < tempSentriesNum; i++)
  810.     {
  811.         //if (entity_get_float(tempSentries[i], EV_FL_nextthink) < g_game
  812.         sentry_pendulum(tempSentries[i], g_deltaTime)
  813.        
  814.         if (entity_get_edict(tempSentries[i], SENTRY_ENT_SPYCAM) != 0)
  815.         {
  816.             entity_get_vector(tempSentries[i], EV_VEC_angles, angles)
  817.             entity_set_vector(entity_get_edict(tempSentries[i], SENTRY_ENT_SPYCAM), EV_VEC_angles, angles)
  818.         }
  819.     }
  820.  
  821.     g_lastGameTime = g_gameTime
  822.     return PLUGIN_CONTINUE
  823. }
  824.  
  825. sentry_pendulum(sentry, Float:deltaTime)
  826. {
  827.     switch (entity_get_int(sentry, SENTRY_INT_FIRE))
  828.     {
  829.         case SENTRY_FIREMODE_NO:
  830.         {
  831.             new Float:angles[3]
  832.             entity_get_vector(sentry, EV_VEC_angles, angles)
  833.             new Float:baseAngle = entity_get_float(sentry, SENTRY_FL_ANGLE)
  834.             new directions = entity_get_int(sentry, SENTRY_INT_PENDDIR)
  835.            
  836.             if (directions & (1<<SENTRY_DIR_CANNON))
  837.             {
  838.                 angles[1] -= (PENDULUM_INCREMENT * deltaTime)           // PENDULUM_INCREMENT get_cvar_float("pend_inc")
  839.                
  840.                 if (angles[1] < baseAngle - PENDULUM_MAX)
  841.                 {
  842.                     angles[1] = baseAngle - PENDULUM_MAX
  843.                     directions &= ~(1<<SENTRY_DIR_CANNON)
  844.                     entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
  845.                 }
  846.             }
  847.            
  848.             else
  849.             {
  850.                 angles[1] += (PENDULUM_INCREMENT * deltaTime)           // PENDULUM_INCREMENT get_cvar_float("pend_inc")
  851.                
  852.                 if (angles[1] > baseAngle + PENDULUM_MAX)
  853.                 {
  854.                     angles[1] = baseAngle + PENDULUM_MAX
  855.                     directions |= (1<<SENTRY_DIR_CANNON)
  856.                     entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
  857.                 }
  858.             }
  859.        
  860.             entity_set_vector(sentry, EV_VEC_angles, angles);
  861.  
  862.             if (entity_get_int(sentry, SENTRY_INT_LEVEL) == SENTRY_LEVEL_3)
  863.             {
  864.                     //new radarAngle = entity_get_byte(sentry, SENTRY_TILT_RADAR)
  865.                     //SENTRY_FL_RADARANGLE
  866.                     new Float:radarAngle = entity_get_float(sentry, SENTRY_FL_RADARANGLE)
  867.  
  868.                     if (directions & (1<<SENTRY_DIR_RADAR))
  869.                     {
  870.                         radarAngle = radarAngle - RADAR_INCREMENT           // get_cvar_float("radar_increment")
  871.                
  872.                         if (radarAngle < 0.0)
  873.                         {
  874.                             radarAngle = 0.0
  875.                             directions &= ~(1<<SENTRY_DIR_RADAR)
  876.                             entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
  877.                         }
  878.                     }
  879.            
  880.                     else
  881.                     {  
  882.                         radarAngle = radarAngle + RADAR_INCREMENT           // get_cvar_float("radar_increment")
  883.                    
  884.                         if (radarAngle > 255.0)
  885.                         {
  886.                             radarAngle = 255.0
  887.                             directions |= (1<<SENTRY_DIR_RADAR)
  888.                             entity_set_int(sentry, SENTRY_INT_PENDDIR, directions)
  889.                         }
  890.                     }
  891.                     entity_set_float(sentry, SENTRY_FL_RADARANGLE, radarAngle)
  892.                     set_pev(sentry, PEV_SENTRY_TILT_RADAR, floatround(radarAngle))      //entity_set_byte(sentry, SENTRY_TILT_RADAR, floatround(radarAngle))
  893.             }
  894.             return
  895.     }
  896.     case SENTRY_FIREMODE_NUTS:
  897.     {
  898.         new Float:angles[3]
  899.         entity_get_vector(sentry, EV_VEC_angles, angles)
  900.  
  901.         new Float:spinSpeed = entity_get_float(sentry, SENTRY_FL_SPINSPEED)
  902.        
  903.         if (entity_get_int(sentry, SENTRY_INT_PENDDIR) & (1<<SENTRY_DIR_CANNON))
  904.         {
  905.             angles[1] -= (spinSpeed * deltaTime)
  906.            
  907.             if (angles[1] < 0.0)
  908.             {
  909.                 angles[1] = 360.0 + angles[1]
  910.             }
  911.         }
  912.        
  913.         else
  914.         {
  915.             angles[1] += (spinSpeed * deltaTime)
  916.            
  917.             if (angles[1] > 360.0)
  918.             {
  919.                 angles[1] = angles[1] - 360.0
  920.             }
  921.         }
  922.         // Increment speed raise
  923.         entity_set_float(sentry, SENTRY_FL_SPINSPEED, (spinSpeed += random_float(1.0, 2.0)))
  924.  
  925.         new Float:maxSpin = entity_get_float(sentry, SENTRY_FL_MAXSPIN)
  926.        
  927.         if (maxSpin == 0.0)
  928.         {
  929.             // Set rotation speed to explode at
  930.             entity_set_float(sentry, SENTRY_FL_MAXSPIN, maxSpin = random_float(500.0, 750.0))
  931.             //client_print(0, print_chat, "parm3 set to %d", parm[3])
  932.         }
  933.        
  934.         else if (spinSpeed >= maxSpin)
  935.         {
  936.             //client_print(0, print_chat, "Detonating!")
  937.             sentry_detonate(sentry, false, false)
  938.             //remove_entity(parm[0])
  939.             return
  940.         }
  941.         entity_set_vector(sentry, EV_VEC_angles, angles);
  942.  
  943.         return
  944.         }
  945.     }
  946. }
  947.  
  948.  
  949. // Checks the contents of eight points corresponding to the bbox around ent origin. Also does a trace from origin to each point. If anything goes wrong, report a hit.
  950. // TODO: high bounds should get higher, so that building in tight places not gets sentries stuck in roof... TraceCheckCollides
  951. bool:TraceCheckCollides(Float:origin[3], const Float:BOUNDS)
  952. {
  953.     new Float:traceEnds[8][3], Float:traceHit[3], hitEnt
  954.  
  955.     // x, z, y
  956.     traceEnds[0][0] = origin[0] - BOUNDS
  957.     traceEnds[0][1] = origin[1] - BOUNDS
  958.     traceEnds[0][2] = origin[2] - BOUNDS
  959.  
  960.     traceEnds[1][0] = origin[0] - BOUNDS
  961.     traceEnds[1][1] = origin[1] - BOUNDS
  962.     traceEnds[1][2] = origin[2] + BOUNDS
  963.  
  964.     traceEnds[2][0] = origin[0] + BOUNDS
  965.     traceEnds[2][1] = origin[1] - BOUNDS
  966.     traceEnds[2][2] = origin[2] + BOUNDS
  967.  
  968.     traceEnds[3][0] = origin[0] + BOUNDS
  969.     traceEnds[3][1] = origin[1] - BOUNDS
  970.     traceEnds[3][2] = origin[2] - BOUNDS
  971.     //
  972.     traceEnds[4][0] = origin[0] - BOUNDS
  973.     traceEnds[4][1] = origin[1] + BOUNDS
  974.     traceEnds[4][2] = origin[2] - BOUNDS
  975.  
  976.     traceEnds[5][0] = origin[0] - BOUNDS
  977.     traceEnds[5][1] = origin[1] + BOUNDS
  978.     traceEnds[5][2] = origin[2] + BOUNDS
  979.  
  980.     traceEnds[6][0] = origin[0] + BOUNDS
  981.     traceEnds[6][1] = origin[1] + BOUNDS
  982.     traceEnds[6][2] = origin[2] + BOUNDS
  983.  
  984.     traceEnds[7][0] = origin[0] + BOUNDS
  985.     traceEnds[7][1] = origin[1] + BOUNDS
  986.     traceEnds[7][2] = origin[2] - BOUNDS
  987.  
  988.     for (new i = 0; i < 8; i++)
  989.     {
  990.         if (point_contents(traceEnds[i]) != CONTENTS_EMPTY)
  991.             return true
  992.  
  993.         hitEnt = trace_line(0, origin, traceEnds[i], traceHit)
  994.        
  995.         if (hitEnt != 0)
  996.             return true
  997.            
  998.         for (new j = 0; j < 3; j++)
  999.         {
  1000.             if (traceEnds[i][j] != traceHit[j])
  1001.                 return true
  1002.         }
  1003.     }
  1004.  
  1005.     return false
  1006. }
  1007.  
  1008.  
  1009. //#define TE_TRACER             6       // tracer effect from point to point
  1010. // coord, coord, coord (start)
  1011. // coord, coord, coord (end)
  1012.  
  1013. tracer(Float:start[3], Float:end[3])
  1014. {
  1015.     //new start_[3]
  1016.     new start_[3], end_[3]
  1017.     FVecIVec(start, start_)
  1018.     FVecIVec(end, end_)
  1019.     message_begin(MSG_BROADCAST, SVC_TEMPENTITY)        //  MSG_PAS MSG_BROADCAST
  1020.     write_byte(TE_TRACER)
  1021.     write_coord(start_[0])
  1022.     write_coord(start_[1])
  1023.     write_coord(start_[2])
  1024.     write_coord(end_[0])
  1025.     write_coord(end_[1])
  1026.     write_coord(end_[2])
  1027.     message_end()
  1028. }
  1029.  
  1030. /*
  1031. #define TE_BREAKMODEL               108     // box of models or sprites
  1032. // coord, coord, coord (position)
  1033. // coord, coord, coord (size)
  1034. // coord, coord, coord (velocity)
  1035. // byte (random velocity in 10's)
  1036. // short (sprite or model index)
  1037. // byte (count)
  1038. // byte (life in 0.1 secs)
  1039. // byte (flags)
  1040. */
  1041.  
  1042. stock create_explosion(Float:origin_[3])
  1043. {
  1044.     new origin[3]
  1045.     FVecIVec(origin_, origin)
  1046.     //client_print(0, print_chat, "Creating explosion at %d %d %d", origin[0], origin[1], origin[2])
  1047.  
  1048.     message_begin(MSG_BROADCAST, SVC_TEMPENTITY, origin) // MSG_PAS not really good here
  1049.     write_byte(TE_EXPLOSION)
  1050.     write_coord(origin[0])
  1051.     write_coord(origin[1])
  1052.     write_coord(origin[2])
  1053.     write_short(g_sModelIndexFireball)
  1054.     write_byte(random_num(0, 20) + 50) // scale * 10 // random_num(0, 20) + 20
  1055.     write_byte(12) // framerate
  1056.     write_byte(TE_EXPLFLAG_NONE)
  1057.     message_end()
  1058.  
  1059.     // Blast stuff away
  1060.     genericShock(origin_, SENTRYEXPLODERADIUS, "weaponbox", 32, SENTRYSHOCKPOWER, OBJECT_GENERIC)
  1061.     genericShock(origin_, SENTRYEXPLODERADIUS, "armoury_entity", 32, SENTRYSHOCKPOWER, OBJECT_ARMOURY)
  1062.     genericShock(origin_, SENTRYEXPLODERADIUS, "player", 32, SENTRYSHOCKPOWER, OBJECT_PLAYER)
  1063.     genericShock(origin_, SENTRYEXPLODERADIUS, "grenade", 32, SENTRYSHOCKPOWER, OBJECT_GRENADE)
  1064.     genericShock(origin_, SENTRYEXPLODERADIUS, "hostage_entity", 32, SENTRYSHOCKPOWER, OBJECT_GENERIC)
  1065.  
  1066.     // Hurt ppl in vicinity
  1067.  
  1068.     new Float:playerOrigin[3], Float:distance, Float:flDmgToDo, Float:dmgbase = DMG_EXPLOSION_TAKE + 0.0, newHealth
  1069.     for (new i = 1; i <= g_MAXPLAYERS; i++)
  1070.     {
  1071.         if (!is_user_alive(i) || !ze_is_user_zombie(i) || get_user_godmode(i))
  1072.             continue
  1073.  
  1074.         entity_get_vector(i, EV_VEC_origin, playerOrigin)
  1075.         distance = vector_distance(playerOrigin, origin_)
  1076.    
  1077.         if (distance <= SENTRYEXPLODERADIUS)
  1078.         {
  1079.             flDmgToDo = dmgbase - (dmgbase * (distance / SENTRYEXPLODERADIUS))
  1080.             //client_print(i, print_chat, "flDmgToDo = %f, dmgbase = %f, distance = %f, SENTRYEXPLODERADIUS = %f", flDmgToDo, dmgbase, distance, SENTRYEXPLODERADIUS)
  1081.             newHealth = get_user_health(i) - floatround(flDmgToDo)
  1082.        
  1083.             if (newHealth <= 0)
  1084.             {
  1085.                 // Somehow if player is killed here server crashes out saying some message (Damage or Death) has not been sent yet when trying to send another message.
  1086.                 // By delaying death with 0.0 (huuh?) seconds this seems to be fixed.
  1087.                 set_task(0.0, "TicketToHell", i)
  1088.                 continue
  1089.             }
  1090.  
  1091.             set_user_health(i, newHealth)
  1092.  
  1093.             message_begin(MSG_ONE_UNRELIABLE, g_msgDamage, {0,0,0}, i)
  1094.             write_byte(floatround(flDmgToDo))
  1095.             write_byte(floatround(flDmgToDo))
  1096.             write_long(DMG_BLAST)
  1097.             write_coord(origin[0])
  1098.             write_coord(origin[1])
  1099.             write_coord(origin[2])
  1100.             message_end()
  1101.         }
  1102.     }
  1103. }
  1104.  
  1105. // Hacks, damn you!
  1106. public TicketToHell(player)
  1107. {
  1108.     if (!is_user_connected(player))
  1109.         return
  1110.        
  1111.     new frags = get_user_frags(player)
  1112.     user_kill(player, 1)                    // don't decrease frags
  1113.     new parms[4]
  1114.     parms[0] = player
  1115.     parms[1] = frags
  1116.     parms[2] = cs_get_user_deaths(player)
  1117.     parms[3] = int:cs_get_user_team(player)
  1118.     set_task(0.0, "DelayedScoreInfoUpdate", 0, parms, 4)
  1119. }
  1120.  
  1121. public DelayedScoreInfoUpdate(parms[4])
  1122. {
  1123.     scoreinfo_update(parms[0], parms[1], parms[2], parms[3])
  1124. }
  1125.  
  1126. stock genericShock(Float:hitPointOrigin[3], Float:radius, classString[], maxEntsToFind, Float:power, OBJECTTYPE:objecttype)
  1127. { // bool:isthisplayer, bool:isthisarmouryentity, bool:isthisgrenade/*, Float:pullup*/) {
  1128.     new entList[32]
  1129.     if (maxEntsToFind > 32)
  1130.         maxEntsToFind = 32
  1131.  
  1132.     new entsFound = find_sphere_class(0, classString, radius, entList, maxEntsToFind, hitPointOrigin)
  1133.  
  1134.     new Float:entOrigin[3]
  1135.     new Float:velocity[3]
  1136.     new Float:cOrigin[3]
  1137.  
  1138.     for (new j = 0; j < entsFound; j++)
  1139.     {
  1140.         switch (objecttype)
  1141.         {
  1142.             case OBJECT_PLAYER:
  1143.             {
  1144.                 if (!is_user_alive(entList[j]))         // Don't move dead players
  1145.                     continue
  1146.             }
  1147.            
  1148.             case OBJECT_GRENADE:
  1149.             {
  1150.                 new l_model[16]
  1151.                 entity_get_string(entList[j], EV_SZ_model, l_model, 15)
  1152.                
  1153.                 if (equal(l_model, "models/w_c4.mdl"))                      // don't move planted c4s :-P
  1154.                     continue
  1155.             }
  1156.         }
  1157.         entity_get_vector(entList[j], EV_VEC_origin, entOrigin) // get_entity_origin(entList[j],entOrigin)
  1158.  
  1159.         new Float:distanceNadePl = vector_distance(entOrigin, hitPointOrigin)
  1160.  
  1161.         // Stuff on ground AND below explosion are "placed" a distance above explosion Y-wise ([2]), so that they fly off ground etc.
  1162.         if (entity_is_on_ground(entList[j]) && entOrigin[2] < hitPointOrigin[2])
  1163.         entOrigin[2] = hitPointOrigin[2] + distanceNadePl
  1164.  
  1165.         entity_get_vector(entList[j], EV_VEC_velocity, velocity)
  1166.  
  1167.         cOrigin[0] = (entOrigin[0] - hitPointOrigin[0]) * radius / distanceNadePl + hitPointOrigin[0]
  1168.         cOrigin[1] = (entOrigin[1] - hitPointOrigin[1]) * radius / distanceNadePl + hitPointOrigin[1]
  1169.         cOrigin[2] = (entOrigin[2] - hitPointOrigin[2]) * radius / distanceNadePl + hitPointOrigin[2]
  1170.  
  1171.         velocity[0] += (cOrigin[0] - entOrigin[0]) * power
  1172.         velocity[1] += (cOrigin[1] - entOrigin[1]) * power
  1173.         velocity[2] += (cOrigin[2] - entOrigin[2]) * power
  1174.  
  1175.         entity_set_vector(entList[j], EV_VEC_velocity, velocity)
  1176.  
  1177.     }
  1178. }
  1179.  
  1180. stock entity_is_on_ground(entity)
  1181. {
  1182.     return entity_get_int(entity, EV_INT_flags) & FL_ONGROUND
  1183. }
  1184.  
  1185.  
  1186. public message_tempentity()
  1187. {
  1188.     if (get_msg_args() != 15 && get_msg_arg_int(1) != TE_BREAKMODEL)
  1189.         return PLUGIN_CONTINUE
  1190.  
  1191.     // Something broke, maybe it was one of our sentries. Loop through all sentries to see if any of them has health <=0.
  1192.     for (new i = 0; i < g_sentriesNum; i++)
  1193.     {
  1194.         if (entity_get_float(g_sentries[i], EV_FL_health) <= 0.0)
  1195.         {
  1196.             //server_cmd("amx_box %d", g_sentries[i])
  1197.             sentry_detonate(i, false, true)
  1198.  
  1199.             //origin[0] = get_msg_arg_float(2)
  1200.             //origin[1] = get_msg_arg_float(3)
  1201.             //origin[2] = get_msg_arg_float(4)
  1202.  
  1203.             // Rewind iteration loop; the last sentry may have been destroyed also
  1204.             i--
  1205.         }
  1206.     }
  1207.  
  1208.     return PLUGIN_CONTINUE
  1209. }
  1210.  
  1211. /*
  1212. public think_sentry(ent)
  1213. {
  1214.     // Hmm this place can be used to tell when a sentry breaks...
  1215.  
  1216.     sentry_detonate(ent, false)
  1217.     // All of these always give 0 values :-(
  1218.     //client_print(0, print_chat, "%d thinks: inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))
  1219.  
  1220.     return PLUGIN_CONTINUE
  1221. }
  1222. */
  1223. public think_sentrybase(sentrybase)
  1224. {
  1225.     // Hmm this place can be used to tell when a sentrybase breaks...
  1226.  
  1227.     sentrybase_broke(sentrybase)
  1228.     //sentry_detonate(ent, false)
  1229.  
  1230.     // All of these always give 0 values :-(
  1231.     //client_print(0, print_chat, "%d thinks: inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))
  1232.  
  1233.     return PLUGIN_CONTINUE
  1234. }
  1235.  
  1236. sentrybase_broke(sentrybase)
  1237. {
  1238.     new sentry = entity_get_edict(sentrybase, BASE_ENT_SENTRY)
  1239.     if (is_valid_ent(sentrybase))
  1240.         remove_entity(sentrybase)
  1241.  
  1242.     // Sentry could be 0 which should mean it has not been built yet. No need to do anything in that case.
  1243.     if (sentry == 0)
  1244.         return
  1245.  
  1246.     entity_set_int(sentry, SENTRY_INT_FIRE, SENTRY_FIREMODE_NUTS)
  1247.     // Set cannon tower straight, calculate tower tilt offset to angles later... entityviewhitpoint fn needs changing for this to use a custom angle vector
  1248.     set_pev(sentry, PEV_SENTRY_TILT_TURRET, 127)                        //entity_set_byte(sentry, SENTRY_TILT_TURRET, 127)
  1249. }
  1250.  
  1251. sentry_detonate(sentry, bool:quiet, bool:isIndex)
  1252. {
  1253.     // Explode!
  1254.     new i
  1255.    
  1256.     if (isIndex)
  1257.     {
  1258.         i = sentry
  1259.         sentry = g_sentries[sentry]
  1260.         if (!is_valid_ent(sentry))
  1261.             return
  1262.     }
  1263.    
  1264.     else
  1265.     {
  1266.         if (!is_valid_ent(sentry))
  1267.             return
  1268.         // Find index of this sentry
  1269.         for (new j = 0; j < g_sentriesNum; j++)
  1270.         {
  1271.             if (g_sentries[j] == sentry)
  1272.             {
  1273.                 i = j
  1274.                 break
  1275.             }
  1276.         }
  1277.     }
  1278.  
  1279.     // Kill tasks
  1280.     remove_task(TASKID_THINK + sentry)                  // removes think
  1281.     remove_task(TASKID_THINKPENDULUM + sentry)              // removes think
  1282.     remove_task(TASKID_SENTRYONRADAR + sentry)              // in case someone's displaying this on radar
  1283.  
  1284.     new owner = GetSentryPeople(sentry, OWNER)
  1285.  
  1286.     // If sentry has a spycam, call the stuff to remove it now
  1287.     if (entity_get_edict(sentry, SENTRY_ENT_SPYCAM) != 0)
  1288.     {
  1289.         remove_task(TASKID_SPYCAM + owner)              // remove the ongoing task...
  1290.         // And call this now on our own...
  1291.         new parms[3]
  1292.         parms[0] = owner
  1293.         parms[1] = entity_get_edict(sentry, SENTRY_ENT_SPYCAM)
  1294.         parms[2] = sentry
  1295.         DestroySpyCam(parms)
  1296.     }
  1297.  
  1298.     if (!quiet)
  1299.     {
  1300.         #if defined EXPLODINGSENTRIES
  1301.         new Float:origin[3]
  1302.         entity_get_vector(sentry, EV_VEC_origin, origin)
  1303.         create_explosion(origin)
  1304.         #endif
  1305.  
  1306.         // Report to owner that it broke
  1307.         client_print(owner, print_center, "Your sentry detonated!")
  1308.     }
  1309.     DecreaseSentryCount(owner, sentry)
  1310.     //SetHasSentry(GetSentryPeople(sentry, OWNER), false)
  1311.  
  1312.     // Remove base first
  1313.     //server_cmd("amx_entinfo %d", ent)
  1314.     if (entity_get_int(sentry, SENTRY_INT_FIRE) != SENTRY_FIREMODE_NUTS)
  1315.     set_task(0.0, "delayedremovalofentity", entity_get_edict(sentry, SENTRY_ENT_BASE))
  1316.     //remove_entity(entity_get_edict(g_sentries[i], SENTRY_ENT_BASE))
  1317.     // Remove this entity
  1318.     //server_cmd("amx_entinfo %d", ent)
  1319.     set_task(0.0, "delayedremovalofentity", sentry)
  1320.     //remove_entity(g_sentries[i])
  1321.     // Put the last sentry in the deleted entity's place
  1322.     g_sentries[i] = g_sentries[g_sentriesNum - 1]
  1323.     // Lower nr of sentries
  1324.     g_sentriesNum--
  1325. }
  1326.  
  1327. public delayedremovalofentity(entity)
  1328. {
  1329.     if (!is_valid_ent(entity))
  1330.     {
  1331.         //client_print(0, print_chat, "Was gonna remove %d, but it's not valid", entity)
  1332.         return
  1333.     }
  1334.     //client_print(0, print_chat, "removing %d", entity)
  1335.     remove_entity(entity)
  1336. }
  1337.  
  1338. sentry_detonate_by_owner(owner, bool:quiet = false)
  1339. {
  1340. /*
  1341. for (new i = g_MAXPLAYERS + 1, classname[7]; i < g_MAXENTITIES; i++)
  1342. {
  1343.     if (!is_valid_ent(i))
  1344.         continue
  1345.     entity_get_string(i, EV_SZ_classname, classname, 6)
  1346.     if (!equal(classname, "sentry"))
  1347.         continue
  1348.  
  1349.     if (entity_get_edict(i, SENTRY_ENT_OWNER) == owner)
  1350.     {
  1351.         sentry_detonate(i, quiet)
  1352.         return
  1353.     }
  1354. }
  1355. */
  1356.  
  1357.     for(new i = 0; i < g_sentriesNum; i++)
  1358.     {
  1359.         if (GetSentryPeople(g_sentries[i], OWNER) == owner)
  1360.         {
  1361.             sentry_detonate(i, quiet, true)
  1362.             break
  1363.         }
  1364.     }
  1365. }
  1366.  
  1367. public client_disconnect(id)
  1368. {
  1369.     while (GetSentryCount(id) > 0)
  1370.         sentry_detonate_by_owner(id)
  1371. }
  1372.  
  1373. public sentry_think(parm[1])
  1374. {
  1375.     if (!is_valid_ent(parm[0]))
  1376.     {
  1377.         client_print(0, print_chat, "%d is not a valid ent, ending sentry_think!", parm[0])
  1378.         return
  1379.     }
  1380.  
  1381.     new ent = parm[0]
  1382.  
  1383.     new Float:sentryOrigin[3], Float:hitOrigin[3], hitent
  1384.     entity_get_vector(ent, EV_VEC_origin, sentryOrigin)
  1385.     sentryOrigin[2] += CANNONHEIGHTFROMFEET                     // Move up some, this should be the Y origin of the cannon
  1386.  
  1387.     // If fire, do a trace and fire
  1388.     new firemode = entity_get_int(ent, SENTRY_INT_FIRE)
  1389.    
  1390.     new target = entity_get_edict(ent, SENTRY_ENT_TARGET)
  1391.     if (firemode == SENTRY_FIREMODE_YES && is_valid_ent(target) && ze_is_user_zombie(target)   != entity_get_int(ent, SENTRY_INT_TEAM))
  1392.     {   // temp removed team check:  && get_user_team(target) != entity_get_int(ent, SENTRY_INT_TEAM)
  1393.         new sentryLevel = entity_get_int(ent, SENTRY_INT_LEVEL)
  1394.  
  1395.         // Is target still visible?
  1396.         new Float:targetOrigin[3]
  1397.         entity_get_vector(target, EV_VEC_origin, targetOrigin)
  1398.  
  1399.         // Adjust for ducking. This is still not 100%. :-(
  1400.         if (entity_get_int(target, EV_INT_flags) & FL_DUCKING)
  1401.         {
  1402.             //client_print(0, print_chat, "%d: Target %d is ducking, moving its origin up by %f", ent, target, TARGETUPMODIFIER)
  1403.             targetOrigin[2] += TARGETUPMODIFIER
  1404.         }
  1405.  
  1406.         hitent = trace_line(ent, sentryOrigin, targetOrigin, hitOrigin)
  1407.         if (hitent == entity_get_edict(ent, SENTRY_ENT_BASE))
  1408.         {
  1409.             // We traced into our base, do another trace from there
  1410.             hitent = trace_line(hitent, hitOrigin, targetOrigin, hitOrigin)
  1411.             //client_print(0, print_chat, "%d: I first hit my own base, and after doing another trace I hit %d, target: %d", ent, hitent, target)
  1412.         }
  1413.  
  1414.         if (hitent != target && is_user_alive(hitent) && ze_is_user_zombie(hitent) || !ze_is_user_zombie(hitent) && ze_is_user_zombie(hitent) && entity_get_int(ent, SENTRY_INT_TEAM) != ze_is_user_zombie(hitent))
  1415.         {
  1416.             // Another new enemy target got into scope, pick this new enemy as a new target...
  1417.             target = hitent
  1418.             entity_set_edict(ent, SENTRY_ENT_TARGET, hitent)
  1419.         }
  1420.        
  1421.         if (hitent == target)
  1422.         {
  1423.             // Fire here
  1424.             //client_print(0, print_chat, "%d: I see %d, will fire. Dist: %f, Hitorigin: %f %f %f", ent, hitent, dist, hitOrigin[0], hitOrigin[1], hitOrigin[2])
  1425.             sentry_turntotarget(ent, sentryOrigin, target, targetOrigin)
  1426.             // Firing sound
  1427.             emit_sound(ent, CHAN_WEAPON, "weapons/m249-1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  1428.  
  1429.             new Float:hitRatio = random_float(0.0, 1.0) - g_HITRATIOS[sentryLevel]      // ie 0.5 - 0.7 = -0.2, a hit and 0.8 - 0.7 = a miss by 0.1
  1430.  
  1431.             if (!get_user_godmode(target) && hitRatio <= 0.0)
  1432.             {
  1433.                 // Do damage to player
  1434.                 sentry_damagetoplayer(ent, sentryLevel, sentryOrigin, target)
  1435.                 // Tracer effect to target
  1436.             }
  1437.            
  1438.             else
  1439.             {
  1440.                 // Tracer hitOrigin adjusted for miss...
  1441.                 /*
  1442.                 MAKE_VECTORS(pEnt->v.v_angle);
  1443.                 vVector = gpGlobals->v_forward * iVelocity;
  1444.  
  1445.                 vRet[0] = amx_ftoc(vVector.x);
  1446.                 vRet[1] = amx_ftoc(vVector.y);
  1447.                 vRet[2] = amx_ftoc(vVector.z);
  1448.                 */
  1449.                 new Float:sentryAngle[3] = {0.0, 0.0, 0.0}
  1450.  
  1451.                 new Float:x = hitOrigin[0] - sentryOrigin[0]
  1452.                 new Float:z = hitOrigin[1] - sentryOrigin[1]
  1453.                 new Float:radians = floatatan(z/x, radian)
  1454.                 sentryAngle[1] = radians * g_ONEEIGHTYTHROUGHPI
  1455.                
  1456.                 if (hitOrigin[0] < sentryOrigin[0])
  1457.                     sentryAngle[1] -= 180.0
  1458.  
  1459.                 new Float:h = hitOrigin[2] - sentryOrigin[2]
  1460.                 new Float:b = vector_distance(sentryOrigin, hitOrigin)
  1461.                 radians = floatatan(h/b, radian)
  1462.                 sentryAngle[0] = radians * g_ONEEIGHTYTHROUGHPI;
  1463.  
  1464.                 sentryAngle[0] += random_float(-10.0 * hitRatio, 10.0 * hitRatio)       // aim is a little off here :-)
  1465.                 sentryAngle[1] += random_float(-10.0 * hitRatio, 10.0 * hitRatio)       // aim is a little off here :-)
  1466.                 engfunc(EngFunc_MakeVectors, sentryAngle)
  1467.                 new Float:vector[3]
  1468.                 get_global_vector(GL_v_forward, vector)
  1469.                 for (new i = 0; i < 3; i++)
  1470.                     vector[i] *= 1000;
  1471.  
  1472.                 new Float:traceEnd[3]
  1473.                 for (new i = 0; i < 3; i++)
  1474.                     traceEnd[i] = vector[i] + sentryOrigin[i]
  1475.  
  1476.                 new hitEnt = ent
  1477.                 while((hitEnt = trace_line(hitEnt, hitOrigin, traceEnd, hitOrigin)))
  1478.                 {
  1479.                     // continue tracing until hit nothing...
  1480.                 }
  1481.  
  1482.                 //for (new i = 0; i < 3; i++)
  1483.                     //hitOrigin[i] += random_float(-5.0, 5.0)
  1484.             }
  1485.             tracer(sentryOrigin, hitOrigin)
  1486.  
  1487.             // Don't do any more here
  1488.             //set_task(THINKFIREFREQUENCY, "sentry_think", ent)
  1489.             set_task(THINKFIREFREQUENCY, "sentry_think", TASKID_THINK + parm[0], parm, 1)
  1490.             return
  1491.         }
  1492.         else
  1493.         {
  1494.             //client_print(target, print_chat, "%d: Lost track of you! Hit: %d", ent, target, hitent)
  1495.             //client_print(0, print_chat, "%d: I can't see %d, i see %d... will not fire. Dist: %f, Hitorigin: %f %f %f", ent, entity_get_edict(ent, SENTRY_ENT_TARGET), hitent, dist, hitOrigin[0], hitOrigin[1], hitOrigin[2])
  1496.             // Else target isn't still visible, unset fire state.
  1497.             entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NO)
  1498.             // vvvv - Not really necessary but it's cleaner. Leave it out for now and be sure to set a fresh target each time SENTRY_INT_FIRE is set to 1!!!
  1499.             // vvvv - Else this is breaking this think altogether! :-(
  1500.             //entity_set_edict(ent, SENTRY_ENT_TARGET, 0)
  1501.  
  1502.             // Don't return here, continue with searching for targets below...
  1503.         }
  1504.     }
  1505.    
  1506.     else if (firemode == SENTRY_FIREMODE_NUTS)
  1507.     {
  1508.         //client_print(0, print_chat, "Gone nuts firing... spin speed: %f", entity_get_float(ent, SENTRY_FL_SPINSPEED))
  1509.         new hitEnt = entityviewhitpoint(ent, sentryOrigin, hitOrigin)
  1510.         // Firing sound
  1511.         emit_sound(ent, CHAN_WEAPON, "weapons/m249-1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  1512.         // Tracer effect
  1513.         tracer(sentryOrigin, hitOrigin)
  1514.  
  1515.         if (is_user_connected(hitEnt) && is_user_alive(hitEnt) || !ze_is_user_zombie(hitEnt) && ze_is_user_zombie(hitEnt) && !get_user_godmode(hitEnt))
  1516.         {
  1517.             // Do damage to player
  1518.             sentry_damagetoplayer(ent, entity_get_int(ent, SENTRY_INT_LEVEL), sentryOrigin, hitEnt)
  1519.         }
  1520.  
  1521.         // Don't do any more here
  1522.         set_task(THINKFIREFREQUENCY, "sentry_think", TASKID_THINK + parm[0], parm, 1)
  1523.         return
  1524.     }
  1525.    
  1526.     else
  1527.     {
  1528.         //client_print(0, print_chat, "My firemode: %d", firemode)
  1529.         // Either wasn't meant to fire or target was not a valid entity or dead, set both to 0.
  1530.         //client_print(target, print_chat, "%d: Fire: %d Target: %d (%s)", ent, entity_get_int(ent, SENTRY_INT_FIRE), target, is_valid_ent(target) ? (is_user_alive(target) ? "alive" : "dead") : "invalid")
  1531.  
  1532.         //entity_set_int(ent, SENTRY_INT_FIRE, 0)
  1533.         //entity_set_edict(ent, SENTRY_ENT_TARGET, 0)
  1534.     }
  1535.  
  1536.     // Tell what players you see
  1537.     if (random_num(0, 99) < 10)
  1538.         emit_sound(ent, CHAN_AUTO, "sentries/turridle.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  1539.  
  1540.     new closestTarget = 0, Float:closestDistance, Float:distance, Float:closestOrigin[3], Float:playerOrigin[3], sentryTeam = entity_get_int(ent, SENTRY_INT_TEAM)
  1541.     for (new i = 1; i <= g_MAXPLAYERS; i++)
  1542.     {
  1543.         if (!is_user_connected(i) || !is_user_alive(i) || !ze_is_user_zombie(i) || get_user_team(i) == sentryTeam) // temporarily dont check team:  || get_user_team(i) == sentryTeam
  1544.             continue
  1545.  
  1546.         entity_get_vector(i, EV_VEC_origin, playerOrigin)
  1547.  
  1548.         // Adjust for ducking. This is still not 100%. :-(
  1549.         if (entity_get_int(i, EV_INT_flags) & FL_DUCKING)
  1550.         {
  1551.             //client_print(0, print_chat, "%d: Target %d is ducking, moving its origin up by %f", ent, target, TARGETUPMODIFIER)
  1552.             playerOrigin[2] += TARGETUPMODIFIER
  1553.         }
  1554.  
  1555.         //playerOrigin[2] += TARGETUPMODIFIER
  1556.  
  1557.         hitent = trace_line(ent, sentryOrigin, playerOrigin, hitOrigin)
  1558.        
  1559.         if (hitent == entity_get_edict(ent, SENTRY_ENT_BASE))
  1560.         {
  1561.             // We traced into our base, do another trace from there
  1562.             hitent = trace_line(hitent, hitOrigin, playerOrigin, hitOrigin)
  1563.             //client_print(0, print_chat, "%d (scanning): I first hit my own base, and after doing another trace I hit %d, target: %d", ent, hitent, i)
  1564.         }
  1565.         //client_print(0, print_chat, "%d: t: %f %f %f - %f %f %f - %f %f %f, i: %d hitent: %d", ent, sentryOrigin[0], sentryOrigin[1], sentryOrigin[2], playerOrigin[0], playerOrigin[1], playerOrigin[2], hitOrigin[0], hitOrigin[1], hitOrigin[2], i, hitent)
  1566.         if (hitent == i)
  1567.         {
  1568.             //len += format(seethese[len], 63 - len, "%d,", hitent)
  1569.  
  1570.             distance = vector_distance(sentryOrigin, playerOrigin)
  1571.             closestOrigin = playerOrigin
  1572.  
  1573.             if (distance < closestDistance || closestTarget == 0)
  1574.             {
  1575.                 closestTarget = i
  1576.                 closestDistance = distance
  1577.             }
  1578.         }
  1579.     }
  1580.  
  1581.     if (closestTarget)
  1582.     {
  1583.         // We found a target, play sound and turn to target
  1584.         emit_sound(ent, CHAN_AUTO, "sentries/turrspot.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  1585.         sentry_turntotarget(ent, sentryOrigin, closestTarget, closestOrigin)
  1586.  
  1587.         // Set to fire mode and set target (always set also a new target when setting fire mode 1!!!)
  1588.         entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_YES)
  1589.         entity_set_edict(ent, SENTRY_ENT_TARGET, closestTarget)
  1590.         // Set radar straight...
  1591.         entity_set_float(ent, SENTRY_FL_RADARANGLE, 127.0)
  1592.         set_pev(ent, PEV_SENTRY_TILT_RADAR, 127)
  1593.     }
  1594.    
  1595.     else
  1596.         entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NO)
  1597.  
  1598.     //set_task(g_THINKFREQUENCIES[entity_get_int(ent, SENTRY_INT_LEVEL)], "sentry_think", ent)
  1599.     //client_print(0, print_chat, "%d: my inflictor: %d, EV_ENT_enemy: %d, EV_ENT_aiment: %d, EV_ENT_chain: %d, EV_ENT_owner: %d", ent, entity_get_edict(ent, EV_ENT_dmg_inflictor), entity_get_edict(ent, EV_ENT_enemy), entity_get_edict(ent, EV_ENT_aiment), entity_get_edict(ent, EV_ENT_chain), entity_get_edict(ent, EV_ENT_owner))
  1600.     set_task(g_THINKFREQUENCIES[entity_get_int(ent, SENTRY_INT_LEVEL)], "sentry_think", TASKID_THINK + parm[0], parm, 1)
  1601. }
  1602.  
  1603. stock sentry_damagetoplayer(sentry, sentryLevel, Float:sentryOrigin[3], target)
  1604. {
  1605.     new newHealth = get_user_health(target) - g_DMG[sentryLevel]
  1606.  
  1607.     if (newHealth <= 0)
  1608.     {
  1609.         new targetFrags = get_user_frags(target) + 1
  1610.         new owner = GetSentryPeople(sentry, OWNER)
  1611.         new ownerFrags = get_user_frags(owner) + 1
  1612.         set_user_frags(target, targetFrags) // otherwise frags are subtracted from victim for dying (!!)
  1613.         set_user_frags(owner, ownerFrags)
  1614.         // Give money to player here
  1615.         new contributors[3], moneyRewards[33] = {0, ...}
  1616.         contributors[0] = owner
  1617.         contributors[1] = GetSentryPeople(sentry, UPGRADER_1)
  1618.         contributors[2] = GetSentryPeople(sentry, UPGRADER_2)
  1619.         for (new i = SENTRY_LEVEL_1; i <= sentryLevel; i++)
  1620.         {
  1621.             moneyRewards[contributors[i]] += g_SENTRYFRAGREWARDS[i]
  1622.         }
  1623.    
  1624.         for (new i = 1; i <= g_MAXPLAYERS; i++)
  1625.         {
  1626.             if (moneyRewards[i] && is_user_connected(i) && !ze_is_user_zombie(i))
  1627.             {
  1628.                 ze_set_escape_coins(i, ze_get_escape_coins(i) + moneyRewards[i])
  1629.             }
  1630.         }
  1631.  
  1632.         message_begin(MSG_ALL, g_msgDeathMsg, {0, 0, 0} ,0)
  1633.         write_byte(owner)
  1634.         write_byte(target)
  1635.         write_byte(0)
  1636.         write_string("sentry gun")
  1637.         message_end()
  1638.  
  1639.         scoreinfo_update(owner, ownerFrags, cs_get_user_deaths(owner), int:cs_get_user_team(owner))
  1640.         //scoreinfo_update(target, targetFrags, targetDeaths, targetTeam) // dont need to update frags of victim, because it's done after set_user_health
  1641.  
  1642.         set_msg_block(g_msgDeathMsg, BLOCK_ONCE)
  1643.     }
  1644.  
  1645.     set_user_health(target, newHealth)
  1646.  
  1647.     message_begin(MSG_ONE_UNRELIABLE, g_msgDamage, {0,0,0}, target) //
  1648.     write_byte(g_DMG[sentryLevel]) // write_byte(DMG_SAVE)
  1649.     write_byte(g_DMG[sentryLevel])
  1650.     write_long(DMG_BULLET)
  1651.     write_coord(floatround(sentryOrigin[0]))
  1652.     write_coord(floatround(sentryOrigin[1]))
  1653.     write_coord(floatround(sentryOrigin[2]))
  1654.     message_end()
  1655. }
  1656.  
  1657. scoreinfo_update(id, frags, deaths, team)
  1658. {
  1659.     // Send msg to update ppls scoreboards.
  1660.     /*
  1661.     MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo );
  1662.         WRITE_BYTE( params[1] );
  1663.         WRITE_SHORT( pPlayer->v.frags );
  1664.         WRITE_SHORT( params[2] );
  1665.         WRITE_SHORT( 0 );
  1666.         WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 );
  1667.     MESSAGE_END();
  1668.  
  1669. */
  1670.     message_begin(MSG_ALL, g_msgScoreInfo)
  1671.     write_byte(id)
  1672.     write_short(frags)
  1673.     write_short(deaths)
  1674.     write_short(0)
  1675.     write_short(team)
  1676.     message_end()
  1677. }
  1678.  
  1679. sentry_turntotarget(ent, Float:sentryOrigin[3], target, Float:closestOrigin[3])
  1680. {
  1681.     if (target)
  1682.     {
  1683.         new name[32]
  1684.         get_user_name(target, name, 31)
  1685.  
  1686.         // Alter ent's angle
  1687.         new Float:newAngle[3]
  1688.         entity_get_vector(ent, EV_VEC_angles, newAngle)
  1689.         new Float:x = closestOrigin[0] - sentryOrigin[0]
  1690.         new Float:z = closestOrigin[1] - sentryOrigin[1]
  1691.         //new Float:y = closestOrigin[2] - sentryOrigin[2]
  1692.         /*
  1693.         //newAngle[0] = floatasin(x/floatsqroot(x*x+y*y), degrees)
  1694.         newAngle[1] = floatasin(z/floatsqroot(x*x+z*z), degrees)
  1695.         */
  1696.  
  1697.         new Float:radians = floatatan(z/x, radian)
  1698.         newAngle[1] = radians * g_ONEEIGHTYTHROUGHPI
  1699.         if (closestOrigin[0] < sentryOrigin[0])
  1700.  
  1701.             newAngle[1] -= 180.0
  1702.  
  1703.         entity_set_float(ent, SENTRY_FL_ANGLE, newAngle[1])
  1704.         // Tilt is handled thorugh the EV_BYTE_controller1 member. 0-255 are the values, 127ish should be horisontal aim, 255 is furthest down (about 50 degrees off)
  1705.         // and 0 is also about 50 degrees up. Scope = ~100 degrees
  1706.    
  1707.         // Set tilt
  1708.         new Float:h = closestOrigin[2] - sentryOrigin[2]
  1709.         new Float:b = vector_distance(sentryOrigin, closestOrigin)
  1710.         radians = floatatan(h/b, radian)
  1711.         new Float:degs = radians * g_ONEEIGHTYTHROUGHPI;
  1712.         // Now adjust EV_BYTE_controller1
  1713.         // Each degree corresponds to about 100/256 "bytes", = ~0,39 byte / degree (ok this is not entirely true, just tweaked for now with SENTRYTILTRADIUS)
  1714.         new Float:RADIUS = SENTRYTILTRADIUS                     // get_cvar_float("sentry_tiltradius");
  1715.         new Float:degreeByte = RADIUS/256.0;                    // tweak radius later
  1716.         new Float:tilt = 127.0 - degreeByte * degs;                 // 127 is center of 256... well, almost
  1717.         //client_print(GetSentryPeople(ent, OWNER), print_chat, "%d: Setting tilt to %d", ent, floatround(tilt))
  1718.         set_pev(ent, PEV_SENTRY_TILT_TURRET, floatround(tilt))          //entity_set_byte(ent, SENTRY_TILT_TURRET, floatround(tilt))
  1719.         entity_set_vector(ent, EV_VEC_angles, newAngle)
  1720.     }
  1721.    
  1722.     else
  1723.     {
  1724.         //entity_set_int(ent, SENTRY_INT_FIRE, 0)
  1725.         //entity_set_edict(ent, SENTRY_ENT_TARGET, 0)
  1726.         //client_print(0, print_chat, "%d: I don't see anyone.", ent)
  1727.     }
  1728. }
  1729.  
  1730. public menumain(id)
  1731. {
  1732.     if (!is_user_alive(id) && !ze_is_user_zombie(id))
  1733.         return PLUGIN_HANDLED
  1734.  
  1735.     menumain_starter(id)
  1736.  
  1737.     return PLUGIN_HANDLED
  1738. }
  1739.  
  1740. AimingAtSentry(id, bool:alwaysReturn = false)
  1741. {
  1742.     //new Float:hitOrigin[3]
  1743.     //new hitEnt = userviewhitpoint(id, hitOrigin)
  1744.     new hitEnt, bodyPart
  1745.     //
  1746.     if (get_user_aiming(id, hitEnt, bodyPart) == 0.0)
  1747.         return 0
  1748.  
  1749.     //if (get_user_aiming_func(id, hitEnt, bodyPart) == 0.0)
  1750.         //return 0
  1751.  
  1752.     new sentry = 0
  1753.     while (hitEnt)
  1754.     {
  1755.         new classname[32], l_sentry
  1756.         entity_get_string(hitEnt, EV_SZ_classname, classname, 31)
  1757.         if (equal(classname, "sentry_base"))
  1758.             l_sentry = entity_get_edict(hitEnt, BASE_ENT_SENTRY)
  1759.    
  1760.         else if (equal(classname, "sentry"))
  1761.             l_sentry = hitEnt
  1762.         else
  1763.             break
  1764.  
  1765.         if (alwaysReturn)
  1766.             return l_sentry
  1767.  
  1768.         new sentryLevel = entity_get_int(l_sentry, SENTRY_INT_LEVEL)
  1769.         new owner = GetSentryPeople(l_sentry, OWNER)
  1770.    
  1771.         if (cs_get_user_team(owner) == cs_get_user_team(id) && sentryLevel < 2)
  1772.         {
  1773.        
  1774.             #if defined DISALLOW_OWN_UPGRADES
  1775.             // Don't allow builder to upgrade his own sentry first time.
  1776.             if (sentryLevel == SENTRY_LEVEL_1 && id == owner)
  1777.                 break
  1778.             #endif
  1779.             #if defined DISALLOW_TWO_UPGRADES
  1780.             // Don't allow upgrader to upgrade again.
  1781.             if (sentryLevel == SENTRY_LEVEL_2 && id == GetSentryPeople(l_sentry, UPGRADER_1))
  1782.                 break
  1783.             #endif
  1784.             sentry = l_sentry
  1785.         }
  1786.         break
  1787.     }
  1788.  
  1789.     return sentry
  1790. }
  1791.  
  1792. menumain_starter(id)
  1793. {
  1794.     if (g_inSpyCam[id - 1])
  1795.         return
  1796.  
  1797.     g_aimSentry[id - 1] = 0
  1798.     new menuBuffer[256], len = 0, flags = MENUBUTTON0
  1799.     len += format(menuBuffer[len], 255 - len, "\ySentry gun menu^n^n")
  1800.    
  1801.     len += format(menuBuffer[len], 255 - len, "%s1. Build sentry, %d^n ammo packs", GetSentryCount(id) < MAXPLAYERSENTRIES && ze_get_escape_coins(id) >= g_COST(0) ? "\w" : "\d", g_COST(0))
  1802.  
  1803.     //if (GetSentryCount(id) == 1)
  1804.         //g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][0]
  1805.     //g_selectedSentry[id - 1] = GetClosestSentry(id)
  1806.     if (GetSentryCount(id) > 0 && g_selectedSentry[id - 1] == -1)
  1807.         g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][0]
  1808.     // g_playerSentriesEdicts[id - 1]
  1809.  
  1810.     if (g_selectedSentry[id - 1])
  1811.     {
  1812.         new parm[2]
  1813.         parm[0] = id
  1814.         parm[1] = g_selectedSentry[id - 1]
  1815.         set_task(0.0, "SentryRadarBlink", TASKID_SENTRYONRADAR + g_selectedSentry[id - 1], parm, 2)
  1816.     }
  1817.  
  1818.     //len += format(menuBuffer[len], 255 - len, "%s2. Detonate %ssentry^n", GetSentryCount(id) > 0 ? "\w" : "\d", GetSentryCount(id) > 1 ? "closest " : "")
  1819.     len += format(menuBuffer[len], 255 - len, "%s2. Detonate sentry flashing on radar^n", GetSentryCount(id) > 0 ? "\w" : "\d")
  1820.  
  1821.     while (len)
  1822.     {
  1823.         new sentry = AimingAtSentry(id)
  1824.        
  1825.         if (!sentry)
  1826.             break
  1827.    
  1828.         new sentryLevel = entity_get_int(sentry, SENTRY_INT_LEVEL)
  1829.  
  1830.         if (entity_range(sentry, id) <= MAXUPGRADERANGE)
  1831.         {
  1832.             if (ze_get_escape_coins(id) >= g_COST(sentryLevel + 1))
  1833.             {
  1834.                 len += format(menuBuffer[len], 255 - len, "\w3. Upgrade this sentry, %d^n ammo packs", g_COST(sentryLevel + 1))
  1835.                 flags |= MENUBUTTON3
  1836.                 g_aimSentry[id - 1] = sentry
  1837.             }
  1838.             else
  1839.                 len += format(menuBuffer[len], 255 - len, "\d3. Upgrade this sentry (needs %d)^n ammo packs", g_COST(sentryLevel + 1))
  1840.         }
  1841.        
  1842.         else
  1843.             len += format(menuBuffer[len], 255 - len, "\d3. Upgrade this sentry, %d (out of range)^n ammo packs", g_COST(sentryLevel + 1))
  1844.     //}
  1845.  
  1846.         break
  1847.     }
  1848.    
  1849.     if (GetSentryCount(id) > 1)
  1850.     {
  1851.         len += format(menuBuffer[len], 255 - len, "\w4. Detonate all sentries^n")
  1852.         len += format(menuBuffer[len], 255 - len, "^n\w5. Select previous sentry^n")
  1853.         len += format(menuBuffer[len], 255 - len, "\w6. Select next sentry^n")
  1854.         flags |= MENUBUTTON4 | MENUBUTTON5 | MENUBUTTON6
  1855.     }
  1856.  
  1857.     len += format(menuBuffer[len], 255 - len, "%s7. View from sentry flashing on radar^n", g_selectedSentry[id - 1] != -1 ? "\w" : "\d")
  1858.     if (g_selectedSentry[id - 1] != -1)
  1859.         flags |= MENUBUTTON7
  1860.  
  1861.     //len += format(menuBuffer[len], 255 - len, "%s4. View from sentry^n", HasSentry(id) ? "\w" : "\d")
  1862.  
  1863.     len += format(menuBuffer[len], 255 - len, "^n\w0. Exit")
  1864.  
  1865.     if (GetSentryCount(id) > 0)
  1866.     {
  1867.         flags |= MENUBUTTON2
  1868.     }
  1869.  
  1870.     if (GetSentryCount(id) < MAXPLAYERSENTRIES && ze_get_escape_coins(id) >= g_COST(SENTRY_LEVEL_1))
  1871.         flags |= MENUBUTTON1
  1872.  
  1873.     show_menu(id, flags, menuBuffer)
  1874. }
  1875.  
  1876. public SentryRadarBlink(parm[2])
  1877. {
  1878.     // 0 = player
  1879.     // 1 = sentry
  1880.     if (!is_user_connected(parm[0]) || !is_valid_ent(parm[1]))
  1881.         return
  1882.  
  1883.     new Float:sentryOrigin[3]
  1884.     entity_get_vector(parm[1], EV_VEC_origin, sentryOrigin)
  1885.     //client_print(parm[0], print_chat, "Plotting closest sentry %d on radar: %f %f %f", parm[1], sentryOrigin[0], sentryOrigin[1], sentryOrigin[2])
  1886.     message_begin(MSG_ONE, g_msgHostagePos, {0,0,0}, parm[0])
  1887.     write_byte(parm[0])
  1888.     write_byte(SENTRY_RADAR)
  1889.     write_coord(floatround(sentryOrigin[0]))
  1890.     write_coord(floatround(sentryOrigin[1]))
  1891.     write_coord(floatround(sentryOrigin[2]))
  1892.     message_end()
  1893.  
  1894.     message_begin(MSG_ONE, g_msgHostageK, {0,0,0}, parm[0])
  1895.     write_byte(SENTRY_RADAR)
  1896.     message_end()
  1897.  
  1898.     new usermenuid, keys
  1899.     get_user_menu(parm[0], usermenuid, keys)
  1900.     if (g_menuId == usermenuid)
  1901.         set_task(1.5, "SentryRadarBlink", TASKID_SENTRYONRADAR + parm[1], parm, 2)
  1902. }
  1903.  
  1904. stock GetClosestSentry(id)
  1905. {
  1906.     // Find closest sentry
  1907.     new sentry = 0, closestSentry = 0, Float:closestDistance, Float:distance
  1908.     while ((sentry = find_ent_by_class(sentry, "sentry")))
  1909.     {
  1910.         if (GetSentryPeople(sentry, OWNER) != id)
  1911.             continue
  1912.  
  1913.         distance = entity_range(id, sentry)
  1914.         if (distance < closestDistance || closestSentry == 0)
  1915.         {
  1916.             closestSentry = sentry
  1917.             closestDistance = distance
  1918.         }
  1919.     }
  1920.  
  1921.     return closestSentry
  1922. }
  1923.  
  1924. public menumain_handle(id, key)
  1925. {
  1926.     new bool:stayInMenu = false
  1927.     switch (key)
  1928.     {
  1929.         case MENUSELECT1:
  1930.         {
  1931.             // Build if still not has
  1932.             if (GetSentryCount(id) < MAXPLAYERSENTRIES)
  1933.             {
  1934.                 sentry_build(id)
  1935.             }
  1936.             /*
  1937.             else
  1938.             {
  1939.                 new numwords[128]
  1940.                 getnumbers(MAXPLAYERSENTRIES, numwords, MAXPLAYERSENTRIES)
  1941.                 client_print(id, print_center, "You can only build %s sentry gun!", numwords)
  1942.             }
  1943.             */
  1944.         }
  1945.         case MENUSELECT2:
  1946.         {
  1947.             // Detonate if still has
  1948.             new sentryCount = GetSentryCount(id)
  1949.    
  1950.             if (sentryCount == 1)
  1951.                 sentry_detonate_by_owner(id)
  1952.             else if (sentryCount > 1)
  1953.             {
  1954.                 sentry_detonate(g_selectedSentry[id - 1], false, false)
  1955.             }
  1956.         }
  1957.     case MENUSELECT3:
  1958.     {
  1959.         // Upgrade sentry
  1960.         new sentry = g_aimSentry[id - 1]
  1961.         if (is_valid_ent(sentry) && entity_range(sentry, id) <= MAXUPGRADERANGE)
  1962.         {
  1963.             sentry_upgrade(id, sentry)
  1964.         }
  1965.     }
  1966.     case MENUSELECT4: {
  1967.         while(GetSentryCount(id) > 0)
  1968.             sentry_detonate_by_owner(id, true)
  1969.     }
  1970.     case MENUSELECT5:
  1971.     {
  1972.         // one back
  1973.         CycleSelectedSentry(id, -1)
  1974.         stayInMenu = true
  1975.     }
  1976.     case MENUSELECT6:
  1977.     {
  1978.         // one forward
  1979.         CycleSelectedSentry(id, 1)
  1980.         stayInMenu = true
  1981.     }
  1982.     case MENUSELECT7:
  1983.     {
  1984.         if (g_selectedSentry[id - 1] != -1)
  1985.         {
  1986.             new spycam = CreateSpyCam(id, g_selectedSentry[id - 1])
  1987.             if (!spycam)
  1988.                 return PLUGIN_HANDLED
  1989.  
  1990.             new parms[3]
  1991.             parms[0] = id
  1992.             parms[1] = spycam
  1993.             parms[2] = g_selectedSentry[id - 1]
  1994.             set_task(SPYCAMTIME, "DestroySpyCam", TASKID_SPYCAM + id, parms, 3)
  1995.         }
  1996.     }
  1997.     case MENUSELECT0:
  1998.     {
  1999.         // nothing
  2000.         //stayInMenu = false
  2001.     }
  2002. }
  2003.  
  2004.     if (stayInMenu)
  2005.         menumain_starter(id)
  2006.  
  2007.     return PLUGIN_HANDLED
  2008. }
  2009.  
  2010. CreateSpyCam(id, sentry)
  2011. {
  2012.     new spycam = create_entity("info_target")
  2013.     if (!spycam)
  2014.         return 0
  2015.  
  2016.     // Set connection from sentry to this spycam
  2017.     entity_set_edict(sentry, SENTRY_ENT_SPYCAM, spycam)
  2018.  
  2019.     // Set classname
  2020.     entity_set_string(spycam, EV_SZ_classname, "spycam")
  2021.  
  2022.     // Set origin, pull up some
  2023.     new Float:origin[3]
  2024.     entity_get_vector(sentry, EV_VEC_origin, origin)
  2025.     origin[2] += g_spyCamOffset[entity_get_int(sentry, SENTRY_INT_LEVEL)]
  2026.     entity_set_vector(spycam, EV_VEC_origin, origin)
  2027.  
  2028.     // Set model, has to have one... but make it invisible with the stuff below
  2029.     entity_set_model(spycam, "models/sentries/base.mdl")
  2030.     entity_set_int(spycam, EV_INT_rendermode, kRenderTransColor)
  2031.     entity_set_float(spycam, EV_FL_renderamt, 0.0)
  2032.     entity_set_int(spycam, EV_INT_renderfx, kRenderFxNone)
  2033.  
  2034.     // Set initial angle, this is also done in server_frame
  2035.     new Float:angles[3]
  2036.     entity_get_vector(sentry, EV_VEC_angles, angles)
  2037.     entity_set_vector(spycam, EV_VEC_angles, angles)
  2038.  
  2039.     // Set view of player
  2040.     engfunc(EngFunc_SetView, id, spycam)
  2041.     g_inSpyCam[id - 1] = true
  2042.  
  2043.     return spycam
  2044. }
  2045.  
  2046. public DestroySpyCam(parms[3])
  2047. {
  2048.     new id = parms[0]
  2049.     new spycam = parms[1]
  2050.     new sentry = parms[2]
  2051.     g_inSpyCam[id - 1] = false
  2052.  
  2053.     // If user is still around, set his view back
  2054.     if (is_user_connected(id))
  2055.         engfunc(EngFunc_SetView, id, id)
  2056.  
  2057.     // Remove connection from sentry (this sentry could've been removed because of a newround, or it was destroyed...)
  2058.     if (is_valid_ent(sentry) && entity_get_edict(sentry, SENTRY_ENT_SPYCAM) == spycam)
  2059.         entity_set_edict(sentry, SENTRY_ENT_SPYCAM, 0)
  2060.  
  2061.     remove_entity(spycam)
  2062. }
  2063.  
  2064. CycleSelectedSentry(id, steps)
  2065. {
  2066.     // Find current index
  2067.     new index = -1
  2068.     for (new i = 0; i < g_playerSentries[id - 1]; i++)
  2069.     {
  2070.         if (g_playerSentriesEdicts[id - 1][i] == g_selectedSentry[id - 1])
  2071.         {
  2072.             index = i
  2073.             break
  2074.         }
  2075.     }
  2076.     if (index == -1)
  2077.         return // error :-P
  2078.  
  2079.     remove_task(TASKID_SENTRYONRADAR + g_selectedSentry[id - 1])
  2080.  
  2081.     if (steps > 0)
  2082.     {
  2083.         do
  2084.         {
  2085.             index++
  2086.             steps--
  2087.            
  2088.             if (index == g_playerSentries[id - 1])
  2089.                 index = 0
  2090.         }
  2091.         while(steps > 0)
  2092.     }
  2093.     else if (steps < 0)
  2094.     {
  2095.         do
  2096.         {
  2097.             index--
  2098.             steps++
  2099.             if (index == -1)
  2100.                 index = g_playerSentries[id - 1] - 1
  2101.         }
  2102.         while(steps < 0)
  2103.     }
  2104.  
  2105.     g_selectedSentry[id - 1] = g_playerSentriesEdicts[id - 1][index]
  2106. }
  2107.  
  2108. sentry_upgrade(id, sentry)
  2109. {
  2110.     new sentryLevel = entity_get_int(sentry, SENTRY_INT_LEVEL)
  2111.     if (entity_get_int(sentry, SENTRY_INT_FIRE) == SENTRY_FIREMODE_NUTS)
  2112.     {
  2113.         client_print(id, print_center, "This sentry cannot be upgraded.")
  2114.         return
  2115.     }
  2116.     else if (get_user_team(id) != entity_get_int(sentry, SENTRY_INT_TEAM))
  2117.     {
  2118.         client_print(id, print_center, "You can only upgrade your own team's sentries.")
  2119.         return
  2120.     }
  2121.     #if defined DISALLOW_OWN_UPGRADES
  2122.     else if (sentryLevel == SENTRY_LEVEL_1 && GetSentryPeople(sentry, OWNER) == id)
  2123.     {
  2124.         // Don't print anything here, it could get spammy
  2125.         //client_print(id, print_center, "")
  2126.         return
  2127.     }
  2128.     #endif
  2129.     #if defined DISALLOW_TWO_UPGRADES
  2130.     else if (sentryLevel == SENTRY_LEVEL_2 && GetSentryPeople(sentry, UPGRADER_1) == id)
  2131.     {
  2132.         // Don't print anything here, it could get spammy
  2133.         //client_print(id, print_center, "")
  2134.         return
  2135.     }
  2136.  
  2137.     #endif
  2138.     sentryLevel++
  2139.     new bool:newLevelIsOK = true, upgraderField
  2140.     switch (sentryLevel)
  2141.     {
  2142.         case SENTRY_LEVEL_2:
  2143.         {
  2144.             entity_set_model(sentry, "models/sentries/sentry2.mdl")
  2145.             upgraderField = UPGRADER_1
  2146.         }
  2147.         case SENTRY_LEVEL_3:
  2148.         {
  2149.             entity_set_model(sentry, "models/sentries/sentry3.mdl")
  2150.             upgraderField = UPGRADER_2
  2151.         }
  2152.         default:
  2153.         {
  2154.             // Error... can only upgrade to level 2 and 3... so far! ;-)
  2155.             newLevelIsOK = false
  2156.         }
  2157.     }
  2158.  
  2159.     if (newLevelIsOK)
  2160.     {
  2161.         if (ze_get_escape_coins(id) - g_COST(sentryLevel) < 0)
  2162.         {
  2163.             client_print(id, print_center, "You don't have enough money to upgrade this sentry gun! (needed %d) ammo packs", g_COST(sentryLevel))
  2164.             return
  2165.         }
  2166.  
  2167.         ze_set_escape_coins(id, ze_get_escape_coins(id) - g_COST(sentryLevel))
  2168.  
  2169.         new Float:mins[3], Float:maxs[3]
  2170.         mins[0] = -16.0
  2171.         mins[1] = -16.0
  2172.         mins[2] = 0.0
  2173.         maxs[0] = 16.0
  2174.         maxs[1] = 16.0
  2175.         maxs[2] = 48.0 // 4.0
  2176.         entity_set_size(sentry, mins, maxs)
  2177.         emit_sound(sentry, CHAN_AUTO, "sentries/turrset.wav", 1.0, ATTN_NORM, 0, PITCH_NORM)
  2178.         entity_set_int(sentry, SENTRY_INT_LEVEL, sentryLevel)
  2179.         entity_set_float(sentry, EV_FL_health, g_HEALTHS[sentryLevel])
  2180.         entity_set_float(entity_get_edict(sentry, SENTRY_ENT_BASE), EV_FL_health, g_HEALTHS[0])
  2181.         SetSentryPeople(sentry, upgraderField, id)
  2182.  
  2183.         if (id != GetSentryPeople(sentry, OWNER))
  2184.         {
  2185.             new upgraderName[32]
  2186.             get_user_name(id, upgraderName, 31)
  2187.             client_print(GetSentryPeople(sentry, OWNER), print_center, "%s upgraded your sentry to level %d", upgraderName, sentryLevel + 1)
  2188.         }
  2189.     }
  2190. }
  2191.  
  2192. stock userviewhitpoint(index, Float:hitorigin[3])
  2193. {
  2194.     if (!is_user_connected(index))
  2195.     {
  2196.         // Error
  2197.         log_amx("ERROR in plugin - %d is not a valid player index", index)
  2198.         return 0
  2199.     }
  2200.     new Float:origin[3], Float:pos[3], Float:v_angle[3], Float:vec[3], Float:f_dest[3]
  2201.  
  2202.     entity_get_vector(index, EV_VEC_origin, origin)
  2203.     entity_get_vector(index, EV_VEC_view_ofs, pos)
  2204.  
  2205.     pos[0] += origin[0]
  2206.     pos[1] += origin[1]
  2207.     pos[2] += origin[2]
  2208.  
  2209.     entity_get_vector(index, EV_VEC_v_angle, v_angle)
  2210.  
  2211.     engfunc(EngFunc_AngleVectors, v_angle, vec, 0, 0)
  2212.  
  2213.     f_dest[0] = pos[0] + vec[0] * 9999
  2214.     f_dest[1] = pos[1] + vec[1] * 9999
  2215.     f_dest[2] = pos[2] + vec[2] * 9999
  2216.  
  2217.     return trace_line(index, pos, f_dest, hitorigin)
  2218. }
  2219.  
  2220. stock entityviewhitpoint(index, Float:origin[3], Float:hitorigin[3])
  2221. {
  2222.     if (!is_valid_ent(index))
  2223.     {
  2224.         // Error
  2225.         log_amx("ERROR in plugin - %d is not a valid entity index", index)
  2226.         return 0
  2227.     }
  2228.     new Float:angle[3], Float:vec[3], Float:f_dest[3]
  2229.  
  2230.     //entity_get_vector(index, EV_VEC_origin, origin)
  2231.     /*
  2232.     entity_get_vector(index, EV_VEC_view_ofs, pos)
  2233.    
  2234.     pos[0] += origin[0]
  2235.     pos[1] += origin[1]
  2236.     pos[2] += origin[2]
  2237.     */
  2238.  
  2239.     entity_get_vector(index, EV_VEC_angles, angle)
  2240.  
  2241.     engfunc(EngFunc_AngleVectors, angle, vec, 0, 0)
  2242.  
  2243.     f_dest[0] = origin[0] + vec[0] * 9999
  2244.     f_dest[1] = origin[1] + vec[1] * 9999
  2245.     f_dest[2] = origin[2] + vec[2] * 9999
  2246.  
  2247.     return trace_line(index, origin, f_dest, hitorigin)
  2248. }
  2249.  
  2250. public newround_event(id)
  2251. {
  2252.     //Shaman: Disallow building and enable it after some time
  2253.     g_allowBuild= false
  2254.     set_task(get_pcvar_float(sentry_wait), "enablesentrybur")
  2255.  
  2256.     g_inBuilding[id - 1] = false
  2257.  
  2258.     #if !defined SENTRIES_SURVIVE_ROUNDS
  2259.     while(GetSentryCount(id) > 0)
  2260.         sentry_detonate_by_owner(id, true)
  2261.     #endif
  2262.  
  2263.     if (!g_resetArmouryThisRound && g_hasArmouries)
  2264.     {
  2265.         ResetArmoury()
  2266.         g_resetArmouryThisRound = true
  2267.     }
  2268.  
  2269.     return PLUGIN_CONTINUE
  2270. }
  2271.  
  2272. public endround_event()
  2273. {
  2274.     if (!g_hasArmouries)
  2275.         return PLUGIN_CONTINUE
  2276.    
  2277.     set_task(4.0, "ResetArmouryFalse")
  2278.  
  2279.     return PLUGIN_CONTINUE
  2280. }
  2281.  
  2282. public ResetArmouryFalse()
  2283. {
  2284.     //client_print(0, print_chat, "Setting g_resetArmouryThisRound to false!")
  2285.     g_resetArmouryThisRound = false
  2286. }
  2287.  
  2288. public client_putinserver(id)
  2289. {
  2290.     if (is_user_bot(id) && !ze_is_user_zombie(id))
  2291.     {
  2292.         new parm[1]
  2293.         parm[0] = id
  2294.         botbuildsrandomly(parm)
  2295.  
  2296.     }
  2297.    
  2298.     else
  2299.         set_task(15.0, "dispInfo", id)
  2300.  
  2301.     return PLUGIN_CONTINUE
  2302. }
  2303.  
  2304. public dispInfo(id)
  2305. {
  2306.     client_print(id, print_chat, "[ZE] You can build sentries, for more help, say /sentryhelp")
  2307. }
  2308.  
  2309. public check_say(id)
  2310. {
  2311.     new said[32]
  2312.     read_args(said, 31)
  2313.  
  2314.     if (equali(said, "^"sentryhelp^"") || equali(said, "^"/sentryhelp^""))
  2315.     {
  2316.         const SIZE = MAXHTMLSIZE
  2317.         new msg[SIZE + 1], len = 0
  2318.  
  2319.         len += format(msg[len], SIZE - len, "<html><body>")
  2320.         len += format(msg[len], SIZE - len, "<p>Sentries in TFC were cool. Sentries in CS are cool.<br/>")
  2321.         len += format(msg[len], SIZE - len, "Sentry guns are stationary engineering wonders that fire bullets at and kill your enemies.<br/>")
  2322.         len += format(msg[len], SIZE - len, "Sentry guns can be upgraded twice, with the help of a team member, to be bigger and meaner.</p>")
  2323.         len += format(msg[len], SIZE - len, "<p>Open console and type ^"bind j sentry_menu^" to bind the menu button to J. You can also bind ^"sentry_build^".<br/>")
  2324.         len += format(msg[len], SIZE - len, "Note that you can bind to any button you choose. To bind the fast build/update command to a mouse button 4, write ^"bind mouse4 sentry_build^".</p>")
  2325.     #if defined DISALLOW_OWN_UPGRADES
  2326.         len += format(msg[len], SIZE - len, "<p>You <b>cannot</b> upgrade your own sentry from level 1 to level 2. A team mate must do this.</p>")
  2327.     #else
  2328.         len += format(msg[len], SIZE - len, "<p>You <b>can</b> upgrade your own sentry from level 1 to level 2.</p>")
  2329.     #endif
  2330.     #if defined DISALLOW_TWO_UPGRADES
  2331.         len += format(msg[len], SIZE - len, "<p>A sentry at level 2 <b>cannot</b> be upgraded to level 3 by the same player that performed the first upgrade. A team mate must do this (original builder is OK).</p>")
  2332.     #else
  2333.         len += format(msg[len], SIZE - len, "<p>A sentry at level 2 <b>can</b> be further upgraded to level 3 by the the same player that performed the first upgrade.</p>")
  2334.     #endif
  2335.         len += format(msg[len], SIZE - len, "<center>")
  2336.         len += format(msg[len], SIZE - len, "<table width=^"50%^" border=^"1^">")
  2337.         len += format(msg[len], SIZE - len, "<tr><td><b>Command</b></td><td><b>Description</b></td>")
  2338.         len += format(msg[len], SIZE - len, "<tr><td>sentry_menu</td><td>From this menu you can build, upgrade and detonate sentry guns. To upgrade a sentry, first point at it, then open menu.</td>")
  2339.         len += format(msg[len], SIZE - len, "<tr><td>sentry_build</td><td>Quick button to build and upgrade sentry guns. To upgrade a sentry, first point at it, then press this button.</td>")
  2340.         len += format(msg[len], SIZE - len, "</table>")
  2341.         len += format(msg[len], SIZE - len, "<table width=^"50%^" border=^"1^">")
  2342.         len += format(msg[len], SIZE - len, "<tr><td><b>Sentry gun level</b></td><td><b>Cost to build/upgrade to</b></td>")
  2343.         len += format(msg[len], SIZE - len, "<tr><td>1</td><td>%d</td>", g_COST(0))
  2344.         len += format(msg[len], SIZE - len, "<tr><td>2</td><td>%d</td>", g_COST(1))
  2345.         len += format(msg[len], SIZE - len, "<tr><td>3</td><td>%d</td>", g_COST(2))
  2346.         len += format(msg[len], SIZE - len, "</table>")
  2347.         len += format(msg[len], SIZE - len, "</center>")
  2348.         len += format(msg[len], SIZE - len, "</body></html>")
  2349.         show_motd(id, msg, "Sentry guns help")
  2350.     }
  2351.    
  2352.     else if (containi(said, "sentr") != -1)
  2353.     {
  2354.         dispInfo(id)
  2355.     }
  2356.  
  2357.     return PLUGIN_CONTINUE
  2358. }
  2359.  
  2360. public plugin_modules()
  2361. {
  2362.     require_module("engine")
  2363.     require_module("fun")
  2364.     require_module("cstrike")
  2365.     require_module("fakemeta")
  2366. }
  2367.  
  2368. public plugin_precache()
  2369. {
  2370.     // Sentries below
  2371.     precache_model("models/sentries/base.mdl")
  2372.     precache_model("models/sentries/sentry1.mdl")
  2373.     precache_model("models/sentries/sentry2.mdl")
  2374.     precache_model("models/sentries/sentry3.mdl")
  2375.  
  2376.     g_sModelIndexFireball = precache_model("sprites/zerogxplode.spr")       // explosion
  2377.  
  2378.     precache_sound("debris/bustmetal1.wav")                     // metal, computer breaking
  2379.     precache_sound("debris/bustmetal2.wav")                     // metal, computer breaking
  2380.     precache_sound("debris/metal1.wav")                         // metal breaking (needed for comp also?!)
  2381.     //precache_sound("debris/metal2.wav")                       // metal breaking
  2382.     precache_sound("debris/metal3.wav")                         // metal breaking (needed for comp also?!)
  2383.     //precache_model("models/metalplategibs.mdl")                   // metal breaking
  2384.     precache_model("models/computergibs.mdl")                   // computer breaking
  2385.  
  2386.     precache_sound("sentries/asscan1.wav")
  2387.     precache_sound("sentries/asscan2.wav")
  2388.     precache_sound("sentries/asscan3.wav")
  2389.     precache_sound("sentries/asscan4.wav")
  2390.     precache_sound("sentries/turridle.wav")
  2391.     precache_sound("sentries/turrset.wav")
  2392.     precache_sound("sentries/turrspot.wav")
  2393.     precache_sound("sentries/building.wav")
  2394.  
  2395.     precache_sound("weapons/m249-1.wav")
  2396. }
  2397.  
  2398.  
  2399. stock spambits(to, bits)
  2400. {
  2401.     new buffer[512], len = 0
  2402.     for (new i = 31; i >= 0; i--)
  2403.     {
  2404.         len += format(buffer[len], 511 - len, "%d", bits & (1<<i) ? 1 : 0)
  2405.     }
  2406.     client_print(to, print_chat, buffer)
  2407.     server_print(buffer)
  2408. }
  2409.  
  2410. public forward_traceline_post(Float:start[3], Float:end[3], noMonsters, player)
  2411. {
  2412.     if (is_user_bot(player) || player < 1 || player > g_MAXPLAYERS)
  2413.     return FMRES_IGNORED
  2414.  
  2415.     if (!is_user_alive(player) || !ze_is_user_zombie(player))
  2416.         return FMRES_IGNORED
  2417.  
  2418.     SetStatusTrigger(player, false)
  2419.  
  2420.     new hitEnt = get_tr(TR_pHit)
  2421.     if (hitEnt <= g_MAXPLAYERS)
  2422.         return FMRES_IGNORED
  2423.  
  2424.     new classname[11], sentry = 0, base = 0
  2425.     entity_get_string(hitEnt, EV_SZ_classname, classname, 10)
  2426.     if (equal(classname, "sentrybase"))
  2427.     {
  2428.         base = hitEnt
  2429.         sentry = entity_get_edict(hitEnt, BASE_ENT_SENTRY)
  2430.     }
  2431.     else if (equal(classname, "sentry"))
  2432.     {
  2433.         sentry = hitEnt
  2434.         base = entity_get_edict(sentry, SENTRY_ENT_BASE)
  2435.     }
  2436.     if (!sentry || !base || entity_get_int(sentry, SENTRY_INT_FIRE) == SENTRY_FIREMODE_NUTS)
  2437.         return FMRES_IGNORED
  2438.     new Float:health = entity_get_float(sentry, EV_FL_health)
  2439.  
  2440.     if (health <= 0)
  2441.         return FMRES_IGNORED
  2442.    
  2443.     new Float:basehealth = entity_get_float(base, EV_FL_health)
  2444.    
  2445.     if (basehealth <= 0)
  2446.         return FMRES_IGNORED
  2447.  
  2448.     new team = entity_get_int(sentry, SENTRY_INT_TEAM)
  2449.    
  2450.     if (team != get_user_team(player))
  2451.         return FMRES_IGNORED
  2452.  
  2453.     // Display health
  2454.     new level = entity_get_int(sentry, SENTRY_INT_LEVEL)
  2455.     new upgradeInfo[128]
  2456.     if (PlayerCanUpgradeSentry(player, sentry))
  2457.         format(upgradeInfo, 127, "^n(Run into me to upgrade me to level %d for $%d)", level + 2, g_COST(level + 1))
  2458.     else if (level < SENTRY_LEVEL_3)
  2459.         format(upgradeInfo, 127, "^n(Upgrade cost: $%d)", g_COST(level + 1))
  2460.     else
  2461.         upgradeInfo = ""
  2462.  
  2463.     new tempStatusBuffer[256]
  2464.  
  2465.     format(tempStatusBuffer, 255, "Health: %d/%d^nBase health: %d/%d^nLevel: %d%s", floatround(health), floatround(g_HEALTHS[level]), floatround(basehealth), floatround(g_HEALTHS[0]), level + 1, upgradeInfo)
  2466.     SetStatusTrigger(player, true)
  2467.     if (!task_exists(TASKID_SENTRYSTATUS + player) || !equal(tempStatusBuffer, g_sentryStatusBuffer[player - 1]))
  2468.     {
  2469.         // may still exist if !equal was true, so we remove previous task. This happens when sentry is being fired upon, player gets enough money to upgrade or sentry
  2470.         // suddenly is upgradeable because another teammate upgraded it or something. This should make for instant updates to message without risking sending a lot of messages
  2471.         // just in case data updated, now we only send more often if data changed often enough.
  2472.         //client_print(player, print_chat, "Starting to send: %s", tempStatusBuffer)
  2473.         remove_task(TASKID_SENTRYSTATUS + player)
  2474.  
  2475.         g_sentryStatusBuffer[player - 1] = tempStatusBuffer
  2476.         new parms[2]
  2477.         parms[0] = player
  2478.         parms[1] = team
  2479.         set_task(0.0, "displaysentrystatus", TASKID_SENTRYSTATUS + player, parms, 2)
  2480.     }
  2481.  
  2482.     return FMRES_IGNORED
  2483. }
  2484.  
  2485.     // Counting level, team, money and DEFINES
  2486.     bool:PlayerCanUpgradeSentry(player, sentry)
  2487.     {
  2488.         new level = entity_get_int(sentry, SENTRY_INT_LEVEL)
  2489.         switch(level)
  2490.         {
  2491.             case SENTRY_LEVEL_1:
  2492.             {
  2493.                 #if defined DISALLOW_OWN_UPGRADES
  2494.                 if (player == GetSentryPeople(sentry, OWNER))
  2495.                     return false
  2496.                 #endif
  2497.                 return get_user_team(player) == entity_get_int(sentry, SENTRY_INT_TEAM) && ze_get_escape_coins(player) >= g_COST(level + 1)
  2498.             }  
  2499.             case SENTRY_LEVEL_2:
  2500.             {
  2501.                 #if defined DISALLOW_TWO_UPGRADES
  2502.                 if (player == GetSentryPeople(sentry, UPGRADER_1))
  2503.                     return false
  2504.                 #endif
  2505.                 return get_user_team(player) == entity_get_int(sentry, SENTRY_INT_TEAM) && ze_get_escape_coins(player) >= g_COST(level + 1)
  2506.             }
  2507.         }
  2508.         return false
  2509. }
  2510.  
  2511. public displaysentrystatus(parms[2])
  2512. {
  2513.     // parm 0 = player
  2514.     // parm 1 = team
  2515.     if (!GetStatusTrigger(parms[0]))
  2516.         return
  2517.  
  2518.     set_hudmessage(parms[1] == 1 ? 150 : 0, 0, parms[1] == 2 ? 150 : 0, -1.0, 0.35, 0, 0.0, STATUSINFOTIME + 0.1, 0.0, 0.0, 2) // STATUSINFOTIME + 0.1 = overlapping a little..
  2519.     show_hudmessage(parms[0], g_sentryStatusBuffer[parms[0] - 1])
  2520.  
  2521.     set_task(STATUSINFOTIME, "displaysentrystatus", TASKID_SENTRYSTATUS + parms[0], parms, 2)
  2522. }
  2523.  
  2524.  
  2525. ResetArmoury()
  2526. {
  2527.     // Find all armoury_entity:s, restore their initial origins
  2528.     new entity = 0, Float:NULLVELOCITY[3] = {0.0, 0.0, 0.0}, Float:origin[3]
  2529.     while ((entity = find_ent_by_class(entity, "armoury_entity")))
  2530.     {
  2531.         // Reset speed in case it's flying around...
  2532.         entity_set_vector(entity, EV_VEC_velocity, NULLVELOCITY)
  2533.  
  2534.         // Get origin and set it.
  2535.         entity_get_vector(entity, EV_VEC_vuser1, origin)
  2536.         entity_set_origin(entity, origin)
  2537.     }
  2538. }
  2539.  
  2540. public InitArmoury()
  2541. {
  2542.     // Find all armoury_entity:s, store their initial origins
  2543.     new entity = 0, Float:origin[3], counter = 0
  2544.     while ((entity = find_ent_by_class(entity, "armoury_entity")))
  2545.     {
  2546.         entity_get_vector(entity, EV_VEC_origin, origin)
  2547.         entity_set_vector(entity, EV_VEC_vuser1, origin)
  2548.         counter++
  2549.     }
  2550.     if (counter > 0)
  2551.         g_hasArmouries = true
  2552. }
  2553.  
  2554. stock getnumbers(number, wordnumbers[], length)
  2555. {
  2556.     if (number < 0)
  2557.     {
  2558.         format(wordnumbers, length, "error")
  2559.         return
  2560.     }
  2561.  
  2562.     new numberstr[20]
  2563.     num_to_str(number, numberstr, 19)
  2564.     new stlen = strlen(numberstr), bool:getzero = false, bool:jumpnext = false
  2565.     if (stlen == 1)
  2566.         getzero = true
  2567.  
  2568.     do
  2569.     {
  2570.         if (jumpnext)
  2571.             jumpnext = false
  2572.            
  2573.         else if (numberstr[0] != '0')
  2574.         {
  2575.             switch (stlen)
  2576.             {
  2577.                 case 9:
  2578.                 {
  2579.                     if (getsingledigit(numberstr[0], wordnumbers, length))
  2580.                         format(wordnumbers, length, "%s hundred%s", wordnumbers, numberstr[1] == '0' && numberstr[2] == '0' ? " million" : "")
  2581.                 }
  2582.                
  2583.                 case 8:
  2584.                 {
  2585.                     jumpnext = gettens(wordnumbers, length, numberstr)
  2586.                    
  2587.                     if (jumpnext)
  2588.                         format(wordnumbers, length, "%s million", wordnumbers)
  2589.                 }
  2590.            
  2591.                 case 7:
  2592.                 {
  2593.                     getsingledigit(numberstr[0], wordnumbers, length)
  2594.                     format(wordnumbers, length, "%s million", wordnumbers)
  2595.                 }
  2596.                
  2597.                 case 6:
  2598.                 {
  2599.                     if (getsingledigit(numberstr[0], wordnumbers, length))
  2600.                         format(wordnumbers, length, "%s hundred%s", wordnumbers, numberstr[1] == '0' && numberstr[2] == '0' ? " thousand" : "")
  2601.                 }
  2602.                
  2603.                 case 5:
  2604.                 {
  2605.                     jumpnext = gettens(wordnumbers, length, numberstr)
  2606.                     if (numberstr[0] == '1' || numberstr[1] == '0')
  2607.                         format(wordnumbers, length, "%s thousand", wordnumbers)
  2608.                 }
  2609.                
  2610.                 case 4:
  2611.                 {
  2612.                     getsingledigit(numberstr[0], wordnumbers, length)
  2613.                     format(wordnumbers, length, "%s thousand", wordnumbers)
  2614.                 }
  2615.                
  2616.                 case 3:
  2617.                 {
  2618.                     getsingledigit(numberstr[0], wordnumbers, length)
  2619.                     format(wordnumbers, length, "%s hundred", wordnumbers)
  2620.                 }
  2621.                
  2622.                 case 2: jumpnext = gettens(wordnumbers, length, numberstr)
  2623.                 case 1:
  2624.                 {
  2625.                     getsingledigit(numberstr[0], wordnumbers, length, getzero)
  2626.                     break // could've trimmed, but of no use here
  2627.                 }
  2628.                
  2629.                 default:
  2630.                 {
  2631.                     format(wordnumbers, length, "%s TOO LONG", wordnumbers)
  2632.                     break
  2633.                 }
  2634.             }
  2635.         }
  2636.  
  2637.         jghg_trim(numberstr, length, 1)
  2638.         stlen = strlen(numberstr)
  2639.     }
  2640.    
  2641.     while (stlen > 0)
  2642.  
  2643.     // Trim a char from left if first char is a space (very likely)
  2644.     if (wordnumbers[0] == ' ')
  2645.         jghg_trim(wordnumbers, length, 1)
  2646. }
  2647.  
  2648. // Returns true if next char should be jumped
  2649. stock bool:gettens(wordnumbers[], length, numberstr[])
  2650. {
  2651.     new digitstr[11], bool:dont = false, bool:jumpnext = false
  2652.     switch (numberstr[0])
  2653.     {
  2654.         case '1':
  2655.         {
  2656.             jumpnext = true
  2657.             switch (numberstr[1])
  2658.             {
  2659.                 case '0': digitstr = "ten"
  2660.                 case '1': digitstr = "eleven"
  2661.                 case '2': digitstr = "twelve"
  2662.                 case '3': digitstr = "thirteen"
  2663.                 case '4': digitstr = "fourteen"
  2664.                 case '5': digitstr = "fifteen"
  2665.                 case '6': digitstr = "sixteen"
  2666.                 case '7': digitstr = "seventeen"
  2667.                 case '8': digitstr = "eighteen"
  2668.                 case '9': digitstr = "nineteen"
  2669.                 default: digitstr = "TEENSERROR"
  2670.             }
  2671.         }
  2672.        
  2673.         case '2': digitstr = "twenty"
  2674.         case '3': digitstr = "thirty"
  2675.         case '4': digitstr = "fourty"
  2676.         case '5': digitstr = "fifty"
  2677.         case '6': digitstr = "sixty"
  2678.         case '7': digitstr = "seventy"
  2679.         case '8': digitstr = "eighty"
  2680.         case '9': digitstr = "ninety"
  2681.         case '0': dont = true // do nothing
  2682.         default : digitstr = "TENSERROR"
  2683.     }
  2684.    
  2685.     if (!dont)
  2686.         format(wordnumbers, length, "%s %s", wordnumbers, digitstr)
  2687.  
  2688.     return jumpnext
  2689. }
  2690.  
  2691. // Returns true when sets, else false
  2692. stock getsingledigit(digit[], numbers[], length, bool:getzero = false)
  2693. {
  2694.     new digitstr[11]
  2695.     switch (digit[0])
  2696.     {
  2697.         case '1': digitstr = "one"
  2698.         case '2': digitstr = "two"
  2699.         case '3': digitstr = "three"
  2700.         case '4': digitstr = "four"
  2701.         case '5': digitstr = "five"
  2702.         case '6': digitstr = "six"
  2703.         case '7': digitstr = "seven"
  2704.         case '8': digitstr = "eight"
  2705.         case '9': digitstr = "nine"
  2706.         case '0':
  2707.         {
  2708.             if (getzero)
  2709.                 digitstr = "zero"
  2710.             else
  2711.                 return false
  2712.         }
  2713.         default : digitstr = "digiterror"
  2714.     }
  2715.     format(numbers, length, "%s %s", numbers, digitstr)
  2716.  
  2717.     return true
  2718. }
  2719.  
  2720. stock jghg_trim(stringtotrim[], len, charstotrim, bool:fromleft = true)
  2721. {
  2722.     if (charstotrim <= 0)
  2723.         return
  2724.  
  2725.     if (fromleft)
  2726.     {
  2727.         new maxlen = strlen(stringtotrim)
  2728.         if (charstotrim > maxlen)
  2729.             charstotrim = maxlen
  2730.  
  2731.         format(stringtotrim, len, "%s", stringtotrim[charstotrim])
  2732.     }
  2733.    
  2734.     else
  2735.     {
  2736.         new maxlen = strlen(stringtotrim) - charstotrim
  2737.         if (maxlen < 0)
  2738.             maxlen = 0
  2739.  
  2740.         format(stringtotrim, maxlen, "%s", stringtotrim)
  2741.     }
  2742. }
  2743.  
  2744. BotBuild(bot, Float:closestTime = 0.1, Float:longestTime = 5.0)
  2745. {
  2746.     // This function should only be used to build sentries at objective related targets.
  2747.     // So as to not try to build all the time if recently started a build task when touched a objective related target
  2748.     if (task_exists(bot))
  2749.         return
  2750.  
  2751.     new teamSentriesNear = GetStuffInVicinity(bot, BOT_MAXSENTRIESDISTANCE, true, "sentry") + GetStuffInVicinity(bot, BOT_MAXSENTRIESDISTANCE, true, "sentrybase")
  2752.     if (teamSentriesNear >= BOT_MAXSENTRIESNEAR)
  2753.     {
  2754.         new name[32]
  2755.         get_user_name(bot, name, 31)
  2756.         //client_print(0, print_chat, "There are already %d sentries near me, I won't build here, %s says. (objective)", teamSentriesNear, name)
  2757.         return
  2758.     }
  2759.  
  2760.     new Float:ltime = random_float(closestTime, longestTime)
  2761.     set_task(ltime, "sentry_build", bot)
  2762.     //server_print("Bot task %d set to %f seconds", bot, ltime)
  2763.  
  2764.     /*new tempname[32]
  2765.     get_user_name(bot, tempname, 31)
  2766.     client_print(0, print_chat, "Bot %s will build a sentry in %f seconds...", tempname, ltime)*/
  2767. }
  2768.  
  2769. public sentry_build_randomlybybot(taskid_and_id)
  2770. {
  2771.     //Shaman: Check if the player is allowed to build
  2772.     if(!g_allowBuild)
  2773.         return
  2774.  
  2775.     if (!is_user_alive(taskid_and_id - TASKID_BOTBUILDRANDOMLY))
  2776.         return
  2777.  
  2778.     // Now finally do a short check if there already are enough (2-3 sentries) in this vicinity... then don't build.
  2779.     new teamSentriesNear = GetStuffInVicinity(taskid_and_id - TASKID_BOTBUILDRANDOMLY, BOT_MAXSENTRIESDISTANCE, true, "sentry") + GetStuffInVicinity(taskid_and_id - TASKID_BOTBUILDRANDOMLY, BOT_MAXSENTRIESDISTANCE, true, "sentrybase")
  2780.     if (teamSentriesNear >= BOT_MAXSENTRIESNEAR)
  2781.     {
  2782.         //new name[32]
  2783.         //get_user_name(taskid_and_id - TASKID_BOTBUILDRANDOMLY, name, 31)
  2784.         //client_print(0, print_chat, "There are already %d sentries near me, I won't build here, %s says. (random)", teamSentriesNear, name)
  2785.         return
  2786.     }
  2787.  
  2788.     sentry_build(taskid_and_id - TASKID_BOTBUILDRANDOMLY)
  2789. }
  2790.  
  2791. GetStuffInVicinity(entity, const Float:RADIUS, bool:followTeam, STUFF[])
  2792. {
  2793.     new classname[32], sentryTeam, nrOfStuffNear = 0
  2794.     entity_get_string(entity, EV_SZ_classname, classname, 31)
  2795.     if (followTeam)
  2796.     {
  2797.         if (equal(classname, "player"))
  2798.             sentryTeam = get_user_team(entity)
  2799.         else if (equal(classname, "sentry"))
  2800.             sentryTeam = entity_get_int(entity, SENTRY_INT_TEAM)
  2801.     }  
  2802.  
  2803.     if (followTeam)
  2804.     {
  2805.         if (equal(STUFF, "sentry"))
  2806.         {
  2807.             for (new i = 0; i < g_sentriesNum; i++)
  2808.             {
  2809.                 if (g_sentries[i] == entity || (followTeam && entity_get_int(g_sentries[i], SENTRY_INT_TEAM) != sentryTeam) || entity_range(g_sentries[i], entity) > RADIUS)
  2810.                     continue
  2811.  
  2812.                 nrOfStuffNear++
  2813.             }
  2814.         }
  2815.         else if (equal(STUFF, "sentrybase"))
  2816.         {
  2817.             new ent = 0
  2818.             while ((ent = find_ent_by_class(ent, STUFF)))
  2819.             {
  2820.                 // Don't count if:
  2821.                 // If follow team then if team is not same
  2822.                 // If ent is the same as what we're searching from, which is entity
  2823.                 // Don't count a base if it has a head, we consider sentry+base only as one item (a sentry)
  2824.                 // Or if out of range
  2825.                 if ((followTeam && entity_get_int(ent, BASE_INT_TEAM) != sentryTeam)
  2826.                 || ent == entity
  2827.                 || entity_get_edict(ent, BASE_ENT_SENTRY) != 0
  2828.                 || entity_range(ent, entity) > RADIUS)
  2829.                     continue
  2830.  
  2831.                 nrOfStuffNear++
  2832.             }
  2833.         }
  2834.     }
  2835.     //client_print(0, print_chat, "Found %d sentries within %f distance of entity %d...", nrOfSentriesNear, RADIUS, entity)
  2836.     return nrOfStuffNear
  2837. }
  2838.  
  2839. BotBuildRandomly(bot, Float:closestTime = 0.1, Float:longestTime = 5.0)
  2840. {
  2841.     // This function is used to stark tasks that will build sentries randomly regardless of map objectives and its targets.
  2842.     new Float:ltime = random_float(closestTime, longestTime)
  2843.     set_task(ltime, "sentry_build_randomlybybot", TASKID_BOTBUILDRANDOMLY + bot)
  2844.  
  2845.     new tempname[32]
  2846.     get_user_name(bot, tempname, 31)
  2847.     //client_print(0, print_chat, "Bot %s will build a random sentry in %f seconds...", tempname, ltime)
  2848.     //server_print("Bot %s will build a random sentry in %f seconds...", tempname, ltime)
  2849. }
  2850.  
  2851. public playerreachedtarget(target, bot)
  2852. {
  2853.     if (!is_user_bot(bot) && !ze_is_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || entity_get_int(bot, EV_INT_bInDuck) || cs_get_user_vip(bot) || get_systime() < g_lastObjectiveBuild[bot - 1] + BOT_OBJECTIVEWAIT)
  2854.         return PLUGIN_CONTINUE
  2855.  
  2856.     //client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)
  2857.     BotBuild(bot)
  2858.     g_lastObjectiveBuild[bot - 1] = get_systime()
  2859.  
  2860.     return PLUGIN_CONTINUE
  2861. }
  2862.  
  2863. public playertouchedweaponbox(weaponbox, bot)
  2864. {
  2865.     if (!is_user_bot(bot) && !ze_is_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || cs_get_user_team(bot) != CS_TEAM_CT)
  2866.     return PLUGIN_CONTINUE
  2867.  
  2868.     new model[22]
  2869.     entity_get_string(weaponbox, EV_SZ_model, model, 21)
  2870.     if (!equal(model, "models/w_backpack.mdl"))
  2871.         return PLUGIN_CONTINUE
  2872.  
  2873.     // A ct will build near a dropped bomb
  2874.     BotBuild(bot, 0.0, 2.0)
  2875.  
  2876.     return PLUGIN_CONTINUE
  2877. }
  2878.  
  2879. public playerreachedhostagerescue(target, bot)
  2880. {
  2881.     if (!is_user_bot(bot) && !ze_is_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES) //  || cs_get_user_team(bot) != CS_TEAM_T
  2882.     return PLUGIN_CONTINUE
  2883.  
  2884.     // ~5% chance that a ct will build a sentry here, a t always builds
  2885.     if (cs_get_user_team(bot) == CS_TEAM_CT)
  2886.     {
  2887.         if (random_num(0, 99) < 95)
  2888.             return PLUGIN_CONTINUE
  2889.     }
  2890.  
  2891.     BotBuild(bot)
  2892.  
  2893.     //client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)
  2894.  
  2895.     return PLUGIN_CONTINUE
  2896. }
  2897.  
  2898. public playertouchedhostage(hostage, bot)
  2899. {
  2900.     if (!is_user_bot(bot) && !ze_is_user_zombie(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || cs_get_user_team(bot) != CS_TEAM_T)
  2901.         return PLUGIN_CONTINUE
  2902.  
  2903.     // Build a sentry close to a hostage
  2904.     BotBuild(bot)
  2905.  
  2906.     //client_print(bot, print_chat, "You touched bombtarget %d!", bombtarget)
  2907.  
  2908.     return PLUGIN_CONTINUE
  2909. }
  2910.  
  2911.  
  2912. public playertouchedsentry(sentry, player)
  2913. {
  2914.     if (PlayerCanUpgradeSentry(player, sentry))
  2915.         sentry_upgrade(player, sentry)
  2916.    
  2917.     //client_print(bot, print_chat, "You touched a sentry %d!", sentry)
  2918.  
  2919.     return PLUGIN_CONTINUE
  2920. }
  2921.  
  2922. public botbuildsrandomly(parm[1])
  2923. {
  2924.     if (!is_user_connected(parm[0]))
  2925.     {
  2926.         //server_print("********* %d is no longer in server!", parm[0])
  2927.         return
  2928.     }
  2929.  
  2930.     new Float:ltime = random_float(BOT_WAITTIME_MIN, BOT_WAITTIME_MAX)
  2931.     new Float:ltime2 = ltime + random_float(BOT_NEXT_MIN, BOT_NEXT_MAX)
  2932.     BotBuildRandomly(parm[0], ltime, ltime2)
  2933.  
  2934.     set_task(ltime2, "botbuildsrandomly", 0, parm, 1)
  2935. }
  2936.  
  2937. #if defined DEBUG
  2938. public botbuild_fn(id, level, cid)
  2939. {
  2940.     if (!cmd_access(id, level, cid, 1))
  2941.         return PLUGIN_HANDLED
  2942.  
  2943.     new asked = 0
  2944.     for(new i = 1; i <= g_MAXPLAYERS; i++)
  2945.     {
  2946.         if (!is_user_connected(i) || !is_user_bot(i) || !is_user_alive(i))
  2947.             continue
  2948.  
  2949.         sentry_build(i)
  2950.         asked++
  2951.     }
  2952.     console_print(id, "Asked %d bots to build sentries (not counting money etc)", asked)
  2953.  
  2954.     return PLUGIN_HANDLED
  2955. }
  2956.    
  2957. #endif
  2958. g_COST(i)
  2959. {
  2960.     switch(i)
  2961.     {
  2962.         case 0: return COST_INIT
  2963.         case 1: return COST_UP
  2964.         case 2: return COST_UPTWO
  2965.     }
  2966.     return 0;
  2967. }
He who fails to plan is planning to fail

User avatar
alexnovac18
Member
Member
Romania
Posts: 28
Joined: 5 years ago
Location: Romania
Contact:

#3

Post by alexnovac18 » 4 years ago

Thank you raheem, but knockback not work...

User avatar
Night Fury
Mod Developer
Mod Developer
Posts: 677
Joined: 7 years ago
Contact:

#4

Post by Night Fury » 4 years ago

alexnovac18 wrote: 4 years ago Thank you raheem, but knockback not work...
Have you tested in in ZP mod before Raheem concerts it?
Want your own mod edition? PM me.
Accepting private projects.
Discord: Fury#7469
Image

User avatar
alexnovac18
Member
Member
Romania
Posts: 28
Joined: 5 years ago
Location: Romania
Contact:

#5

Post by alexnovac18 » 4 years ago

Jack GamePlay wrote: 4 years ago
alexnovac18 wrote: 4 years ago Thank you raheem, but knockback not work...
Have you tested in in ZP mod before Raheem concerts it?
Nope bro

Post Reply

Create an account or sign in to join the discussion

You need to be a member in order to post a reply

Create an account

Not a member? register to join our community
Members can start their own topics & subscribe to topics
It’s free and only takes a minute

Register

Sign in

Who is online

Users browsing this forum: No registered users and 11 guests