Skip to content

Commit 1f6a078

Browse files
BotoXmaxime1907
authored andcommitted
Added hack to make plugins open a menu with all possible targets on ReplyToTargetError COMMAND_TARGET_AMBIGUOUS.
Explanation: There are two clients in the server, one named gene, the other one "Ene ~special characters~". An admin issues "sm_slay Ene" and gets following error message: More than one client matched the given pattern. What this hack will do is: Use GetCmdArg(0, ...); to get the command name "sm_slay". Use GetCmdArgString(...); to get the arguments supplied to the command. Use GetLastProcessTargetString(...); (which was implemented in this commit) to retrieve the arguments that were passed to the last ProcessTargetString call. It will then pass this data to the DynamicTargeting plugin through its AmbiguousMenu native. The plugin will open up a menu on the client and list all targets which match the pattern that was supplied to ProcessTargetString. If the client selects a menu entry, FakeClientCommand will be used to re-execute the command with the correct target.
1 parent b09a675 commit 1f6a078

File tree

10 files changed

+392
-19
lines changed

10 files changed

+392
-19
lines changed

core/logic/smn_players.cpp

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,24 +1471,23 @@ static cell_t IsClientInKickQueue(IPluginContext *pContext, const cell_t *params
14711471
return pPlayer->IsInKickQueue() ? 1 : 0;
14721472
}
14731473

1474+
cmd_target_info_t g_ProcessTargetString_info;
14741475
static cell_t ProcessTargetString(IPluginContext *pContext, const cell_t *params)
14751476
{
1476-
cmd_target_info_t info;
1477-
1478-
pContext->LocalToString(params[1], (char **) &info.pattern);
1479-
info.admin = params[2];
1480-
pContext->LocalToPhysAddr(params[3], &info.targets);
1481-
info.max_targets = params[4];
1482-
info.flags = params[5];
1483-
pContext->LocalToString(params[6], &info.target_name);
1484-
info.target_name_maxlength = params[7];
1477+
pContext->LocalToString(params[1], (char **) &g_ProcessTargetString_info.pattern);
1478+
g_ProcessTargetString_info.admin = params[2];
1479+
pContext->LocalToPhysAddr(params[3], &g_ProcessTargetString_info.targets);
1480+
g_ProcessTargetString_info.max_targets = params[4];
1481+
g_ProcessTargetString_info.flags = params[5];
1482+
pContext->LocalToString(params[6], &g_ProcessTargetString_info.target_name);
1483+
g_ProcessTargetString_info.target_name_maxlength = params[7];
14851484

14861485
cell_t *tn_is_ml;
14871486
pContext->LocalToPhysAddr(params[8], &tn_is_ml);
14881487

1489-
playerhelpers->ProcessCommandTarget(&info);
1488+
playerhelpers->ProcessCommandTarget(&g_ProcessTargetString_info);
14901489

1491-
if (info.target_name_style == COMMAND_TARGETNAME_ML)
1490+
if (g_ProcessTargetString_info.target_name_style == COMMAND_TARGETNAME_ML)
14921491
{
14931492
*tn_is_ml = 1;
14941493
}
@@ -1497,16 +1496,30 @@ static cell_t ProcessTargetString(IPluginContext *pContext, const cell_t *params
14971496
*tn_is_ml = 0;
14981497
}
14991498

1500-
if (info.num_targets == 0)
1499+
if (g_ProcessTargetString_info.num_targets == 0)
15011500
{
1502-
return info.reason;
1501+
return g_ProcessTargetString_info.reason;
15031502
}
15041503
else
15051504
{
1506-
return info.num_targets;
1505+
return g_ProcessTargetString_info.num_targets;
15071506
}
15081507
}
15091508

