Skip to content

Commit 0bda47d

Browse files
committed
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 d93f698 commit 0bda47d

File tree

10 files changed

+478
-102
lines changed

10 files changed

+478
-102
lines changed

core/logic/smn_players.cpp

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

1461+
cmd_target_info_t g_ProcessTargetString_info;
14611462
static cell_t ProcessTargetString(IPluginContext *pContext, const cell_t *params)
14621463
{
1463-
cmd_target_info_t info;
1464-
1465-
pContext->LocalToString(params[1], (char **) &info.pattern);
1466-
info.admin = params[2];
1467-
pContext->LocalToPhysAddr(params[3], &info.targets);
1468-
info.max_targets = params[4];
1469-
info.flags = params[5];
1470-
pContext->LocalToString(params[6], &info.target_name);
1471-
info.target_name_maxlength = params[7];
1464+
pContext->LocalToString(params[1], (char **) &g_ProcessTargetString_info.pattern);
1465+
g_ProcessTargetString_info.admin = params[2];
1466+
pContext->LocalToPhysAddr(params[3], &g_ProcessTargetString_info.targets);
1467+
g_ProcessTargetString_info.max_targets = params[4];
1468+
g_ProcessTargetString_info.flags = params[5];
1469+
pContext->LocalToString(params[6], &g_ProcessTargetString_info.target_name);
1470+
g_ProcessTargetString_info.target_name_maxlength = params[7];
14721471

14731472
cell_t *tn_is_ml;
14741473
pContext->LocalToPhysAddr(params[8], &tn_is_ml);
14751474

1476-
playerhelpers->ProcessCommandTarget(&info);
1475+
playerhelpers->ProcessCommandTarget(&g_ProcessTargetString_info);
14771476

1478-
if (info.target_name_style == COMMAND_TARGETNAME_ML)
1477+
if (g_ProcessTargetString_info.target_name_style == COMMAND_TARGETNAME_ML)
14791478
{
14801479
*tn_is_ml = 1;
14811480
}
@@ -1484,16 +1483,30 @@ static cell_t ProcessTargetString(IPluginContext *pContext, const cell_t *params
14841483
*tn_is_ml = 0;
14851484
}
14861485

1487-
if (info.num_targets == 0)
1486+
if (g_ProcessTargetString_info.num_targets == 0)
14881487
{
1489-
return info.reason;
1488+
return g_ProcessTargetString_info.reason;
14901489
}
14911490
else
14921491
{
1493-
return info.num_targets;
1492+
return g_ProcessTargetString_info.num_targets;
14941493
}
14951494
}
14961495

1496+
static cell_t GetLastProcessTargetString(IPluginContext *pContext, const cell_t *params)
1497+
{
1498+
cell_t *admin, *flags;
1499+
1500+
pContext->StringToLocalUTF8(params[1], params[2], g_ProcessTargetString_info.pattern, NULL);
1501+
pContext->LocalToPhysAddr(params[3], &admin);
1502+
pContext->LocalToPhysAddr(params[4], &flags);
1503+
1504+
*admin = g_ProcessTargetString_info.admin;
1505+
*flags = g_ProcessTargetString_info.flags;
1506+
1507+
return 0;
1508+
}
1509+
14971510
static cell_t FormatActivitySource(IPluginContext *pContext, const cell_t *params)
14981511
{
14991512
int value;
@@ -1645,6 +1658,7 @@ REGISTER_NATIVES(playernatives)
16451658
{ "NotifyPostAdminCheck", NotifyPostAdminCheck },
16461659
{ "IsClientInKickQueue", IsClientInKickQueue },
16471660
{ "ProcessTargetString", ProcessTargetString },
1661+
{ "GetLastProcessTargetString", GetLastProcessTargetString },
16481662
{ "FormatActivitySource", FormatActivitySource },
16491663
{ "GetClientSerial", sm_GetClientSerial },
16501664
{ "GetClientFromSerial", sm_GetClientFromSerial },

core/smn_console.cpp

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

792+
static cell_t sm_IsCommandCallback(IPluginContext *pContext, const cell_t *params)
793+
{
794+
const ICommandArgs *pCmd = g_HL2.PeekCommandStack();
795+
796+
if (!pCmd)
797+
return 0;
798+
799+
return 1;
800+
}
801+
792802
static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params)
793803
{
794804
const ICommandArgs *pCmd = g_HL2.PeekCommandStack();
@@ -1467,6 +1477,7 @@ REGISTER_NATIVES(consoleNatives)
14671477
{"GetConVarDefault", GetConVarDefault},
14681478
{"RegServerCmd", sm_RegServerCmd},
14691479
{"RegConsoleCmd", sm_RegConsoleCmd},
1480+
{"IsCommandCallback", sm_IsCommandCallback},
14701481
{"GetCmdArgString", sm_GetCmdArgString},
14711482
{"GetCmdArgs", sm_GetCmdArgs},
14721483
{"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)