446 lines
11 KiB
SourcePawn
446 lines
11 KiB
SourcePawn
/* Plugin Template generated by Pawn Studio */
|
|
|
|
#include <sourcemod>
|
|
#include <tf2>
|
|
#include <tf2_stocks>
|
|
#include <sdkhooks>
|
|
|
|
new String:clientParticle[MAXPLAYERS+1][256];
|
|
new Handle:hProjectileArray[56];
|
|
new curSecCount=-1;
|
|
new Handle:hDb = INVALID_HANDLE;
|
|
|
|
|
|
public Plugin:myinfo =
|
|
{
|
|
name = "ClientParticles",
|
|
author = "Chefe",
|
|
description = "Set your own particle effect",
|
|
version = "1.0",
|
|
url = "oktoberfest-servers.net"
|
|
}
|
|
|
|
public OnPluginStart()
|
|
{
|
|
RegConsoleCmd("sm_particles", Command_Particles, "Set your own custom projectile particle effects");
|
|
|
|
HookEvent("player_changeclass", Event_PlayerChangeClass);
|
|
|
|
InitDB();
|
|
|
|
ProcessConfigFile("configs/clientparticles.cfg");
|
|
}
|
|
|
|
InitDB()
|
|
{
|
|
SQL_TConnect(InitDBHnld, "clientparticles");
|
|
}
|
|
|
|
public InitDBHnld(Handle:owner, Handle:hndl, const String:error[], any:data)
|
|
{
|
|
if (hndl == INVALID_HANDLE)
|
|
{
|
|
SetFailState("Database connection failed: %s", error);
|
|
}
|
|
else
|
|
{
|
|
hDb = hndl;
|
|
}
|
|
}
|
|
|
|
public Event_PlayerChangeClass(Handle:event,const String:name[],bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
|
|
PrintToServer("Class changed, update requested");
|
|
|
|
CreateTimer(0.5, Timer_Update, client);
|
|
}
|
|
|
|
public Action:Timer_Update(Handle:timer, any:client)
|
|
{
|
|
updateClientParticle(client);
|
|
}
|
|
|
|
stock ProcessConfigFile(const String:file[])
|
|
{
|
|
new String:sConfigFile[PLATFORM_MAX_PATH];
|
|
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), file);
|
|
if (!FileExists(sConfigFile))
|
|
{
|
|
/**
|
|
Config file doesn't exists, stop the plugin
|
|
*/
|
|
LogError("[CP] Plugin startup failed! Could not find file %s", sConfigFile);
|
|
SetFailState("Could not find file %s", sConfigFile);
|
|
}
|
|
else if (!ParseConfigFile(sConfigFile))
|
|
{
|
|
/**
|
|
Couldn't parse the file, stop the plugin
|
|
*/
|
|
LogError("[SM] Plugin is not running! Failed to parse %s", sConfigFile);
|
|
SetFailState("Parse error on file %s", sConfigFile);
|
|
}
|
|
}
|
|
|
|
stock bool:ParseConfigFile(const String:file[])
|
|
{
|
|
new Handle:hParser = SMC_CreateParser();
|
|
new String:error[128];
|
|
new line = 0;
|
|
new col = 0;
|
|
|
|
/**
|
|
Define the parser functions
|
|
*/
|
|
SMC_SetReaders(hParser, Config_NewSection, Config_KeyValue, Config_EndSection);
|
|
SMC_SetParseEnd(hParser, Config_End);
|
|
|
|
/**
|
|
Parse the file and get the result
|
|
*/
|
|
new SMCError:result = SMC_ParseFile(hParser, file, line, col);
|
|
CloseHandle(hParser);
|
|
|
|
if (result != SMCError_Okay)
|
|
{
|
|
SMC_GetErrorString(result, error, sizeof(error));
|
|
LogError("%s on line %d, col %d of %s", error, line, col, file);
|
|
}
|
|
|
|
return (result == SMCError_Okay);
|
|
}
|
|
|
|
public SMCResult:Config_NewSection(Handle:parser, const String:section[], bool:quotes)
|
|
{
|
|
if (StrEqual(section, "projectiles"))
|
|
{
|
|
return SMCParse_Continue;
|
|
}
|
|
|
|
curSecCount++;
|
|
hProjectileArray[curSecCount] = CreateArray(32, 0);
|
|
PushArrayString(hProjectileArray[curSecCount], section);
|
|
|
|
return SMCParse_Continue;
|
|
}
|
|
|
|
public SMCResult:Config_KeyValue(Handle:parser, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
|
|
{
|
|
if(StrEqual(key, "display", false))
|
|
{
|
|
PushArrayString(hProjectileArray[curSecCount], value);
|
|
PrintToServer("[PC_DEBUG] New value %s to key %s to array %i", value, key, curSecCount);
|
|
}
|
|
else if (StrEqual(key, "effect", false))
|
|
{
|
|
new index = PushArrayString(hProjectileArray[curSecCount], value);
|
|
PrintToServer("[PC_DEBUG] New value %s to key %s to array %i with index %i", value, key, curSecCount, index);
|
|
}
|
|
|
|
return SMCParse_Continue;
|
|
}
|
|
|
|
public SMCResult:Config_EndSection(Handle:parser)
|
|
{
|
|
return SMCParse_Continue;
|
|
}
|
|
|
|
public Config_End(Handle:parser, bool:halted, bool:failed)
|
|
{
|
|
if (failed)
|
|
{
|
|
SetFailState("Plugin configuration error");
|
|
}
|
|
}
|
|
|
|
public Action:Command_Particles(client, args)
|
|
{
|
|
new Handle:menu = CreateMenu(Menu_Particles);
|
|
SetMenuTitle(menu, "Choose the projectile");
|
|
|
|
for (new i=0; i<32; i++)
|
|
{
|
|
if (hProjectileArray[i] != INVALID_HANDLE && hProjectileArray[i])
|
|
{
|
|
new String:projectileName[256];
|
|
GetArrayString(hProjectileArray[i], 0, projectileName, sizeof(projectileName));
|
|
|
|
new String:displayName[256];
|
|
GetArrayString(hProjectileArray[i], 1, displayName, sizeof(displayName));
|
|
|
|
//new String:secNum[5];
|
|
//IntToString(i, secNum, sizeof(secNum));
|
|
AddMenuItem(menu, projectileName, displayName);
|
|
}
|
|
}
|
|
|
|
SetMenuExitButton(menu, true);
|
|
DisplayMenu(menu, client, MENU_TIME_FOREVER);
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Menu_Particles(Handle:menu, MenuAction:action, param1, param2)
|
|
{
|
|
/* If an option was selected, tell the client about the item. */
|
|
if (action == MenuAction_Select)
|
|
{
|
|
new String:projectile[256];
|
|
GetMenuItem(menu, param2, projectile, sizeof(projectile));
|
|
|
|
new Handle:effectmenu = CreateMenu(Menu_Effect);
|
|
SetMenuTitle(effectmenu, "Choose your effect");
|
|
|
|
new client = param1;
|
|
|
|
for (new a=0; a<32; a++)
|
|
{
|
|
if (hProjectileArray[a] != INVALID_HANDLE && hProjectileArray[a])
|
|
{
|
|
new String:projectileName[256];
|
|
GetArrayString(hProjectileArray[a], 0, projectileName, sizeof(projectileName));
|
|
|
|
if (StrEqual(projectile, projectileName))
|
|
{
|
|
for (new g=1; g<=(GetArraySize(hProjectileArray[a])-2); g++)
|
|
{
|
|
new String:myNewItem[256];
|
|
GetArrayString(hProjectileArray[a], g+1, myNewItem, sizeof(myNewItem));
|
|
|
|
if (!StrEqual(myNewItem, NULL_STRING))
|
|
{
|
|
AddMenuItem(effectmenu, myNewItem, myNewItem);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass projetile to menu as item for next menu handler
|
|
AddMenuItem(effectmenu, projectile, "", ITEMDRAW_IGNORE);
|
|
|
|
SetMenuExitButton(effectmenu, true);
|
|
DisplayMenu(effectmenu, client, MENU_TIME_FOREVER);
|
|
}
|
|
/* If the menu was cancelled, print a message to the server about it. */
|
|
else if (action == MenuAction_Cancel)
|
|
{
|
|
|
|
}
|
|
/* If the menu has ended, destroy it */
|
|
else if (action == MenuAction_End)
|
|
{
|
|
CloseHandle(menu);
|
|
}
|
|
}
|
|
|
|
public Menu_Effect(Handle:menu, MenuAction:action, param1, param2)
|
|
{
|
|
/* If an option was selected, tell the client about the item. */
|
|
if (action == MenuAction_Select)
|
|
{
|
|
new String:effect[256];
|
|
GetMenuItem(menu, param2, effect, sizeof(effect));
|
|
|
|
new String:projectile[256];
|
|
GetMenuItem(menu, GetMenuItemCount(menu)-1, projectile, sizeof(projectile));
|
|
|
|
PrintToChat(param1, "[CP] Effect: %s", effect);
|
|
|
|
new String:steamid[256];
|
|
GetClientAuthString(param1, steamid, sizeof(steamid));
|
|
|
|
new String:sqlstr[1024];
|
|
Format(sqlstr, sizeof(sqlstr), "SELECT particle FROM clientParticles WHERE steamid='%s' AND proj='%s'", steamid, projectile);
|
|
|
|
new Handle:pck = CreateDataPack();
|
|
WritePackCell(pck, param1);
|
|
WritePackString(pck, steamid);
|
|
WritePackString(pck, projectile);
|
|
WritePackString(pck, effect);
|
|
|
|
SQL_TQuery(hDb, searchClassEffect, sqlstr, pck);
|
|
}
|
|
/* If the menu was cancelled, print a message to the server about it. */
|
|
else if (action == MenuAction_Cancel)
|
|
{
|
|
|
|
}
|
|
/* If the menu has ended, destroy it */
|
|
else if (action == MenuAction_End)
|
|
{
|
|
CloseHandle(menu);
|
|
}
|
|
}
|
|
|
|
public searchClassEffect(Handle:owner, Handle:hndl, const String:error[], any:data)
|
|
{
|
|
if (hndl == INVALID_HANDLE || strlen(error) > 0)
|
|
{
|
|
LogError("Query error: %s", error);
|
|
return;
|
|
}
|
|
|
|
ResetPack(data);
|
|
new client = ReadPackCell(data);
|
|
new String:steamid[256];
|
|
ReadPackString(data, steamid, sizeof(steamid));
|
|
new String:projectile[256];
|
|
ReadPackString(data, projectile, sizeof(projectile));
|
|
new String:effect[256];
|
|
ReadPackString(data, effect, sizeof(effect));
|
|
|
|
if (SQL_GetRowCount(hndl)) {
|
|
while (SQL_FetchRow(hndl))
|
|
{
|
|
new String:name[150];
|
|
SQL_FetchString(hndl, 0, name, sizeof(name))
|
|
|
|
if (strcmp(name, projectile) != 0)
|
|
{
|
|
PrintToServer("%s was foudn in db. Update!", name);
|
|
|
|
new String:sqlstr[1024];
|
|
Format(sqlstr, sizeof(sqlstr), "UPDATE clientParticles SET particle = '%s' WHERE steamid='%s' AND proj='%s'", effect, steamid, projectile);
|
|
|
|
SQL_TQuery(hDb, noRtnCllbck, sqlstr);
|
|
|
|
CreateTimer(0.5, Timer_Update, client);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PrintToServer("NOT foudn in db. Insert!");
|
|
|
|
new String:sqlstr[1024];
|
|
Format(sqlstr, sizeof(sqlstr), "INSERT INTO clientParticles (steamid,proj,particle) VALUES ('%s','%s','%s')", steamid, projectile, effect);
|
|
|
|
SQL_TQuery(hDb, noRtnCllbck, sqlstr);
|
|
|
|
CreateTimer(0.5, Timer_Update, client);
|
|
}
|
|
}
|
|
|
|
public noRtnCllbck(Handle:owner, Handle:hndl, const String:error[], any:data)
|
|
{
|
|
if (hndl == INVALID_HANDLE || strlen(error) > 0)
|
|
{
|
|
LogError("Query error: %s", error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
stock updateClientParticle(client)
|
|
{
|
|
if (IsClientInGame(client))
|
|
{
|
|
new String:steamid[256];
|
|
GetClientAuthString(client, steamid, sizeof(steamid));
|
|
|
|
new TFClassType:class = TF2_GetPlayerClass(client);
|
|
new String:proj[256];
|
|
|
|
if (class == TFClass_Soldier)
|
|
{
|
|
proj = "tf_projectile_rocket";
|
|
} else if (class == TFClass_DemoMan)
|
|
{
|
|
proj = "tf_projectile_pipe_remote";
|
|
} else
|
|
{
|
|
clientParticle[client] = NULL_STRING;
|
|
return;
|
|
}
|
|
|
|
new String:sqlstr[1024];
|
|
Format(sqlstr, sizeof(sqlstr), "SELECT particle FROM clientParticles WHERE steamid='%s' AND proj='%s'", steamid, proj);
|
|
|
|
SQL_TQuery(hDb, updateClientParticleDbCallback, sqlstr, client);
|
|
}
|
|
}
|
|
|
|
public updateClientParticleDbCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
|
|
{
|
|
if (hndl == INVALID_HANDLE || strlen(error) > 0)
|
|
{
|
|
LogError("Query error: %s", error);
|
|
return;
|
|
}
|
|
|
|
if (SQL_GetRowCount(hndl)) {
|
|
while (SQL_FetchRow(hndl))
|
|
{
|
|
new String:effect[256];
|
|
SQL_FetchString(hndl, 0, effect, sizeof(effect));
|
|
|
|
clientParticle[data] = effect;
|
|
|
|
PrintToServer("Effect for %N activated: %s", data, effect);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public OnEntityCreated(entity, const String:classname[])
|
|
{
|
|
if (StrEqual(classname, "tf_projectile_rocket") || StrEqual(classname, "tf_projectile_pipe_remote")) {
|
|
SDKHook(entity, SDKHook_Spawn, OnEntitySpawned);
|
|
}
|
|
}
|
|
|
|
public OnEntitySpawned(entity)
|
|
{
|
|
new client = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity");
|
|
if (client > 0 && IsClientInGame(client) && !StrEqual(clientParticle[client], NULL_STRING))
|
|
{
|
|
AddParticle(client, entity);
|
|
}
|
|
SDKUnhook(entity, SDKHook_Spawn, OnEntitySpawned);
|
|
}
|
|
|
|
stock AddParticle(client, entity)
|
|
{
|
|
new iClusters = 5, iIndex = -1;
|
|
new String:sParticle[256];
|
|
sParticle = clientParticle[client];
|
|
decl String:sExtras[64];
|
|
|
|
for (new i = 0; i < iClusters; i++)
|
|
{
|
|
while ((iIndex = SplitString(sParticle, ",", sExtras, sizeof(sExtras))) > -1)
|
|
{
|
|
CreateParticle(entity, sExtras, true);
|
|
strcopy(sParticle, sizeof(sParticle), sParticle[iIndex]);
|
|
}
|
|
|
|
CreateParticle(entity, sParticle, true);
|
|
}
|
|
}
|
|
|
|
stock CreateParticle(iEntity, String:sParticle[], bool:bAttach = false)
|
|
{
|
|
new iParticle = CreateEntityByName("info_particle_system");
|
|
if (IsValidEdict(iParticle))
|
|
{
|
|
decl Float:fPosition[3];
|
|
GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fPosition);
|
|
|
|
TeleportEntity(iParticle, fPosition, NULL_VECTOR, NULL_VECTOR);
|
|
DispatchKeyValue(iParticle, "effect_name", sParticle);
|
|
|
|
if (bAttach)
|
|
{
|
|
SetVariantString("!activator");
|
|
AcceptEntityInput(iParticle, "SetParent", iEntity, iParticle, 0);
|
|
}
|
|
|
|
DispatchSpawn(iParticle);
|
|
ActivateEntity(iParticle);
|
|
AcceptEntityInput(iParticle, "Start");
|
|
}
|
|
return iParticle;
|
|
} |