#include <amxmodx>
#include <amxmisc>
#include <engine>
#include <fun>
#include <cstrike>
#include <fakemeta>
#include <cssb/war3ft_natives.inc>
#include <cssb/messages.inc>
#if defined DEBUG
#include <amxmisc>
#endif
#define MAXSENTRIESTOTAL 20
#define MAXPLAYERSENTRIES get_pcvar_num(iCvarSentryMax) // 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 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] = {3000,1500,500} // how many money 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.8} // 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
#if !defined PI
#define PI 3.141592654 // feel free to find a PI more exact than this
#endif
#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
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 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]
//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
#define PLUGIN "CSSB [WC3] Sentry Guns"
#define VERSION "1.1"
#define AUTHOR "CSSB"
new const szItemName[] = {"Сторожевая пушка"}
//Идентификаторы оружия
new iIdItemSentry;
//Переменные Cvar
new iCvarItemCost,iCvarSentryMax, iCvarSentryCost1, iCvarSentryCost2, iCvarSentryCost3, iCvarSentryTeam,iCvarSentryWait,iCvarOnOffItem;
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Встроенные функции--------------------BEGIN
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR);
//Команды
register_clcmd("say sentryguns","cmdBuyExtraItem", -1 );
register_clcmd("say /sentryguns","cmdBuyExtraItem", -1 );
register_clcmd("sentryguns", "cmdBuyExtraItem",-1);
//Регистрация переменных
iCvarItemCost = register_cvar("wc3_sentry_cost", "8000");
iCvarSentryMax = register_cvar("wc3_sentry_max", "2");
iCvarSentryCost1 = register_cvar("wc3_sentry_cost_level_1", "0");
iCvarSentryCost2 = register_cvar("wc3_sentry_cost_level_2", "2000");
iCvarSentryCost3 = register_cvar("wc3_sentry_cost_level_3", "3000");
iCvarSentryTeam = register_cvar("wc3_sentry_team", "2");
iCvarSentryWait = register_cvar("wc3_sentry_wait", "1.0");
iCvarOnOffItem = register_cvar("wc3_sentry_on_off", "1");
//FM Форварды
register_forward(FM_TraceLine, "fwTraceLinePost", 1);
//События
register_event("ResetHUD", "evNewRound", "b");
register_event("SendAudio", "evEndRound", "a", "2&%!MRAD_terwin", "2&%!MRAD_ctwin", "2&%!MRAD_rounddraw");
register_event("TextMsg", "evEndRound", "a", "2&#Game_C", "2&#Game_w");
register_event("TextMsg", "evEndRound", "a", "2&#Game_will_restart_in");
register_event( "TeamInfo", "evJoinTeam", "a");
//Регистрация предмета
iIdItemSentry = wc3_register_extra_item(szItemName, get_pcvar_num(iCvarItemCost),iCvarOnOffItem,1);
//Процедуры 'прикосновения'
if (find_ent_by_class(0, "func_bomb_target"))
{
register_touch("func_bomb_target", "player", "thPlayerReachedTarget")
register_touch("weaponbox", "player", "thPlayerWeaponBox")
}
if (find_ent_by_class(0, "func_hostage_rescue"))
register_touch("func_hostage_rescue", "player", "thPlayerReachedHostageRescue")
if (find_ent_by_class(0, "func_vip_safetyzone"))
register_touch("func_vip_safetyzone", "player", "thPlayerReachedTarget")
if (find_ent_by_class(0, "hostage_entity"))
register_touch("hostage_entity", "player", "thPlayerHostage")
register_touch("sentry", "player", "thPlayerSentry")
//Сообщения
register_message(23, "msgTempEntity") // <-- works for 0.16 as well
//Действия
register_think("sentrybase", "tnkSentryBase")
//Сообщения ID
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_ONEEIGHTYTHROUGHPI = 180.0 / PI
// InitArmoury saves the location of all onground weapons. Later we restore them to these origins when a newround begin.
set_task(5.0, "InitArmoury")
//Загрузка файла конфигурации
new szConfigsDir[256];
get_configsdir(szConfigsDir, sizeof(szConfigsDir) - 1);
server_cmd("exec %s/CSSBConfigs/cssbWC3Items/wc3_sentry_guns.cfg", szConfigsDir);
}
public client_disconnect(id)
{
while (GetSentryCount(id) > 0)
sentry_detonate_by_owner(id)
}
public client_putinserver(id)
{
if (is_user_bot(id))
{
new parm[1]
parm[0] = id
botbuildsrandomly(parm)
}
return PLUGIN_CONTINUE
}
public plugin_modules()
{
require_module("engine")
require_module("fun")
require_module("cstrike")
require_module("fakemeta")
}
public plugin_precache()
{
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/metal3.wav") // metal breaking (needed for comp also?!)
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")
}
//--------------------Встроенные функции--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Команды плагина--------------------BEGIN
//Команда покупки предмета
public cmdBuyExtraItem(idUser)
{
wc3_buy_extra_item(idUser, iIdItemSentry);
}
//--------------------Команды плагина--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Forwards--------------------BEGIN
public fwTraceLinePost(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))
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, charsmax(upgradeInfo), "%L",LANG_PLAYER,"WC3SHM_SG_UPGRADE_ME_LEVEL",
level + 2,
fCostSentryGuns(level + 1));
else if (level < SENTRY_LEVEL_3)
format(upgradeInfo, charsmax(upgradeInfo), "%L",LANG_PLAYER,"WC3SHM_SG_UPGRADE_COST", fCostSentryGuns(level + 1))
else
upgradeInfo = ""
new tempStatusBuffer[256]
format(tempStatusBuffer, charsmax(tempStatusBuffer), "%L",LANG_PLAYER,"WC3SHM_SG_DATA",
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.
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) && cs_get_user_money(player) >= fCostSentryGuns(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) && cs_get_user_money(player) >= fCostSentryGuns(level + 1)
}
}
return false
}
//--------------------Forwards--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Вызов Natives--------------------BEGIN
//wc3_register_extra_item
public wc3_extra_item_selected(id, itemid)
{
if (itemid == iIdItemSentry)
{
createsentryhere(id);
}
}
//--------------------Вызов Natives--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------События--------------------BEGIN
//Когда игрок меняет команду (пушка взрывается)
public evJoinTeam()
{
//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
}
//Новый раунд
public evNewRound(id)
{
//Disallow building and enable it after some time
g_allowBuild= false
set_task(get_pcvar_float(iCvarSentryWait), "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 evEndRound()
{
if (!g_hasArmouries)
return PLUGIN_CONTINUE
set_task(4.0, "ResetArmouryFalse")
return PLUGIN_CONTINUE
}
//--------------------События--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Touch--------------------BEGIN
public thPlayerReachedTarget(target, bot)
{
if (!is_user_bot(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
BotBuild(bot)
g_lastObjectiveBuild[bot - 1] = get_systime()
return PLUGIN_CONTINUE
}
public thPlayerWeaponBox(weaponbox, bot)
{
if (!is_user_bot(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 thPlayerReachedHostageRescue(target, bot)
{
if (!is_user_bot(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES)
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)
return PLUGIN_CONTINUE
}
public thPlayerHostage(hostage, bot)
{
if (!is_user_bot(bot) || GetSentryCount(bot) >= MAXPLAYERSENTRIES || cs_get_user_team(bot) != CS_TEAM_T)
return PLUGIN_CONTINUE
// Build a sentry close to a hostage
BotBuild(bot)
return PLUGIN_CONTINUE
}
public thPlayerSentry(sentry, player)
{
if (PlayerCanUpgradeSentry(player, sentry))
sentry_upgrade(player, sentry)
return PLUGIN_CONTINUE
}
//--------------------Touch--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Сообщения--------------------BEGIN
public msgTempEntity()
{
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)
{
sentry_detonate(i, false, true)
i--
}
}
return PLUGIN_CONTINUE
}
//--------------------Сообщения--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//--------------------Think--------------------BEGIN
public tnkSentryBase(sentrybase)
{
sentrybase_broke(sentrybase)
return PLUGIN_CONTINUE
}
//--------------------Think--------------------END
//=============================================================================
//=============================================================================
//=============================================================================
//The function that enables building
public enablesentrybur()
{
g_allowBuild= true;
}
public createsentryhere(id)
{
//Check if the player is allowed to build
if(!g_allowBuild)
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_MUST_WAIT_UNTIL");
cssbColoredPrint(id, szMessage);
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)
{
#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))
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_UPGRADLVL_2");
cssbColoredPrint(id, szMessage);
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))
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_UPGRADLVL_3");
cssbColoredPrint(id, szMessage);
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)
{
//Check if the player is allowed to build
if(!g_allowBuild)
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_MUST_WAIT_UNTIL");
cssbColoredPrint(id, szMessage);
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
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_NOBUILD",
get_pcvar_num(iCvarSentryMax));
cssbColoredPrint(id, szMessage);
return
}
else if (g_inBuilding[id - 1])
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_WOW");
cssbColoredPrint(id, szMessage);
return
}
else if (!is_user_alive(id))
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
return;
}
/* else if (cs_get_user_money(id) < get_pcvar_num(iCvarItemCost))
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_EN_MONEY",
get_pcvar_num(iCvarItemCost));
cssbColoredPrint(id, szMessage);
return
}*/
else if (!entity_is_on_ground(id))
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_STAND_GROUND");
cssbColoredPrint(id, szMessage);
return
}
//else if (entity_get_int(id, EV_INT_flags) & FL_DUCKING)
//{
else if (entity_get_int(id, EV_INT_bInDuck))
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_YEAH_RIGHT");
cssbColoredPrint(id, szMessage);
return
}
else if ( get_pcvar_num( iCvarSentryTeam ) )
{
if( get_pcvar_num( iCvarSentryTeam ) < 0 && !is_user_admin(id) )
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_ADMINS_CAN_BUILD");
cssbColoredPrint(id, szMessage);
return
}
}
else if ( !(abs(get_pcvar_num( iCvarSentryTeam )) & _:cs_get_user_team(id)) )
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_TEAM_CANNOT_BUILD");
cssbColoredPrint(id, szMessage);
}
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))
{
cs_set_user_money(id,cs_get_user_money(id) + get_pcvar_num(iCvarItemCost),0);
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_CANNOT_BUILD");
cssbColoredPrint(id, szMessage);
}
}
GetSentryCount(id)
{
return g_playerSentries[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) || cs_get_user_team(i) != builderTeam || id == i)
continue
// format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",LANG_PLAYER,"WC3SHM_SG_CHATTAG",LANG_PLAYER,"WC3SHM_SG_HAS_BUILT_SENTRY",name,floatround(entity_range(i, sentryEntity)) );
// cssbColoredPrint(id, szMessage);
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()
}
}
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
}
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)
new Float:difference = PLAYERORIGINHEIGHT - baDistanceFromGround
if (difference < -1 * HEIGHTDIFFERENCEALLOWED || difference > HEIGHTDIFFERENCEALLOWED)
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)
// Set origin
entity_set_origin(entbase, 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)
// 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)
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)
g_inBuilding[creator - 1] = false;
if (!is_valid_ent(entbase))
entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NUTS);
}
public server_frame()
{
g_gameTime = get_gametime()
g_deltaTime = g_gameTime - g_lastGameTime
new tempSentries[MAXSENTRIES], Float:angles[3]
new tempSentriesNum = 0
for (new i = 0; i < g_sentriesNum; i++)
{
tempSentries[i] = g_sentries[i]
tempSentriesNum++
}
for (new i = 0; i < tempSentriesNum; i++)
{
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 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))
}
else if (spinSpeed >= maxSpin)
{
sentry_detonate(sentry, false, false)
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()
}
stock create_explosion(Float:origin_[3])
{
new origin[3]
FVecIVec(origin_, origin)
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))
continue
entity_get_vector(i, EV_VEC_origin, playerOrigin)
distance = vector_distance(playerOrigin, origin_)
if (distance <= SENTRYEXPLODERADIUS)
{
flDmgToDo = dmgbase - (dmgbase * (distance / SENTRYEXPLODERADIUS))
newHealth = get_user_health(i) - floatround(flDmgToDo)
if (newHealth <= 0)
{
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)
{
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
}
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
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_SENTRY_GUN_DETONATED");
cssbColoredPrint(owner, szMessage);
}
DecreaseSentryCount(owner, sentry)
//SetHasSentry(GetSentryPeople(sentry, OWNER), false)
// Remove base first
if (entity_get_int(sentry, SENTRY_INT_FIRE) != SENTRY_FIREMODE_NUTS)
set_task(0.0, "delayedremovalofentity", entity_get_edict(sentry, SENTRY_ENT_BASE))
// Remove this entity
set_task(0.0, "delayedremovalofentity", sentry)
// 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))
return;
remove_entity(entity);
}
sentry_detonate_by_owner(owner, bool:quiet = false)
{
for(new i = 0; i < g_sentriesNum; i++)
{
if (GetSentryPeople(g_sentries[i], OWNER) == owner)
{
sentry_detonate(i, quiet, true)
break
}
}
}
public sentry_think(parm[1])
{
if (!is_valid_ent(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))
if (firemode == SENTRY_FIREMODE_YES && is_valid_ent(target))
{ // 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)
targetOrigin[2] += TARGETUPMODIFIER
hitent = trace_line(ent, sentryOrigin, targetOrigin, hitOrigin)
if (hitent == entity_get_edict(ent, SENTRY_ENT_BASE))
hitent = trace_line(hitent, hitOrigin, targetOrigin, hitOrigin)
if (hitent != target && is_user_alive(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
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)
{
sentry_damagetoplayer(ent, sentryLevel, sentryOrigin, target)
}
else
{
// Tracer hitOrigin adjusted for miss...
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...
}
}
tracer(sentryOrigin, hitOrigin)
// Don't do any more here
set_task(THINKFIREFREQUENCY, "sentry_think", TASKID_THINK + parm[0], parm, 1)
return
}
else
{
// Else target isn't still visible, unset fire state.
entity_set_int(ent, SENTRY_INT_FIRE, SENTRY_FIREMODE_NO)
// Don't return here, continue with searching for targets below...
}
}
else if (firemode == SENTRY_FIREMODE_NUTS)
{
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))
{
// 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
}
// 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) || 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)
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)
}
if (hitent == i)
{
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", 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) )
{
cs_set_user_money(i, cs_get_user_money(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))
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, 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: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
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)
}
}
AimingAtSentry(id, bool:alwaysReturn = false)
{
new hitEnt, bodyPart
if (get_user_aiming(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
}
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)
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 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)
}
sentry_upgrade(id, sentry)
{
new sentryLevel = entity_get_int(sentry, SENTRY_INT_LEVEL)
if (entity_get_int(sentry, SENTRY_INT_FIRE) == SENTRY_FIREMODE_NUTS)
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_SENTRY_GUN_DETONATED");
cssbColoredPrint(id, szMessage);
return
}
else if (get_user_team(id) != entity_get_int(sentry, SENTRY_INT_TEAM))
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_ONLY_UPGRADE_YOUR_OWN");
cssbColoredPrint(id, szMessage);
return
}
#if defined DISALLOW_OWN_UPGRADES
else if (sentryLevel == SENTRY_LEVEL_1 && GetSentryPeople(sentry, OWNER) == id)
{
return
}
#endif
#if defined DISALLOW_TWO_UPGRADES
else if (sentryLevel == SENTRY_LEVEL_2 && GetSentryPeople(sentry, UPGRADER_1) == id)
{
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 (cs_get_user_money(id) - fCostSentryGuns(sentryLevel) < 0)
{
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_MONEY_UPGRADE",
fCostSentryGuns(sentryLevel));
cssbColoredPrint(id, szMessage);
return
}
cs_set_user_money(id, cs_get_user_money(id) - fCostSentryGuns(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)
format(szMessage, sizeof(szMessage) - 1, "^x01[^x03%L^x01] ^x04%L",
LANG_PLAYER,"WC3SHM_SG_CHATTAG",
LANG_PLAYER,"WC3SHM_SG_UPGRADED_SG_LVL",
upgraderName,
sentryLevel + 1);
cssbColoredPrint(GetSentryPeople(sentry, OWNER), szMessage);
}
}
}
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_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 ResetArmouryFalse()
{
g_resetArmouryThisRound = false
}
/*
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 displaysentrystatus(parms[2])
{
// parm 0 = player
// parm 1 = team
if (!GetStatusTrigger(parms[0]))
return
if(parms[1] == 1)
set_hudmessage(255,51,0, -1.0, 0.35, 0, 0.0, STATUSINFOTIME + 0.1, 0.0, 0.0, 2) // STATUSINFOTIME + 0.1 = overlapping a little..
if(parms[1] == 2)
set_hudmessage(0,153,255, -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)
return;
new Float:ltime = random_float(closestTime, longestTime)
set_task(ltime, "sentry_build", bot)
}
public sentry_build_randomlybybot(taskid_and_id)
{
//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)
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++
}
}
}
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)
}
public botbuildsrandomly(parm[1])
{
if (!is_user_connected(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)
}
fCostSentryGuns(i)
{
switch(i)
{
case 0: return get_pcvar_num(iCvarSentryCost1)
case 1: return get_pcvar_num(iCvarSentryCost2)
case 2: return get_pcvar_num(iCvarSentryCost3)
}
return 0;
}