1509+
static cell_t GetLastProcessTargetString(IPluginContext *pContext, const cell_t *params)
1510+
{
1511+
cell_t *admin, *flags;
1512+
1513+
pContext->StringToLocalUTF8(params[1], params[2], g_ProcessTargetString_info.pattern, NULL);
1514+
pContext->LocalToPhysAddr(params[3], &admin);
1515+
pContext->LocalToPhysAddr(params[4], &flags);
1516+
1517+
*admin = g_ProcessTargetString_info.admin;
1518+
*flags = g_ProcessTargetString_info.flags;
1519+
1520+
return 0;
1521+
}
1522+
15101523
static cell_t FormatActivitySource(IPluginContext *pContext, const cell_t *params)
15111524
{
15121525
int value;
@@ -1658,6 +1671,7 @@ REGISTER_NATIVES(playernatives)
16581671
{ "NotifyPostAdminCheck", NotifyPostAdminCheck },
16591672
{ "IsClientInKickQueue", IsClientInKickQueue },
16601673
{ "ProcessTargetString", ProcessTargetString },
1674+
{ "GetLastProcessTargetString", GetLastProcessTargetString },
16611675
{ "FormatActivitySource", FormatActivitySource },
16621676
{ "GetClientSerial", sm_GetClientSerial },
16631677
{ "GetClientFromSerial", sm_GetClientFromSerial },

core/smn_console.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,16 @@ static cell_t sm_RegAdminCmd(IPluginContext *pContext, const cell_t *params)
827827
return 1;
828828
}
829829

