//////////////////////////////// HEADER ////////////////////////////////
#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>
#include <nvault_array>
#include <reapi>
#include <time>
#include <xs>
#define PLUGIN_NAME "[ReAPI] Damager"
#define PLUGIN_VERSION "1.0.0"
#define PLUGIN_AUTHOR "steelzzz & Albertio"
//////////////////////////////// CONSTANTS ////////////////////////////////
// The path to the configuration file
new const CFG_FILE_PATH[] = "addons/amxmodx/configs/reapi_damager/reapi_damager.ini";
// The name of the dictionary file
new const DICT_FILE_PATH[] = "reapi_damager.txt";
// The name of the nVault file
new const NVAULT_FILE[] = "reapi_damager";
//////////////////////////////// GLOBAL VARIABLES ////////////////////////////////
enum Sections {
SECTION_NONE = -1,
SECTION_SETTINGS
};
new Sections:ParserCurSection;
enum DamagerDataStruct {
DD_THROUGH_WALLS,
DD_THROUGH_SMOKE,
DD_NVAULT_EXPIRE_TIME,
Float:DD_SMOKE_RADIUS
};
new DamagerData[DamagerDataStruct];
new Array:DamagerCmds;
new DamagerCmdsNum;
enum _:PlayerDataStruct {
PD_STATE = 0,
PD_STYLE,
PD_TYPE,
PD_INCOMING,
PD_COLOR,
PD_INCOMING_COLOR,
PD_THROUGH_WALLS,
PD_THROUGH_SMOKE,
PD_POSITION,
PD_AUTHID[MAX_AUTHID_LENGTH]
};
new PlayerData[MAX_PLAYERS + 1][PlayerDataStruct];
enum _:LastDamageStruct {
Float:TIME = 0,
Float:DAMAGE
};
new LastDamage[MAX_PLAYERS + 1][LastDamageStruct];
enum _:DamagerState {
DISABLED = 0,
ENABLED
};
enum _:DamagerStyle {
NUMBERS = 0,
STARS
};
enum _:DamagerType {
DEFAULT = 0,
CIRCLE
};
enum _:DamagerColor {
COLOR_RANDOM = 0,
COLOR_WHITE,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
COLOR_YELLOW,
COLOR_MAGENTA,
COLOR_CYAN
};
new const DamagerColors[][][] = {
{ "COLOR_RANDOM", { 0, 0, 0 } },
{ "COLOR_WHITE", { 255, 255, 255 } },
{ "COLOR_RED", { 255, 0, 0 } },
{ "COLOR_GREEN", { 0, 255, 0 } },
{ "COLOR_BLUE", { 0, 0, 255 } },
{ "COLOR_YELLOW", { 255, 255, 0 } },
{ "COLOR_MAGENTA", { 255, 0, 255 } },
{ "COLOR_CYAN", { 0, 255, 255 } }
};
new const Float:DamagerCoords[][] = {
{ 0.50, 0.40 },
{ 0.56, 0.44 },
{ 0.60, 0.50 },
{ 0.56, 0.56 },
{ 0.50, 0.60 },
{ 0.44, 0.56 },
{ 0.40, 0.50 },
{ 0.44, 0.44 }
};
new HudSyncObjs[8];
new nVaultHandle;
new Array:ArrayActiveSmokes;
new bool:ShootsThroughSmoke[MAX_PLAYERS + 1];
//////////////////////////////// CONFIGURATION ////////////////////////////////
public plugin_precache() {
ParseConfig();
}
public ParseConfig() {
DamagerCmds = ArrayCreate(32);
new INIParser:parser = INI_CreateParser();
INI_SetReaders(parser, "ReadKeyValue", "ReadNewSection");
INI_ParseFile(parser, CFG_FILE_PATH);
INI_DestroyParser(parser);
DamagerCmdsNum = ArraySize(DamagerCmds);
}
public bool:ReadNewSection(INIParser:parser, const section[], bool:invalidTokens, bool:closeBracket) {
if(!closeBracket) {
log_amx("Closing bracket was not detected! Current section name '%s'.", section);
return false;
}
if(equal(section, "settings")) {
ParserCurSection = SECTION_SETTINGS;
return true;
}
return false;
}
public bool:ReadKeyValue(INIParser:parser, const key[], const value[]) {
switch(ParserCurSection) {
case SECTION_NONE: {
return false;
}
case SECTION_SETTINGS: {
if(equal(key, "THROUGH_WALLS")) {
DamagerData[DD_THROUGH_WALLS] = str_to_num(value);
} else if(equal(key, "THROUGH_SMOKE")) {
DamagerData[DD_THROUGH_SMOKE] = str_to_num(value);
} else if(equal(key, "NVAULT_EXPIRE_TIME")) {
DamagerData[DD_NVAULT_EXPIRE_TIME] = str_to_num(value);
} else if(equal(key, "SMOKE_RADIUS")) {
DamagerData[DD_SMOKE_RADIUS] = str_to_float(value);
} else if(equal(key, "CHAT_COMMANDS")) {
new cmd[32], allCmds[MAX_FMT_LENGTH];
copy(allCmds, charsmax(allCmds), value);
while(allCmds[0] != 0 && strtok(allCmds, cmd, charsmax(cmd), allCmds, charsmax(allCmds), ',')) {
trim(cmd), trim(allCmds);
ArrayPushString(DamagerCmds, cmd);
}
}
}
}
return true;
}
public plugin_init() {
register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR);
register_dictionary(DICT_FILE_PATH);
RegisterCmds();
RegisterHooks();
for(new i; i < sizeof(HudSyncObjs); i++)
HudSyncObjs[i] = CreateHudSyncObj();
ArrayActiveSmokes = ArrayCreate();
}
public RegisterCmds() {
for(new i, cmd[32]; i < DamagerCmdsNum; i++) {
ArrayGetString(DamagerCmds, i, cmd, charsmax(cmd));
register_clcmd(fmt("say %s", cmd), "ShowDamagerMenu");
register_clcmd(fmt("say_team %s", cmd), "ShowDamagerMenu");
}
}
public RegisterHooks() {
RegisterHookChain(RG_CBasePlayer_TakeDamage, "Player_TakeDamage_Post", true);
RegisterHookChain(RG_CBasePlayer_TraceAttack, "Player_TraceAttack_Post", true);
RegisterHookChain(RG_CGrenade_ExplodeSmokeGrenade, "Grenade_ExplodeSmokeGrenade_Post", true);
RegisterHam(Ham_StopSneaking, "grenade", "Grenade_StopSneaking_Post", true);
}
public plugin_cfg() {
NVaultInit();
}
//////////////////////////////// MAIN FUNCTIONS ////////////////////////////////
public client_authorized(playerId, const authId[]) {
if(is_user_bot(playerId) || is_user_hltv(playerId))
return;
copy(PlayerData[playerId][PD_AUTHID], charsmax(PlayerData[][PD_AUTHID]), authId);
if(nvault_get_array(nVaultHandle, PlayerData[playerId][PD_AUTHID], PlayerData[playerId], PlayerDataStruct) <= 0) {
PlayerData[playerId][PD_STATE] = ENABLED;
PlayerData[playerId][PD_STYLE] = NUMBERS;
PlayerData[playerId][PD_TYPE] = CIRCLE;
PlayerData[playerId][PD_INCOMING] = ENABLED;
PlayerData[playerId][PD_COLOR] = COLOR_GREEN;
PlayerData[playerId][PD_INCOMING_COLOR] = COLOR_RED;
PlayerData[playerId][PD_THROUGH_WALLS] = (DamagerData[DD_THROUGH_WALLS] == ENABLED) ? ENABLED : DISABLED;
PlayerData[playerId][PD_THROUGH_SMOKE] = (DamagerData[DD_THROUGH_SMOKE] == ENABLED) ? ENABLED : DISABLED;
NVaultSaveData(playerId);
}
}
public ShowDamagerMenu(const playerId) {
SetGlobalTransTarget(playerId);
new menuItem[MAX_FMT_LENGTH];
new menu = menu_create(menuItem, "HandlerDamagerMenu");
BuildDamagerMenu(playerId, menu);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_EXIT");
menu_setprop(menu, MPROP_EXITNAME, menuItem);
menu_setprop(menu, MPROP_PERPAGE, 0);
menu_setprop(menu, MPROP_EXIT, MEXIT_FORCE);
menu_display(playerId, menu);
return PLUGIN_HANDLED;
}
public BuildDamagerMenu(const playerId, const menu) {
new menuItem[MAX_FMT_LENGTH];
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM1", (PlayerData[playerId][PD_STATE] == ENABLED) ? "ENABLED" : "DISABLED");
menu_additem(menu, menuItem);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM2", (PlayerData[playerId][PD_STYLE] == STARS) ? "STARS" : "NUMBERS");
menu_additem(menu, menuItem);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM3", (PlayerData[playerId][PD_TYPE] == CIRCLE) ? "CIRCLE" : "DEFAULT");
menu_additem(menu, menuItem);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM4", (PlayerData[playerId][PD_INCOMING] == ENABLED) ? "ENABLED" : "DISABLED");
menu_additem(menu, menuItem);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM5", DamagerColors[PlayerData[playerId][PD_COLOR]][0][0]);
menu_additem(menu, menuItem);
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM6", DamagerColors[PlayerData[playerId][PD_INCOMING_COLOR]][0][0]);
menu_additem(menu, menuItem);
if(DamagerData[DD_THROUGH_WALLS] == ENABLED) {
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM7", (PlayerData[playerId][PD_THROUGH_WALLS] == ENABLED) ? "ENABLED" : "DISABLED");
menu_additem(menu, menuItem);
} else {
menu_addblank2(menu);
}
if(DamagerData[DD_THROUGH_SMOKE] == ENABLED) {
formatex(menuItem, charsmax(menuItem), "%l", "MENU_ITEM8", (PlayerData[playerId][PD_THROUGH_SMOKE] == ENABLED) ? "ENABLED" : "DISABLED");
menu_additem(menu, menuItem);
} else {
menu_addblank2(menu);
}
menu_addblank2(menu);
}
public HandlerDamagerMenu(const playerId, menu, item) {
if(item == MENU_EXIT) {
if(PlayerData[playerId][PD_AUTHID][0] != EOS)
NVaultSaveData(playerId);
menu_destroy(menu);
return PLUGIN_HANDLED;
}
switch(item) {
case 4: {
if(PlayerData[playerId][PD_COLOR] == charsmax(DamagerColors)) {
PlayerData[playerId][PD_COLOR] = 0;
} else {
PlayerData[playerId][PD_COLOR]++;
}
}
case 5: {
if(PlayerData[playerId][PD_INCOMING_COLOR] == charsmax(DamagerColors)) {
PlayerData[playerId][PD_INCOMING_COLOR] = 0;
} else {
PlayerData[playerId][PD_INCOMING_COLOR]++;
}
}
default: {
PlayerData[playerId][item] = !PlayerData[playerId][item];
}
}
menu_destroy(menu);
ShowDamagerMenu(playerId);
return PLUGIN_HANDLED;
}
public Player_TakeDamage_Post(const victimId, inflictorId, attackerId, Float:damage, bitsDamageType) {
if(!CheckIfDamagerCanWork(attackerId, victimId, damage, bitsDamageType))
return;
new attackerColor = GetAttackerDamagerColor(attackerId);
new victimColor = GetVictimDamagerColor(victimId);
if(PlayerData[attackerId][PD_TYPE] == CIRCLE) {
ShowCircleDamager(attackerId, victimId, damage, attackerColor, victimColor);
} else {
ShowDamager(attackerId, victimId, damage, bitsDamageType, attackerColor, victimColor);
}
}
public Player_TraceAttack_Post(const victimId, attackerId, Float:damage, Float:vecDir[3], trace) {
if(!is_user_alive(attackerId))
return;
if((PlayerData[victimId][PD_STATE] == DISABLED) && (PlayerData[attackerId][PD_STATE] == DISABLED))
return;
ShootsThroughSmoke[attackerId] = false;
new Float:vecStart[3];
ExecuteHam(Ham_Player_GetGunPosition, attackerId, vecStart);
new Float:vecEnd[3];
get_tr2(trace, TR_vecEndPos, vecEnd);
if(FindLineIntersectsSmoke(vecStart, vecEnd))
ShootsThroughSmoke[attackerId] = true;
}
public Grenade_ExplodeSmokeGrenade_Post(const grenadeId) {
ArrayPushCell(ArrayActiveSmokes, grenadeId);
}
public Grenade_StopSneaking_Post(const grenadeId) {
new itemIndex = ArrayFindValue(ArrayActiveSmokes, grenadeId);
if(itemIndex >= 0)
ArrayDeleteItem(ArrayActiveSmokes, itemIndex);
}
//////////////////////////////// STOCK FUNCTIONS ////////////////////////////////
stock NVaultInit() {
NVaultOpen();
NVaultCheckVersion();
if(DamagerData[DD_NVAULT_EXPIRE_TIME])
nvault_prune(nVaultHandle, 0, get_systime() - (SECONDS_IN_DAY * DamagerData[DD_NVAULT_EXPIRE_TIME]));
}
stock NVaultOpen() {
if((nVaultHandle = nvault_open(NVAULT_FILE)) == INVALID_HANDLE)
set_fail_state("Error opening nVault!");
}
stock NVaultCheckVersion() {
new nVaultVersion[12];
nvault_get(nVaultHandle, "version", nVaultVersion, charsmax(nVaultVersion));
if(!equal(PLUGIN_VERSION, nVaultVersion)) {
nvault_close(nVaultHandle);
new dataDir[MAX_RESOURCE_PATH_LENGTH];
get_localinfo("amxx_datadir", dataDir, charsmax(dataDir));
delete_file(fmt("%s/vault/%s.vault", dataDir, NVAULT_FILE));
delete_file(fmt("%s/vault/%s.journal", dataDir, NVAULT_FILE));
NVaultOpen();
nvault_set(nVaultHandle, "version", PLUGIN_VERSION);
}
}
stock NVaultSaveData(const playerId) {
nvault_set_array(nVaultHandle, PlayerData[playerId][PD_AUTHID], PlayerData[playerId], PlayerDataStruct);
}
stock bool:CheckIfDamagerCanWork(const attackerId, const victimId, const Float:damage, const bitsDamageType) {
if(!is_user_connected(attackerId))
return false;
if((PlayerData[victimId][PD_STATE] == DISABLED) && (PlayerData[attackerId][PD_STATE] == DISABLED))
return false;
if(bitsDamageType & DMG_BLAST)
return false;
if(victimId == attackerId)
return false;
if(!rg_is_player_can_takedamage(victimId, attackerId))
return false;
if(FloatNearlyEqualOrLess(damage, 0.0))
return false;
if(!CheckVisibilityForDamage(attackerId, victimId))
return false;
return true;
}
stock ShowCircleDamager(const attackerId, const victimId, const Float:damage, const attackerColor, const victimColor) {
new pos = ++PlayerData[attackerId][PD_POSITION];
if(pos == sizeof(DamagerCoords))
pos = PlayerData[attackerId][PD_POSITION] = 0;
if(PlayerData[victimId][PD_INCOMING] == ENABLED)
ShowHudToPlayer(victimId, HudSyncObjs[pos], victimColor, DamagerCoords[pos][0], DamagerCoords[pos][1] + 0.06, damage);
ShowHudToPlayer(attackerId, HudSyncObjs[pos], attackerColor, DamagerCoords[pos][0], DamagerCoords[pos][1], damage);
}
stock ShowDamager(const attackerId, const victimId, const Float:damage, const bitsDamageType, const attackerColor, const victimColor) {
if(bitsDamageType & DMG_GRENADE) {
new Float:damageTime = get_gametime();
if(FloatNearlyEqual((damageTime - LastDamage[attackerId][TIME]), 0.0)) {
LastDamage[attackerId][DAMAGE] += damage;
} else {
LastDamage[attackerId][DAMAGE] = damage;
}
LastDamage[attackerId][TIME] = damageTime;
if(PlayerData[victimId][PD_INCOMING] == ENABLED)
ShowHudToPlayer(victimId, HudSyncObjs[0], victimColor, -1.0, 0.45, LastDamage[attackerId][DAMAGE]);
ShowHudToPlayer(attackerId, HudSyncObjs[0], attackerColor, -1.0, 0.55, LastDamage[attackerId][DAMAGE]);
return;
}
if(PlayerData[victimId][PD_INCOMING] == ENABLED)
ShowHudToPlayer(victimId, HudSyncObjs[0], victimColor, -1.0, 0.45, damage);
ShowHudToPlayer(attackerId, HudSyncObjs[0], attackerColor, -1.0, 0.55, damage);
}
stock bool:FloatNearlyEqualOrLess(const Float:value1, const Float:value2) {
return bool:(value1 <= value2);
}
stock bool:FloatNearlyEqual(const Float:value1, const Float:value2) {
return bool:((value1 == value2) || (floatabs(value1 - value2) < 0.001));
}
stock bool:CheckVisibilityForDamage(const attackerId, const victimId) {
if(CheckPlayerIsBehindWall(attackerId, victimId))
return false;
if(CheckPlayerIsBehindSmoke(attackerId))
return false;
return true;
}
stock bool:CheckPlayerIsBehindWall(const attackerId, const victimId) {
new bool:victimIsVisible = bool:ExecuteHam(Ham_FVisible, attackerId, victimId);
if(victimIsVisible)
return false;
if(DamagerData[DD_THROUGH_WALLS] == DISABLED)
return true;
if((DamagerData[DD_THROUGH_WALLS] == ENABLED) && (PlayerData[attackerId][PD_THROUGH_WALLS] == DISABLED))
return true;
return false;
}
stock bool:CheckPlayerIsBehindSmoke(const attackerId) {
if(!ShootsThroughSmoke[attackerId])
return false;
if(DamagerData[DD_THROUGH_SMOKE] == DISABLED)
return true;
if((DamagerData[DD_THROUGH_SMOKE] == ENABLED) && (PlayerData[attackerId][PD_THROUGH_SMOKE] == DISABLED))
return true;
return false;
}
public GetAttackerDamagerColor(const attackerId) {
new attackerColor = PlayerData[attackerId][PD_COLOR];
if(PlayerData[attackerId][PD_COLOR] == COLOR_RANDOM)
attackerColor = random_num(1, charsmax(DamagerColors));
return attackerColor;
}
public GetVictimDamagerColor(const victimId) {
new victimColor = PlayerData[victimId][PD_INCOMING_COLOR];
if(PlayerData[victimId][PD_INCOMING_COLOR] == COLOR_RANDOM)
victimColor = random_num(1, charsmax(DamagerColors));
return victimColor;
}
stock ShowHudToPlayer(const playerId, syncObj, color, Float:posX, Float:posY, Float:damage) {
if(PlayerData[playerId][PD_STATE] == DISABLED)
return;
set_hudmessage(DamagerColors[color][1][0], DamagerColors[color][1][1], DamagerColors[color][1][2], posX, posY, 0, 0.1, 2.5, 0.02, 0.02);
ShowSyncHudMsg(playerId, syncObj, (PlayerData[playerId][PD_STYLE] == STARS) ? "*" : "%.0f", damage);
}
stock bool:FindLineIntersectsSmoke(const Float:vecStart[3], const Float:vecEnd[3]) {
new smokesNum = ArraySize(ArrayActiveSmokes);
for(new i, grenadeId, Float:vecSmokeOrigin[3]; i < smokesNum; i++) {
grenadeId = ArrayGetCell(ArrayActiveSmokes, i);
get_member(grenadeId, m_Grenade_vSmokeDetonate, vecSmokeOrigin);
vecSmokeOrigin[2] += DamagerData[DD_SMOKE_RADIUS] - 10.0;
if(LineIntersectsSmoke(vecStart, vecEnd, vecSmokeOrigin, DamagerData[DD_SMOKE_RADIUS]))
return true;
}
return false;
}
stock bool:LineIntersectsSmoke(const Float:vecStart[3], const Float:vecEnd[3], const Float:vecSmokeOrigin[3], const Float:radius) {
new Float:vecDir[3];
xs_vec_sub(vecEnd, vecStart, vecDir);
new Float:vecToCenter[3];
xs_vec_sub(vecSmokeOrigin, vecStart, vecToCenter);
new Float:proj = xs_vec_dot(vecDir, vecToCenter) / xs_vec_dot(vecDir, vecDir);
proj = floatclamp(proj, 0.0, 1.0);
new Float:vecClosest[3];
xs_vec_add_scaled(vecStart, vecDir, proj, vecClosest);
new Float:vecDist[3];
xs_vec_sub(vecSmokeOrigin, vecClosest, vecDist);
return bool:(xs_vec_len(vecDist) <= radius);
}