#pragma semicolon 1
//#define FFA_MODE uncomment to enable FFA Mode support
#include <amxmodx>
#include <fakemeta>
#include <reapi>
#include <json>
#include <nvault>
new const PLUGIN_VERSION[] = "1.1.0";
new const JSON_CONFIG_FILE[] = "/CSDM_Menu.json";
#define IsPlayer(%1) (1 <= %1 <= MaxClients)
#if defined FFA_MODE
#define IS_VALID_ATTACK(%1,%2) (%1 != %2)
#else
#define IS_VALID_ATTACK(%1,%2) (%1 != %2 && get_user_team(%1) != get_user_team(%2))
#endif
enum (+=1000) {
TASK_KILLS,
TASK_CHAT
}
enum _:RGB { R, G, B }
enum _:CSDM_CONFIG {
Float:HUD_HS_POS_X,
Float:HUD_HS_POS_Y,
Float:HUD_KILL_POS_X,
Float:HUD_KILL_POS_Y,
Float:BONUS_MAX_HP,
Float:BONUS_HP_NORMAL,
Float:BONUS_HP_HS
}
enum _:CSDM_COLORS {
COLOR_HUD_HS,
COLOR_HUD_KILL,
COLOR_FADE
}
enum _:CsdmSettings {
bool:bHeadshotMode,
bool:bKillFeed,
bool:bScreenFade,
bool:bBulletDamage,
bool:bHeadshotMsg,
bool:bKillsCounter,
bool:bHealing
}
enum _:KillType {
Normal,
Headshot
}
new const g_iMenuSettings[][] = {
"CSDM_ONLY_HS",
"CSDM_KILLFEED",
"CSDM_FADE_SCREEN",
"CSDM_BULLET_DAMAGE",
"CSDM_HEADSHOT_MSG",
"CSDM_ALL_KILLS",
"CSDM_BONUS_HP"
};
new g_eConfig[CSDM_CONFIG], g_iHudSyncObj,
g_ePlayerSettings[MAX_PLAYERS + 1][CsdmSettings],
g_iKillsCounter[MAX_PLAYERS + 1][KillType],
g_eColors[CSDM_COLORS][RGB],
g_hVault,
g_szVaultName[32],
g_iObsoleteDays,
g_iMsgDeath, g_iMsgScreenFade;
new bool:g_bIsUserDead[MAX_PLAYERS + 1],
bool:PlayerSettings[CsdmSettings] = {false, false, true, true, true, true, true};
public plugin_init() {
register_plugin("[ReAPI] CSDM Menu", PLUGIN_VERSION, "mIDnight");
create_cvar("csdm_menu_ver", PLUGIN_VERSION, FCVAR_SERVER | FCVAR_SPONLY);
register_dictionary("csdm_menu.txt");
register_message(get_user_msgid("DeathMsg"), "event_MessageDeathMsg");
RegisterHookChain(RG_CBasePlayer_TakeDamage, "Player_Damage_Post", .post = true);
RegisterHookChain(RG_CBasePlayer_TraceAttack, "Player_TraceAttack_Pre", .post = false);
RegisterHookChain(RG_CBasePlayer_Killed, "Player_Killed_Post", .post = true);
RegisterHookChain(RG_CBasePlayer_Spawn, "Player_Spawn_Post", .post = true);
g_iHudSyncObj = CreateHudSyncObj();
g_iMsgDeath = get_user_msgid("DeathMsg");
g_iMsgScreenFade = get_user_msgid("ScreenFade");
}
public plugin_cfg() {
LoadJsonConfig();
g_hVault = nvault_open(g_szVaultName);
if (g_hVault == INVALID_HANDLE) {
set_fail_state("Failed to open nVault for CSDM Menu");
}
nvault_prune(g_hVault, 0, get_systime() - (g_iObsoleteDays * 86400));
}
public plugin_end() {
if (g_hVault != INVALID_HANDLE) {
nvault_close(g_hVault);
}
}
LoadJsonConfig() {
new szConfigPath[PLATFORM_MAX_PATH];
get_localinfo("amxx_configsdir", szConfigPath, charsmax(szConfigPath));
add(szConfigPath, charsmax(szConfigPath), JSON_CONFIG_FILE);
new JSON:jConfig = json_parse(szConfigPath, true, true);
if (jConfig == Invalid_JSON) {
set_fail_state("[CSDM Menu] JSON syntax error in config file (%s)", szConfigPath);
}
if (!json_is_object(jConfig)) {
set_fail_state("[CSDM Menu] JSON config must contain a valid object (%s)", szConfigPath);
}
new JSON:jSettings = json_object_get_value(jConfig, "settings", .dot_not = true);
g_iObsoleteDays = json_object_get_number(jSettings, "vault_obsolete_days", .dot_not = true);
json_object_get_string(jSettings, "vault_name", g_szVaultName, charsmax(g_szVaultName), .dot_not = true);
g_eConfig[HUD_HS_POS_X] = json_object_get_real(jSettings, "hud_hs_position.x", .dot_not = true);
g_eConfig[HUD_HS_POS_Y] = json_object_get_real(jSettings, "hud_hs_position.y", .dot_not = true);
g_eConfig[HUD_KILL_POS_X] = json_object_get_real(jSettings, "hud_kills_position.x", .dot_not = true);
g_eConfig[HUD_KILL_POS_Y] = json_object_get_real(jSettings, "hud_kills_position.y", .dot_not = true);
g_eConfig[BONUS_MAX_HP] = json_object_get_real(jSettings, "bonus_max_hp", .dot_not = true);
g_eConfig[BONUS_HP_NORMAL] = json_object_get_real(jSettings, "bonus_hp_normal", .dot_not = true);
g_eConfig[BONUS_HP_HS] = json_object_get_real(jSettings, "bonus_hp_hs", .dot_not = true);
LoadColorArray(jSettings, "hud_hs_color", g_eColors[COLOR_HUD_HS]);
LoadColorArray(jSettings, "hud_kills_color", g_eColors[COLOR_HUD_KILL]);
LoadColorArray(jSettings, "fade_color", g_eColors[COLOR_FADE]);
new JSON:jCommands = json_object_get_value(jSettings, "menu_commands", .dot_not = true);
if (jCommands != Invalid_JSON && json_is_array(jCommands)) {
new iCommandCount = json_array_get_count(jCommands);
for (new i = 0; i < iCommandCount; i++) {
new szCommand[32];
json_array_get_string(jCommands, i, szCommand, charsmax(szCommand));
if (szCommand[0] != EOS) {
register_clcmd(szCommand, "Clcmd_CSDM_Menu");
}
}
}
json_free(jConfig);
server_print("[CSDM Menu] Configuration loaded successfully");
}
LoadColorArray(JSON:settings, const szKey[], iColorArray[RGB]) {
new JSON:jsonColors = json_object_get_value(settings, szKey, .dot_not = true);
if (jsonColors != Invalid_JSON && json_is_array(jsonColors)) {
for (new i = 0; i < RGB; i++) {
iColorArray[i] = json_array_get_number(jsonColors, i);
}
}
}
public client_putinserver(id) {
if (task_exists(id + TASK_KILLS)) {
remove_task(id + TASK_KILLS);
}
if (task_exists(id + TASK_CHAT)) {
remove_task(id + TASK_CHAT);
}
for (new i = 0; i < CsdmSettings; i++) {
g_ePlayerSettings[id][i] = PlayerSettings[i];
}
LoadPlayerSettings(id);
arrayset(g_iKillsCounter[id], 0, KillType);
g_bIsUserDead[id] = true;
set_task(180.0, "task_show_chat_ad", id + TASK_CHAT, .flags = "b");
}
public event_MessageDeathMsg(msgid, dest, receiver) {
enum { arg_killer = 1, arg_victim, arg_headshot, arg_weapon_name };
new killer = get_msg_arg_int(arg_killer);
new victim = get_msg_arg_int(arg_victim);
new headshot = get_msg_arg_int(arg_headshot);
new killerWeaponName[64];
get_msg_arg_string(arg_weapon_name, killerWeaponName, charsmax(killerWeaponName));
if (g_ePlayerSettings[killer][bKillFeed]) {
UTIL_DeathMsg(MSG_ONE, killer, killer, victim, headshot, killerWeaponName);
}
if (g_ePlayerSettings[victim][bKillFeed]) {
UTIL_DeathMsg(MSG_ONE, victim, killer, victim, headshot, killerWeaponName);
}
for (new p = 1; p <= MaxClients; p++) {
if (is_user_connected(p) && !is_user_hltv(p) && !is_user_bot(p) && !g_ePlayerSettings[p][bKillFeed]) {
UTIL_DeathMsg(MSG_ONE, p, killer, victim, headshot, killerWeaponName);
}
}
return PLUGIN_HANDLED;
}
public Player_Damage_Post(iVictim, iInflictor, iAttacker, Float:fDamage, bitsDamageType) {
if(!IsPlayer(iVictim) || !IsPlayer(iAttacker) || !IS_VALID_ATTACK(iAttacker, iVictim)) {
return HC_CONTINUE;
}
static const Float: iDamageCoords[][] = { {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} };
static iDamageCoordPos[MAX_CLIENTS + 1];
if(g_ePlayerSettings[iAttacker][bBulletDamage] && !(g_ePlayerSettings[iAttacker][bHeadshotMode] && get_member(iAttacker , m_LastHitGroup) == HIT_HEAD)) {
set_hudmessage(random_num(0, 255), random_num(0, 255), random_num(0, 255), iDamageCoords[iDamageCoordPos[iAttacker]][0], iDamageCoords[iDamageCoordPos[iAttacker]][1], _, _, 1.0);
ShowSyncHudMsg(iAttacker, g_iHudSyncObj, "%.0f", fDamage);
iDamageCoordPos[iAttacker] = (iDamageCoordPos[iAttacker] + 1) % sizeof(iDamageCoords);
}
return HC_CONTINUE;
}
public Player_TraceAttack_Pre(iVictim, iAttacker, Float:fDamage, Float:fDirection[3], trhandle) {
if (!IsPlayer(iVictim) || !IsPlayer(iAttacker) || !IS_VALID_ATTACK(iAttacker, iVictim)) {
return HC_CONTINUE;
}
if (g_ePlayerSettings[iAttacker][bHeadshotMode] &&
get_tr2(trhandle, TR_iHitgroup) != HIT_HEAD &&
get_user_weapon(iAttacker) != CSW_KNIFE) {
return HC_SUPERCEDE;
}
return HC_CONTINUE;
}
public Player_Killed_Post(const iVictim, iAttacker, iGibs) {
g_bIsUserDead[iVictim] = true;
if(!is_user_connected(iAttacker) || iVictim == iAttacker) {
return HC_CONTINUE;
}
if(g_ePlayerSettings[iAttacker][bScreenFade]) {
UTIL_FadeScreen(iAttacker);
}
if(g_ePlayerSettings[iAttacker][bHeadshotMsg] && get_member(iVictim, m_bHeadshotKilled)) {
set_dhudmessage(g_eColors[COLOR_HUD_HS][R], g_eColors[COLOR_HUD_HS][G], g_eColors[COLOR_HUD_HS][B], g_eConfig[HUD_HS_POS_X], g_eConfig[HUD_HS_POS_Y], 0, 0.1, 0.5, 0.02, 0.02);
show_dhudmessage(iAttacker, "HEAD SHOT");
}
if (g_ePlayerSettings[iAttacker][bKillsCounter]) {
g_iKillsCounter[iAttacker][Normal]++;
g_iKillsCounter[iAttacker][Headshot] += get_member(iVictim, m_bHeadshotKilled);
if (g_iKillsCounter[iAttacker][Normal] || g_iKillsCounter[iAttacker][Headshot]) {
remove_task(iAttacker + TASK_KILLS);
set_task(0.1, "task_show_hudkills", iAttacker + TASK_KILLS);
}
}
if (g_ePlayerSettings[iAttacker][bHealing]) {
set_entvar(iAttacker, var_health, floatmin(Float:get_entvar(iAttacker, var_health) + (get_member(iVictim, m_bHeadshotKilled) ? g_eConfig[BONUS_HP_HS] : g_eConfig[BONUS_HP_NORMAL]), g_eConfig[BONUS_MAX_HP]));
}
return HC_CONTINUE;
}
public Player_Spawn_Post(iEntity) {
if (IsPlayer(iEntity) && g_bIsUserDead[iEntity]) {
arrayset(g_iKillsCounter[iEntity], 0, KillType);
g_bIsUserDead[iEntity] = false;
}
}
public Clcmd_CSDM_Menu(id) {
new szBuffer[128];
formatex(szBuffer, charsmax(szBuffer), "\w%L", LANG_PLAYER, "CSDM_SETTINGS_TITLE");
new menu = menu_create(szBuffer, "settings_menu_handler");
for (new setting = 0; setting < sizeof(g_iMenuSettings); setting++) {
formatex(szBuffer, charsmax(szBuffer), "\w%L %s", LANG_PLAYER, g_iMenuSettings[setting],
g_ePlayerSettings[id][setting] ? "\w[\yON\w]" : "\w[\rOFF\w]");
menu_additem(menu, szBuffer);
}
menu_display(id, menu);
return PLUGIN_HANDLED;
}
public settings_menu_handler(id, menu, item) {
if (item == MENU_EXIT || !is_user_connected(id)) {
return MenuExit(menu);
}
if (item >= 0 && item < CsdmSettings) {
g_ePlayerSettings[id][item] = !g_ePlayerSettings[id][item];
SavePlayerSettings(id);
Clcmd_CSDM_Menu(id);
}
return MenuExit(menu);
}
public task_show_chat_ad(id) {
id -= TASK_CHAT;
if(is_user_connected(id)) {
client_print_color(id, print_chat, "%L", LANG_PLAYER, "CSDM_CHAT_AD");
}
}
public task_show_hudkills(id) {
id -= TASK_KILLS;
if(g_ePlayerSettings[id][bKillsCounter]) {
set_dhudmessage(g_eColors[COLOR_HUD_KILL][R], g_eColors[COLOR_HUD_KILL][G], g_eColors[COLOR_HUD_KILL][B], g_eConfig[HUD_KILL_POS_X], g_eConfig[HUD_KILL_POS_Y], 0, 0.1, 1.5, 0.02, 0.02);
show_dhudmessage(id, "%i (%i)", g_iKillsCounter[id][Normal], g_iKillsCounter[id][Headshot]);
if(g_iKillsCounter[id][Normal] > 0 || g_iKillsCounter[id][Headshot] > 0) {
remove_task(id + TASK_KILLS);
set_task(1.0, "task_show_hudkills", id + TASK_KILLS);
}
}
else {
remove_task(id + TASK_KILLS);
}
if(g_bIsUserDead[id]) {
g_iKillsCounter[id][Normal] = 0;
g_iKillsCounter[id][Headshot] = 0;
}
return PLUGIN_HANDLED;
}
SavePlayerSettings(id) {
if (g_hVault == INVALID_HANDLE || !is_user_connected(id)) {
return;
}
new szAuthID[MAX_AUTHID_LENGTH], szSettings[16];
get_user_authid(id, szAuthID, charsmax(szAuthID));
for (new i = 0; i < CsdmSettings; i++) {
szSettings[i] = g_ePlayerSettings[id][i] ? '1' : '0';
}
szSettings[CsdmSettings] = 0;
nvault_set(g_hVault, szAuthID, szSettings);
}
LoadPlayerSettings(id) {
if (g_hVault == INVALID_HANDLE || !is_user_connected(id)) {
return;
}
new szAuthID[MAX_AUTHID_LENGTH], szSettings[16];
get_user_authid(id, szAuthID, charsmax(szAuthID));
if (nvault_get(g_hVault, szAuthID, szSettings, charsmax(szSettings)) > 0) {
for (new i = 0; i < CsdmSettings; i++) {
g_ePlayerSettings[id][i] = (szSettings[i] == '1');
}
nvault_touch(g_hVault, szAuthID);
}
}
stock MenuExit(menu) {
menu_destroy(menu);
return PLUGIN_HANDLED;
}
stock UTIL_DeathMsg(dest, const receiver, const killer, const victim, const headshot, const weaponName[]) {
message_begin(dest, g_iMsgDeath, _, receiver);
write_byte(killer);
write_byte(victim);
write_byte(headshot);
write_string(weaponName);
message_end();
}
stock UTIL_FadeScreen(id) {
message_begin(MSG_ONE_UNRELIABLE, g_iMsgScreenFade, {0,0,0}, id);
write_short(1 << 10);
write_short(1 << 9);
write_short(0x0000);
write_byte(52);
write_byte(g_eColors[COLOR_FADE][R]);
write_byte(g_eColors[COLOR_FADE][G]);
write_byte(g_eColors[COLOR_FADE][B]);
message_end();
}