Page 1 of 1

Convert Plugin ZP / ZE

Posted: 20 Apr 2019, 23:55
by alexnovac18
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;
}

Re: Convert Plugin ZP / ZE

Posted: 28 Apr 2019, 11:13
by Raheem
  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. }

Re: Convert Plugin ZP / ZE

Posted: 17 Jun 2019, 23:48
by alexnovac18
Thank you raheem, but knockback not work...

Re: Convert Plugin ZP / ZE

Posted: 18 Jun 2019, 11:05
by Night Fury
alexnovac18 wrote: 4 years ago Thank you raheem, but knockback not work...
Have you tested in in ZP mod before Raheem concerts it?

Re: Convert Plugin ZP / ZE

Posted: 25 Jun 2019, 04:33
by alexnovac18
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