830+
static cell_t sm_IsCommandCallback(IPluginContext *pContext, const cell_t *params)
831+
{
832+
const ICommandArgs *pCmd = g_HL2.PeekCommandStack();
833+
834+
if (!pCmd)
835+
return 0;
836+
837+
return 1;
838+
}
839+
830840
static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params)
831841
{
832842
const ICommandArgs *pCmd = g_HL2.PeekCommandStack();
@@ -1505,6 +1515,7 @@ REGISTER_NATIVES(consoleNatives)
15051515
{"GetConVarDefault", GetConVarDefault},
15061516
{"RegServerCmd", sm_RegServerCmd},
15071517
{"RegConsoleCmd", sm_RegConsoleCmd},
1518+
{"IsCommandCallback", sm_IsCommandCallback},
15081519
{"GetCmdArgString", sm_GetCmdArgString},
15091520
{"GetCmdArgs", sm_GetCmdArgs},
15101521
{"GetCmdArg", sm_GetCmdArg},

plugins/AMBuilder

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ files = [
2424
'basecommands.sp',
2525
'mapchooser.sp',
2626
'randomcycle.sp',
27-
'sql-admin-manager.sp'
27+
'sql-admin-manager.sp',
28+
'DynamicTargeting.sp'
2829
]
2930

3031
spcomp_argv = [

plugins/DynamicTargeting.sp

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#pragma semicolon 1
2+
#define PLUGIN_VERSION "1.0"
3+
4+
#include <sourcemod>
5+
#include <DynamicTargeting>
6+
7+
#pragma newdecls required
8+
9+
public Plugin myinfo =
10+
{
11+
name = "Dynamic Targeting",
12+
author = "BotoX",
13+
description = "",
14+
version = PLUGIN_VERSION,
15+
url = ""
16+
}
17+
18+
char g_PlayerNames[MAXPLAYERS + 1][MAX_NAME_LENGTH];
19+
Handle g_PlayerData[MAXPLAYERS + 1];
20+
21+
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
22+
{
23+
CreateNative("AmbiguousMenu", Native_AmbiguousMenu);
24+
RegPluginLibrary("DynamicTargeting");
25+
26+
return APLRes_Success;
27+
}
28+
29+
public void OnClientDisconnect(int client)
30+
{
31+
if(g_PlayerData[client] != INVALID_HANDLE)
32+
{
33+
CloseHandle(g_PlayerData[client]);
34+
g_PlayerData[client] = INVALID_HANDLE;
35+
}
36+
}
37+
38+
int CreateAmbiguousMenu(int client, const char[] sCommand, const char[] sArgString, const char[] sPattern, int FilterFlags)
39+
{
40+
Menu menu = new Menu(MenuHandler_AmbiguousMenu, MenuAction_Select|MenuAction_Cancel|MenuAction_End|MenuAction_DrawItem|MenuAction_DisplayItem);
41+
menu.ExitButton = true;
42+
43+
char sTitle[32 + MAX_TARGET_LENGTH];
44+
FormatEx(sTitle, sizeof(sTitle), "Target \"%s\" is ambiguous.", sPattern);
45+
menu.SetTitle(sTitle);
46+
47+
int Players = 0;
48+
int[] aClients = new int[MaxClients + 1];
49+
50+
for(int i = 1; i <= MaxClients; i++)
51+
{
52+
if(!IsClientConnected(i) || i == client)
53+
continue;
54+
55+
if(FilterFlags & COMMAND_FILTER_NO_BOTS && IsFakeClient(i))
56+
continue;
57+
58+
if(!(FilterFlags & COMMAND_FILTER_CONNECTED) && !IsClientInGame(i))
59+
continue;
60+
61+
if(FilterFlags & COMMAND_FILTER_ALIVE && !IsPlayerAlive(i))
62+
continue;
63+
64+
if(FilterFlags & COMMAND_FILTER_DEAD && IsPlayerAlive(i))
65+
continue;
66+
67+
// insert player names into g_PlayerNames array
68+
GetClientName(i, g_PlayerNames[i], sizeof(g_PlayerNames[]));
69+
70+
if(StrContains(g_PlayerNames[i], sPattern, false) != -1)
71+
aClients[Players++] = i;
72+
}
73+
74+
// sort aClients array by player name
75+
SortCustom1D(aClients, Players, SortByPlayerName);
76+
77+
// insert players sorted
78+
char sUserId[12];
79+
char sDisp[MAX_NAME_LENGTH + 16];
80+
for(int i = 0; i < Players; i++)
81+
{
82+
IntToString(GetClientUserId(aClients[i]), sUserId, sizeof(sUserId));
83+
84+
FormatEx(sDisp, sizeof(sDisp), "%s (%s)", g_PlayerNames[aClients[i]], sUserId);
85+
menu.AddItem(sUserId, sDisp);
86+
}
87+
88+
DataPack pack = new DataPack();
89+
pack.WriteString(sCommand);
90+
pack.WriteString(sArgString);
91+
pack.WriteString(sPattern);
92+
pack.WriteCell(FilterFlags);
93+
94+
if(g_PlayerData[client] != INVALID_HANDLE)
95+
{
96+
CloseHandle(g_PlayerData[client]);
97+
g_PlayerData[client] = INVALID_HANDLE;
98+
}
99+
CancelClientMenu(client);
100+
101+
g_PlayerData[client] = pack;
102+
menu.Display(client, MENU_TIME_FOREVER);
103+
104+
return 0;
105+
}
106+
107+
public int MenuHandler_AmbiguousMenu(Menu menu, MenuAction action, int param1, int param2)
108+
{
109+
switch(action)
110+
{
111+
case MenuAction_End:
112+
{
113+
CloseHandle(menu);
114+
}
115+
case MenuAction_Cancel:
116+
{
117+
if(g_PlayerData[param1] != INVALID_HANDLE)
118+
{
119+
CloseHandle(g_PlayerData[param1]);
120+
g_PlayerData[param1] = INVALID_HANDLE;
121+
}
122+
}
123+
case MenuAction_Select:
124+
{
125+
int Style;
126+
char sItem[32];
127+
char sDisp[MAX_NAME_LENGTH + 16];
128+
menu.GetItem(param2, sItem, sizeof(sItem), Style, sDisp, sizeof(sDisp));
129+
130+
int UserId = StringToInt(sItem);
131+
int client = GetClientOfUserId(UserId);
132+
if(!client)
133+
{
134+
PrintToChat(param1, "\x04[DynamicTargeting]\x01 Player no longer available.");
135+
menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
136+
return 0;
137+
}
138+
139+
DataPack pack = view_as<DataPack>(g_PlayerData[param1]);
140+
pack.Reset();
141+
142+
char sCommand[128];
143+
pack.ReadString(sCommand, sizeof(sCommand));
144+
145+
char sArgString[256];
146+
pack.ReadString(sArgString, sizeof(sArgString));
147+
148+
char sPattern[MAX_TARGET_LENGTH];
149+
pack.ReadString(sPattern, sizeof(sPattern));
150+
151+
int Result = ReCallAmbiguous(param1, client, sCommand, sArgString, sPattern);
152+
153+
return Result;
154+
}
155+
case MenuAction_DrawItem:
156+
{
157+
int Style;
158+
char sItem[32];
159+
menu.GetItem(param2, sItem, sizeof(sItem), Style);
160+
161+
int UserId = StringToInt(sItem);
162+
int client = GetClientOfUserId(UserId);
163+
if(!client) // Player disconnected
164+
return ITEMDRAW_DISABLED;
165+
166+
return Style;
167+
}
168+
case MenuAction_DisplayItem:
169+
{
170+
int Style;
171+
char sItem[32];
172+
char sDisp[MAX_NAME_LENGTH + 16];
173+
menu.GetItem(param2, sItem, sizeof(sItem), Style, sDisp, sizeof(sDisp));
174+
175+
if(!sItem[0])
176+
return 0;
177+
178+
char sBuffer[MAX_NAME_LENGTH + 16];
179+
int UserId = StringToInt(sItem);
180+
int client = GetClientOfUserId(UserId);
181+
if(!client) // Player disconnected
182+
return 0;
183+
184+
GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[]));
185+
FormatEx(sBuffer, sizeof(sBuffer), "%s (%d)", g_PlayerNames[client], UserId);
186+
187+
if(!StrEqual(sDisp, sBuffer))
188+
return RedrawMenuItem(sBuffer);
189+
190+
return 0;
191+
}
192+
}
193+
194+
return 0;
195+
}
196+
197+
int ReCallAmbiguous(int client, int newClient, const char[] sCommand, const char[] sArgString, const char[] sPattern)
198+
{
199+
char sTarget[16];
200+
FormatEx(sTarget, sizeof(sTarget), "#%d", GetClientUserId(newClient));
201+
202+
char sNewArgString[256];
203+
strcopy(sNewArgString, sizeof(sNewArgString), sArgString);
204+
205+
char sPart[256];
206+
int CurrentIndex = 0;
207+
int NextIndex = 0;
208+
209+
while(NextIndex != -1 && CurrentIndex < sizeof(sNewArgString))
210+
{
211+
NextIndex = BreakString(sNewArgString[CurrentIndex], sPart, sizeof(sPart));
212+
213+
if(StrEqual(sPart, sPattern))
214+
{
215+
ReplaceStringEx(sNewArgString[CurrentIndex], sizeof(sNewArgString) - CurrentIndex, sPart, sTarget);
216+
break;
217+
}
218+
219+
CurrentIndex += NextIndex;
220+
}
221+
222+
FakeClientCommandEx(client, "%s %s", sCommand, sNewArgString);
223+
224+
return 0;
225+
}
226+
227+
public int Native_AmbiguousMenu(Handle plugin, int numParams)
228+
{
229+
int client = GetNativeCell(1);
230+
231+
if(client > MaxClients || client <= 0)
232+
{
233+
ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid.");
234+
return -1;
235+
}
236+
237+
if(!IsClientInGame(client))
238+
{
239+
ThrowNativeError(SP_ERROR_NATIVE, "Client is not in-game.");
240+
return -1;
241+
}
242+
243+
if(IsFakeClient(client))
244+
{
245+
ThrowNativeError(SP_ERROR_NATIVE, "Client is fake-client.");
246+
return -1;
247+
}
248+
249+
char sCommand[128];
250+
GetNativeString(2, sCommand, sizeof(sCommand));
251+
252+
char sArgString[256];
253+
GetNativeString(3, sArgString, sizeof(sArgString));
254+
255+
char sPattern[MAX_TARGET_LENGTH];
256+
GetNativeString(4, sPattern, sizeof(sPattern));
257+
258+
int FilterFlags = GetNativeCell(5);
259+
260+
return CreateAmbiguousMenu(client, sCommand, sArgString, sPattern, FilterFlags);
261+
}
262+
263+
public int SortByPlayerName(int elem1, int elem2, const int[] array, Handle hndl)
264+
{
265+
return strcmp(g_PlayerNames[elem1], g_PlayerNames[elem2], false);
266+
}

0 commit comments

Comments
 (0)