https://wiki.alliedmods.net/api.php?action=feedcontributions&user=Joinedsenses&feedformat=atomAlliedModders Wiki - User contributions [en]2024-03-28T22:53:24ZUser contributionsMediaWiki 1.31.6https://wiki.alliedmods.net/index.php?title=Left_4_Voting_2&diff=11358Left 4 Voting 22022-09-16T14:26:09Z<p>Joinedsenses: /* Example voting plugin */ Fix ret on command</p>
<hr />
<div>Left 4 Dead 2 has a VGUI voting system controller by a bunch of Events and UserMessages. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument.<br />
# The VoteStart User Message is sent.<br />
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers.<br />
# When the vote is complete, the server sends either a VotePass or VoteFail UserMessage.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_activeIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int|m_votesYes|Current Yes votes}}<br />
{{hl2msg|int|m_votesNo|Current No votes}}<br />
{{hl2msg|int|m_potentialVotes|Number of players eligible to vote}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== Vote ===<br />
<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|Vote|string}}<br />
{{hl2msg|string|option|"Yes" or "No"}}<br />
{{end-hl2msg}}<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since Valve represents no team as -1, no team is instead represented as its two's complement, 255.}}<br><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players in the vote group, corresponding with teams. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|byte|initiator|Client index (NOT USERID) of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|issue|Vote issue translation string}}<br />
{{hl2msg|string|param1|Vote issue text}}<br />
{{hl2msg|string|initiatorName|Name of person who started the vote}}<br />
{{end-hl2msg}}<br />
====Values for "issue" and "param1" in standard votes====<br />
{| class="wikitable"<br />
|-<br />
! Vote type !! issue !! param1<br />
|-<br />
| Kick || #L4D_vote_kick_player || Nickname of the person to be kicked without "#"<br />
|-<br />
| ReturnToLobby || #L4D_vote_return_to_lobby || <br />
|-<br />
| ChangeCampaign || #L4D_vote_mission_change || #L4D360UI_CampaignName_C5<br />
|-<br />
| RestartChapter || #L4D_vote_passed_versus_level_restart ||<br />
|-<br />
| ChangeDifficulty (easy) || #L4D_vote_change_difficulty || #L4D_DifficultyEasy<br />
|-<br />
| ChangeDifficulty (hard) || #L4D_vote_change_difficulty || #L4D_DifficultyHard<br />
|-<br />
| Alltalk On || #L4D_vote_alltalk_change || #L4D_vote_alltalk_enable<br />
|-<br />
| Alltalk Off || #L4D_vote_alltalk_change || #L4D_vote_alltalk_disable<br />
|}<br />
*Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish"<br />
<br />
===VoteRegistered===<br />
{{qnotice|Only sent to player who voted}}<br /><br />
{{begin-hl2msg|VoteRegistered|string}}<br />
{{hl2msg|byte|vote|0 for No, 1 for Yes}}<br />
{{end-hl2msg}}<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|string|details|Vote success translation string}}<br />
{{hl2msg|string|param1|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
===VoteFail===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFail|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{end-hl2msg}}<br />
<br />
== Events ==<br />
{{qnotice|team is -1 when sent to all players)}}<br><br />
<br />
=== vote_changed ===<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|yesVotes|}}<br />
{{hl2msg|byte|noVotes|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
<br />
#define L4D2_TEAM_ALL -1<br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
int g_iPlayersCount;<br />
bool VoteInProgress;<br />
bool CanPlayerVote[MAXPLAYERS + 1];<br />
<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("sm_testvote", cmdTestVote);<br />
RegConsoleCmd("sm_vote", cmdVote);<br />
}<br />
<br />
public Action cmdTestVote(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteByte(0);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteString("Server");<br />
EndMessage();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
g_iPlayersCount = 0;<br />
VoteInProgress = true;<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = true;<br />
g_iPlayersCount ++;<br />
}<br />
}<br />
<br />
UpdateVotes();<br />
CreateTimer(10.0, timerVoteCheck, TIMER_FLAG_NO_MAPCHANGE);<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
public Action timerVoteCheck(Handle timer)<br />
{<br />
if (VoteInProgress)<br />
{<br />
VoteInProgress = false;<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
<br />
public void UpdateVotes()<br />
{<br />
Event event = CreateEvent("vote_changed");<br />
event.SetInt("yesVotes", g_iYesVotes);<br />
event.SetInt("noVotes", g_iNoVotes);<br />
event.SetInt("potentialVotes", g_iPlayersCount);<br />
event.Fire();<br />
<br />
if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress)<br />
{<br />
PrintToServer("voting complete!");<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = false;<br />
}<br />
}<br />
<br />
VoteInProgress = false;<br />
<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
public Action cmdVote(int client, int args)<br />
{<br />
if (VoteInProgress && CanPlayerVote[client])<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, sizeof arg);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
if (strcmp(arg, "Yes", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "No", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
}<br />
<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Handled;<br />
}<br />
</sourcepawn><br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[TF2 Voting]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Left_4_Voting_2&diff=11357Left 4 Voting 22022-09-16T14:25:25Z<p>Joinedsenses: /* Example voting plugin */ Add newline</p>
<hr />
<div>Left 4 Dead 2 has a VGUI voting system controller by a bunch of Events and UserMessages. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument.<br />
# The VoteStart User Message is sent.<br />
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers.<br />
# When the vote is complete, the server sends either a VotePass or VoteFail UserMessage.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_activeIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int|m_votesYes|Current Yes votes}}<br />
{{hl2msg|int|m_votesNo|Current No votes}}<br />
{{hl2msg|int|m_potentialVotes|Number of players eligible to vote}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== Vote ===<br />
<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|Vote|string}}<br />
{{hl2msg|string|option|"Yes" or "No"}}<br />
{{end-hl2msg}}<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since Valve represents no team as -1, no team is instead represented as its two's complement, 255.}}<br><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players in the vote group, corresponding with teams. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|byte|initiator|Client index (NOT USERID) of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|issue|Vote issue translation string}}<br />
{{hl2msg|string|param1|Vote issue text}}<br />
{{hl2msg|string|initiatorName|Name of person who started the vote}}<br />
{{end-hl2msg}}<br />
====Values for "issue" and "param1" in standard votes====<br />
{| class="wikitable"<br />
|-<br />
! Vote type !! issue !! param1<br />
|-<br />
| Kick || #L4D_vote_kick_player || Nickname of the person to be kicked without "#"<br />
|-<br />
| ReturnToLobby || #L4D_vote_return_to_lobby || <br />
|-<br />
| ChangeCampaign || #L4D_vote_mission_change || #L4D360UI_CampaignName_C5<br />
|-<br />
| RestartChapter || #L4D_vote_passed_versus_level_restart ||<br />
|-<br />
| ChangeDifficulty (easy) || #L4D_vote_change_difficulty || #L4D_DifficultyEasy<br />
|-<br />
| ChangeDifficulty (hard) || #L4D_vote_change_difficulty || #L4D_DifficultyHard<br />
|-<br />
| Alltalk On || #L4D_vote_alltalk_change || #L4D_vote_alltalk_enable<br />
|-<br />
| Alltalk Off || #L4D_vote_alltalk_change || #L4D_vote_alltalk_disable<br />
|}<br />
*Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish"<br />
<br />
===VoteRegistered===<br />
{{qnotice|Only sent to player who voted}}<br /><br />
{{begin-hl2msg|VoteRegistered|string}}<br />
{{hl2msg|byte|vote|0 for No, 1 for Yes}}<br />
{{end-hl2msg}}<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|string|details|Vote success translation string}}<br />
{{hl2msg|string|param1|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
===VoteFail===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFail|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{end-hl2msg}}<br />
<br />
== Events ==<br />
{{qnotice|team is -1 when sent to all players)}}<br><br />
<br />
=== vote_changed ===<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|yesVotes|}}<br />
{{hl2msg|byte|noVotes|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
<br />
#define L4D2_TEAM_ALL -1<br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
int g_iPlayersCount;<br />
bool VoteInProgress;<br />
bool CanPlayerVote[MAXPLAYERS + 1];<br />
<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("sm_testvote", cmdTestVote);<br />
RegConsoleCmd("sm_vote", cmdVote);<br />
}<br />
<br />
public Action cmdTestVote(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteByte(0);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteString("Server");<br />
EndMessage();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
g_iPlayersCount = 0;<br />
VoteInProgress = true;<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = true;<br />
g_iPlayersCount ++;<br />
}<br />
}<br />
<br />
UpdateVotes();<br />
CreateTimer(10.0, timerVoteCheck, TIMER_FLAG_NO_MAPCHANGE);<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
public Action timerVoteCheck(Handle timer)<br />
{<br />
if (VoteInProgress)<br />
{<br />
VoteInProgress = false;<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
<br />
public void UpdateVotes()<br />
{<br />
Event event = CreateEvent("vote_changed");<br />
event.SetInt("yesVotes", g_iYesVotes);<br />
event.SetInt("noVotes", g_iNoVotes);<br />
event.SetInt("potentialVotes", g_iPlayersCount);<br />
event.Fire();<br />
<br />
if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress)<br />
{<br />
PrintToServer("voting complete!");<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = false;<br />
}<br />
}<br />
<br />
VoteInProgress = false;<br />
<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
public Action cmdVote(int client, int args)<br />
{<br />
if (VoteInProgress && CanPlayerVote[client])<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, sizeof arg);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
if (strcmp(arg, "Yes", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "No", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
}<br />
<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
</sourcepawn><br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[TF2 Voting]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Left_4_Voting_2&diff=11356Left 4 Voting 22022-09-16T14:25:07Z<p>Joinedsenses: /* Example voting plugin */ Formatting and remove IsValidClient</p>
<hr />
<div>Left 4 Dead 2 has a VGUI voting system controller by a bunch of Events and UserMessages. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument.<br />
# The VoteStart User Message is sent.<br />
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers.<br />
# When the vote is complete, the server sends either a VotePass or VoteFail UserMessage.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_activeIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int|m_votesYes|Current Yes votes}}<br />
{{hl2msg|int|m_votesNo|Current No votes}}<br />
{{hl2msg|int|m_potentialVotes|Number of players eligible to vote}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== Vote ===<br />
<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|Vote|string}}<br />
{{hl2msg|string|option|"Yes" or "No"}}<br />
{{end-hl2msg}}<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since Valve represents no team as -1, no team is instead represented as its two's complement, 255.}}<br><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players in the vote group, corresponding with teams. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|byte|initiator|Client index (NOT USERID) of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|issue|Vote issue translation string}}<br />
{{hl2msg|string|param1|Vote issue text}}<br />
{{hl2msg|string|initiatorName|Name of person who started the vote}}<br />
{{end-hl2msg}}<br />
====Values for "issue" and "param1" in standard votes====<br />
{| class="wikitable"<br />
|-<br />
! Vote type !! issue !! param1<br />
|-<br />
| Kick || #L4D_vote_kick_player || Nickname of the person to be kicked without "#"<br />
|-<br />
| ReturnToLobby || #L4D_vote_return_to_lobby || <br />
|-<br />
| ChangeCampaign || #L4D_vote_mission_change || #L4D360UI_CampaignName_C5<br />
|-<br />
| RestartChapter || #L4D_vote_passed_versus_level_restart ||<br />
|-<br />
| ChangeDifficulty (easy) || #L4D_vote_change_difficulty || #L4D_DifficultyEasy<br />
|-<br />
| ChangeDifficulty (hard) || #L4D_vote_change_difficulty || #L4D_DifficultyHard<br />
|-<br />
| Alltalk On || #L4D_vote_alltalk_change || #L4D_vote_alltalk_enable<br />
|-<br />
| Alltalk Off || #L4D_vote_alltalk_change || #L4D_vote_alltalk_disable<br />
|}<br />
*Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish"<br />
<br />
===VoteRegistered===<br />
{{qnotice|Only sent to player who voted}}<br /><br />
{{begin-hl2msg|VoteRegistered|string}}<br />
{{hl2msg|byte|vote|0 for No, 1 for Yes}}<br />
{{end-hl2msg}}<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|string|details|Vote success translation string}}<br />
{{hl2msg|string|param1|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
===VoteFail===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFail|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{end-hl2msg}}<br />
<br />
== Events ==<br />
{{qnotice|team is -1 when sent to all players)}}<br><br />
<br />
=== vote_changed ===<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|yesVotes|}}<br />
{{hl2msg|byte|noVotes|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
<br />
#define L4D2_TEAM_ALL -1<br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
int g_iPlayersCount;<br />
bool VoteInProgress;<br />
bool CanPlayerVote[MAXPLAYERS + 1];<br />
<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("sm_testvote", cmdTestVote);<br />
RegConsoleCmd("sm_vote", cmdVote);<br />
}<br />
<br />
public Action cmdTestVote(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteByte(0);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteString("Server");<br />
EndMessage();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
g_iPlayersCount = 0;<br />
VoteInProgress = true;<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = true;<br />
g_iPlayersCount ++;<br />
}<br />
}<br />
<br />
UpdateVotes();<br />
CreateTimer(10.0, timerVoteCheck, TIMER_FLAG_NO_MAPCHANGE);<br />
<br />
return Plugin_Handled;<br />
}<br />
public Action timerVoteCheck(Handle timer)<br />
{<br />
if (VoteInProgress)<br />
{<br />
VoteInProgress = false;<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
<br />
public void UpdateVotes()<br />
{<br />
Event event = CreateEvent("vote_changed");<br />
event.SetInt("yesVotes", g_iYesVotes);<br />
event.SetInt("noVotes", g_iNoVotes);<br />
event.SetInt("potentialVotes", g_iPlayersCount);<br />
event.Fire();<br />
<br />
if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress)<br />
{<br />
PrintToServer("voting complete!");<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (IsClientInGame(i) && !IsFakeClient(i))<br />
{<br />
CanPlayerVote[i] = false;<br />
}<br />
}<br />
<br />
VoteInProgress = false;<br />
<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
public Action cmdVote(int client, int args)<br />
{<br />
if (VoteInProgress && CanPlayerVote[client])<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, sizeof arg);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
if (strcmp(arg, "Yes", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "No", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
}<br />
<br />
UpdateVotes();<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
</sourcepawn><br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[TF2 Voting]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Admin_Menu_(SourceMod_Scripting)&diff=11355Admin Menu (SourceMod Scripting)2022-09-16T14:17:08Z<p>Joinedsenses: Minor formatting of params</p>
<hr />
<div>The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a <tt>TopMenu</tt>, from the TopMenus extension, and is provided by <tt>adminmenu.sp</tt>. <br />
<br />
The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.<br />
<br />
=Menu Organization=<br />
The menu is organized into ''categories'' and ''items''. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:<br />
*There is no nesting. Categories cannot have sub-categories.<br />
*Only categories can exist at the top level (i.e. items must have a parent category).<br />
<br />
All entries on the menu, be they categories or items, are called ''top menu objects'', and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.<br />
<br />
Unique names are used to identify items for sorting. For more information, see [[Admin Menu Configuration (SourceMod)|Admin Menu Configuration]].<br />
<br />
<br />
=Interfacing to the Admin Menu=<br />
The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:<br />
*The admin menu is unloaded while you are interfaced to it.<br />
*The admin menu is loaded while your plugin is already loaded.<br />
*The admin menu is loaded before your plugin is loaded.<br />
<br />
You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.<br />
<br />
Example code is below:<br />
<sourcepawn><br />
#include <sourcemod><br />
<br />
/* Make the admin menu plugin optional */<br />
#undef REQUIRE_PLUGIN<br />
#include <adminmenu><br />
<br />
/* Keep track of the top menu */<br />
TopMenu hAdminMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
/* See if the menu plugin is already ready */<br />
TopMenu topmenu;<br />
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))<br />
{<br />
/* If so, manually fire the callback */<br />
OnAdminMenuReady(topmenu);<br />
}<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "adminmenu", false))<br />
{<br />
hAdminMenu = null;<br />
}<br />
}<br />
<br />
public void OnAdminMenuReady(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Try to add the category first, if we want to add one.<br />
Leave this out, if you don't add a new category. */<br />
if (obj_dmcommands == INVALID_TOPMENUOBJECT)<br />
{<br />
OnAdminMenuCreated(topmenu);<br />
}<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu)<br />
{<br />
return;<br />
}<br />
<br />
hAdminMenu = topmenu;<br />
<br />
/* :TODO: Add everything to the menu! */<br />
}</sourcepawn><br />
<br />
=Top Menu Objects=<br />
Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:<br />
*<tt>ID</tt> (OUTPUT): The ID of the object in memory.<br />
*<tt>Name</tt> (INPUT): The unique name of the object.<br />
*<tt>Type</tt> (INPUT): Either a category or an item.<br />
*<tt>Handler</tt> (INPUT): The callback function.<br />
*<tt>Parent</tt> (INPUT): Empty for categories, or a category object ID for an item.<br />
*<tt>Override</tt> (INPUT): The name of the override associated with the command. For more information, see [[Overriding Command Access (SourceMod)|Overriding Command Access]]. Overrides don't need to be a command name.<br />
*<tt>Flags</tt> (INPUT): Default admin flags that should be associated with the object if the override is not set.<br />
<br />
The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.<br />
<br />
<br />
=Adding Categories=<br />
Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:<br />
*<tt>TopMenuAction_DisplayOption</tt> - The category is being displayed.<br />
*<tt>TopMenuAction_DisplayTitle</tt> - The category is being displayed as a title.<br />
<br />
For example, let's create a category called "CS:S DM Commands".<br />
<br />
<sourcepawn><br />
TopMenuObject obj_dmcommands;<br />
<br />
public void OnAdminMenuCreated(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)<br />
{<br />
return;<br />
}<br />
<br />
obj_dmcommands = AddToTopMenu(<br />
topmenu,<br />
"CS:S DM Commands",<br />
TopMenuObject_Category,<br />
CategoryHandler,<br />
INVALID_TOPMENUOBJECT<br />
);<br />
}<br />
<br />
public void CategoryHandler(<br />
TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayTitle:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands:");<br />
}<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands");<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one category. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Adding Items=<br />
Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.<br />
<br />
<sourcepawn><br />
void AttachAdminMenu()<br />
{<br />
/* If the category is third party, it will have its own unique name. */<br />
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);<br />
<br />
if (player_commands == INVALID_TOPMENUOBJECT)<br />
{<br />
/* Error! */<br />
return;<br />
}<br />
<br />
AddToTopMenu(<br />
hAdminMenu, <br />
"sm_poke",<br />
TopMenuObject_Item,<br />
AdminMenu_Poke,<br />
player_commands,<br />
"sm_poke",<br />
ADMFLAG_SLAY<br />
);<br />
}<br />
<br />
public void AdminMenu_Poke(<br />
TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "Poke");<br />
}<br />
case TopMenuAction_SelectOption:<br />
{<br />
/* Do something! client who selected item is in param */<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one item. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Returning to the Menu=<br />
Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via <tt>RedisplayAdminMenu</tt> in <tt>adminmenu.inc</tt>.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Admin_Menu_(SourceMod_Scripting)&diff=11354Admin Menu (SourceMod Scripting)2022-09-16T14:15:13Z<p>Joinedsenses: Minor formatting</p>
<hr />
<div>The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a <tt>TopMenu</tt>, from the TopMenus extension, and is provided by <tt>adminmenu.sp</tt>. <br />
<br />
The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.<br />
<br />
=Menu Organization=<br />
The menu is organized into ''categories'' and ''items''. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:<br />
*There is no nesting. Categories cannot have sub-categories.<br />
*Only categories can exist at the top level (i.e. items must have a parent category).<br />
<br />
All entries on the menu, be they categories or items, are called ''top menu objects'', and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.<br />
<br />
Unique names are used to identify items for sorting. For more information, see [[Admin Menu Configuration (SourceMod)|Admin Menu Configuration]].<br />
<br />
<br />
=Interfacing to the Admin Menu=<br />
The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:<br />
*The admin menu is unloaded while you are interfaced to it.<br />
*The admin menu is loaded while your plugin is already loaded.<br />
*The admin menu is loaded before your plugin is loaded.<br />
<br />
You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.<br />
<br />
Example code is below:<br />
<sourcepawn><br />
#include <sourcemod><br />
<br />
/* Make the admin menu plugin optional */<br />
#undef REQUIRE_PLUGIN<br />
#include <adminmenu><br />
<br />
/* Keep track of the top menu */<br />
TopMenu hAdminMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
/* See if the menu plugin is already ready */<br />
TopMenu topmenu;<br />
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))<br />
{<br />
/* If so, manually fire the callback */<br />
OnAdminMenuReady(topmenu);<br />
}<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "adminmenu", false))<br />
{<br />
hAdminMenu = null;<br />
}<br />
}<br />
<br />
public void OnAdminMenuReady(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Try to add the category first, if we want to add one.<br />
Leave this out, if you don't add a new category. */<br />
if (obj_dmcommands == INVALID_TOPMENUOBJECT)<br />
{<br />
OnAdminMenuCreated(topmenu);<br />
}<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu)<br />
{<br />
return;<br />
}<br />
<br />
hAdminMenu = topmenu;<br />
<br />
/* :TODO: Add everything to the menu! */<br />
}</sourcepawn><br />
<br />
=Top Menu Objects=<br />
Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:<br />
*<tt>ID</tt> (OUTPUT): The ID of the object in memory.<br />
*<tt>Name</tt> (INPUT): The unique name of the object.<br />
*<tt>Type</tt> (INPUT): Either a category or an item.<br />
*<tt>Handler</tt> (INPUT): The callback function.<br />
*<tt>Parent</tt> (INPUT): Empty for categories, or a category object ID for an item.<br />
*<tt>Override</tt> (INPUT): The name of the override associated with the command. For more information, see [[Overriding Command Access (SourceMod)|Overriding Command Access]]. Overrides don't need to be a command name.<br />
*<tt>Flags</tt> (INPUT): Default admin flags that should be associated with the object if the override is not set.<br />
<br />
The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.<br />
<br />
<br />
=Adding Categories=<br />
Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:<br />
*<tt>TopMenuAction_DisplayOption</tt> - The category is being displayed.<br />
*<tt>TopMenuAction_DisplayTitle</tt> - The category is being displayed as a title.<br />
<br />
For example, let's create a category called "CS:S DM Commands".<br />
<br />
<sourcepawn><br />
TopMenuObject obj_dmcommands;<br />
<br />
public void OnAdminMenuCreated(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)<br />
{<br />
return;<br />
}<br />
<br />
obj_dmcommands = AddToTopMenu(<br />
topmenu,<br />
"CS:S DM Commands",<br />
TopMenuObject_Category,<br />
CategoryHandler,<br />
INVALID_TOPMENUOBJECT<br />
);<br />
}<br />
<br />
public void CategoryHandler(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayTitle:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands:");<br />
}<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands");<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one category. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Adding Items=<br />
Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.<br />
<br />
<sourcepawn><br />
void AttachAdminMenu()<br />
{<br />
/* If the category is third party, it will have its own unique name. */<br />
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);<br />
<br />
if (player_commands == INVALID_TOPMENUOBJECT)<br />
{<br />
/* Error! */<br />
return;<br />
}<br />
<br />
AddToTopMenu(<br />
hAdminMenu, <br />
"sm_poke",<br />
TopMenuObject_Item,<br />
AdminMenu_Poke,<br />
player_commands,<br />
"sm_poke",<br />
ADMFLAG_SLAY<br />
);<br />
}<br />
<br />
public void AdminMenu_Poke(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "Poke");<br />
}<br />
case TopMenuAction_SelectOption:<br />
{<br />
/* Do something! client who selected item is in param */<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one item. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Returning to the Menu=<br />
Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via <tt>RedisplayAdminMenu</tt> in <tt>adminmenu.inc</tt>.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Admin_Menu_(SourceMod_Scripting)&diff=11353Admin Menu (SourceMod Scripting)2022-09-16T14:13:02Z<p>Joinedsenses: Change Format to strcopy. Change if/else if to switch</p>
<hr />
<div>The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a <tt>TopMenu</tt>, from the TopMenus extension, and is provided by <tt>adminmenu.sp</tt>. <br />
<br />
The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.<br />
<br />
=Menu Organization=<br />
The menu is organized into ''categories'' and ''items''. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:<br />
*There is no nesting. Categories cannot have sub-categories.<br />
*Only categories can exist at the top level (i.e. items must have a parent category).<br />
<br />
All entries on the menu, be they categories or items, are called ''top menu objects'', and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.<br />
<br />
Unique names are used to identify items for sorting. For more information, see [[Admin Menu Configuration (SourceMod)|Admin Menu Configuration]].<br />
<br />
<br />
=Interfacing to the Admin Menu=<br />
The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:<br />
*The admin menu is unloaded while you are interfaced to it.<br />
*The admin menu is loaded while your plugin is already loaded.<br />
*The admin menu is loaded before your plugin is loaded.<br />
<br />
You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.<br />
<br />
Example code is below:<br />
<sourcepawn><br />
#include <sourcemod><br />
<br />
/* Make the admin menu plugin optional */<br />
#undef REQUIRE_PLUGIN<br />
#include <adminmenu><br />
<br />
/* Keep track of the top menu */<br />
TopMenu hAdminMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
/* See if the menu plugin is already ready */<br />
TopMenu topmenu;<br />
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))<br />
{<br />
/* If so, manually fire the callback */<br />
OnAdminMenuReady(topmenu);<br />
}<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "adminmenu", false))<br />
{<br />
hAdminMenu = null;<br />
}<br />
}<br />
<br />
public void OnAdminMenuReady(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Try to add the category first, if we want to add one.<br />
Leave this out, if you don't add a new category. */<br />
if (obj_dmcommands == INVALID_TOPMENUOBJECT)<br />
{<br />
OnAdminMenuCreated(topmenu);<br />
}<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu)<br />
{<br />
return;<br />
}<br />
<br />
hAdminMenu = topmenu;<br />
<br />
/* :TODO: Add everything to the menu! */<br />
}</sourcepawn><br />
<br />
=Top Menu Objects=<br />
Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:<br />
*<tt>ID</tt> (OUTPUT): The ID of the object in memory.<br />
*<tt>Name</tt> (INPUT): The unique name of the object.<br />
*<tt>Type</tt> (INPUT): Either a category or an item.<br />
*<tt>Handler</tt> (INPUT): The callback function.<br />
*<tt>Parent</tt> (INPUT): Empty for categories, or a category object ID for an item.<br />
*<tt>Override</tt> (INPUT): The name of the override associated with the command. For more information, see [[Overriding Command Access (SourceMod)|Overriding Command Access]]. Overrides don't need to be a command name.<br />
*<tt>Flags</tt> (INPUT): Default admin flags that should be associated with the object if the override is not set.<br />
<br />
The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.<br />
<br />
<br />
=Adding Categories=<br />
Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:<br />
*<tt>TopMenuAction_DisplayOption</tt> - The category is being displayed.<br />
*<tt>TopMenuAction_DisplayTitle</tt> - The category is being displayed as a title.<br />
<br />
For example, let's create a category called "CS:S DM Commands".<br />
<br />
<sourcepawn><br />
TopMenuObject obj_dmcommands;<br />
<br />
public void OnAdminMenuCreated(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)<br />
{<br />
return;<br />
}<br />
<br />
obj_dmcommands = AddToTopMenu(topmenu,<br />
"CS:S DM Commands",<br />
TopMenuObject_Category,<br />
CategoryHandler,<br />
INVALID_TOPMENUOBJECT);<br />
}<br />
<br />
public void CategoryHandler(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayTitle:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands:");<br />
}<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "CS:S DM Commands");<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one category. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Adding Items=<br />
Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.<br />
<br />
<sourcepawn><br />
void AttachAdminMenu()<br />
{<br />
/* If the category is third party, it will have its own unique name. */<br />
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);<br />
<br />
if (player_commands == INVALID_TOPMENUOBJECT)<br />
{<br />
/* Error! */<br />
return;<br />
}<br />
<br />
AddToTopMenu(hAdminMenu, <br />
"sm_poke",<br />
TopMenuObject_Item,<br />
AdminMenu_Poke,<br />
player_commands,<br />
"sm_poke",<br />
ADMFLAG_SLAY);<br />
}<br />
<br />
public void AdminMenu_Poke(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
switch (action)<br />
{<br />
case TopMenuAction_DisplayOption:<br />
{<br />
strcopy(buffer, maxlength, "Poke");<br />
}<br />
case TopMenuAction_SelectOption:<br />
{<br />
/* Do something! client who selected item is in param */<br />
}<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one item. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Returning to the Menu=<br />
Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via <tt>RedisplayAdminMenu</tt> in <tt>adminmenu.inc</tt>.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11313SourcePawn Transitional Syntax2022-05-26T21:20:39Z<p>Joinedsenses: Add quantifier to methodmap property decl/* Grammar */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any[] list, int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public native get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body newline<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body newline<br />
| "property" type-expr symbol "{" property-decl* "}" newline<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body newline<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body newline<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11311SourcePawn Transitional Syntax2022-05-24T16:54:44Z<p>Joinedsenses: Update methodmap grammar, replacing term with newlines /* Grammar */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any[] list, int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public native get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body newline<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body newline<br />
| "property" type-expr symbol "{" property-decl "}" newline<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body newline<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body newline<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11310SourcePawn Transitional Syntax2022-05-24T16:11:24Z<p>Joinedsenses: Fix property assignment by making get() native /* Inline Methods */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any[] list, int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public native get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11309SourcePawn Transitional Syntax2022-05-24T16:10:28Z<p>Joinedsenses: Fix bracket position /* Inline Methods */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any[] list, int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11308SourcePawn Transitional Syntax2022-05-24T13:59:51Z<p>Joinedsenses: Update param type /* Constructors and Destructors */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any list[], int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11307SourcePawn Transitional Syntax2022-05-24T13:59:21Z<p>Joinedsenses: Update syntax /* Inline Methods */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, any value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(any value);<br />
<br />
public void PushCells(any list[], int count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(int value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11306SourcePawn Transitional Syntax2022-05-24T13:57:37Z<p>Joinedsenses: Update parameter syntax /* Constructors and Destructors */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(int blocksize = 1);<br />
public native void PushCell(int value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11305SourcePawn Transitional Syntax2022-05-24T13:56:27Z<p>Joinedsenses: Update methodmap declarations with 'native' /* Constructors and Destructors */</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native AdtArray(blocksize = 1);<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11304SourcePawn Transitional Syntax2022-05-24T03:15:26Z<p>Joinedsenses: Fix methodmap grammar - Add quotes to body curly braces</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public AdtArray(blocksize = 1);<br />
public void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? "{" methodmap-item* "}" term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol "{" property-decl "}" term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11303SourcePawn Transitional Syntax2022-05-24T03:11:02Z<p>Joinedsenses: Fix/Update methodmap grammar</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public AdtArray(blocksize = 1);<br />
public void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol methodmap-inheritance? { methodmap-item* } term<br />
methodmap-inheritance ::= "<" symbol<br />
methodmap-item ::=<br />
visibility "native" "~"? symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "~"? symbol "(" method-args* ")" func-body term<br />
| visibility "static"? "native" type-expr symbol "(" method-args* ")" ("=" symbol)? term<br />
| visibility "static"? type-expr symbol "(" method-args* ")" func-body term<br />
| "property" type-expr symbol { property-decl } term<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" "get" "(" ")" ("=" symbol)? term<br />
| "get" "(" ")" func-body term<br />
| "native" "set" "(" type-expr symbol ")" ("=" symbol)? term<br />
| "set" "(" type-expr symbol ")" func-body term<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11302SourcePawn Transitional Syntax2022-05-24T02:40:17Z<p>Joinedsenses: Update methodmap introduction code sample</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native Handle CloneHandle(Handle handle);<br />
native void CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public native Handle Clone() = CloneHandle;<br />
public native void Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public AdtArray(blocksize = 1);<br />
public void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol? { methodmap-item* } term<br />
methodmap-item ::=<br />
visibility "~"? symbol "(" ")" "=" symbol term<br />
| visibility "native" type-expr "~"? symbol methodmap-symbol "(" method-args ")" term<br />
| visibility type-expr symbol "(" method-args ")" func-body term<br />
| "property" type-expr symbol { property-decl } term<br />
property-func ::= "get" | "set"<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" property-func "(" ")" term<br />
| property-func "(" ")" func-body term<br />
| property-func "(" ")" "=" symbol<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Generic_Source_Events&diff=11289Generic Source Events2022-05-05T01:45:47Z<p>Joinedsenses: Update notes for a few events</p>
<hr />
<div>:''Refer back to [[Game Events (Source)]] for more events.''<br />
<br />
These '''should''' apply to all Source engine games. It is taken from the Orange Box resource\gameevents.res list.<br />
<br />
=== team_info ===<br />
{{qnotice|Info about team}}<br><br />
{{begin-hl2msg|team_info|string}}<br />
{{hl2msg|byte|teamid|unique team id}}<br />
{{hl2msg|string|teamname|team name eg "Team Blue"}}<br />
{{end-hl2msg}}<br />
<br />
=== team_score ===<br />
{{qnotice|Team score changed}}<br><br />
Note: This is not called in Counter-Strike: Source<br><br />
{{begin-hl2msg|team_info|string}}<br />
{{hl2msg|byte|teamid|team id}}<br />
{{hl2msg|short|score|total team score}}<br />
{{end-hl2msg}}<br />
<br />
=== teamplay_broadcast_audio ===<br />
{{qnotice|emits a sound to everyone on a team. (OB Only)}}<br><br />
{{begin-hl2msg|teamplay_broadcast_audio|string}}<br />
{{hl2msg|byte|team|unique team id}}<br />
{{hl2msg|string|sound|name of the sound to emit}}<br />
{{end-hl2msg}}<br />
<br />
=== player_team ===<br />
{{qnotice|A player changed their team}}<br><br />
{{begin-hl2msg|player_team|string}}<br />
{{hl2msg|short|userid|user ID on the server}}<br />
{{hl2msg|byte|team|team id}}<br />
{{hl2msg|byte|oldteam|old team id}}<br />
{{hl2msg|bool|disconnect|team change because player disconnects}}<br />
{{hl2msg|bool|autoteam|true if the player was auto assigned to the team (OB only)}}<br />
{{hl2msg|bool|silent|if true wont print the team join messages (OB only)}}<br />
{{hl2msg|string|name|player's name (OB only)}}<br />
{{end-hl2msg}}<br />
<br />
=== player_class ===<br />
{{qnotice|A player changed their class}}<br><br />
{{begin-hl2msg|player_class|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|string|class|new player class / model}}<br />
{{end-hl2msg}}<br />
<br />
=== player_death ===<br />
{{qnotice|A player has died}}<br><br />
{{begin-hl2msg|player_death|string}}<br />
{{hl2msg|short|userid|user ID who died}}<br />
{{hl2msg|short|attacker|user ID who killed}}<br />
{{end-hl2msg}}<br />
<br />
=== player_hurt ===<br />
{{qnotice|A player was hurt}}<br><br />
{{begin-hl2msg|player_hurt|string}}<br />
{{hl2msg|short|userid|player index who was hurt}}<br />
{{hl2msg|short|attacker|player index who attacked}}<br />
{{hl2msg|byte|health|remaining health points}}<br />
{{end-hl2msg}}<br />
<br />
=== player_chat ===<br />
{{qnotice|A public player chat}}<br><br />
{{begin-hl2msg|player_chat|string}}<br />
{{hl2msg|bool|teamonly|true if team only chat}}<br />
{{hl2msg|short|userid|chatting player}}<br />
{{hl2msg|string|text|chat text}}<br />
{{end-hl2msg}}<br />
<br />
=== player_score ===<br />
{{qnotice|Player's scores changed}}<br><br />
{{begin-hl2msg|player_score|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|short|kills|# of kills}}<br />
{{hl2msg|short|deaths|# of deaths}}<br />
{{hl2msg|short|score|total game score}}<br />
{{end-hl2msg}}<br />
<br />
=== player_spawn ===<br />
{{qnotice|Player spawned in game}}<br><br />
{{begin-hl2msg|player_spawn|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{end-hl2msg}}<br />
<br />
=== player_shoot ===<br />
{{qnotice|Player shot their weapon}}<br><br />
{{begin-hl2msg|player_shoot|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|byte|weapon|weapon ID}}<br />
{{hl2msg|byte|mode|weapon mode}}<br />
{{end-hl2msg}}<br />
<br />
=== player_use ===<br />
{{qnotice|When a player uses an option}}<br><br />
{{begin-hl2msg|player_use|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|short|entity|entity used by player}}<br />
{{end-hl2msg}}<br />
<br />
=== player_changename ===<br />
{{qnotice|Player changed name}}<br><br />
{{begin-hl2msg|player_changename|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|string|oldname|players old (current) name}}<br />
{{hl2msg|string|newname|players new name}}<br />
{{end-hl2msg}}<br />
<br />
=== player_hintmessage ===<br />
{{qnotice|??? (OB only)}}<br><br />
{{begin-hl2msg|player_hintmessage|string}}<br />
{{hl2msg|string|hintmessage|localizable string of a hint}}<br />
{{end-hl2msg}}<br />
<br />
=== base_player_teleported ===<br />
{{qnotice|??? (OB only)}}<br><br />
{{begin-hl2msg|base_player_teleported|string}}<br />
{{hl2msg|short|entindex|}}<br />
{{end-hl2msg}}<br />
<br />
=== game_init ===<br />
{{qnotice|Sent when a new game is started (OB only)}}<br><br />
<br />
=== game_newmap ===<br />
{{qnotice|Sent when new map is completely loaded}}<br><br />
{{begin-hl2msg|game_newmap|string}}<br />
{{hl2msg|string|mapname|map name}}<br />
{{end-hl2msg}}<br />
<br />
=== game_start ===<br />
{{qnotice|A new game starts}}<br><br />
{{begin-hl2msg|game_start|string}}<br />
{{hl2msg|long|roundslimit|max round}}<br />
{{hl2msg|long|timelimit|time limit}}<br />
{{hl2msg|long|fraglimit|frag limit}}<br />
{{hl2msg|string|objective|round objective}}<br />
{{end-hl2msg}}<br />
<br />
=== game_end ===<br />
{{qnotice|A game ended}}<br><br />
{{begin-hl2msg|game_end|string}}<br />
{{hl2msg|byte|winner|winner team/user id}}<br />
{{end-hl2msg}}<br />
<br />
=== round_start ===<br />
{{qnotice|The round started}}<br><br />
{{begin-hl2msg|round_start|string}}<br />
{{hl2msg|long|timelimit|round time limit in seconds}}<br />
{{hl2msg|long|fraglimit|frag limit}}<br />
{{hl2msg|string|objective|round objective}}<br />
{{end-hl2msg}}<br />
<br />
=== round_end ===<br />
{{qnotice|The round ended}}<br><br />
{{begin-hl2msg|round_end|string}}<br />
{{hl2msg|byte|winner|winner team/user id}}<br />
{{hl2msg|byte|reason|reason why the team won}}<br />
{{hl2msg|string|message|end round message}}<br />
{{end-hl2msg}}<br />
<br />
=== game_message ===<br />
{{qnotice|A message sent by game logic to everyone}}<br><br />
{{begin-hl2msg|game_message|string}}<br />
{{hl2msg|byte|target|0 console, 1 HUD}}<br />
{{hl2msg|string|text|the message text}}<br />
{{end-hl2msg}}<br />
<br />
=== break_breakable ===<br />
{{qnotice|A breakable (func_break) is broken.}}<br><br />
{{begin-hl2msg|break_breakable|string}}<br />
{{hl2msg|long|entindex|index of the entity}}<br />
{{hl2msg|short|userid|userid who broke the entity}}<br />
{{hl2msg|byte|material|BREAK_GLASS, BREAK_WOOD, etc}}<br />
{{end-hl2msg}}<br />
<br />
=== break_prop ===<br />
{{qnotice|A breakable prop is broken.}}<br><br />
{{begin-hl2msg|break_prop|string}}<br />
{{hl2msg|long|entindex|index of the entity}}<br />
{{hl2msg|short|userid|userid who broke the entity}}<br />
{{end-hl2msg}}<br />
<br />
=== entity_killed ===<br />
{{qnotice|An entity has been killed (OB only)}}<br><br />
{{begin-hl2msg|entity_killed|string}}<br />
{{hl2msg|long|entindex_killed|index of the killed entity}}<br />
{{hl2msg|long|entindex_attacker|index of the attacker}}<br />
{{hl2msg|long|entindex_inflictor|index of the inflictor (weapon id, etc)}}<br />
{{hl2msg|long|damagebits|the damagebits of the attack}}<br />
{{end-hl2msg}}<br />
<br />
=== bonus_updated ===<br />
{{qnotice|??? (OB only)}}<br><br />
{{begin-hl2msg|bonus_updated|string}}<br />
{{hl2msg|short|numadvanced|}}<br />
{{hl2msg|short|numbronze|}}<br />
{{hl2msg|short|numsilver|}}<br />
{{hl2msg|short|numgold|}}<br />
{{end-hl2msg}}<br />
<br />
=== achievement_event ===<br />
{{qnotice|A player has received an achievement (OB only)}}<br><br />
{{begin-hl2msg|achievement_earned|string}}<br />
{{hl2msg|string|achievement_name|non-localized name of achievement}}<br />
{{hl2msg|short|cur_val|# of steps toward achievement}}<br />
{{hl2msg|short|max_val|total # of steps in achievement}}<br />
{{end-hl2msg}}<br />
<br />
=== achievement_increment ===<br />
{{qnotice|Sent whenever an achievement that's tracked on the HUD increases (OB only)}}<br><br />
{{begin-hl2msg|achievement_increment|string}}<br />
{{hl2msg|long|achievement_id|ID of achievement that went up}}<br />
{{hl2msg|short|cur_val|# of steps toward achievement}}<br />
{{hl2msg|short|max_val|total # of steps in achievement}}<br />
{{end-hl2msg}}<br />
<br />
=== physgun_pickup ===<br />
{{qnotice|Player picked up a physgun (OB only)}}<br><br />
{{begin-hl2msg|physgun_pickup|string}}<br />
{{hl2msg|long|entindex|entity picked up}}<br />
{{end-hl2msg}}<br />
<br />
=== flare_ignite_npc ===<br />
{{qnotice|A flare has ignited a NPC (OB only)}}<br><br />
{{begin-hl2msg|flare_ignite_npc|string}}<br />
{{hl2msg|long|entindex|entity ignited}}<br />
{{end-hl2msg}}<br />
<br />
=== helicopter_grenade_punt_miss ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== user_data_downloaded ===<br />
{{qnotice|Fired when achievements/stats are downloaded from Steam or XBOX Live (OB only)}}<br><br />
<br />
=== ragdoll_dissolved ===<br />
{{qnotice|A ragdoll has dissolved (OB only)}}<br><br />
{{begin-hl2msg|ragdoll_dissolved|string}}<br />
{{hl2msg|long|entindex|index of the dissolved entity}}<br />
{{end-hl2msg}}<br />
<br />
=== hltv_changed_mode ===<br />
{{qnotice|??? (OB only)}}<br><br />
{{begin-hl2msg|hltv_changed_mode|string}}<br />
{{hl2msg|short|oldmode|}}<br />
{{hl2msg|short|newmode|}}<br />
{{hl2msg|short|obs_target|}}<br />
{{end-hl2msg}}<br />
<br />
=== hltv_changed_target ===<br />
{{qnotice|??? (OB only)}}<br><br />
{{begin-hl2msg|hltv_changed_target|string}}<br />
{{hl2msg|short|mode|}}<br />
{{hl2msg|short|old_target|}}<br />
{{hl2msg|short|obs_target|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_ended ===<br />
{{qnotice|Not currently implemented. See VotePass and VoteFailed UserMessages instead. (OB only)}}<br><br />
<br />
=== vote_started ===<br />
{{qnotice|Currently unused. See the VoteStart UserMessage instead. (OB only)}}<br><br />
{{begin-hl2msg|vote_started|string}}<br />
{{hl2msg|string|issue|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|initiator|entity id of the player who initiated the vote}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_changed ===<br />
{{qnotice|Currently unused. See vote_manager entity netprops instead.(OB only)}}<br><br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|vote_option1|}}<br />
{{hl2msg|byte|vote_option2|}}<br />
{{hl2msg|byte|vote_option3|}}<br />
{{hl2msg|byte|vote_option4|}}<br />
{{hl2msg|byte|vote_option5|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_passed ===<br />
{{qnotice|Currently unused. See the VotePass UserMessage instead. (OB only)}}<br><br />
{{begin-hl2msg|vote_passed|string}}<br />
{{hl2msg|string|details|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_failed ===<br />
{{qnotice|Currently unused. See the VoteFailed UserMessage instead. (OB only)}}<br><br />
{{begin-hl2msg|vote_failed|string}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
<br />
=== vote_cast ===<br />
{{qnotice|Sent to all players when a player chooses a vote option (or more specifically, the server receives a vote command) (OB only)}}<br><br />
{{begin-hl2msg|vote_cast|string}}<br />
{{hl2msg|byte|vote_option|which option the player voted on}}<br />
{{hl2msg|short|team|}}<br />
{{hl2msg|long|entityid|entity id of the voter}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_options ===<br />
{{qnotice|Sent to players before VoteStart UserMessage to populate choices for a multiple choice vote (OB only)}}<br><br />
{{begin-hl2msg|vote_options|string}}<br />
{{hl2msg|byte|count|Number of options - up to MAX_VOTE_OPTIONS [ed: 5 for OB]}}<br />
{{hl2msg|string|option1|}}<br />
{{hl2msg|string|option2|}}<br />
{{hl2msg|string|option3|}}<br />
{{hl2msg|string|option4|}}<br />
{{hl2msg|string|option5|}}<br />
{{end-hl2msg}}<br />
<br />
=== replay_saved ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== entered_performance_mode ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== browse_replays ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== replay_youtube_stats ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
{{begin-hl2msg|replay_youtube_stats|string}}<br />
{{hl2msg|long|views|}}<br />
{{hl2msg|long|likes|}}<br />
{{hl2msg|long|favorited|}}<br />
{{end-hl2msg}}<br />
<br />
=== inventory_updated ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== cart_updated ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== store_pricesheet_updated ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== gc_connected ===<br />
{{qnotice|??? (OB only)}}<br><br />
<br />
=== item_schema_initialized ===<br />
{{qnotice|??? (OB only)}}<br></div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Generic_Source_Server_Events&diff=11288Generic Source Server Events2022-05-05T01:40:35Z<p>Joinedsenses: /* player_info */</p>
<hr />
<div>:''Refer back to [[Game Events (Source)]] for more events.''<br />
<br />
These '''should''' apply to all Source Engine Servers<br />
=== server_spawn ===<br />
{{qnotice|Send once a server starts (spawns)}}<br><br />
{{begin-hl2msg|server_spawn|string}}<br />
{{hl2msg|string|hostname|public host name}}<br />
{{hl2msg|string|address|hostame, IP or DNS name}}<br />
{{hl2msg|string|ip|}}<br />
{{hl2msg|string|port|server port}}<br />
{{hl2msg|string|game|game dir}}<br />
{{hl2msg|string|mapname|map name}}<br />
{{hl2msg|long|maxplayers|max players}}<br />
{{hl2msg|string|os|WIN32, LINUX}}<br />
{{hl2msg|bool|dedicated|true if dedicated server}}<br />
{{hl2msg|bool|official|true if official valve dedicated server}}<br />
{{hl2msg|bool|password|true if password protected}}<br />
{{end-hl2msg}}<br />
<br />
=== server_shutdown ===<br />
{{qnotice|Server shut down}}<br />
{{begin-hl2msg|server_shutdown|string}}<br />
{{hl2msg|string|reason|reason why server was shut down}}<br />
{{end-hl2msg}}<br />
<br />
=== server_cvar ===<br />
{{qnotice|A server console var has changed}}<br />
{{begin-hl2msg|server_cvar|string}}<br />
{{hl2msg|string|cvarname|cvar name, eg "mp_roundtime"}}<br />
{{hl2msg|string|cvarvalue|new cvar value}}<br />
{{end-hl2msg}}<br />
<br />
=== server_message ===<br />
{{qnotice|A generic server message}}<br />
{{begin-hl2msg|server_message|string}}<br />
{{hl2msg|string|text|the message text}}<br />
{{end-hl2msg}}<br />
<br />
=== server_addban ===<br />
{{qnotice|When the server has a ban added}}<br />
{{begin-hl2msg|server_addban|string}}<br />
{{hl2msg|string|name|player name}}<br />
{{hl2msg|string|userid|user ID on server}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|string|ip|IP address}}<br />
{{hl2msg|string|duration|length of the ban}}<br />
{{hl2msg|string|by|banned by...}}<br />
{{hl2msg|bool|kicked|whether the player was also kicked}}<br />
{{end-hl2msg}}<br />
<br />
=== server_removeban ===<br />
{{qnotice|When the server has a ban removed}}<br />
{{begin-hl2msg|server_removeban|string}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|string|ip|IP address}}<br />
{{hl2msg|string|by|removed by...}}<br />
{{end-hl2msg}}<br />
<br />
=== player_connect ===<br />
{{qnotice|A new client connected}}<br />
{{begin-hl2msg|player_connect|string}}<br />
{{hl2msg|string|name|player name}}<br />
{{hl2msg|byte|index|player slot (entity index-1)}}<br />
{{hl2msg|short|userid|user ID on server (unique on server)}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|string|address|ip:port}}<br />
{{hl2msg|short|bot|is a bot}}<br />
{{end-hl2msg}}<br />
<br />
=== player_connect_client ===<br />
{{qnotice|A new client connected, only present in OB}}<br />
{{begin-hl2msg|player_connect_client|string}}<br />
{{hl2msg|string|name|player name}}<br />
{{hl2msg|byte|index|player slot (entity index-1)}}<br />
{{hl2msg|short|userid|user ID on server (unique on server)}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|short|bot|is a bot}}<br />
{{end-hl2msg}}<br />
<br />
=== player_info ===<br />
{{qnotice|A player changed their name}}<br />
{{begin-hl2msg|player_info|string}}<br />
{{hl2msg|string|name|player name}}<br />
{{hl2msg|byte|index|player slot (entity index-1)}}<br />
{{hl2msg|short|userid|user ID on server (unique on server)}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|bool|bot|true if player is a AI bot}}<br />
{{end-hl2msg}}<br />
<br />
=== player_disconnect ===<br />
{{qnotice|A client was disconnected}}<br />
{{begin-hl2msg|player_disconnect|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|string|reason|reason the player left the server}}<br />
{{hl2msg|string|name|player name}}<br />
{{hl2msg|string|networkid|player network (i.e steam) id}}<br />
{{hl2msg|short|bot|is a bot}}<br />
{{end-hl2msg}}<br />
<br />
=== player_activate ===<br />
{{qnotice|A client has entered the game (connected and loaded)}}<br />
{{begin-hl2msg|player_activate|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{end-hl2msg}}<br />
<br />
=== player_say ===<br />
{{qnotice|When a client sends a message in chat}}<br />
{{begin-hl2msg|player_say|string}}<br />
{{hl2msg|short|userid|user ID on server}}<br />
{{hl2msg|string|text|the say text}}<br />
{{end-hl2msg}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=11274SourcePawn Transitional Syntax2022-03-11T19:39:36Z<p>Joinedsenses: /* Inheritance */ Fix syntax for PushCell()</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native CloneHandle(Handle handle);<br />
native CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public Clone() = CloneHandle;<br />
public Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public native void PushCell(any value) = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public AdtArray(blocksize = 1);<br />
public void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
[https://forums.alliedmods.net/showpost.php?p=2332183&postcount=1 Support for defining destructors on methodmaps was removed in SourcePawn 1.8.] Handle types can implement destructors as part of native code in SourceMod's core or as an extension.<br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol? { methodmap-item* } term<br />
methodmap-item ::=<br />
visibility "~"? symbol "(" ")" "=" symbol term<br />
| visibility "native" type-expr "~"? symbol methodmap-symbol "(" method-args ")" term<br />
| visibility type-expr symbol "(" method-args ")" func-body term<br />
| "property" type-expr symbol { property-decl } term<br />
property-func ::= "get" | "set"<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" property-func "(" ")" term<br />
| property-func "(" ")" func-body term<br />
| property-func "(" ")" "=" symbol<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=List_of_custom_SourceMod_includes&diff=11273List of custom SourceMod includes2022-03-08T23:47:58Z<p>Joinedsenses: Add section linking to maintained includes repo "SourceMod-IncludeLibrary"</p>
<hr />
<div><!--<br />
To do:<br />
â–ª Add linkage to each specific raw .inc file;<br />
â–ª Best way to do this?<br />
â–ª Are we allowed to upload .inc files to the wiki?<br />
â–ª Would there be any objections to it?<br />
â–ª Find and add any other custom includes;<br />
â–ª 404UNF: Currently scanning through all of Snippets & Tutorials<br />
--><br />
<br />
The following is a list of custom includes created by various users on AlliedModders. Some are standalone includes designed to be used with any project, others are includes that come with a plugin or extension but can also be used in other projects.<br />
== Maintained Includes ==<br />
https://github.com/JoinedSenses/SourceMod-IncludeLibrary<br />
<br />
== Standalone Includes ==<br />
{| style="width: 80em; text-align: center;" class="wikitable"<br />
<br />
|-<br />
! scope="col" style="width: auto;" | Name<br />
! scope="col" style="width: auto;" | Author<br />
! scope="col" style="width: auto;" | Filename<br />
! scope="col" style="width: 30em;" | Notes<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=232476 Advanced MOTDPanel]<br />
| [https://forums.alliedmods.net/member.php?u=150845 Dr. McKay]<br />
| <code>advanced_motd.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=233282 Append New ConVar]<br />
| [https://forums.alliedmods.net/member.php?u=210752 KissLick]<br />
| <code>convar_append.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=204254 AutoExecConfig]<br />
| [https://forums.alliedmods.net/member.php?u=157964 Impact123]<br />
| <code>autoexecconfig.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=299839 CBaseAnimatingOverlay]<br />
| [https://forums.alliedmods.net/member.php?u=181730 Pelipoika]<br />
| <code>cbaseanimatingoverlay.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=307157 Client Methodmaps]<br />
| [https://forums.alliedmods.net/member.php?u=232360 ThatKidWhoGames]<br />
| <code>clients_methodmap.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=306620 Client Preferences Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=224722 xXDeathreusXx]<br />
| <code>clientprefs_stocks.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=96831 Colors]<br />
| [https://forums.alliedmods.net/member.php?u=17252 exvel]<br />
| <code>colors.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=302788 Color Manipulation]<br />
| [https://forums.alliedmods.net/member.php?u=278689 hmmmmm]<br />
| <code>colourmanip.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=267743 ColorVariables]<br />
| [https://forums.alliedmods.net/member.php?u=210752 KissLick]<br />
| <code>colorvariables.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=279187 CS:GO Items]<br />
| [https://forums.alliedmods.net/member.php?u=237260 xCoderx]<br />
| <code>csgoitems.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=285744 Data String Parameter]<br />
| [https://forums.alliedmods.net/member.php?u=59694 Drixevel]<br />
| <code>data_string_parameter.inc</code><br />
| Allows you to pass a string through a data parameter.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=264897 DString - Dynamic Strings]<br />
| [https://forums.alliedmods.net/member.php?u=102471 Eun]<br />
| <code>DString.inc</code><br />
| Allows you to use strings with dynamic lengths.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=237045 EmitSoundAny]<br />
| [https://forums.alliedmods.net/member.php?u=38996 Powerlord]<br />
| <code>emitsoundany.inc</code><br />
| '''Outdated'''; CSGO no longer requires special sound handling for mp3s<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=303716 L4D/L4D2 Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=59694 Drixevel]<br />
| <code>l4d.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=100084 LogHelper]<br />
| [https://forums.alliedmods.net/member.php?u=37514 psychonic]<br />
| <code>loghelper.inc</code><br />
| Contains stocks for many HL Standard log line formats, and also gets around the current limitations of Sourcemod's <code>%L</code> format operator and <code>FormatUserLogText()</code> function (not including team name on log line).<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=265325 Menu Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=210752 KissLick]<br />
| <code>menu_stocks.inc</code><br />
| Allows you to pass a value (cell, float or string) to menu callback.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=306954 Menu Targeting]<br />
| [https://forums.alliedmods.net/member.php?u=278689 hmmmmm]<br />
| <code>menu_targeting.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=185016 MoreColors]<br />
| [https://forums.alliedmods.net/member.php?u=150845 Dr. McKay]<br />
| <code>morecolors.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=247770 MultiColors]<br />
| [https://forums.alliedmods.net/member.php?u=178115 Bara]<br />
| <code>multicolors.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=304459 Nested StringMaps]<br />
| [https://forums.alliedmods.net/member.php?u=226515 Kinsi]<br />
| <code>NestedStringMap.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=298248 Overlays]<br />
| [https://forums.alliedmods.net/member.php?u=259929 shanapu]<br />
| <code>overlays.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=307742 ParseRange]<br />
| [https://forums.alliedmods.net/member.php?u=180597 ddhoward]<br />
| <code>parseRange.inc</code><br />
| Takes a string indicating a range of numbers or multiple ranges of numbers, and returns an ArrayList containing individual values.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=314096 Print Valve Translations]<br />
| [https://forums.alliedmods.net/member.php?u=180597 Powerlord]<br />
| <code>printvalvetranslation.inc</code><br />
| Wrapper around the TextMsg usermessage to print game translations to chat, hintbox, center text, or client console<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=172593 SHA-1 Hashing Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=41418 Peace-Maker]<br />
| <code>sha1.inc</code><br />
| Provides 2 stock functions to calculate the SHA-1 hash for a given string or file.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=206496 Smart Download Manager]<br />
| [https://forums.alliedmods.net/member.php?u=79786 Zephyrus]<br />
| <code>smartdm.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=306471 SM-JSON]<br />
| [https://forums.alliedmods.net/member.php?u=270503 clug]<br />
| <code>json.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=148387 SMLib]<br />
| [https://forums.alliedmods.net/member.php?u=27799 Berni]<br />
| <code>smlib.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=304040 SourceMod Miscellaneous Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=59694 Drixevel]<br />
| <code>sourcemod-misc.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=283913 Table Buffer for Console]<br />
| [https://forums.alliedmods.net/member.php?u=190571 ofir753]<br />
| <code>consoletable.inc</code><br />
| Allows you to format an oriented table for console output.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=155911 TF2 Alternative HUD Text]<br />
| [https://forums.alliedmods.net/member.php?u=51338 GNCMatt]<br />
| <code>tf2_hud.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=300350 Unix Time for SourceMod]<br />
| [https://forums.alliedmods.net/member.php?u=185471 milutinke]<br />
| <code>unixtime_sourcemod.inc</code> <br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=300303 Variable Arguments for Functions]<br />
| [https://forums.alliedmods.net/member.php?u=253813 Kailo]<br />
| <code>valist.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=307906 Vector Helpers]<br />
| [https://forums.alliedmods.net/member.php?u=224722 xXDeathreusXx]<br />
| <code>vector_helpers.inc</code><br />
| Extends functionality of SourceMod vectors so you don't have to iterate through array blocks every time you want to do a simple operation on a vector.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=302597 WebFix]<br />
| [https://forums.alliedmods.net/member.php?u=261613 Byte]<br />
| <code>webfix.inc</code><br />
| '''Outdated'''; Use [https://forums.alliedmods.net/showthread.php?t=302530 VGUI URL Cache Buster] instead.<br />
<br />
|}<br />
<br />
== Plugins/Extensions ==<br />
{| style="width: 80em; text-align: center;" class="wikitable"<br />
<br />
|-<br />
! scope="col" style="width: auto;" | Name<br />
! scope="col" style="width: auto;" | Author<br />
! scope="col" style="width: auto;" | Filename<br />
! scope="col" style="width: 30em;" | Notes<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=300927 ASteamBot]<br />
| [https://forums.alliedmods.net/member.php?u=198439 Arkarr]<br />
| <code>asteambot.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=294511 Bank]<br />
| [https://forums.alliedmods.net/member.php?u=198439 Arkarr]<br />
| <code>bank.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=193067 Be the Robot]<br />
| [https://forums.alliedmods.net/member.php?u=152150 MasterOfTheXP]<br />
| <code>betherobot.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=197815 Collision Hooks]<br />
| [https://forums.alliedmods.net/member.php?u=49537 VoiDeD]<br />
| <code>collisionhooks.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=180114 DHooks]<br />
| [https://forums.alliedmods.net/member.php?u=26021 Dr!fter]<br />
| <code>dhooks.inc</code><br />
| Allows plugins to create dynamic hooks which normally would need an extension<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=292663 Discord/Slack API]<br />
| [https://forums.alliedmods.net/member.php?u=74431 zipcore]<br />
| <code>discord.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=270519 Dynamic Objects]<br />
| [https://forums.alliedmods.net/member.php?u=240520 Neuro Toxin]<br />
| <code>dynamic.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=311477 GeoIP2]<br />
| [https://forums.alliedmods.net/member.php?u=100698 Accelerator74]<br />
| <code>geoip.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=303671 HexTags Chat/Score Colors]<br />
| [https://forums.alliedmods.net/member.php?u=273262 Papero]<br />
| <code>hextags.inc</code><br />
|<br />
<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=270962 HTTP Server w/ RCON Multiplexer]<br />
| [https://forums.alliedmods.net/member.php?u=59029 asherkin]<br />
| <code>webcon.inc</code>, <code>conplex.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=114979 L4D2 Infected Spawn API]<br />
| [https://forums.alliedmods.net/member.php?u=67285 V10]<br />
| <code>l4d2_InfectedSpawnApi.inc</code><br />
| Custom infected boss spawning API.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=298024 REST in Pawn]<br />
| [https://forums.alliedmods.net/member.php?u=34668 DJ Tsunami]<br />
| <code>ripext.inc</code><br />
| High performance HTTP client for JSON REST APIs. Supports HTTP/2, HTTPS and gzip.<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=199794 Server Whitelist Advanced]<br />
| [https://forums.alliedmods.net/member.php?u=10216 RedSword]<br />
| <code>serverwhitelistadvanced.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=67640 Socket]<br />
| [https://forums.alliedmods.net/member.php?u=33075 sfPlayer]<br />
| <code>socket.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=61000 SourceBans]<br />
| [https://forums.alliedmods.net/member.php?u=26272 Olly]<br />
| <code>sourcebans.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=263735 SourceBans++]<br />
| [https://forums.alliedmods.net/member.php?u=246631 Sarabveer]<br />
| <code>sourcebanspp.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=129146 SourceIRC]<br />
| [https://forums.alliedmods.net/member.php?u=80180 Azelphur]<br />
| <code>sourceirc.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=170630 SteamTools]<br />
| [https://forums.alliedmods.net/member.php?u=59029 asherkin]<br />
| <code>steamtools.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=229556 SteamWorks]<br />
| [https://forums.alliedmods.net/member.php?u=57030 KyleS]<br />
| <code>SteamWorks.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=281488 Super Spray Handler]<br />
| [https://forums.alliedmods.net/member.php?u=194280 TheWreckingCrew6]<br />
| <code>ssh.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=210221 TF2Attributes]<br />
| [https://forums.alliedmods.net/member.php?u=84304 FlaminSarge]<br />
| <code>tf2attributes.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=315011 TF2 Econ Data]<br />
| [https://forums.alliedmods.net/member.php?u=252787 nosoop]<br />
| <code>tf_econ_data.inc</code><br />
| <br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=255885 TF2 Item DB]<br />
| [https://forums.alliedmods.net/member.php?u=101497 bottiger]<br />
| <code>tf2idb.inc</code><br />
| '''Outdated'''; Use [https://forums.alliedmods.net/showthread.php?t=315011 TF2 Econ Data] instead. For plugins that are already written for TF2 Item DB, use [https://github.com/nosoop/SM-TFEconDataCompat the compatibility shim].<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=115100 TF2Items]<br />
| [https://forums.alliedmods.net/member.php?u=59029 asherkin]<br />
| <code>tf2items.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=255743 TF2Items Extended Stocks]<br />
| [https://forums.alliedmods.net/member.php?u=149090 ReFlexPoison]<br />
| <code>tf2itemsextended.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?p=1337899 TF2Items Give Weapon]<br />
| [https://forums.alliedmods.net/member.php?u=84304 FlaminSarge]<br />
| <code>tf2items_giveweapon.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=293722 TF2 Taunts TF2IDB]<br />
| [https://forums.alliedmods.net/member.php?u=264797 fakuivan]<br />
| <code>tf2_taunts_tf2idb.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=189562 TF2 Pyro Air Jump]<br />
| [https://forums.alliedmods.net/member.php?u=70143 Leonardo]<br />
| <code>tf2pyroairjump.inc</code><br />
|<br />
<br />
|-<br />
| [https://forums.alliedmods.net/showthread.php?t=302530 VGUI URL Cache Buster]<br />
| [https://forums.alliedmods.net/member.php?u=252787 nosoop]<br />
| <code>vgui_motd_stocks.inc</code><br />
|<br />
<br />
|}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Introduction_to_SourcePawn_1.7&diff=11195Introduction to SourcePawn 1.72021-06-21T06:31:51Z<p>Joinedsenses: /* Declaration */ Update syntax</p>
<hr />
<div>This guide is designed to give you a very basic overview to fundamentals of scripting in SourcePawn. [[Pawn]] is a "scripting" language used to embed functionality in other programs. That means it is not a standalone language, like C++ or Java, and its details will differ based on the application. SourcePawn is the version of Pawn used in [[SourceMod]].<br />
<br />
This guide does not tell you how to write SourceMod plugins; it is intended as an overview of the syntax and semantics of the language instead. Read the separate article, [[Introduction to SourceMod Plugins]] for SourceMod API specifics. <br />
<br />
=Non-Programmer Intro=<br />
This section is intended for non-programmers. If you're still confused, you may want to pick up a book on another language, such as PHP, Python, or Java, to get a better idea of what programming is like.<br />
<br />
==Symbols/Keywords==<br />
A symbol is a series of letters, numbers, and/or underscores, that uniquely represents something. Symbols are case-sensitive, and usually start with a letter.<br />
<br />
There are a few reserved symbols that have special meaning. For example, <tt>if</tt>, <tt>for</tt>, and <tt>return</tt> are special constructs in the language that will explained later. They cannot be used as symbol names.<br />
<br />
==Variables==<br />
There a few important constructs you should know before you begin to script. The first is a '''variable'''. A variable is a symbol, or name, that holds data. For example, the variable "a" could hold the number "2", "16", "0", et cetera. Since a variable holds data, it also allocates the memory needed to store that data.<br />
<br />
In addition to a name, variables have a '''type'''. A type tells the program how to interpret the data, and how much memory the data will use. Pawn has three types of data that are most commonly used:<br />
* Integers, using the <tt>int</tt> type. Integer types may store a whole number from -2147483648 to 2147483647.<br />
* Floats, using the <tt>float</tt> type. Float types may store fractional numbers in a huge range, though they are not as precise as integers.<br />
* Characters, using the <tt>char</tt> type. Character types store one byte of character information, typically an [http://www.asciitable.com/ ASCII] character.<br />
* Booleans, using the <tt>bool</tt> type. Booleans store either true or false.<br />
<br />
Example of creating variables and assigning values:<br />
<br />
<sourcepawn>int money = 5400;<br />
float percent = 67.3;<br />
char key = 'A';<br />
bool enabled = false;<br />
</sourcepawn><br />
<br />
==Functions==<br />
The next important concept is '''functions'''. Functions are symbols or names that perform an action. When you invoke, or call them, they carry out a specific sequence of code and then return a result. There are a few types of functions, but every function is activated the same way. Example:<br />
<br />
<sourcepawn><br />
show(56); // Calls the "show" function, and gives it the number 56.<br />
enable(); // Calls the "enable" function with no values.<br />
bool visible = show(a); //Calls the "show" function, stores its result in a variable.<br />
</sourcepawn><br />
<br />
Every piece of data passed to a function is called a '''parameter'''. A function can have any number of parameters (there is a "reasonable" limit of 32 in SourceMod). Parameters will be explained further in the article.<br />
<br />
==Comments==<br />
Note any text that appears after a "//" is considered a "comment" and is not actual code. There are two comment styles:<br />
*<tt>//</tt> - Double slash, everything following on that line is ignored.<br />
*<tt>/* */</tt> - Multi-line comment, everything in between the asterisks is ignored. You cannot nest these.<br />
<br />
<br />
==Block Coding==<br />
The next concept is block coding. You can group code into "blocks" separated by { and }. This effectively makes one large block of code act as one statement. For example:<br />
<br />
<sourcepawn>{<br />
here;<br />
is;<br />
some;<br />
code;<br />
}</sourcepawn><br />
<br />
Block coding using braces is used everywhere in programming. Blocks of code can be nested within each other. It is a good idea to adapt a consistent and readable indentation style early on to prevent spaghetti-looking code.<br />
<br />
=Language Paradigms=<br />
Pawn may seem similar to other languages, like C, but it has fundamental differences. It is not important that you immediately understand these differences, but they may be helpful if you're familiar with another language already.<br />
*'''Pawn is sort of typed.''' Before SourceMod 1.7, Pawn did not have types. Older code and older natives will reflect this by using tags and the <tt>new</tt> keyword. As of SourceMod 1.7, we recommend that all code use types. For more information see [[SourcePawn Transitional Syntax]].<br />
*'''Pawn is not garbage collected.''' Pawn, as a language, has no built-in memory allocation, and thus has no garbage. If a function allocates memory, you may be responsible for freeing it.<br />
*'''Pawn is not object oriented.''' Pawn does not have structs or objects. As of SourceMod 1.7, it has limited sugaring for treating some data types as objects, but users cannot create their own objects or classes.<br />
*'''Pawn is single-threaded.''' As of this writing, Pawn is not thread safe. <br />
*'''Pawn is compiled.''' Pawn is compiled to an intermediate, machine-independent code, which is stored in a ".smx" file. When loading .smx files, SourceMod translates this code to machine code for the platform and CPU it's running on.<br />
<br />
Early language design decisions were made by ITB CompuPhase. It is designed for low-level embedded devices and is thus very small and very fast.<br />
<br />
=Variables=<br />
Pawn currently supports the following basic variable types:<br />
*<tt>bool</tt> - true or false.<br />
*<tt>char</tt> - an 8-bit ASCII character.<br />
*<tt>int</tt> - a 32-bit signed integer.<br />
*<tt>float</tt> - a 32-bit IEEE-754 floating point number.<br />
*<tt>Handle</tt> - the base type of a SourceMod object<br />
<br />
Other types may exist when defined in include files - for example, enums create new types for named integers, and many types derive from <tt>Handle</tt>.<br />
<br />
Strings, currently, are 0-terminated arrays of <tt>char</tt>s. They're described a little further ahead.<br />
<br />
==Declaration==<br />
Below we include some examples of variable declarations, both valid and invalid. Keep in mind that SourcePawn has recently added new syntax, and that's what's documented below. Older code may use older declaration syntax, which is no longer supported.<br />
<br />
<sourcepawn><br />
int a = 5;<br />
float b = 5.0;<br />
bool c = true;<br />
bool d = false;<br />
</sourcepawn><br />
<br />
Invalid variable usage:<br />
<sourcepawn><br />
int a = 5.0; // Type mismatch. 5.0 is a float.<br />
float b = 5; // Type mismatch. 5 is an integer.<br />
</sourcepawn><br />
<br />
If a variable is not assigned upon declaration, it will be set to 0. For example:<br />
<sourcepawn><br />
int a; // Set to 0<br />
float b; // Set to 0.0<br />
bool c; // Set to false<br />
</sourcepawn><br />
<br />
==Assignment==<br />
Variables can be re-assigned data after they are created. For example:<br />
<sourcepawn>int a;<br />
float b;<br />
bool c;<br />
<br />
a = 5;<br />
b = 5.0;<br />
c = true;<br />
</sourcepawn><br />
<br />
=Arrays=<br />
An array is a sequence of data in a list. Arrays are useful for storing multiple pieces of data in one variable, or mapping one type of data to another.<br />
<br />
==Declaration==<br />
An array is declared using brackets. Some examples of arrays:<br />
<sourcepawn><br />
int players[32]; // Stores 32 integers.<br />
float origin[3]; // Stores 3 floating point numbers<br />
</sourcepawn><br />
<br />
By default, arrays are initialized to 0. You can assign them different default values, however:<br />
<sourcepawn><br />
int numbers[5] = {1, 2, 3, 4, 5}; // Stores 1, 2, 3, 4, 5.<br />
float origin[3] = {1.0, 2.0, 3.0}; // Stores 1.0, 2.0, 3.0.<br />
</sourcepawn><br />
<br />
You can leave out the array size if you're going to pre-assign data to it. For example:<br />
<sourcepawn><br />
int numbers[] = {1, 3, 5, 7, 9};<br />
</sourcepawn><br />
<br />
The compiler will automatically deduce that you intended an array of size 5.<br />
<br />
When array is declared with brackets after its name, Pawn considers that array to have a '''fixed size'''. The size of a fixed-size array is always known. Some arrays can be '''dynamically sized''', by putting the brackets before the name. For example,<br />
<br />
<sourcepawn><br />
int[] numbers = new int[MaxClients]<br />
</sourcepawn><br />
<br />
This creates an array of size <tt>MaxClients</tt>, which could be anything, so the size of the array is not known until the array is allocated.<br />
<br />
==Usage==<br />
Using an array is just like using a normal variable. The only difference is the array must be '''indexed'''. Indexing an array means choosing the element which you wish to use.<br />
<br />
For example, here is an example of the above code using indexes:<br />
<sourcepawn><br />
int numbers[5];<br />
float origin[3];<br />
<br />
numbers[0] = 1;<br />
numbers[1] = 2;<br />
numbers[2] = 3;<br />
numbers[3] = 4;<br />
numbers[4] = 5;<br />
origin[0] = 1.0;<br />
origin[1] = 2.0;<br />
origin[2] = 3.0;<br />
</sourcepawn><br />
<br />
Note that the '''index''' is what's in between the brackets. The index always starts from 0. That is, if an array has N elements, its valid indexes are from 0 to N-1. Accessing the data at these indexes works like a normal variable.<br />
<br />
Using an incorrect index will cause an error. For example:<br />
<sourcepawn><br />
int numbers[5];<br />
<br />
numbers[5] = 20;</sourcepawn><br />
<br />
This may look correct, but 5 is not a valid index. The highest valid index is 4.<br />
<br />
You can use any expression as an index. For example:<br />
<sourcepawn>int a, numbers[5];<br />
<br />
a = 1; // Set a = 1<br />
numbers[a] = 4; // Set numbers[1] = 4<br />
numbers[numbers[a]] = 2; // Set numbers[4] = 2<br />
</sourcepawn><br />
<br />
Expressions will be discussed in depth later in the article.<br />
<br />
=Enums=<br />
Enums are retyped integers. A new enum can be declared as follows:<br />
<br />
<sourcepawn>enum MyEnum // the name is optional<br />
{<br />
MyFirstValue, // == 0, if not explicitly assigned, the first value is zero<br />
MySecondValue, // == 1, implicitly set to the previous value plus one<br />
MyThirdValue = 1, // == 1, explicitly assign to one -- multiple names can share the same value<br />
MyFourthValue, // == 2<br />
}<br />
</sourcepawn><br />
<br />
To allow implicit conversions of enums back to integers (that is, so functions expecting a value of type 'int' accepts it as one instead of generating a warning), the enum must either be anonymous (unnamed) or must start with a lowercase character.<br />
<br />
=Strings=<br />
Strings are a construct for storing text (or even raw binary data). A string is just an array of characters, except that the final character must be 0 (called the null terminator). Without a null terminator, Pawn would not know where to stop reading the string. All strings are [http://en.wikipedia.org/wiki/UTF-8 UTF-8] in SourcePawn.<br />
<br />
In general, you must have an idea of how large a string will be before you store it. SourcePawn does not yet have the capability of pre-determining storage space for strings.<br />
<br />
==Usage==<br />
Strings are usually declared as arrays. For example:<br />
<sourcepawn><br />
char message[] = "Hello!";<br />
char clams[6] = "Clams";<br />
</sourcepawn><br />
<br />
These are equivalent to doing:<br />
<sourcepawn><br />
char message[7];<br />
char clams[6];<br />
<br />
message[0] = 'H';<br />
message[1] = 'e';<br />
message[2] = 'l';<br />
message[3] = 'l';<br />
message[4] = 'o';<br />
message[5] = '!';<br />
message[6] = 0;<br />
clams[0] = 'C';<br />
clams[1] = 'l';<br />
clams[2] = 'a';<br />
clams[3] = 'm';<br />
clams[4] = 's';<br />
clams[5] = 0;<br />
</sourcepawn><br />
<br />
Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.<br />
<br />
Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.<br />
<br />
==Characters==<br />
A character of text can be used in either a String or a cell. For example:<br />
<sourcepawn>char text[] = "Crab";<br />
char clam;<br />
<br />
clam = 'D'; //Set clam to 'D'<br />
text[0] = 'A'; //Change the 'C' to 'A', it is now 'Arab'<br />
clam = text[0]; //Set clam to 'A'<br />
text[1] = clam; //Change the 'r' to 'A', is is now 'AAab'<br />
</sourcepawn><br />
<br />
What you can't do is mix character arrays with strings. The internal storage is different. For example:<br />
<sourcepawn><br />
int clams[] = "Clams"; // Invalid.<br />
int clams[] = {'C', 'l', 'a', 'm', 's', 0}; // Valid, but NOT A STRING.<br />
</sourcepawn><br />
<br />
==Concatenation==<br />
In programming, concatenation is the operation of joining character strings end-to-end. The benefit of this is to improve the readability of the source code for humans, since the compiler will continue to treat the strings as a single string. String literals can be concatenated with the <tt>...</tt> or <tt>\</tt> operator:<br />
<br />
<sourcepawn><br />
char text[] = "This is a really long string of text that should be split over multiple lines for the sake of readability."<br />
<br />
char text[] = "This is a really long string of text that should be "<br />
... "split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char text[] = "This is a really long string of text that should be \<br />
split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char prefix[] = "What you can't do, however, ";<br />
char moreText[] = prefix ... "is concatenate using a non-literal string."; // Invalid, not a constant expression.<br />
</sourcepawn><br />
<br />
=Functions=<br />
Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or '''called''', with '''parameters''' that give specific options.<br />
<br />
There are two types of ways functions are called:<br />
*'''direct call''' - You specifically call a function in your code.<br />
*'''callback''' - The application calls a function in your code, as if it were an event trigger.<br />
<br />
There are six types of functions:<br />
*'''native''': A direct, internal function provided by the application.<br />
*'''public''': A callback function that is visible to the application and other scripts.<br />
*'''normal''': A normal function that only you can call.<br />
*'''static''': The scope of this function is restricted to the current file, can be used in combination with stock.<br />
*'''stock''': A normal function provided by an include file. If unused, it won't be compiled.<br />
*'''forward''': This function is a global event provided by the application. If you implement it, it will be a callback.<br />
<br />
All code in Pawn must exist in functions. This is in contrast to languages like PHP, Perl, and Python which let you write global code. That is because Pawn is a callback-based language: it responds to actions from a parent application, and functions must be written to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.<br />
<br />
==Declaration==<br />
Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the '''signature''' and the '''body'''. The signature contains the name of your function and the parameters it will accept. The body is the contents of its code.<br />
<br />
Example of a function:<br />
<sourcepawn><br />
int AddTwoNumbers(int first, int second)<br />
{<br />
int sum = first + second;<br />
return sum;<br />
}</sourcepawn><br />
<br />
This is a simple function. The prototype is this line:<br />
<sourcepawn>int AddTwoNumbers(int first, int second)</sourcepawn><br />
<br />
Broken down, it means:<br />
*<tt>int</tt> - Return value type (integer).<br />
*<tt>AddTwoNumbers</tt> - Name of the function.<br />
*<tt>int first</tt> - First parameter, an integer.<br />
*<tt>int second</tt> - Second parameter, an integer.<br />
<br />
The body is a block of code. It creates a new variable, called <tt>sum</tt>, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the <tt>return</tt> statement, which tells the function to end and return a value to the caller of the function. All functions return something on completion, unless they return a special type called <tt>void</tt>.<br />
<br />
A function can accept any type of input, and it can return any non-array type.<br />
<br />
You can, of course, pass variables to functions:<br />
<sourcepawn>int numbers[3] = {1, 2, 0};<br />
<br />
numbers[2] = AddTwoNumbers(numbers[0], numbers[1]);</sourcepawn><br />
<br />
Note that cells are passed '''by value'''. That is, their value cannot be changed by the function. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
ChangeValue(a);<br />
<br />
void ChangeValue(int b)<br />
{<br />
b = 5;<br />
}</sourcepawn><br />
<br />
This code would not change the value of <tt>a</tt>. That is because a copy of the value in <tt>a</tt> is passed instead of <tt>a</tt> itself. <br />
<br />
More examples of functions will be provided throughout the article.<br />
<br />
==Publics==<br />
Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from <tt>sourcemod.inc</tt>:<br />
<br />
<sourcepawn>forward void OnPluginStart();<br />
forward void OnClientDisconnected(int client);</sourcepawn><br />
<br />
To implement and receive these two events, you would write functions as such:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
/* Code here */<br />
}<br />
<br />
public void OnClientDisconnected(int client)<br />
{<br />
/* Code here */<br />
}</sourcepawn><br />
<br />
The '''public''' keyword signals to the parent application that it should attach the function to the appropriate forwarded event.<br />
<br />
==Natives==<br />
Natives are builtin functions provided by SourceMod. You can call them as if they were a normal function. For example, SourceMod has the following function:<br />
<br />
<sourcepawn>native float FloatRound(float num);</sourcepawn><br />
<br />
It can be called like so:<br />
<sourcepawn>int rounded = FloatRound(5.2); // rounded will be 5</sourcepawn><br />
<br />
==Array Parameters==<br />
You can pass arrays or Strings as parameters. It is important to note that these are passed '''by reference'''. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.<br />
<br />
<sourcepawn><br />
int example[] = {1, 2, 3, 4, 5};<br />
<br />
ChangeArray(example, 2, 29);<br />
<br />
void ChangeArray(int[] array, int index, int value)<br />
{<br />
array[index] = value;<br />
}</sourcepawn><br />
<br />
The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:<br />
<sourcepawn>example[2] = 29;</sourcepawn><br />
<br />
Note two important things here. First, arrays are not copied when they are passed to functions - they are passed ''by reference'', so the view of the array is consistent at all times. Second, the brackets changed position in our function signature. This is because our function accepts an array of any size, and since we don't know the size, we must use the dynamic array syntax.<br />
<br />
To prevent an array from being modified in a function, you can mark it as <tt>const</tt>. This will raise an error on code that attempts to modify it. For example:<br />
<br />
<sourcepawn>void CantChangeArray(const int[] array, int index, int value)<br />
{<br />
array[index] = value; //Won't compile<br />
}</sourcepawn><br />
<br />
It is a good idea to use <tt>const</tt> in array parameters if you know the array won't be modified; this can prevent coding mistakes.<br />
<br />
When a function takes an array as a parameter, you can also pass any indexed array element which will be interpreted as a sub-array that begins from that index. For example,<br />
if you want to get the length of a string, you can use the strlen function like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString); // Set length = 8<br />
</sourcepawn><br />
<br />
And if you pass an indexed array element, it will be interpreted as a sub-array that begins from that index like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString[2]); // Set length = 6<br />
</sourcepawn><br />
<br />
==Named Parameters==<br />
With functions that have many parameters with default values, you can call the function with named parameters to assign a value based on its name instead of which position it is in the argument list. For example:<br />
<br />
<sourcepawn><br />
void SomeFunctionWithDefaultParams(int a, int b = 1, int c = 2, int d = 3, int e = 4);<br />
<br />
// If you want to call it with a different value for `d` -- note the dotted argument assignment<br />
SomeFunctionWithDefaultParams(0, .d = 14);<br />
<br />
// This is effectively the same, but less readable:<br />
SomeFunctionWithDefaultParams(0, _, _, 14, _);<br />
</sourcepawn><br />
<br />
=Expressions=<br />
Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.<br />
<br />
The simplest expression is a single number. For example:<br />
<sourcepawn><br />
0; //Returns the number 0<br />
(0); //Returns the number 0 as well<br />
</sourcepawn><br />
<br />
Although expressions can return any value, they are also said to either return ''zero or non-zero''. In that sense, ''zero'' is ''false'', and ''non-zero'' is ''true''. For example, -1 is '''true''' in Pawn, since it is non-zero. Do not assume negative numbers are false.<br />
<br />
The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:<br />
<sourcepawn><br />
5 + 6; //Evaluates to 11<br />
5 * 6 + 3; //Evaluates to 33<br />
5 * (6 + 3); //Evaluates to 45<br />
5.0 + 2.3; //Evaluates to 7.3<br />
(5 * 6) % 7; //Modulo operator, evaluates to 2<br />
(5 + 3) / 2 * 4 - 9; //Evaluates to -8<br />
</sourcepawn><br />
<br />
As noted, expressions can contain variables, or even functions:<br />
<sourcepawn><br />
int a = 5 * 6;<br />
int b = a * 3; //Evaluates to 90<br />
int c = AddTwoNumbers(a, b) + (a * b);<br />
</sourcepawn><br />
<br />
Note: String manipulation routines may be found in the string.inc file located in the include subdirectory. They may be browsed through the [http://docs.sourcemod.net/api/ API Reference] as well.<br />
<br />
==Operators==<br />
There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
a = a + 5;</sourcepawn><br />
<br />
Can be rewritten as:<br />
<sourcepawn>int a = 5;<br />
a += 5;</sourcepawn><br />
<br />
This is true of the following operators in Pawn:<br />
*Four-function: *, /, -, +<br />
*Bit-wise: |, &, ^, ~, <<, >><br />
<br />
Additionally, there are increment/decrement operators:<br />
<sourcepawn>a = a + 1;<br />
a = a - 1;</sourcepawn><br />
<br />
Can be simplified as:<br />
<sourcepawn>a++;<br />
a--;</sourcepawn><br />
<br />
As an advanced note, the ++ or -- can come before the variable (pre-increment, pre-decrement) or after the variable (post-increment, post-decrement). The difference is in how the rest of the expression containing them sees their result.<br />
<br />
* ''Pre:'' The variable is incremented before evaluation, and the rest of the expression sees the new value.<br />
* ''Post:'' The variable is incremented after evaluation, and the rest of the expression sees the old value.<br />
<br />
In other words, <tt>a++</tt> evaluates to the value of <tt>a</tt> while <tt>++a</tt> evaluates to the value of <tt>a + 1</tt>. In both cases <tt>a</tt> is incremented by <tt>1</tt>.<br />
<br />
For example:<br />
<br />
<sourcepawn>int a = 5;<br />
int b = a++; // b = 5, a = 6 (1)<br />
int c = ++a; // a = 7, c = 7 (2)<br />
</sourcepawn><br />
<br />
In (1) <tt>b</tt> is assigned <tt>a</tt>'s ''old'' value ''before'' it is incremented to <tt>6</tt>, but in (2) <tt>c</tt> is assigned <tt>a</tt>'s ''int'' value ''after'' it is incremented to <tt>7</tt>.<br />
<br />
==Comparison Operators==<br />
There are six operators for comparing two values numerically, and the result is either true (non-zero) or false (zero):<br />
*<tt>a == b</tt> - True if a and b have the same value.<br />
*<tt>a != b</tt> - True if a and b have different values.<br />
*<tt>a &gt; b</tt> - True if a is greater than b<br />
*<tt>a &gt;= b</tt> - True if a is greater than or equal to b<br />
*<tt>a &lt; b</tt> - True if a is less than b<br />
*<tt>a &lt;= b</tt> - True if a is less than or equal to b<br />
<br />
For example:<br />
<sourcepawn><br />
(1 != 3); //Evaluates to true because 1 is not equal to 3.<br />
(3 + 3 == 6); //Evaluates to true because 3+3 is 6.<br />
(5 - 2 >= 4); //Evaluates to false because 3 is less than 4.<br />
</sourcepawn><br />
<br />
Note that these operators do not work on arrays or strings. That is, you cannot compare either using <tt>==</tt>.<br />
<br />
==Truth Operators==<br />
These truth values can be combined using three boolean operators:<br />
*<tt>a && b</tt> - True if both a and b are true. False if a or b (or both) is false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>&&</tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 0<br />
|-<br />
! 1<br />
| 0 || 1<br />
|}<br />
*<tt>a || b</tt> - True if a or b (or both) is true. False if both a and b are false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt><nowiki>||</nowiki></tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 1<br />
|-<br />
! 1<br />
| 1 || 1<br />
|}<br />
*<tt>!a</tt> - True if a is false. False if a is true.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>!</tt> !! 0 !! 1<br />
|- <br />
!<br />
| 1 || 0<br />
|}<br />
<br />
For example:<br />
<sourcepawn><br />
(1 || 0); //Evaluates to true because the expression 1 is true<br />
(1 && 0); //Evaluates to false because the expression 0 is false<br />
(!1 || 0); //Evaluates to false because !1 is false.<br />
</sourcepawn><br />
<br />
==Left/Right Values==<br />
Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.<br />
<br />
For example:<br />
<sourcepawn><br />
int a = 5;</sourcepawn><br />
<br />
In this example <tt>a</tt> is an l-value and <tt>5</tt> is an r-value.<br />
<br />
The rules:<br />
*'''Expressions are never l-values'''.<br />
*'''Variables are both l-values and r-values'''.<br />
<br />
=Conditionals=<br />
Conditional statements let you only run code if a certain condition is matched.<br />
<br />
==If Statements==<br />
If statements test one or more conditions. For example:<br />
<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code that will run if the expression was true */<br />
}</sourcepawn><br />
<br />
They can be extended to handle more cases as well:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 6)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 7)<br />
{<br />
/* Code */<br />
}</sourcepawn><br />
<br />
You can also handle the case of no expression being matched. For example:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else<br />
{<br />
/* Code that will run if no expressions were true */<br />
}</sourcepawn><br />
<br />
==Switch Statements==<br />
Switch statements are restricted if statements. They test one expression for a series of possible values. For example:<br />
<br />
<sourcepawn><br />
switch (a)<br />
{<br />
case 5:<br />
{<br />
/* code */<br />
}<br />
case 6:<br />
{<br />
/* code */<br />
}<br />
case 7:<br />
{<br />
/* code */<br />
}<br />
case 8, 9, 10:<br />
{<br />
/* Code */<br />
}<br />
default:<br />
{<br />
/* will run if no case matched */<br />
}<br />
}</sourcepawn><br />
<br />
Unlike some other languages, switches are not fall-through. That is, multiple cases will never be run. When a case matches its code is executed, and the switch is then immediately terminated.<br />
<br />
=Loops=<br />
Loops allow you to conveniently repeat a block of code while a given condition remains true. <br />
<br />
==For Loops==<br />
For loops are loops which have four parts:<br />
*The '''initialization''' statement - run once before the first loop.<br />
*The '''condition''' statement - checks whether the next loop should run, including the first one. The loop terminates when this expression evaluates to false.<br />
*The '''iteration''' statement - run after each loop.<br />
*The '''body''' block - run each time the '''condition''' statement evaluates to true.<br />
<br />
<sourcepawn><br />
for ( /* initialization */ ; /* condition */ ; /* iteration */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
A simple example is a function to sum an array:<br />
<sourcepawn><br />
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};<br />
int sum = SumArray(array, 10);<br />
<br />
int SumArray(const int[] array, int count)<br />
{<br />
int total;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
total += array[i];<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
Broken down:<br />
*<tt>int i = 0</tt> - Creates a new variable for the loop, sets it to 0.<br />
*<tt>i < count</tt> - Only runs the loop if <tt>i</tt> is less than <tt>count</tt>. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.<br />
*<tt>i++</tt> - Increments <tt>i</tt> by one after each loop. This ensures that the loop doesn't run forever; eventually <tt>i</tt> will become too big and the loop will end.<br />
<br />
Thus, the <tt>SumArray</tt> function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.<br />
<br />
==While Loops==<br />
While loops are less common than for loops but are actually the simplest possible loop. They have only two parts:<br />
*The '''condition''' statement - checked before each loop. The loop terminates when it evaluates to false.<br />
*The '''body''' block - run each time through the loop.<br />
<br />
<sourcepawn><br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
As long as the condition expression remains true, the loop will continue. Every for loop can be rewritten as a while loop:<br />
<br />
<sourcepawn><br />
/* initialization */<br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
/* iteration */<br />
}<br />
</sourcepawn><br />
<br />
Here is the previous for loop rewritten as a while loop:<br />
<sourcepawn><br />
int SumArray(const int[] array, int count)<br />
{<br />
int total, i;<br />
<br />
while (i < count)<br />
{<br />
total += array[i];<br />
i++;<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
There are also '''do...while''' loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:<br />
<br />
<sourcepawn><br />
do<br />
{<br />
/* body */<br />
}<br />
while ( /* condition */ );<br />
</sourcepawn><br />
<br />
==Loop Control==<br />
There are two cases in which you want to selectively control a loop:<br />
*'''skipping''' one iteration of the loop but continuing as normal, or;<br />
*'''breaking''' the loop entirely before it's finished.<br />
<br />
Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:<br />
<sourcepawn><br />
/**<br />
* Returns the array index where the value is, or -1 if not found.<br />
*/<br />
int SearchInArray(const int[] array, int count, int value)<br />
{<br />
int index = -1;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
if (array[i] == value)<br />
{<br />
index = i;<br />
break;<br />
}<br />
}<br />
<br />
return index;<br />
}</sourcepawn><br />
<br />
Certainly, this function could simply <tt>return i</tt> instead, but the example shows how <tt>break</tt> will terminate the loop.<br />
<br />
Similarly, the <tt>continue</tt> keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:<br />
<sourcepawn><br />
int SumEvenNumbers(const int[] array, int count)<br />
{<br />
int sum;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
/* If divisibility by 2 is 1, we know it's odd */<br />
if (array[i] % 2 == 1)<br />
{<br />
/* Skip the rest of this loop iteration */<br />
continue;<br />
}<br />
sum += array[i];<br />
}<br />
<br />
return sum;<br />
}</sourcepawn><br />
<br />
=Scope=<br />
Scope refers to the '''visibility''' of code. That is, code at one level may not be "visible" to code at another level. For example:<br />
<br />
<sourcepawn><br />
int A, B, C;<br />
<br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function2();<br />
}<br />
<br />
void Function2()<br />
{<br />
int C;<br />
}</sourcepawn><br />
<br />
In this example, <tt>A</tt>, <tt>B</tt>, and <tt>C</tt> exist at '''global scope'''. They can be seen by any function. However, the <tt>B</tt> in <tt>Function1</tt> is not the same variable as the <tt>B</tt> at the global level. Instead, it is at '''local scope''', and is thus a '''local variable'''.<br />
<br />
Similarly, <tt>Function1</tt> and <tt>Function2</tt> know nothing about each other's variables.<br />
<br />
Not only is the variable private to <tt>Function1</tt>, but it is re-created each time the function is invoked. Imagine this:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function1();<br />
}</sourcepawn><br />
<br />
In the above example, <tt>Function1</tt> calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of <tt>B</tt>. When the function ends, <tt>B</tt> is destroyed, and the value is lost.<br />
<br />
This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:<br />
<br />
<sourcepawn>void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
A = 5;<br />
}<br />
}</sourcepawn><br />
<br />
The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
int B = 5;<br />
}<br />
<br />
B = 5;<br />
}</sourcepawn><br />
<br />
Notice that <tt>B</tt> is declared in a new code block. That means <tt>B</tt> is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, <tt>B</tt> is no longer valid.<br />
<br />
=Dynamic Arrays=<br />
Dynamic arrays are arrays which don't have a hardcoded size. For example:<br />
<br />
<sourcepawn>void Function1(int size)<br />
{<br />
int[] array = new int[size];<br />
<br />
/* Code */<br />
}</sourcepawn><br />
<br />
Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.<br />
<br />
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.<br />
<br />
=Extended Variable Declarations=<br />
Variables can be declared in more ways than simply <tt>int float or char</tt>.<br />
<br />
==static==<br />
The <tt>static</tt> keyword is available at global and local scope. It has different meanings in each.<br />
<br />
===Global static===<br />
A global static variable can only be accessed from within the same file. For example:<br />
<br />
<sourcepawn>//file1.inc<br />
static float g_value1 = 0.15f;<br />
<br />
//file2.inc<br />
static float g_value2 = 0.15f;</sourcepawn><br />
<br />
If a plugin includes both of these files, it will not be able to use either <tt>g_value1</tt> or <tt>g_value2</tt>. This is a simple information hiding mechanism, and is similar to declaring member variables as <tt>private</tt> in languages like C++, Java, or C#.<br />
<br />
===Local static===<br />
A local static variable is a global variable that is only visible from its local lexical scope. For example:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
static int counter = -1;<br />
<br />
counter += inc;<br />
<br />
return counter;<br />
}</sourcepawn><br />
<br />
In this example, <tt>counter</tt> is technically a global variable -- it is initialized once to -1 and is never initialized again. It does not exist on the stack. That means each time <tt>MyFunction</tt> runs, the <tt>counter</tt> variable and its storage in memory is the same.<br />
<br />
Take this example:<br />
<sourcepawn>MyFunction(5);<br />
MyFunction(6);<br />
MyFunction(10);</sourcepawn><br />
<br />
In this example, <tt>counter</tt> will be <tt>-1 + 5 + 6 + 10</tt>, or <tt>20</tt>, because it persists beyond the frame of the function. Note this may pose problems for recursive functions: if your function may be recursive, then <tt>static</tt> is usually not a good idea unless your code is re-entrant. <br />
<br />
The benefit of a local static variable is that you don't have to clutter your script with global variables. As long as the variable doesn't need to be read by another function, you can squirrel it inside the function and its persistence will be guaranteed.<br />
<br />
Note that statics can exist in any local scope:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
if (inc > 0)<br />
{<br />
static int counter;<br />
return (counter += inc);<br />
}<br />
return -1;<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Entity_References_(SourceMod)&diff=11015Entity References (SourceMod)2020-05-04T14:09:29Z<p>Joinedsenses: Update highlighting</p>
<hr />
<div>An entity reference is a combination of a standard entity index (used for all entities in SourceMod previously) and a pseudo-unique serial number that identifies the entity. Since entities are constantly being created and destroyed a cached index doesn't guarantee the underlying entity is the same, references provide a way of verifying the same entity still exists. Generally speaking, if you have an entity index you wish to cache you should convert it to a reference first. Most Sourcemod-provided natives should be able to accept references in place of indexes (wherever the parameter asks for an entity - Not clients), and these will use the serial to check that the entity index contained in the reference is the same as when the reference was created. If you find a native that does not seem to support references in place of indexes, please file a bug report.<br />
<br />
SourceMod now also supports handling of logical (non-networked entities) and these use references exclusively. Natives that used to return an entity index will continue to do so in the case of networked entities (for backwards compatability) and will only return a reference for non-networked ones.<br />
<br />
==Converting a backwards-compat index/ref to a guaranteed reference for safely caching==<br />
<br />
<sourcepawn><br />
int index = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");<br />
<br />
/* Convert our backwards-compat index/ref to a guaranteed reference */<br />
int ref = EntIndexToEntRef(index);<br />
</sourcepawn><br />
<br />
==Converting a reference back to an entity index==<br />
<br />
<sourcepawn><br />
int index = EntRefToEntIndex(ref);<br />
<br />
if (index == INVALID_ENT_REFERENCE)<br />
{<br />
/* Reference wasn't valid - Entity must have been deleted */<br />
}<br />
</sourcepawn><br />
<br />
==Using References to handle a logic entity==<br />
<br />
<sourcepawn><br />
int ent = -1; <br />
<br />
while ((ent = FindEntityByClassname(ent, "logic_auto")) != -1) <br />
{ <br />
/** <br />
* Get the entity index from this reference - This works on references and indexes for convenience. <br />
* Should be a number >2048 since logic_auto is a non networked entity <br />
*/ <br />
int ref = EntIndexToEntRef(ent); <br />
<br />
/* Fire an input on this entity - We use the reference version since this includes a serial check */ <br />
AcceptEntityInput(ref, "Kill"); <br />
<br />
}<br />
</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Entity_References_(SourceMod)&diff=11014Entity References (SourceMod)2020-05-04T14:09:13Z<p>Joinedsenses: Update highlighting</p>
<hr />
<div>An entity reference is a combination of a standard entity index (used for all entities in SourceMod previously) and a pseudo-unique serial number that identifies the entity. Since entities are constantly being created and destroyed a cached index doesn't guarantee the underlying entity is the same, references provide a way of verifying the same entity still exists. Generally speaking, if you have an entity index you wish to cache you should convert it to a reference first. Most Sourcemod-provided natives should be able to accept references in place of indexes (wherever the parameter asks for an entity - Not clients), and these will use the serial to check that the entity index contained in the reference is the same as when the reference was created. If you find a native that does not seem to support references in place of indexes, please file a bug report.<br />
<br />
SourceMod now also supports handling of logical (non-networked entities) and these use references exclusively. Natives that used to return an entity index will continue to do so in the case of networked entities (for backwards compatability) and will only return a reference for non-networked ones.<br />
<br />
==Converting a backwards-compat index/ref to a guaranteed reference for safely caching==<br />
<br />
<sourcepawn><br />
int index = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");<br />
<br />
/* Convert our backwards-compat index/ref to a guaranteed reference */<br />
int ref = EntIndexToEntRef(index);<br />
</sourcepawn><br />
<br />
==Converting a reference back to an entity index==<br />
<br />
<sourcepawn><br />
int index = EntRefToEntIndex(ref);<br />
<br />
if (index == INVALID_ENT_REFERENCE)<br />
{<br />
/* Reference wasn't valid - Entity must have been deleted */<br />
}<br />
</pawn><br />
<br />
==Using References to handle a logic entity==<br />
<br />
<sourcepawn><br />
int ent = -1; <br />
<br />
while ((ent = FindEntityByClassname(ent, "logic_auto")) != -1) <br />
{ <br />
/** <br />
* Get the entity index from this reference - This works on references and indexes for convenience. <br />
* Should be a number >2048 since logic_auto is a non networked entity <br />
*/ <br />
int ref = EntIndexToEntRef(ent); <br />
<br />
/* Fire an input on this entity - We use the reference version since this includes a serial check */ <br />
AcceptEntityInput(ref, "Kill"); <br />
<br />
}<br />
</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Admin_Menu_(SourceMod_Scripting)&diff=10997Admin Menu (SourceMod Scripting)2020-04-21T12:22:27Z<p>Joinedsenses: Fix syntax</p>
<hr />
<div>The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a <tt>TopMenu</tt>, from the TopMenus extension, and is provided by <tt>adminmenu.sp</tt>. <br />
<br />
The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.<br />
<br />
=Menu Organization=<br />
The menu is organized into ''categories'' and ''items''. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:<br />
*There is no nesting. Categories cannot have sub-categories.<br />
*Only categories can exist at the top level (i.e. items must have a parent category).<br />
<br />
All entries on the menu, be they categories or items, are called ''top menu objects'', and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.<br />
<br />
Unique names are used to identify items for sorting. For more information, see [[Admin Menu Configuration (SourceMod)|Admin Menu Configuration]].<br />
<br />
<br />
=Interfacing to the Admin Menu=<br />
The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:<br />
*The admin menu is unloaded while you are interfaced to it.<br />
*The admin menu is loaded while your plugin is already loaded.<br />
*The admin menu is loaded before your plugin is loaded.<br />
<br />
You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.<br />
<br />
Example code is below:<br />
<sourcepawn><br />
#include <sourcemod><br />
<br />
/* Make the admin menu plugin optional */<br />
#undef REQUIRE_PLUGIN<br />
#include <adminmenu><br />
<br />
/* Keep track of the top menu */<br />
TopMenu hAdminMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
/* See if the menu plugin is already ready */<br />
TopMenu topmenu;<br />
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))<br />
{<br />
/* If so, manually fire the callback */<br />
OnAdminMenuReady(topmenu);<br />
}<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "adminmenu", false))<br />
{<br />
hAdminMenu = null;<br />
}<br />
}<br />
<br />
public void OnAdminMenuReady(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Try to add the category first, if we want to add one.<br />
Leave this out, if you don't add a new category. */<br />
if (obj_dmcommands == INVALID_TOPMENUOBJECT)<br />
{<br />
OnAdminMenuCreated(topmenu);<br />
}<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu)<br />
{<br />
return;<br />
}<br />
<br />
hAdminMenu = topmenu;<br />
<br />
/* :TODO: Add everything to the menu! */<br />
}</sourcepawn><br />
<br />
=Top Menu Objects=<br />
Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:<br />
*<tt>ID</tt> (OUTPUT): The ID of the object in memory.<br />
*<tt>Name</tt> (INPUT): The unique name of the object.<br />
*<tt>Type</tt> (INPUT): Either a category or an item.<br />
*<tt>Handler</tt> (INPUT): The callback function.<br />
*<tt>Parent</tt> (INPUT): Empty for categories, or a category object ID for an item.<br />
*<tt>Override</tt> (INPUT): The name of the override associated with the command. For more information, see [[Overriding Command Access (SourceMod)|Overriding Command Access]]. Overrides don't need to be a command name.<br />
*<tt>Flags</tt> (INPUT): Default admin flags that should be associated with the object if the override is not set.<br />
<br />
The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.<br />
<br />
<br />
=Adding Categories=<br />
Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:<br />
*<tt>TopMenuAction_DisplayOption</tt> - The category is being displayed.<br />
*<tt>TopMenuAction_DisplayTitle</tt> - The category is being displayed as a title.<br />
<br />
For example, let's create a category called "CS:S DM Commands".<br />
<br />
<sourcepawn><br />
TopMenuObject obj_dmcommands;<br />
<br />
public void OnAdminMenuCreated(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)<br />
{<br />
return;<br />
}<br />
<br />
obj_dmcommands = AddToTopMenu(topmenu,<br />
"CS:S DM Commands",<br />
TopMenuObject_Category,<br />
CategoryHandler,<br />
INVALID_TOPMENUOBJECT);<br />
}<br />
<br />
public void CategoryHandler(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
if (action == TopMenuAction_DisplayTitle)<br />
{<br />
Format(buffer, maxlength, "CS:S DM Commands:");<br />
}<br />
else if (action == TopMenuAction_DisplayOption)<br />
{<br />
Format(buffer, maxlength, "CS:S DM Commands");<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one category. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Adding Items=<br />
Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.<br />
<br />
<sourcepawn><br />
void AttachAdminMenu()<br />
{<br />
/* If the category is third party, it will have its own unique name. */<br />
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);<br />
<br />
if (player_commands == INVALID_TOPMENUOBJECT)<br />
{<br />
/* Error! */<br />
return;<br />
}<br />
<br />
AddToTopMenu(hAdminMenu, <br />
"sm_poke",<br />
TopMenuObject_Item,<br />
AdminMenu_Poke,<br />
player_commands,<br />
"sm_poke",<br />
ADMFLAG_SLAY);<br />
}<br />
<br />
public void AdminMenu_Poke(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
if (action == TopMenuAction_DisplayOption)<br />
{<br />
Format(buffer, maxlength, "Poke");<br />
}<br />
else if (action == TopMenuAction_SelectOption)<br />
{<br />
/* Do something! client who selected item is in param */<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one item. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Returning to the Menu=<br />
Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via <tt>RedisplayAdminMenu</tt> in <tt>adminmenu.inc</tt>.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Admin_Menu_(SourceMod_Scripting)&diff=10996Admin Menu (SourceMod Scripting)2020-04-21T12:20:22Z<p>Joinedsenses: Fix syntax</p>
<hr />
<div>The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a <tt>TopMenu</tt>, from the TopMenus extension, and is provided by <tt>adminmenu.sp</tt>. <br />
<br />
The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.<br />
<br />
=Menu Organization=<br />
The menu is organized into ''categories'' and ''items''. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:<br />
*There is no nesting. Categories cannot have sub-categories.<br />
*Only categories can exist at the top level (i.e. items must have a parent category).<br />
<br />
All entries on the menu, be they categories or items, are called ''top menu objects'', and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.<br />
<br />
Unique names are used to identify items for sorting. For more information, see [[Admin Menu Configuration (SourceMod)|Admin Menu Configuration]].<br />
<br />
<br />
=Interfacing to the Admin Menu=<br />
The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:<br />
*The admin menu is unloaded while you are interfaced to it.<br />
*The admin menu is loaded while your plugin is already loaded.<br />
*The admin menu is loaded before your plugin is loaded.<br />
<br />
You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.<br />
<br />
Example code is below:<br />
<sourcepawn><br />
#include <sourcemod><br />
<br />
/* Make the admin menu plugin optional */<br />
#undef REQUIRE_PLUGIN<br />
#include <adminmenu><br />
<br />
/* Keep track of the top menu */<br />
TopMenu hAdminMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
/* See if the menu plugin is already ready */<br />
TopMenu topmenu;<br />
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))<br />
{<br />
/* If so, manually fire the callback */<br />
OnAdminMenuReady(topmenu);<br />
}<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "adminmenu", false))<br />
{<br />
hAdminMenu = null;<br />
}<br />
}<br />
<br />
public void OnAdminMenuReady(Handle aTopMenu)<br />
{<br />
TopMenu topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Try to add the category first, if we want to add one.<br />
Leave this out, if you don't add a new category. */<br />
if (obj_dmcommands == INVALID_TOPMENUOBJECT)<br />
{<br />
OnAdminMenuCreated(topmenu);<br />
}<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu)<br />
{<br />
return;<br />
}<br />
<br />
hAdminMenu = topmenu;<br />
<br />
/* :TODO: Add everything to the menu! */<br />
}</sourcepawn><br />
<br />
=Top Menu Objects=<br />
Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:<br />
*<tt>ID</tt> (OUTPUT): The ID of the object in memory.<br />
*<tt>Name</tt> (INPUT): The unique name of the object.<br />
*<tt>Type</tt> (INPUT): Either a category or an item.<br />
*<tt>Handler</tt> (INPUT): The callback function.<br />
*<tt>Parent</tt> (INPUT): Empty for categories, or a category object ID for an item.<br />
*<tt>Override</tt> (INPUT): The name of the override associated with the command. For more information, see [[Overriding Command Access (SourceMod)|Overriding Command Access]]. Overrides don't need to be a command name.<br />
*<tt>Flags</tt> (INPUT): Default admin flags that should be associated with the object if the override is not set.<br />
<br />
The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.<br />
<br />
<br />
=Adding Categories=<br />
Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:<br />
*<tt>TopMenuAction_DisplayOption</tt> - The category is being displayed.<br />
*<tt>TopMenuAction_DisplayTitle</tt> - The category is being displayed as a title.<br />
<br />
For example, let's create a category called "CS:S DM Commands".<br />
<br />
<sourcepawn><br />
TopMenuObject obj_dmcommands;<br />
<br />
public void OnAdminMenuCreated(Handle aTopMenu)<br />
{<br />
Handle topmenu = TopMenu.FromHandle(aTopMenu);<br />
<br />
/* Block us from being called twice */<br />
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)<br />
{<br />
return;<br />
}<br />
<br />
obj_dmcommands = AddToTopMenu(topmenu,<br />
"CS:S DM Commands",<br />
TopMenuObject_Category,<br />
CategoryHandler,<br />
INVALID_TOPMENUOBJECT);<br />
}<br />
<br />
public void CategoryHandler(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
if (action == TopMenuAction_DisplayTitle)<br />
{<br />
Format(buffer, maxlength, "CS:S DM Commands:");<br />
}<br />
else if (action == TopMenuAction_DisplayOption)<br />
{<br />
Format(buffer, maxlength, "CS:S DM Commands");<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one category. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Adding Items=<br />
Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.<br />
<br />
<sourcepawn><br />
void AttachAdminMenu()<br />
{<br />
/* If the category is third party, it will have its own unique name. */<br />
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);<br />
<br />
if (player_commands == INVALID_TOPMENUOBJECT)<br />
{<br />
/* Error! */<br />
return;<br />
}<br />
<br />
AddToTopMenu(hAdminMenu, <br />
"sm_poke",<br />
TopMenuObject_Item,<br />
AdminMenu_Poke,<br />
player_commands,<br />
"sm_poke",<br />
ADMFLAG_SLAY);<br />
}<br />
<br />
public void AdminMenu_Poke(TopMenu topmenu, <br />
TopMenuAction action,<br />
TopMenuObject object_id,<br />
int param,<br />
char[] buffer,<br />
int maxlength)<br />
{<br />
if (action == TopMenuAction_DisplayOption)<br />
{<br />
Format(buffer, maxlength, "Poke");<br />
}<br />
else if (action == TopMenuAction_SelectOption)<br />
{<br />
/* Do something! client who selected item is in param */<br />
}<br />
}</sourcepawn><br />
<br />
Note that a callback can handle more than one item. If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.<br />
<br />
=Returning to the Menu=<br />
Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via <tt>RedisplayAdminMenu</tt> in <tt>adminmenu.inc</tt>.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SourcePawn_Transitional_Syntax&diff=10995SourcePawn Transitional Syntax2020-04-14T01:40:00Z<p>Joinedsenses: Update highlighting</p>
<hr />
<div>__FORCETOC__<br />
We would like to give our users a more modern language. Pawn is showing its age; manual memory management, buffers, tags, and lack of object-oriented API are very frustrating. We can't solve everything all at once, but we can begin to take steps in the right direction.<br />
<br />
SourceMod 1.7 introduces a ''Transitional API''. It is built on a new ''Transitional Syntax'' in SourcePawn, which is a set of language tools to make Pawn feel more modern. In particular, it allows developers to use older APIs in an object-oriented manner, without breaking compatibility. Someday, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.<br />
<br />
The transitional API has the following major features and changes:<br />
* New Declarators - A cleaner way of declaring variables, similar to Java and C#.<br />
* Methodmaps - Object-oriented wrappers around older APIs.<br />
* Real Types - SourcePawn now has "int", "float", "bool", "void", and "char" as real types.<br />
* <tt>null</tt> - A new, general keyword to replace <tt>INVALID_HANDLE</tt>.<br />
<br />
=New Declarators=<br />
Developers familiar with pawn will recognize Pawn's original declaration style:<br />
<sourcepawn><br />
new Float:x = 5.0;<br />
new y = 7;<br />
</sourcepawn><br />
<br />
In the transitional syntax, this can be reworded as:<br />
<sourcepawn><br />
float x = 5.0;<br />
int y = 7;<br />
</sourcepawn><br />
<br />
The following builtin tags now have types:<br />
* <tt>Float:</tt> is <tt>float</tt>.<br />
* <tt>bool:</tt> is <tt>bool</tt>.<br />
* <tt>_:</tt> (or no tag) is <tt>int</tt>.<br />
* <tt>String:</tt> is <tt>char</tt>.<br />
* <tt>void</tt> can now be used as a return type for functions.<br />
<br />
===Rationale===<br />
In the old style, tagged variables are not real types. <tt>Float:x</tt> does not indicate a float-typed variable, it indicates a 32-bit "cell" <i>tagged</i> as a float. It is possible to remove the tag or change the tag, which while flexible, is dangerous and confusing. The syntax itself is also problematic. The parser does not have the ability to identify characters in between the tag name and the colon. Internally, the compiler cannot represent values that are not exactly 32-bit.<br />
<br />
The takeaway message is: there is no sensible way to represent a concept like "int64" or "X is a type that represents an array of floats". The tagging grammar makes it too awkward, and the compiler itself is incapable of attaching such information to a tag. We can't fix that right away, but we can begin to deprecate the tag system via a more normal declaration syntax.<br />
<br />
An easy example to see why this is necessary, is the weirdness in expressing something like this with tags:<br />
<sourcepawn><br />
native float[3] GetEntOrigin();<br />
</sourcepawn><br />
<br />
''Note on the <tt>String</tt> tag:'' The introduction of <tt>char</tt> might initially seem confusing. The reason for this is that in the future, we would like to introduce a true "string" type that acts as an object, like strings in most other languages. The existing behavior in Pawn is an array of characters, which is much lower-level. The renaming clarifies what the type really is, and leaves the door open for better types in the future.<br />
<br />
==Arrays==<br />
The new style of declaration disambiguates between two kinds of arrays. Pawn has ''indeterminate arrays'', where the size is not known, and ''determinate arrays'', where the size is known. We refer to these as "dynamic" and "fixed-length" arrays, respectively.<br />
<br />
'''A fixed-length array is declared by placing brackets after a variable name'''. For example:<br />
<sourcepawn><br />
int CachedStuff[1000];<br />
int PlayerData[MAXPLAYERS + 1] = { 0, ... };<br />
int Weapons[] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE };<br />
</sourcepawn><br />
<br />
In these examples, the array size is fixed. The size is known ahead of time and cannot change. When using brackets in this position, the array size must be specified, either via an explicit size or inferred from an initial value.<br />
<br />
A dynamic-length array has the brackets '''before the variable name''', that is, '''after the type'''. The most common case is when specifying functions that take an array as input:<br />
<sourcepawn><br />
native void SetPlayerName(int player, const char[] name);<br />
</sourcepawn><br />
<br />
Here, we are specifying that the length of <tt>name</tt> is not always known - it could be anything.<br />
<br />
Dynamic arrays can also be created in local scopes. For example,<br />
<sourcepawn><br />
void FindPlayers()<br />
{<br />
int[] players = new int[MaxClients + 1];<br />
}<br />
</sourcepawn><br />
<br />
This allocates a new array of the given size and places a reference in <tt>players</tt>. The memory is automatically freed when no longer in use.<br />
<br />
It is illegal to initialize a fixed-length array with an indeterminate array, and it is illegal to initialize a dynamic array with a fixed-array. It is also illegal to specify a fixed size on a dynamic length array.<br />
<br />
For the most part, this does not change existing Pawn semantics. It is simply new syntax intended to clarify the way arrays work.<br />
<br />
===Rationale===<br />
In the original syntax, there was a subtle difference in array declaration:<br />
<sourcepawn><br />
new array1[MAXPLAYERS + 1];<br />
new array2[MaxClients + 1];<br />
</sourcepawn><br />
<br />
Here, <tt>array1</tt> and <tt>array2</tt> are very different types: the first is an <tt>int[65]</tt> and the latter is an <tt>int[]</tt>. However, there is no syntactic difference: the compiler has to deduce whether the size expression is constant to determine the type. The new syntax clearly and explicitly disambiguates these cases, so in the future when we introduce fully dynamic and flexible arrays, we're less likely to break existing code.<br />
<br />
This may result in some confusion. Hopefully won't matter long term, as once we have true dynamic arrays, fixed arrays will become much less useful and more obscure.<br />
<br />
The rationale for restricting initializers is similar. Once we have true dynamic arrays, the restrictions will be lifted. In the meantime, we need to make sure we're limited to semantics that won't have subtle differences in the future.<br />
<br />
==Examples==<br />
<sourcepawn><br />
float x = 5.0; // Replacement for "new Float:x = 5.0;"<br />
int y = 4; // Replacement for "new y = 4;"<br />
char name[32]; // Replacement for "new String:name[32];" and "decl String:name[32];"<br />
<br />
void DoStuff(float x, int y, char[] name, int length) { //Replacement for "DoStuff(Float:x, y, String:name[], length)"<br />
Format(name, length, "%f %d", x, y); //No replacement here<br />
}<br />
</sourcepawn><br />
<br />
==View As==<br />
A new operator is available for reinterpreting the bits in a value as another type. This operator is called <tt>view_as</tt>. It is not a safe cast, in that, it can transform one type to another even if the actual value does not conform to either.<br />
<br />
In pre-transitional syntax, this was called "retagging". Retagging does not support new-style types, which is why this operator has been introduced. Example of before and after code:<br />
<br />
<sourcepawn><br />
// Before:<br />
float x = Float:array.Get(i);<br />
<br />
// After:<br />
float y = view_as<float>(array.Get(i));<br />
</sourcepawn><br />
<br />
It is worth reiterating that this is not a cast. If the value in the array is not a float, then when "viewed as" a float it will probably look very odd.<br />
<br />
==Grammar==<br />
The new and old declaration grammar is below. <br />
<br />
<pre><br />
return-type ::= return-old | return-new<br />
return-new ::= type-expr new-dims? // Note, dims not yet supported.<br />
return-old ::= old-dims? label?<br />
<br />
argdecl ::= arg-old | arg-new<br />
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?<br />
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?<br />
<br />
vardecl ::= var-old | var-new<br />
var-new ::= var-new-prefix type-expr symbol old-dims?<br />
var-new-prefix ::= "static" | "const"<br />
var-old ::= var-old-prefix tag? symbol old-dims?<br />
var-old-prefix ::= "new" | "decl" | "static" | "const"<br />
<br />
global ::= global-old | global-new<br />
global-new ::= storage-class* type-expr symbol old-dims?<br />
global-old ::= storage-class* tag? symbol old-dims?<br />
<br />
storage-class ::= "public" | "static" | "const" | "stock"<br />
<br />
type-expr ::= (builtin-type | symbol) new-dims?<br />
builtin-type ::= "void"<br />
| "int"<br />
| "float"<br />
| "char"<br />
| "bool"<br />
<br />
tags ::= tag-vector | tag<br />
tag-vector ::= '{' symbol (',' symbol)* '}' ':'<br />
tag ::= label<br />
<br />
new-dims ::= ('[' ']')*<br />
old-dims ::= ('[' expr? ']')+<br />
<br />
label ::= symbol ':'<br />
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)<br />
</pre><br />
<br />
Also note, there is no equivalent of <tt>decl</tt> in the new declarator syntax. <tt>decl</tt> is considered to be dangerous and unnecessary. If an array's zero initialization is too costly, consider making it static or global.<br />
<br />
=Methodmaps=<br />
==Introduction==<br />
Methodmaps are simple: they attach methods onto an enum. For example, here is our legacy API for Handles:<br />
<br />
<sourcepawn><br />
native CloneHandle(Handle handle);<br />
native CloseHandle(Handle handle);<br />
</sourcepawn><br />
<br />
This is a good example of our legacy API. Using it generally looks something like:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
CloseHandle(array);<br />
</sourcepawn><br />
<br />
Gross! A Methodmap can clean it up by attaching functions to the <tt>Handle</tt> tag, like this:<br />
<sourcepawn><br />
methodmap Handle {<br />
public Clone() = CloneHandle;<br />
public Close() = CloseHandle;<br />
};<br />
</sourcepawn><br />
<br />
Now, our earlier array code can start to look object-oriented:<br />
<sourcepawn><br />
Handle array = CreateAdtArray();<br />
PushArrayCell(array, 4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
With a full methodmap for Arrays, for example,<br />
<sourcepawn><br />
methodmap ArrayList < Handle<br />
{<br />
public native ArrayList(); // constructor<br />
public native void Push(any value);<br />
};<br />
</sourcepawn><br />
<br />
We can write even more object-like code:<br />
<sourcepawn><br />
ArrayList array = new ArrayList();<br />
array.Push(4);<br />
delete array;<br />
</sourcepawn><br />
<br />
(Note: the official API does not expose methods on raw Handles.)<br />
<br />
==Inheritance==<br />
The Handle system has a "weak" hierarchy. All handles can be passed to <tt>CloseHandle</tt>, but only AdtArray handles can be passed to functions like <tt>PushArrayCell</tt>. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.<br />
<br />
For example, here is a transitional API for arrays:<br />
<br />
<sourcepawn><br />
native AdtArray CreateAdtArray();<br />
<br />
methodmap AdtArray < Handle {<br />
public PushCell() = PushArrayCell;<br />
};<br />
</sourcepawn><br />
<br />
Note that <tt>CreateAdtArray</tt> now returns <tt>AdtArray</tt> instead of <tt>Handle</tt>. Normally that would break older code, but since <tt>AdtArray</tt> inherits from <tt>Handle</tt>, there is a special rule in the type system that allows coercing an <tt>AdtArray</tt> to a <tt>Handle</tt> (but not vice versa).<br />
<br />
Now, the API looks much more object-oriented:<br />
<sourcepawn><br />
AdtArray array = CreateAdtArray();<br />
array.PushCell(4);<br />
array.Close();<br />
</sourcepawn><br />
<br />
==Inline Methods==<br />
Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:<br />
<sourcepawn><br />
native void AdtArray.PushCell(AdtArray this, value);<br />
</sourcepawn><br />
<br />
(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)<br />
<br />
It's also possible to define new functions without a native:<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public native void PushCell(value);<br />
<br />
public void PushCells(list[], count) {<br />
for (int i = 0; i < count; i++) {<br />
this.PushCell(i);<br />
}<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Lastly, we can also define accessors. or example,<br />
<sourcepawn><br />
methodmap AdtArray {<br />
property int Size {<br />
public get() = GetArraySize;<br />
}<br />
property bool Empty {<br />
public get() {<br />
return this.Size == 0;<br />
}<br />
}<br />
property int Capacity {<br />
public native get();<br />
}<br />
};<br />
</sourcepawn><br />
<br />
The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:<br />
<br />
<sourcepawn><br />
native int AdtArray.Capacity.get(AdtArray this);<br />
</sourcepawn><br />
<br />
Setters are also supported. For example:<br />
<br />
<sourcepawn><br />
methodmap Player {<br />
property int Health {<br />
public native get();<br />
public native set(int health);<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Custom Tags==<br />
Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:<br />
<br />
<sourcepawn><br />
methodmap AdminId {<br />
public int Rights() {<br />
return GetAdminFlags(this);<br />
}<br />
};<br />
</sourcepawn><br />
<br />
Now, for example, it is possible to do:<br />
<br />
<sourcepawn>GetPlayerAdmin(id).Rights()</sourcepawn><br />
<br />
==Constructors and Destructors==<br />
Methodmaps can also define constructors and destructors, which is useful if they are intended to behave like actual objects. For example,<br />
<br />
<sourcepawn><br />
methodmap AdtArray {<br />
public AdtArray(blocksize = 1);<br />
public ~AdtArray();<br />
public void PushCell(value);<br />
};<br />
</sourcepawn><br />
<br />
Now AdtArrays can be used in a fully object-oriented style:<br />
<sourcepawn><br />
AdtArray array = new AdtArray();<br />
array.PushCell(10);<br />
array.PushCell(20);<br />
array.PushCell(30);<br />
delete array;<br />
</sourcepawn><br />
<br />
==Caveats==<br />
There are a few caveats to methodmaps:<br />
<ol><br />
<li>CloseHandle() is not yet gone. It is required to call <tt>delete</tt> on any object that previously would have required CloseHandle().</li><br />
<li>There can be only one methodmap for a tag.</li><br />
<li>When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!</li><br />
<li>Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via <tt>struct</tt> or <tt>class</tt>). Methodmaps cannot be created on those types.</li><br />
<li>Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like <tt>Float:CreateAdtArray()</tt>. This is necessary for backwards compatibility, so methodmap values can flow into natives like <tt>PrintToServer</tt> or <tt>CreateTimer</tt>.</li><br />
<li>It is not possible to inherit from anything other than another previously declared methodmap.</li><br />
<li>Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.</li><br />
<li>Destructors can only be native. When we are able to achieve garbage collection, destructors will be removed.</li><br />
<li>The signatures of methodmaps must use the new declaration syntax.</li><br />
<li>Methodmaps must be declared before they are used.</li><br />
</ol><br />
<br />
==Grammar==<br />
The grammar for methodmaps is:<br />
<pre><br />
visibility ::= "public"<br />
method-args ::= arg-new* "..."?<br />
<br />
methodmap ::= "methodmap" symbol? { methodmap-item* } term<br />
methodmap-item ::=<br />
visibility "~"? symbol "(" ")" "=" symbol term<br />
| visibility "native" type-expr "~"? symbol methodmap-symbol "(" method-args ")" term<br />
| visibility type-expr symbol "(" method-args ")" func-body term<br />
| "property" type-expr symbol { property-decl } term<br />
property-func ::= "get" | "set"<br />
property-decl ::= visibility property-impl<br />
property-impl ::=<br />
"native" property-func "(" ")" term<br />
| property-func "(" ")" func-body term<br />
| property-func "(" ")" "=" symbol<br />
</pre><br />
<br />
=Typedefs=<br />
<br />
Function tags and function enums have been deprecated in favor of a more modern syntax. Currently, they can still only create tag names for functions. Future versions will support arbitrary types.<br />
<br />
Upgrading both functags and funcenums is simple. Below are two examples:<br />
<br />
<sourcepawn><br />
functag public Action:SrvCmd(args);<br />
<br />
funcenum Timer {<br />
Action:public(Handle:Timer, Handle:hndl),<br />
Action:public(Handle:timer),<br />
};<br />
</sourcepawn><br />
<br />
Now, this becomes:<br />
<sourcepawn><br />
typedef SrvCmd = function Action (int args);<br />
<br />
typeset Timer {<br />
function Action (Handle timer, Handle hndl);<br />
function Action (Handle timer);<br />
};<br />
</sourcepawn><br />
<br />
The grammar for the new syntax is:<br />
<br />
<pre><br />
typedef ::= "typedef" symbol "=" full-type-expr term<br />
full-type-expr ::= "(" type-expr ")"<br />
| type-expr<br />
type-expr ::= "function" type-name "(" typedef-args? ")"<br />
typedef-args ::= "..."<br />
| typedef-arg (", " "...")?<br />
</pre><br />
<br />
Note that typedefs only support new-style types.<br />
<br />
=Enum Structs=<br />
<br />
Enum structs were a previously unsupported mechanism for emulating structs through arrays. As of SourceMod 1.10, this mechanism is now fully supported through Transitional Syntax. Here is an example of enum struct syntax:<br />
<br />
<sourcepawn><br />
enum struct Rectangle {<br />
int x;<br />
int y;<br />
int width;<br />
int height;<br />
<br />
int Area() {<br />
return this.width * this.height;<br />
}<br />
}<br />
<br />
void DoStuff(Rectangle r) {<br />
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);<br />
}<br />
</sourcepawn><br />
<br />
Enum structs are syntactic sugar and are internally represented as arrays. This means they pass by-reference to function arguments, and the "&" token is not required (nor is it allowed).<br />
<br />
Note that even though enum structs are actually arrays, for the most part they cannot be used as arrays. The exception is when interacting with opaque data structures like <tt>ArrayList</tt>. For example, this is considered valid:<br />
<br />
<sourcepawn><br />
void SaveRectangle(ArrayList list, const Rectangle r) {<br />
list.PushArray(r, sizeof(r));<br />
}<br />
<br />
void PopArray(ArrayList list, Rectangle r) {<br />
list.GetArray(list.Length - 1, r, sizeof(r));<br />
list.Erase(list.Length - 1);<br />
}<br />
</sourcepawn><br />
<br />
But this is not allowed:<br />
<br />
<sourcepawn><br />
Rectangle r;<br />
PrintToServer("%d", r[0]);<br />
</sourcepawn><br />
<br />
The grammar for enum structs is as follows:<br />
<br />
<pre><br />
enum-struct ::= "enum" "struct" symbol "{" newline enum-struct-entry enum-struct-entry* "}" term<br />
enum-struct-entry ::= enum-struct-field<br />
| enum-struct-method<br />
enum-struct-field ::= type-expr symbol old-dims? term<br />
enum-struct-method ::= type-expr symbol "(" method-args ")" func-body term<br />
</pre><br />
<br />
=Enforcing new syntax=<br />
<br />
You can enforce the new syntax in 1.7 by using <pawn>#pragma newdecls required</pawn> ontop of your code, after the includes (or else current 1.7 includes which contain old syntax will be read with new-syntax rules).<br />
<br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Writing_Extensions&diff=10987Writing Extensions2020-03-30T05:08:23Z<p>Joinedsenses: /* Dynamic Autoloading */ Updated syntax</p>
<hr />
<div>SourceMod's Extension System is a powerful way to greatly enhance the flexibility of your SourceMod plugins. You can take full advantage of SourceMod's Core, using the interfaces it provides, to create C++ plugins, plugin events, plugin native functions, and shared interfaces. Extensions can also hook into Metamod:Source.<br />
<br />
This article goes into the details of creating a SourceMod Extension. Knowledge of C++ is required.<br />
<br />
=SDK Structure=<br />
First, download the [[SourceMod SDK]] from the [https://github.com/alliedmodders/sourcemod/releases SourceMod github], and one of the SDKs from the [https://github.com/alliedmodders/hl2sdk hl2sdk github]. The directory structure should contain these folders:<br />
<code><br />
* hl2sdk-something - One of the HL2 SDKs<br />
* sourcemod-version - The sourcemod package<br />
** extensions - Sourcemod Extensions code<br />
*** curl - Source code to the cURL extension<br />
*** geoip - Source code to the GeoIP extension<br />
*** mysql - Source code to the MySQL extension<br />
** plugins - Source code to all of SourceMod's plugins<br />
*** include - The include files which document plugin API<br />
** public - Interface files for SourceMod's Core Interfaces<br />
*** extensions - Interfaces that are provided by extensions<br />
*** '''sample_ext''' - The Sample Extension SDK<br />
**** extension.cpp - Sample C++ plugin<br />
**** extension.h - Sample C++ header file<br />
**** Makefile - Sample C++ plugin compiler<br />
**** smsdk_config.h - Sample C++ plugin settings<br />
** sourcepawn - The include/interface files for SourcePawn<br />
*** compiler - The SourcePawn Compiler<br />
</code><br />
<br />
=Starting an Extension=<br />
For simplicity, this article will assume you are using the SDK base files. Navigate to <code>extensions/public/sample_ext</code>, and ensure that you can compile your code. If using the command line, <code>make</code> should do this, if using Visual Studio, see [[#Setting up Visual Studio|Setting up Visual Studio]].<br />
<br />
<br />
The first step of creating your extension is to configure it. Open the <tt>smsdk_config.h</tt> file and edit each of the following defines. Use the information below to guide you.<br />
<code><br />
*SMEXT_CONF_NAME - The general name for your Extension (should be short).<br />
*SMEXT_CONF_DESCRIPTION - A short description of what your extension does.<br />
*SMEXT_CONF_VERSION - A version number string. By convention, SourceMod uses the four set build number notation, so build 1.2.3.4 means:<br />
**1 is the "Major" release version.<br />
**2 is the "Minor" release version.<br />
**3 is the "Maintenance" or "Revision" version.<br />
**4 is the "Build," usually an internal source control number.<br />
*SMEXT_CONF_AUTHOR - Your name (or whoever/whatever authored the plugin).<br />
*SMEXT_CONF_URL - The URL/homepage of this extension.<br />
*SMEXT_CONF_LOGTAG - The logtag your extension will use for SourceMod logging. By convention, try to keep this under ten characters or so, and to make it all caps.<br />
*SMEXT_CONF_LICENSE - Right now, the SourceMod Dev Team mandates that 3rd party extensions must be under an Open Source license. Putting a license abbreviation or very short message here is appropriate. For more information, see [https://www.sourcemod.net/license.php the SourceMod license].<br />
*SMEXT_CONF_DATESTRING - You do not need to modify this.<br />
</code><br />
If your plugin requires using SourceHook or Metamod:Source, uncomment this line:<br />
<code>#define SMEXT_CONF_METAMOD</code><br />
<br />
<br />
Next, you should change the name of your extension. By convention, your extension's class name should start with a capital letter.<br />
* First, open <code>extension.h</code> and change "Sample" in this line:<br />
<cpp>class Sample : public SDKExtension</cpp><br />
* Next, open <code>extension.cpp</code> change these two lines:<br />
<cpp>Sample g_Sample;<br />
SMEXT_LINK(&g_Sample);</cpp><br />
<br />
==Visual Studio Users==<br />
Make sure you change the name of your <tt>.dll</tt> file. The naming convention for SourceMod extensions is <tt>name.ext.dll</tt>, where <tt>name</tt> is a short name for your extension. You are free to change this, but it is highly recommended that you keep the <tt>.ext.dll</tt> intact.<br />
<br />
You can do this by going to <tt>Project</tt> -> <tt>Properties</tt> -> <tt>Configuration Properties</tt> -> <tt>Linker</tt> -> <tt>General</tt>. Set the <tt>Output File</tt> option appropriately.<br />
<br />
==Linux Users==<br />
Simply edit the <tt>Makefile</tt> and change the <tt>BINARY</tt> line near the top. Make sure you do not add any trailing spaces at the end of the name, or the build won't come out right.<br />
<br />
You should now be able to compile a blank extension!<br />
<br />
=Creating Native Functions=<br />
''Main article: [[Natives (SourceMod Development)]]''<br />
<br />
Most of the time, an extension exists to add "native functions" to the plugin system. Native functions are simply the functions that plugins can use in SourceMod. They are coded in C++ and have a short prototype declaration in an include file.<br />
<br />
==Prototyping==<br />
The first step to creating a native is to <tt>spec it</tt>, or <tt>prototype</tt> it. This is a simple but often time consuming process, but if you get in the habit of it, your documentation will be much better. Let's first create a very simply native. Here is a prototype for it, which we will place in <tt>sample.inc</tt>:<br />
<sourcepawn>/**<br />
* Returns the square of a number.<br />
*<br />
* @param num Number to square.<br />
* @return The square of num.<br />
*/<br />
native int SquareNumber(num);</sourcepawn><br />
<br />
The <tt>native</tt> keyword tells the compiler that this function exists in an outside source. The comment style is similar to [http://www.stack.nl/~dimitri/doxygen/ doxygen] or [http://java.sun.com/j2se/javadoc/ Javadoc], and is preferred by the SourceMod Development Team.<br />
<br />
==Implementing==<br />
Now that our function is documented, it's time to create it! Each native function is bound to a C++ function. The prototype for this function looks like:<br />
<cpp>cell_t Function(IPluginContext *pContext, const cell_t *params);</cpp><br />
<br />
A quick explanation of these types:<br />
*<tt>cell_t</tt> - This is a 32bit signed integer, the generic data type for Pawn.<br />
*<tt>IPluginContext</tt> - This interface provides Virtual Machine functions for retrieving or modifying memory in the plugin.<br />
*<tt>params</tt> - This is the "stack" of parameters that the plugin passed. It is an array from [0..N] where 0 contains N. This means <tt>params[0]</tt> contains the number of arguments passed to the native.<br />
**For example, if one parameter was passed, and the parameter is 25:<br />
***<tt>params[0]</tt> == 1<br />
***<tt>params[1]</tt> == 25<br />
**Even though it tells you how many parameters are passed, you do not need to verify this number. The compiler will always pass the correct number of parameters in. The only time you need to verify it is for backwards compatibility or variable parameter lists, which will be discussed later.<br />
<br />
Now, let's write our native function:<br />
<cpp>cell_t SquareNumber(IPluginContext *pContext, const cell_t *params)<br />
{<br />
cell_t number = params[1];<br />
return number * number;<br />
}</cpp><br />
<br />
==Binding==<br />
Now, the final step is to bind our native. Natives are bound in ''native lists''. Each list must be terminated by a <tt>NULL</tt> bind. Example:<br />
<cpp>const sp_nativeinfo_t MyNatives[] = <br />
{<br />
{"SquareNumber", SquareNumber},<br />
{NULL, NULL},<br />
};</cpp><br />
<br />
The first column/member is a string containing the name you've assigned to the native. The second column/member is the C++ function you're binding to. Here, both contain the same name, but it is certainly possible to bind a function to any valid Pawn function name, and likewise, you can bind one function to more than one name.<br />
<br />
Lastly, you must tell Core about your native list. There are two places that are good to do this. Whichever you choose, you must uncomment the function in <tt>extension.h</tt> and implement it in <tt>extension.cpp</tt>.<br />
*<tt>SDK_OnLoad</tt> - This is called when your extension is first loaded. If you do not require any outside interfaces, it is safe to add natives here.<br />
*<tt>SDK_OnAllLoaded</tt> - This is called when all extensions have been loaded. This is generally a better place to add natives.<br />
<br />
Let's say we choose <tt>SDK_OnAllLoaded</tt>, we'd then have code like this:<br />
<cpp>void Sample::SDK_OnAllLoaded()<br />
{<br />
sharesys->AddNatives(myself, MyNatives);<br />
}</cpp><br />
''Note: <tt>myself</tt> is a global variable declared in the SDK. It is a pointer that identifies your extension.''<br />
<br />
For more information on writing natives, see [[Natives (SourceMod Development)]].<br />
<br />
<br />
=Creating Events/Forwards=<br />
Events are an integral part to SourceMod. An Event is formally called a ''Forward''. Forwards are functions inside a plugin which are not called by the plugin itself, but are triggered from an external source. For example, <tt>OnClientConnect</tt> is a forward which is triggered when a player connects to a server.<br />
<br />
There are two major types of forwards:<br />
*'''Managed''': This forward is global and has a name. To use this forward, the function must be declared as <tt>public</tt> inside the user's script. Any plugin having this public function will receive the event.<br />
*'''Unmanaged''': This forward is private in nature. For example, the <tt>HookUserMessage</tt> native lets users specify a function to be called when a specific user message is sent. This is done with an unmanaged forward, and adding functions to this forward is not automatic. In general, function calls for unmanaged forwards are not public, although they can be.<br />
<br />
''Note: To enable the Forward interface, you must uncomment the definition <tt>SMEXT_ENABLE_FORWARDSYS</tt> in smsdk_config.h.''<br />
==Managed Forwards==<br />
Let's say we want to create a forward that will be called when a player uses say chat. For simplicity, let's assume you already have a function that tells you when a player says say chat. We will just be creating the forward for it.<br />
<br />
As we did before, first let's prototype our global forward. In our include file, we add this:<br />
<sourcepawn>/**<br />
* @brief Called whenever a client says something in chat.<br />
*<br />
* @param client Index of the client.<br />
* @param text String containing the say text.<br />
* @return Plugin_Handled to block text from printing, Plugin_Continue otherwise.<br />
*/<br />
forward Action OnPlayerSayChat(int client, const char[] text);</sourcepawn><br />
<br />
The <tt>forward</tt> keyword tells the compiler that any function having this name must be declared exactly the same. In a plugin, this would<br />
<br />
The next step is to declare your forward somewhere at the top of your <tt>extension.cpp</tt> file. This will look like:<br />
<cpp>IForward *g_pSayChat = NULL</cpp><br />
<br />
Next, re-using our code from above, let's create this forward in <tt>SDK_OnAllLoaded</tt>:<br />
<cpp>void Sample::SDK_OnAllLoaded()<br />
{<br />
sharesys->AddNatives(myself, MyNatives);<br />
g_pSayChat = forwards->CreateForward("OnPlayerSayChat", ET_Event, 2, NULL, Param_Cell, Param_String);<br />
}</cpp><br />
<br />
Explanation of parameters:<br />
*<tt>"OnPlayerSayChat"</tt> - The name of our forward.<br />
*<tt>ET_Event</tt> - Since forwards can execute multiple functions in multiple scripts, this is one of the ways of specifying what to do with the return value of each function. <tt>ET_Event</tt> means "return the highest value, do not allow stops." A stop refers to <tt>Pl_Stop</tt>, which lets a plugin block the rest of the event chain.<br />
*<tt>2</tt> - This is the number of parameters our forward will have.<br />
*<tt>NULL</tt> - Not used in our example. This lets you pass parameters by array rather than variable arguments.<br />
*<tt>Param_Cell</tt> - The first parameter is a cell (integer).<br />
*<tt>Param_String</tt> - The second parameter is a string.<br />
<br />
Now, we write a quick function in our module to call this forward:<br />
<cpp>/** Fires the say chat event in plugins. Returns true to allow the text, false to block. */<br />
bool FireChatEvent(int client, const char *text)<br />
{<br />
if (!g_pSayChat)<br />
{<br />
return true;<br />
}<br />
<br />
cell_t result = 0;<br />
g_pSayChat->PushCell(client);<br />
g_pSayChat->PushString(text);<br />
g_pSayChat->Execute(&result);<br />
<br />
if (result == Pl_Handled)<br />
{<br />
return false;<br />
}<br />
<br />
return true;<br />
}</cpp><br />
<br />
As you can see, this was pretty simple. Each parameter is "pushed" one at a time, in ascending order. First we push the client index as a cell, then the text as a string. Once done, we execute, and the value will be returned by reference.<br />
<br />
Lastly, there is a caveat. Unlike natives, SourceMod does not automatically clean up forwards for us when are done. We have to make sure we destroy them. Uncomment <tt>SDK_OnUnload</tt> from <tt>extension.h</tt> and implement it like so:<br />
<cpp>void Sample::SDK_OnUnload()<br />
{<br />
forwards->ReleaseForward(g_pSayChat);<br />
}</cpp><br />
<br />
==Unmanaged Forwards==<br />
Now things get a bit more complicated. However, unmanaged forwards are essentially the same -- they just give you more flexibility. Let's consider the managed example with a new twist. We want to let users add and remove hooks from their plugins, rather than having one global forward. The standard way to do this is with ''function IDs''. <br />
<br />
Example prototype:<br />
<sourcepawn>typedef SayChatHook = function Action (int client, const char[] text);<br />
<br />
native void AddSayChatHook(SayChatHook hook);<br />
native void RemoveSayChatHook(SayChatHook hook);</sourcepawn><br />
<br />
The <tt>funcenum</tt> syntax lets you define all the valid prototypes for your hook. In this example, the only valid method is a function declared like this (only MyHook can be changed):<br />
<sourcepawn>Action MyHook(int client, const char[] text)</sourcepawn><br />
<br />
How do we make such a beast? The first step is setting up our forward.<br />
<cpp>IChangeableForward *g_pSayChat = NULL;<br />
/* ... */<br />
void Sample::SDK_OnAllLoaded()<br />
{<br />
g_pSayChat = forwards->CreateForwardEx(NULL, ET_Hook, 2, NULL, Param_Cell, Param_String); <br />
}</cpp><br />
<br />
Notice two big differences. We now use <tt>IChangeableForward</tt> instead of <tt>IForward</tt>, and we use <tt>CreateForwardEx</tt> instead. The initial first parameter specifies a name for our forward - we're going to ignore this.<br />
<br />
Now, we need to create our natives. These will be fairly simple:<br />
<cpp>cell_t AddSayChatHook(IPluginContext *pContext, const cell_t *params)<br />
{<br />
g_pSayChat->AddFunction(pContext, static_cast<funcid_t>(params[1]));<br />
return 1;<br />
}<br />
<br />
cell_t RemoveSayChatHook(IPluginContext *pContext, const cell_t *params)<br />
{<br />
IPluginFunction *pFunction = pContext->GetFunctionById(static_cast<funcid_t>(params[1]));<br />
g_pSayChat->RemoveFunction(pFunction);<br />
return 1;<br />
}</cpp><br />
''Note: These natives must be added -- that step is removed here and explained earlier.''<br />
<br />
You do not need to worry about a plugin unloading - Core will automatically remove the functions for you. Since an <tt>IChangeableForward</tt> is also an <tt>IForward</tt>, the <tt>FireChatEvent</tt> function from earlier does not need to change. We're done!<br />
<br />
==Creating Interfaces==<br />
Do you want your extension to share an interface? If so, first you should create an ''interface file''. This file should contain everything your interface needs - this way other authors can easily reference it. Your interface must inherit the <tt>SMInterface</tt> class. It must also declare two global macros that uniquely identify your interface. It must also implement two functions: <tt>GetInterfaceName</tt> and <tt>GetInterfaceVersion</tt>.<br />
<br />
The two defines must start with <tt>SMINTERFACE_</tt> and end with <tt>_NAME</tt> and <tt>_VERSION</tt> respectively. The first must be a string and the second must be an unsigned integer. This integer represents your interface's version number. While you are free to use it however you please, by default it should be linearly increased. If you make breaking changes or change the versioning scheme, you must overload the <tt>IsVersionCompat</tt> function in <tt>SMInterface</tt>.<br />
<br />
Example:<br />
<cpp>#ifndef _INCLUDE_MYINTERFACE_FILE_H_<br />
#define _INCLUDE_MYINTERFACE_FILE_H_<br />
<br />
#include <IShareSys.h><br />
<br />
#define SMINTERFACE_MYINTERFACE_NAME "IMyInterface"<br />
#define SMINTERFACE_MYINTERFACE_VERSION 1<br />
<br />
class IMyInterface : public SourceMod::SMInterface<br />
{<br />
public:<br />
virtual const char *GetInterfaceName()<br />
{<br />
return SMINTERFACE_MYINTERFACE_NAME;<br />
}<br />
virtual unsigned int GetInterfaceVersion()<br />
{<br />
return SMINTERFACE_MYINTERFACE_VERSION;<br />
}<br />
public:<br />
/**<br />
* @brief This function does nothing.<br />
*/<br />
virtual void DoNothingAtAll() =0;<br />
};<br />
<br />
#endif //_INCLUDE_MYINTERFACE_FILE_H_</cpp><br />
<br />
Notice, of course, that our interface is ''pure virtual''. This means it must be implemented in the extension. Assuming you know C++, this should be fairly straight forward, so let's skip right to how to add this interface to the SourceMod shared system:<br />
<cpp>bool Sample::SDK_OnLoad(char *error, size_t err_max, bool late)<br />
{<br />
sharesys->AddInterface(myself, &g_MyInterface);<br />
return true;<br />
}</cpp><br />
''Note: We do this in <tt>SDK_OnLoad()</tT> instead of <tt>SDK_OnAllLoaded()</tt>. Otherwise, an extension might try to search for your interface during <tt>SDK_OnAllLoaded()</tt>, and it might fail depending on the load order.''<br />
<br />
That simple? Yup! Now other extensions can use your interface. <br />
<br />
<br />
=Using Interfaces/Other Extensions=<br />
There are a variety of interfaces that are available, but not loaded by default in the Extension SDK. These interfaces can be retrieved and used in your extension.<br />
<br />
==Core Interfaces==<br />
If they are provided by Core, getting them is very simple. Many interfaces can simply be uncommented in <tt>smsdk_config.h</tt> and they will become usable. See the "Available Interfaces" list below for the exposure list.<br />
<br />
Otherwise, most of the <tt>.h</tt> interface files in the root of the <tt>public</tt> folder in the SDK are Core interfaces. For example, code to use the IPluginManager interface might look like this:<br />
<cpp>#include <IPluginSys.h><br />
<br />
IPluginManager *g_pPlugins = NULL;<br />
<br />
bool Sample::SDK_OnLoad(char *error, size_t err_max, bool late)<br />
{<br />
SM_GET_IFACE(PLUGINSYSTEM, g_pPlugins);<br />
return true;<br />
}</cpp><br />
<br />
Where did we get <tt>PLUGINSYSTEM</tt> from? Observe the two lines in <tt>IPluginSys.h</tt>:<br />
<cpp>#define SMINTERFACE_PLUGINSYSTEM_NAME "IPluginManager"<br />
#define SMINTERFACE_PLUGINSYSTEM_VERSION 1</cpp><br />
<br />
The <tt>SM_GET_IFACE</tt> macro uses the text in between the <tt>SMINTERFACE_</tt> and the <tt>_NAME</tt> characters. It also uses the version for compatibility checking. If it can't find the interface, it automatically prints to the error buffer and returns false.<br />
<br />
===Default Interfaces===<br />
There are interfaces which are grabbed by the Extension SDK by default. You do not need to query for them on load, or even check if they are valid or not. They are:<br />
*<tt>IShareSys</tt> - Exposed as <tt>sharesys</tt>, found in <tt>IShareSys.h</tt><br />
*<tt>HANDLESYSTEM</tt> - Exposed as <tt>handlesys</tt>, found in <tt>IHandleSys.h</tt><br />
*<tt>SOURCEMOD</tt> - Exposed as <tt>g_pSM</tt>, found in <tt>ISourceMod.h</tt><br />
*<tt>FORWARDMANAGER</tt> - Exposed as <tt>forwards</tt>, found in <tt>IForwardSys.h</tt><br />
<br />
===Available Interfaces===<br />
*<tt>SMEXT_ENABLE_FORWARDSYS</tt>: <tt>forwards</tt><br />
*<tt>SMEXT_ENABLE_HANDLESYS</tt>: <tt>handlesys</tt><br />
*<tt>SMEXT_ENABLE_PLAYERHELPERS</tt>: <tt>playerhelpers</tt><br />
*<tt>SMEXT_ENABLE_DBMANAGER</tt>: <tt>dbi</tt><br />
*<tt>SMEXT_ENABLE_GAMECONF</tt>: <tt>gameconfs</tt><br />
*<tt>SMEXT_ENABLE_MEMUTILS</tt>: <tt>memutils</tt><br />
*<tt>SMEXT_ENABLE_GAMEHELPERS</tt>: <tt>gamehelpers</tt><br />
*<tt>SMEXT_ENABLE_TIMERSYS</tt>: <tt>timersys</tt><br />
*<tt>SMEXT_ENABLE_THREADER</tt>: <tt>threader</tt><br />
<br />
==External Interfaces==<br />
The situation changes if your extension requires ''another extension'', since extensions may load in a completely random order. The first step is to mark the other extension as a dependency. Let's say you want to use the <tt>IThreader.h</tt> interfaces from <tt>threader.ext.dll</tt>. First, we declare it as such:<br />
<br />
<cpp>bool Sample::SDK_OnLoad(char *error, size_t err_max, bool late)<br />
{<br />
sharesys->AddDependency(myself, "thread.ext", true, true);<br />
return true;<br />
}</cpp><br />
<br />
The two boolean parameters to <tt>AddDependency</tt> mean, respectively: "try to automatically load this extension" and "this extension cannot work without the dependency."<br />
<br />
Second, we query for the interface in <tt>SDK_OnAllLoaded</tt>. Since we don't know when <tt>thread.ext</tt> will actually be loaded, we have to wait until everything is definitely loaded.<br />
<cpp>IThreader *g_pThreader = NULL;<br />
/* ... */<br />
void Sample::SDK_OnAllLoaded()<br />
{<br />
SM_GET_LATE_IFACE(THREADER, g_pThreader);<br />
}</cpp><br />
<br />
Third, we need to find a way to fail if this interface was never found. The way to do this is by uncommenting <tt>QueryRunning</tt> from <tt>extension.h</tt> and implementing it:<br />
<cpp>bool Sample::QueryRunning(char *error, size_t err_max)<br />
{<br />
SM_CHECK_IFACE(THREADER, g_pThreader);<br />
return true;<br />
}</cpp><br />
<br />
When SourceMod queries your extension, the macro above will either validate the pointer or return false. If it returns false, SourceMod marks your extension as failed.<br />
<br />
==Caveats==<br />
===NULL Interfaces===<br />
There are a few ways that external interfaces can make your code complicated. The first is simple but a common oversight. Don't assume interfaces will be okay. For example, say we want to create a thread once we get the <tt>IThreader</tt> interface. Our code should something like this:<br />
<br />
<cpp>void Sample::SDK_OnAllLoaded()<br />
{<br />
SM_GET_IFACE(THREADER, g_pThreader);<br />
<br />
if (QueryRunning(NULL, 0))<br />
{<br />
g_pThreader->MakeThread(/* stuff */);<br />
}<br />
}</cpp><br />
<br />
Note that we ''query ourself'' in order to find if it's safe to continue executing code. We could also have simply checked if <tt>g_pThreader</tt> is <tt>NULL</tt>, but self-querying has a bonus: if your extension continues to do things like hook events or add listeners, you will be preventing your extension from entirely initializing itself while one interface is bad. Always make sure you have sanity checks like this where needed.<br />
<br />
===Dynamic Unloading===<br />
The second major caveat is that extensions can be dynamically unloaded. If you specified yourself as having a dependency and that each dependency is required, you will have no problem. Your extension will be unloaded before the interface is removed, and you can clean up memory/hooks in your <tt>SDK_OnUnload()</tt> code. There are two situations where this is a different story.<br />
<br />
====Optional Interfaces====<br />
The first situation is if you are not requiring an extension that contains an interface. Now, when the extension unloads, your extension will not be unloaded first. This creates a small problem, as you must clean up without being unloaded. There are two functions you can implement in your extension class to resolve this, both documented in <tt>IExtensionSys.h</tt>:<br />
*<tt>QueryInterfaceDrop</tt> - Allows you to request unloading when a specific interface is unloaded. This is the default behavior for all interfaces. If you want to block this functionality, continue reading.<br />
*<tt>NotifyInterfaceDrop</tt> - This is called when an interface is dropped. If your plugin will not be unloaded, you can use this to clean up any resources on a specific interface being removed.<br />
<br />
====Circular Dependencies====<br />
The second situation is a bit more complicated. It is possible for two extensions to have circular dependencies. For example:<br />
*Extension "A" requires extension "B."<br />
*Extension "B" requires extension "A."<br />
<br />
If either extension is unloaded, the opposite extension is unloaded normally. The first extension then receives the interface drop callbacks. In this situation, it is essential that the <tt>NotifyInterfaceDrop</tt> function be implemented and used with the circular interface. Otherwise, you risk crashing when your extension unloads (or at the very least, leaking memory). Since the actual drop order is undefined, it means both extensions must implement this function to be safe.<br />
<br />
<br />
=Automatic Loading=<br />
There are two types of automatic loading: Dynamic and Global. Dynamic loading means your extension is only loaded when a plugin requires it. Global loading means your extension loads no matter what.<br />
<br />
==Dynamic Autoloading==<br />
Dynamic loading requires that you create an include file for plugins. Plugins that include your file will automatically load your extension. This requires implementing the <tt>Extension</tt> structure found in <tt>core.inc</tt>. You must expose the structure as <tt>public</tt>, and the name must start with "<tt>__ext_</tt>".<br />
<br />
<sourcepawn>/**<br />
* Do not edit below this line!<br />
*/<br />
public Extension __ext_geoip = <br />
{<br />
name = "GeoIP",<br />
file = "geoip.ext",<br />
#if defined AUTOLOAD_EXTENSIONS<br />
autoload = 1,<br />
#else<br />
autoload = 0,<br />
#endif<br />
#if defined REQUIRE_EXTENSIONS<br />
required = 1,<br />
#else<br />
required = 0,<br />
#endif<br />
};<br />
</sourcepawn><br />
<br />
Explanations:<br />
*<b>name</b>: The name of your module as written in <tt>SMEXT_CONF_NAME</tt>.<br />
*<b>file</b>: The platform-inspecific portion of your extension's file name.<br />
*<b>autoload</b>: Specifies that your module should always dynamically autoload by default. You can tweak this to either never autoload or always autoload (without letting the user toggle).<br />
*<b>required</b>: Specifies whether or not this extension is '''required''' by your plugin. If for some reason your extension fails to load (or is not found), the plugin will also fail to load. This is changeable if you wish to override the default behavior.<br />
<br />
You can copy and paste this example, but remember to modify the following portions:<br />
*<b>__ext_geoip</b>: Change the "geoip" portion to your own unique variable name.<br />
*<b>"GeoIP</b>: Change the GeoIP portion to your extension's name. This should match the name you chose as <tt>SMEXT_CONF_NAME</tt>.<br />
*<b>"geoip.ext"</b>: Change this to your extension's file name, without the .dll/.so portion.<br />
<br />
==Global Autoloading==<br />
To have your extension always autoload, you must create a ''.autoload'' file in the <tt>extensions</tt> folder. For example, to have the <tt>threader.ext.dll</tt> or <tt>threader.ext.so</tt> extensions autoload, you'd create the following file in <tt>extensions</tt>: <tt>threader.autoload</tt>.<br />
<br />
A few notes:<br />
*This only works for extensions using the .ext.<platform> convention. SourceMod cuts off the ".autoload" portion of the file, then adds the appropriate extension just as if 'sm exts load' was used.<br />
*The file does not need to contain anything, it simply needs to exist.<br />
<br />
<br />
=Conventions=<br />
<br />
==Designing API/Natives==<br />
*Start interface names with the capital letter 'I'.<br />
*Use a consistent naming convention. SourceMod uses [http://msdn2.microsoft.com/en-us/library/ms229002.aspx Microsoft/Pascal] naming. Avoid Java/Camel casing for both API and natives.<br />
*When exposing interfaces, make sure your exposure defines start with <tt>SMINTERFACE_</tt> and end with <tt>_NAME</tt> and <tt>_VERSION</tt><br />
<br />
==External Naming==<br />
*Logtags (<tt>SMEXT_CONF_LOGTAG</tt>) should be short names (under 10 characters or so) in all capital letters.<br />
*Your extension file name should never have <tt>_i486</tt> or other unnecessary platform-specific notations in its name.<br />
*Your extension file name should always end in <tt>.ext.so</tt> or <tt>.ext.dll</tt> depending on the platform. The <tt>.ext.</tt> is SourceMod convention.<br />
<br />
<br />
=Setting up Visual Studio=<br />
<br />
{{qnotice|It is recommended that you make a copy of the sample_ext project and modify that rather than create your own project, as it will set up most of this for you}}<br><br />
<br />
== Sample Project ==<br />
<br />
Instead of manually creating a project, you can make a copy of the sample_ext project and modify it.<br />
<br />
The sample_ext project assumes that it is located in a subdirectory inside the SourceMod public directory. To change this, modify all references to ..\.. to $(SOURCEMOD15)\public and specify the SOURCEMOD15 variable as mentioned in the Optional Environment variables.<br />
<br />
The sample_ext projectuses the following environment variables to locate the various source code parts. To set environment variables:<br />
<br />
'''Windows XP''': Start -> Settings -> Control Panel -> System -> Advanced -> Environment Variables<br />
<br />
'''Windows Vista/7''': Start -> Control Panel -> System -> Advanced system properties -> Advanced -> Environment Variables<br />
<br />
=== Environment Variables ===<br />
<br />
The environment variables used are:<br />
<br />
* '''MMSOURCE17''': Path to MetaMod: Source source code for version 1.7 or newer. Used to compile SourceMod itself. <br />
* '''MMSOURCE18''': Path to MetaMod: Source source code for version 1.8 or newer. Used in SourceMod 1.4 projects. <br />
* '''MMSOURCE19''': Path to MetaMod: Source source code for version 1.9 or newer. Used in SourceMod 1.5 projects. <br />
* '''HL2SDK''': Path to the Episode 1 SDK <br />
* '''HL2SDK-SWARM''': Path to the Alien Swarm SDK <br />
* '''HL2SDK-DARKM''': Path to the Dark Messiah SDK <br />
* '''HL2SDKCSGO''': Path to the Counter-Strike: Global Offensive SDK <br />
* '''HL2SDKCSS''': Path to the Counter-Strike: Source SDK <br />
* '''HL2SDKL4D''': Path to the Left 4 Dead SDK <br />
* '''HL2SDKL4D2''': Path to the Left 4 Dead 2 SDK <br />
* '''HL2SDKOB''': Path to the Orange Box SDK (not Source 2009) <br />
* '''HL2SDKOBVALVE''': Path to the Orange Box Valve / Source 2009 SDK (Half-Life 2: Deathmatch, Day of Defeat: Source, Team Fortress 2)<br />
<br />
Optional environment variables:<br />
<br />
* '''SOURCEMOD15'': Optional path to SourceMod 1.5 or newer source code<br />
<br />
==Manually configuring a project==<br />
<br />
===Paths===<br />
'''Visual Studio 2003''': Tools -> Options -> Projects, VC++ Directories<br />
<br />
'''Visual Studio 2005''': Tools -> Options -> Projects and Solutions -> VC++ Directories<br />
<br />
====Include Paths====<br />
*SourceMod only:<br />
**sdk\public\extensions<br />
**sdk\public\sourcepawn<br />
**sdk\public<br />
*SourceMM (Note that sourcemm might be 'trunk' for you):<br />
**sourcemm\core<br />
**sourcemm\core\sourcehook<br />
*HL2SDK:<br />
**game\shared<br />
**public\vstdlib<br />
**public\tier1<br />
**public\tier0<br />
**public\engine<br />
**public<br />
**dlls<br />
<br />
====Link Paths====<br />
*HL2SDK:<br />
**lib\public for version 2005<br />
**lib-vc7\public for version 2003<br />
<br />
===Compiler Options===<br />
<b>Note:</b> These options are set by default in the sample Extension SDK.<br><br />
<b>Note:</b> These options should be set for every build (Release/Debug/others).<br />
<br />
For VS 2005, goto Project->Properties.<br />
<br />
*General<br />
**<tt>Character Set</tt>: '''Use Multi-Byte Character Set'''<br />
*C/C++<br />
**General<br />
***<tt>Detect 64-bit Portability Issues</tt>: '''No'''<br />
**Preprocessor<br />
***<tt>Preprocessor Defines</tt>: Add <tt>_CRT_SECURE_NO_DEPRECATE</tt>, <tt>_CRT_NONSTDC_NO_DEPRECATE</tt>, <tt>SOURCEMOD_BUILD</tt> and <tt>WIN32</tt><br />
**Code Generation<br />
***<tt>Runtime Library</tt>: Multi-threaded or /MT (for Release), Multi-threaded Debug or /MTd (for Debug)<br />
*Linker<br />
**General<br />
***<tt>Output File</tt>: Change <tt>$(ProjectName)</tt> to <tt>$(ProjectName).ext</tt>, or use your own custom string.<br />
**Input<br />
***<tt>Additional Dependencies</tt>: <tt>tier0.lib tier1.lib vstdlib.lib</tt> (for SourceMM attached extensions only)<br />
<br />
[[Category:SourceMod]]<br />
[[Category:SourceMod Development]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Left_4_Voting_2&diff=10986Left 4 Voting 22020-03-30T04:36:06Z<p>Joinedsenses: /* Example voting plugin */ Update syntax, highlighting, methodmap, and formatting</p>
<hr />
<div>Left 4 Dead 2 has a VGUI voting system controller by a bunch of Events and UserMessages. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument.<br />
# The VoteStart User Message is sent.<br />
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers.<br />
# When the vote is complete, the server sends either a VotePass or VoteFail UserMessage.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_activeIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int|m_votesYes|Current Yes votes}}<br />
{{hl2msg|int|m_votesNo|Current No votes}}<br />
{{hl2msg|int|m_potentialVotes|Number of players eligible to vote}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== Vote ===<br />
<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|Vote|string}}<br />
{{hl2msg|string|option|"Yes" or "No"}}<br />
{{end-hl2msg}}<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since Valve represents no team as -1, no team is instead represented as its two's complement, 255.}}<br><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players in the vote group, corresponding with teams. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|byte|initiator|Client index (NOT USERID) of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|issue|Vote issue translation string}}<br />
{{hl2msg|string|param1|Vote issue text}}<br />
{{hl2msg|string|initiatorName|Name of person who started the vote}}<br />
{{end-hl2msg}}<br />
====Values for "issue" and "param1" in standard votes====<br />
{| class="wikitable"<br />
|-<br />
! Vote type !! issue !! param1<br />
|-<br />
| Kick || #L4D_vote_kick_player || Nickname of the person to be kicked without "#"<br />
|-<br />
| ReturnToLobby || #L4D_vote_return_to_lobby || <br />
|-<br />
| ChangeCampaign || #L4D_vote_mission_change || #L4D360UI_CampaignName_C5<br />
|-<br />
| RestartChapter || #L4D_vote_passed_versus_level_restart ||<br />
|-<br />
| ChangeDifficulty (easy) || #L4D_vote_change_difficulty || #L4D_DifficultyEasy<br />
|-<br />
| ChangeDifficulty (hard) || #L4D_vote_change_difficulty || #L4D_DifficultyHard<br />
|-<br />
| Alltalk On || #L4D_vote_alltalk_change || #L4D_vote_alltalk_enable<br />
|-<br />
| Alltalk Off || #L4D_vote_alltalk_change || #L4D_vote_alltalk_disable<br />
|}<br />
*Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish"<br />
<br />
===VoteRegistered===<br />
{{qnotice|Only sent to player who voted}}<br /><br />
{{begin-hl2msg|VoteRegistered|string}}<br />
{{hl2msg|byte|vote|0 for No, 1 for Yes}}<br />
{{end-hl2msg}}<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{hl2msg|string|details|Vote success translation string}}<br />
{{hl2msg|string|param1|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
===VoteFail===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFail|string}}<br />
{{hl2msg|byte|team|Team index or 255 for all}}<br />
{{end-hl2msg}}<br />
<br />
== Events ==<br />
{{qnotice|team is -1 when sent to all players)}}<br><br />
<br />
=== vote_changed ===<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|yesVotes|}}<br />
{{hl2msg|byte|noVotes|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
<br />
#define L4D2_TEAM_ALL -1<br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
<br />
#define MAX_VOTES 4<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("testvote", Callvote_Handler);<br />
RegConsoleCmd("Vote", vote);<br />
}<br />
<br />
public Action Callvote_Handler(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteByte(0);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteString("Server");<br />
EndMessage();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
public void UpdateVotes()<br />
{<br />
Event event = CreateEvent("vote_changed");<br />
event.SetInt("yesVotes", g_iYesVotes);<br />
event.SetInt("noVotes", g_iNoVotes);<br />
event.SetInt("potentialVotes", MAX_VOTES);<br />
event.Fire();<br />
<br />
if (g_iYesVotes + g_iNoVotes == MAX_VOTES)<br />
{<br />
PrintToServer("voting complete!");<br />
<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
bf.WriteString("#L4D_TargetID_Player");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFailed"));<br />
bf.WriteByte(L4D2_TEAM_ALL);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
public Action vote(int client, int args)<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, sizeof arg);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
if (strcmp(arg, "Yes", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "No", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
}<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[TF2 Voting]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=TF2_Voting&diff=10985TF2 Voting2020-03-30T04:30:11Z<p>Joinedsenses: /* Example voting plugin */ Update naming and formatting</p>
<hr />
<div>''This is for SourceMod plugin development involving the TF2 Vote System. If you are looking into the details on configuring TF2 Voting on your TF2 server, see [http://wiki.teamfortress.com/wiki/Voting Voting] on the Official Team Fortress Wiki.''<br />
<br />
Team Fortress 2 has a new VGUI voting system based on the [[Left 4 Voting|Left 4 Dead Voting system]] and is controlled by [[Game Events (Source)|Game Events]] and [[User Messages]]. You can use either a string from the resource file, or TF_playerid_noteam which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument, or the server calls a vote (server uses entity index 99).<br />
# The server sends a vote_options event.<br />
#* For Yes/No votes, the values it sends are Yes and No (in that order). For Multiple Choice votes, the actual vote options are sent.<br />
# The VoteStart User Message is sent, the last argument determines the vote type.<br />
# Clients use the "vote" command to register their votes (option1 through option5), after which the server sends a vote_cast event with a 0-based option number (so option1 = 0, option5 = 4). It also updates the vote_controller entity's vote counts.<br />
# When the vote is complete, the server sends either a VotePass or VoteFailed User Message.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_iActiveIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_iOnlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int[5]|m_nVoteOptionCount|Table of 5 vote counts numbered 0-4}}<br />
{{hl2msg|int|m_nPotentialVotes|Number of players eligible to vote}}<br />
{{hl2msg|int|m_bIsYesNoVote|Corresponds to VoteStart's yesno argument}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== callvote ===<br />
{{qnotice|This command can take 0 arguments. If it does, a VoteSetup usermessage is returned to that user.}}<br /><br />
{{begin-hl2msg|callvote|string}}<br />
{{hl2msg|string|type|"Kick", "RestartGame", "ChangeLevel", "NextLevel", "ScrambleTeams", or "ChangeMission". Should return VoteSetup Usermessage if left blank.}}<br />
{{hl2msg|string|param1|"userid_short kicktype_string" for Kick, map name for Changelevel/NextLevel, or popfile for ChangeMission. Otherwise, left blank}}<br />
{{end-hl2msg}}<br />
<br />
kicktype_string is one of these (at least in the English version):<br />
* other<br />
* cheating<br />
* idle<br />
* scamming<br />
<br />
This data is primarily there if you want to intercept these messages in a plugin.<br />
<br />
CallVoteFailed UserMessage is sent back if a vote cannot be called unless a vote is already being displayed.<br />
<br />
=== vote ===<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|vote|string}}<br />
{{hl2msg|string|option|F1 is "option1", F2 is "option2", F3 is "option3", F4 is "option4", F5 is "option5"}}<br />
{{end-hl2msg}}<br />
<br />
Note that these are 1-based, but the vote_cast event is 0-based.<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since the October 15, 2014 update, all teams is now represented by 0 (it used to be -1 / 255)}}<br><br />
<br />
<br />
The User Messages that exist in the TF2 Voting system are:<br />
<br />
===VoteSetup===<br />
{{qnotice|Sent to a player when they load the Call Vote screen (which sends the callvote command to the server), lists what votes are allowed on the server}}<br><br />
{{qnotice|This changed in the October 15, 2014 update}}<br><br />
<br />
{{begin-hl2msg|VoteSetup|string}}<br />
{{hl2msg|byte|issue_count|Count of vote issues allowed on this server. Not present in protobuf version}}<br />
{{hl2msg|repeated message|issue|issue fields as shown below}}<br />
{{end-hl2msg}}<br />
<br />
{{begin-hl2msg|issue|string}}<br />
{{hl2msg|string|potential_issue|Vote issue name that will be sent for the callvote command}}<br />
{{hl2msg|string|translation name|Translation name, such as #TF_RestartGame}}<br />
{{hl2msg|byte|enabled|1 if this issue is enabled, 0 if not}}<br />
{{end-hl2msg}}<br />
<br />
There is one "issue" sent for each supported issue.<br />
Valid potential_issue strings are:<br />
* Kick<br />
* RestartGame<br />
* ChangeLevel<br />
* NextLevel<br />
* ScrambleTeams<br />
* ChangeMission<br />
* TeamAutoBalance<br />
* ClassLimits<br />
<br />
Valid translation name strings are:<br />
* #TF_Kick<br />
* #TF_RestartGame<br />
* #TF_ChangeLevel<br />
* #TF_NextLevel<br />
* #TF_ScrambleTeams<br />
* #TF_ChangeMission<br />
* #TF_TeamAutoBalance_Enable<br />
* #TF_TeamAutoBalance_Disable<br />
* #TF_ClassLimit_Enable<br />
* #TF_ClassLimit_Disable<br />
<br />
By default, disabled issues are omitted (sv_vote_ui_hide_disabled_issues 1).<br />
<br />
===CallVoteFailed===<br />
{{qnotice|Sent to a player when they attempt to call a vote and fail.}}<br><br />
{{begin-hl2msg|CallVoteFailed|string}}<br />
{{hl2msg|byte|reason|Failure reason (1-2, 5-10, 12-19)}}<br />
{{hl2msg|short|time|For failure reasons 2 and 8, time in seconds until client can start another vote. 2 is per user, 8 is per vote type.}}<br />
{{end-hl2msg}}<br />
<br />
Valid Failure Codes are:<br /><br />
1 - VOTE_FAILED_TRANSITIONING_PLAYERS - Cannot call vote while other players are still loading. This appears to be a holdover from L4D2; use code 10 instead.<br /><br />
2 - VOTE_FAILED_RATE_EXCEEDED - You called a vote recently and cannot call another one for X seconds (second argument to CallVoteFailed specifies the number of seconds)<br /><br />
5 - VOTE_FAILED_ISSUE_DISABLED - Server has disabled that issue.<br /><br />
6 - VOTE_FAILED_MAP_NOT_FOUND - That map does not exist.<br /><br />
7 - VOTE_FAILED_MAP_NAME_REQUIRED - You must specify a map name<br /><br />
8 - VOTE_FAILED_FAILED_RECENTLY - This vote failed recently<br /><br />
9 - VOTE_FAILED_TEAM_CANT_CALL - Your team cannot call this vote<br /><br />
10 - VOTE_FAILED_WAITINGFORPLAYERS - Voting not allowed while Waiting for Players<br /><br />
11 - VOTE_FAILED_PLAYERNOTFOUND - Doesn't appear to work<br /><br />
12 - VOTE_FAILED_CANNOT_KICK_ADMIN - Can't Kick Server Admin<br /><br />
13 - VOTE_FAILED_SCRAMBLE_IN_PROGRESS - Vote Scramble is pending<br /><br />
14 - VOTE_FAILED_SPECTATOR - Spectators can't vote<br /><br />
15 - VOTE_FAILED_NEXTLEVEL_SET - Next level already set<br /><br />
16 - VOTE_FAILED_MAP_NOT_VALID - Map is not in the map list<br /><br />
17 - VOTE_FAILED_CANNOT_KICK_FOR_TIME - Cannot kick yet. Used for MvM<br /><br />
18 - VOTE_FAILED_CANNOT_KICK_DURING_ROUND - Cannot kick during round. Used for MvM<br /><br />
19 - VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE - Modification is already active, used by Eternaween<br /><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players currently online. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|byte|ent_idx|Client index of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|disp_str|Vote issue translation string}}<br />
{{hl2msg|string|details_str|Vote issue text}}<br />
{{hl2msg|bool|is_yes_no_vote|true for Yes/No, false for Multiple choice}}<br />
{{end-hl2msg}}<br />
<br />
Valid issue strings:<br />
* #TF_vote_kick_player_other - Generic Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_idle - Idler Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_cheating - Cheater Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_scamming - Scammer Kick vote. param1 is player name.<br />
* #TF_vote_restart_game - Restart map vote. param1 ignored.<br />
* #TF_vote_changelevel - Change map vote. param1 is map name.<br />
* #TF_vote_nextlevel - Set next level vote. param1 is map name.<br />
* #TF_vote_nextlevel_choices - End of map map vote. param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_scramble_teams - Scramble teams vote. param1 ignored.<br />
* #TF_vote_should_scramble_round - Scramble teams at round end vote. param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_td_start_round - Start the round? param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_changechallenge - Change MvM mission? param1 is mission. This vote is in the menu on MvM maps.<br />
* #TF_vote_eternaween - Activate Halloween mode? param1 ignored. This vote is activated by someone using the Eternaween item.<br />
* #TF_vote_autobalance_enable - Active Autobalance? param1 ignored.<br />
* #TF_vote_autobalance_disable - Disable Autobalance? param1 ignored.<br />
* #TF_vote_classlimits_enable - Enable class limits? param1 ignored.<br />
* #TF_vote_classlimits_disable - Disable class limits? param1 ignored.<br />
* #TF_playerid_noteam - Unofficially used for a custom vote. param1 is custom vote issue.<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|string|disp_str|Vote success translation string}}<br />
{{hl2msg|string|details_str|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
Valid success strings:<br />
* #TF_vote_passed_kick_player - Kick vote passed. param1 is player name.<br />
* #TF_vote_passed_restart_game - Restart vote passed. param1 ignored.<br />
* #TF_vote_passed_changelevel - Change level vote passed. param1 is map name.<br />
* #TF_vote_passed_nextlevel - Set next level vote passed. param1 is map name.<br />
* #TF_vote_passed_nextlevel_extend - Current map has been extended. param1 ignored.<br />
* #TF_vote_passed_scramble_teams - Team Scramble vote passed. param1 is ignored.<br />
* #TF_vote_passed_td_start_round - Round start vote passed. param1 is ignored.<br />
* #TF_vote_passed_changechallenge - MvM mission change passed. param1 is new mission.<br />
* #TF_vote_passed_eternaween - Eternaween vote passed. param1 ignored.<br />
* #TF_vote_passed_autobalance_enable - Autobalance enable vote passed. param1 ignored.<br />
* #TF_vote_passed_autobalance_disable - Autobalance disable vote passed. param1 ignored.<br />
* #TF_vote_passed_classlimits_enable - Class Limit enable vote passed. param1 ignored.<br />
* #TF_vote_passed_classlimits_disable - Class Limit disable vote passed. param1 ignored.<br />
* #TF_playerid_noteam - Unofficially used for a custom success string. param1 is custom success string.<br />
<br />
===VoteFailed===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFailed|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|byte|reason|Failure reason code (0, 3-4)}}<br />
{{end-hl2msg}}<br />
<br />
Valid Failure codes are:<br /><br />
0 - VOTE_FAILED_GENERIC - Generic "Vote Failed" message<br /> <br />
3 - VOTE_FAILED_YES_MUST_EXCEED_NO - Yes votes must outnumber No votes<br /><br />
4 - VOTE_FAILED_QUORUM_FAILURE - Not Enough Votes<br />
<br />
== Events ==<br />
<br />
=== Server to Client Events ===<br />
<br />
==== vote_cast ====<br />
{{qnotice|Sent to all players when a player chooses a vote option (or more specifically, the server receives a vote command)}}<br><br />
{{begin-hl2msg|vote_cast|string}}<br />
{{hl2msg|byte|vote_option|which option the player voted on}}<br />
{{hl2msg|short|team|[Ed: Usually -1, but team-specific votes can be 2 for RED or 3 for BLU]}}<br />
{{hl2msg|long|entityid|entity id of the voter}}<br />
{{end-hl2msg}}<br />
<br />
This event is unusual as it uses the client's entity ID instead of its userid.<br />
<br />
==== vote_options ====<br />
{{qnotice|Sent to players before VoteStart UserMessage to populate choices for a multiple choice vote}}<br><br />
{{begin-hl2msg|vote_options|string}}<br />
{{hl2msg|byte|count|Number of options - up to MAX_VOTE_OPTIONS [ed: 5]}}<br />
{{hl2msg|string|option1|}}<br />
{{hl2msg|string|option2|}}<br />
{{hl2msg|string|option3|}}<br />
{{hl2msg|string|option4|}}<br />
{{hl2msg|string|option5|}}<br />
{{end-hl2msg}}<br />
<br />
If sv_vote_issue_nextlevel_allowextend is set to 1, option5 is "Extend current Map"<br />
<br />
=== Client-only Events ===<br />
<br />
These events are passed between the TF2 client and the client's VGUI voting panels. They have no server interaction and the server should not be touching them in any way.<br />
<br />
==== vote_ended ====<br />
<br />
{{begin-hl2msg|vote_ended|string}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_started ====<br />
<br />
{{begin-hl2msg|vote_started|string}}<br />
{{hl2msg|string|issue|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|initiator|entity id of the player who initiated the vote}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_changed ====<br />
{{qnotice|These values are read from the vote_controller entity on the server}}<br><br />
<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|option1|}}<br />
{{hl2msg|byte|option2|}}<br />
{{hl2msg|byte|option3|}}<br />
{{hl2msg|byte|option4|}}<br />
{{hl2msg|byte|option5|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_passed ====<br />
<br />
{{begin-hl2msg|vote_passed|string}}<br />
{{hl2msg|string|details|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_failed ====<br />
<br />
{{begin-hl2msg|vote_failed|string}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
// TF2's internal map vote uses client index 99 for the server<br />
#define TF2_SERVER_CLIENT_INDEX 99<br />
#define TF2_TEAM_ALL -1<br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
<br />
#define MAX_VOTES 4<br />
<br />
public Plugin myinfo = <br />
{<br />
name = "Test Yes/No Vote",<br />
author = "Powerlord",<br />
description = "A test vote plugin for the AlliedMods wiki",<br />
version = "1.0",<br />
url = "http://wiki.alliedmods.net/TF2_Voting"<br />
}<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("testvote", Callvote_Handler);<br />
RegConsoleCmd("vote", vote);<br />
}<br />
<br />
public Action Callvote_Handler(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
bf.WriteByte(TF2_SERVER_CLIENT_INDEX);<br />
bf.WriteString("#TF_playerid_noteam");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteBool(true);<br />
EndMessage();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
void UpdateVotes()<br />
{<br />
if (g_iYesVotes + g_iNoVotes >= MAX_VOTES)<br />
{<br />
PrintToServer("voting complete!");<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
bf.WriteString("#TF_playerid_noteam");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFailed"));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
// Check list of failure reasons<br />
bf.WriteByte(3);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
// If the TF2 vote system is running (sv_allow_votes 1), this needs to be a command listener<br />
// because TF2 registers the vote command only when a vote is ongoing, and thus hooking it using RegConsoleCmd doesn't work.<br />
public Action vote(int client, int args)<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, 8);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
int option = 0;<br />
if (strcmp(arg, "option1", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "option2", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
option = 1;<br />
}<br />
<br />
Event event = CreateEvent("vote_cast");<br />
event.SetInt("entityid", client);<br />
event.SetInt("team", -1);<br />
event.SetInt("vote_option", option);<br />
event.Fire();<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
See the following images for examples what this looks like:<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038044135/D38024401C1C8FBA0B78BA6CE8F3FD9CD3A275BE/ Vote started]<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038046803/3DC5FACC1F33696D9340D2DCEE72274E5934D9ED/ Vote passed]<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038049485/C469BD2D358F9524A8B17164AD3FE06A3574E837/ Vote failed]<br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[Left 4 Voting 2]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Left_4_Voting&diff=10984Left 4 Voting2020-03-30T04:28:12Z<p>Joinedsenses: /* Example voting plugin */ Update syntax, highlighting, methodmap, and formatting</p>
<hr />
<div>Left 4 Dead has a new VGUI voting system, it's controlled by a bunch of events. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
<br />
# Server sends a vote_started event<br />
# Servers sends a vote_changed event<br />
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a vote_cast_yes or vote_cast_no event, followed by a vote_changed event with updated numbers.<br />
# Server ends the vote by sending vote_ended event, followed by a vote_passed or vote_failed event.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_activeIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int|m_votesYes|Current Yes votes}}<br />
{{hl2msg|int|m_votesNo|Current No votes}}<br />
{{hl2msg|int|m_potentialVotes|Number of players eligible to vote}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== Vote ===<br />
<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|Vote|string}}<br />
{{hl2msg|string|option|"Yes" or "No"}}<br />
{{end-hl2msg}}<br />
<br />
== Events ==<br />
{{qnotice|team is -1 when sent to all players)}}<br><br />
<br />
=== vote_ended ===<br />
{{begin-hl2msg|vote_ended|string}}<br />
{{hl2msg|None|None|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_started ===<br />
{{begin-hl2msg|vote_started|string}}<br />
{{hl2msg|string|issue|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|initiator|entity id of the player who initiated the vote}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_changed ===<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|yesVotes|}}<br />
{{hl2msg|byte|noVotes|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_passed ===<br />
{{begin-hl2msg|vote_passed|string}}<br />
{{hl2msg|string|details|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_failed ===<br />
{{begin-hl2msg|vote_failed|string}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_cast_yes ===<br />
{{begin-hl2msg|vote_cast_yes|string}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|entityid|entity id of the voter}}<br />
{{end-hl2msg}}<br />
<br />
=== vote_cast_no ===<br />
{{begin-hl2msg|vote_cast_no|string}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|entityid|entity id of the voter}}<br />
{{end-hl2msg}}<br />
<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
<br />
int g_iYesVotes;<br />
int g_iNoVotes;<br />
<br />
#define MAX_VOTES 4<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("testvote", Callvote_Handler);<br />
RegConsoleCmd("Vote", vote);<br />
}<br />
<br />
public Action Callvote_Handler(int client, int args)<br />
{<br />
Event event = CreateEvent("vote_started");<br />
event.SetString("issue", "#L4D_TargetID_Player");<br />
event.SetString("param1", "Is gaben fat?");<br />
event.SetInt("team", 0);<br />
event.SetInt("initiator", 0);<br />
event.Fire();<br />
<br />
g_iYesVotes = 0;<br />
g_iNoVotes = 0;<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
public void UpdateVotes()<br />
{<br />
Event event = CreateEvent("vote_changed");<br />
event.SetInt("yesVotes", g_iYesVotes);<br />
event.SetInt("noVotes", g_iNoVotes);<br />
event.SetInt("potentialVotes", MAX_VOTES);<br />
event.Fire();<br />
<br />
if (g_iYesVotes + g_iNoVotes == MAX_VOTES)<br />
{<br />
PrintToServer("voting complete!");<br />
<br />
event = CreateEvent("vote_ended");<br />
event.Fire();<br />
<br />
if (g_iYesVotes > g_iNoVotes)<br />
{<br />
event = CreateEvent("vote_passed");<br />
event.SetString("details", "#L4D_TargetID_Player");<br />
event.SetString("param1", "Gaben is fat");<br />
event.SetInt("team", 0);<br />
event.Fire();<br />
}<br />
else<br />
{<br />
event = CreateEvent("vote_failed");<br />
event.SetInt("team", 0);<br />
event.Fire();<br />
}<br />
}<br />
}<br />
<br />
public Action vote(int client, int args)<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, sizeof arg); <br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
if (strcmp(arg, "Yes", true) == 0)<br />
{<br />
g_iYesVotes++;<br />
}<br />
else if (strcmp(arg, "No", true) == 0)<br />
{<br />
g_iNoVotes++;<br />
}<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
See the following images for examples what this looks like:<br />
<br />
http://devicenull.org/temp/l4d_question.jpg <br />
<br />
http://devicenull.org/temp/l4d_result.jpg<br />
<br />
==See Also==<br />
* [[Left 4 Voting 2]]<br />
* [[TF2 Voting]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=TF2_Voting&diff=10983TF2 Voting2020-03-30T04:13:28Z<p>Joinedsenses: /* Example voting plugin */ Update syntax and fix BfWrite assignment.</p>
<hr />
<div>''This is for SourceMod plugin development involving the TF2 Vote System. If you are looking into the details on configuring TF2 Voting on your TF2 server, see [http://wiki.teamfortress.com/wiki/Voting Voting] on the Official Team Fortress Wiki.''<br />
<br />
Team Fortress 2 has a new VGUI voting system based on the [[Left 4 Voting|Left 4 Dead Voting system]] and is controlled by [[Game Events (Source)|Game Events]] and [[User Messages]]. You can use either a string from the resource file, or TF_playerid_noteam which will let you create any vote you want.<br />
<br />
== How voting works ==<br />
# A client issues a callvote with the vote type and argument, or the server calls a vote (server uses entity index 99).<br />
# The server sends a vote_options event.<br />
#* For Yes/No votes, the values it sends are Yes and No (in that order). For Multiple Choice votes, the actual vote options are sent.<br />
# The VoteStart User Message is sent, the last argument determines the vote type.<br />
# Clients use the "vote" command to register their votes (option1 through option5), after which the server sends a vote_cast event with a 0-based option number (so option1 = 0, option5 = 4). It also updates the vote_controller entity's vote counts.<br />
# When the vote is complete, the server sends either a VotePass or VoteFailed User Message.<br />
<br />
== Server Entity ==<br />
<br />
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.<br />
<br />
{{begin-hl2msg|vote_controller (CVoteController)|string}}<br />
{{hl2msg|int|m_iActiveIssueIndex|Number of the active issue}}<br />
{{hl2msg|int|m_iOnlyTeamToVote|Corresponds to VoteStart's team argument.}}<br />
{{hl2msg|int[5]|m_nVoteOptionCount|Table of 5 vote counts numbered 0-4}}<br />
{{hl2msg|int|m_nPotentialVotes|Number of players eligible to vote}}<br />
{{hl2msg|int|m_bIsYesNoVote|Corresponds to VoteStart's yesno argument}}<br />
{{end-hl2msg}}<br />
<br />
== Console Commands ==<br />
<br />
=== callvote ===<br />
{{qnotice|This command can take 0 arguments. If it does, a VoteSetup usermessage is returned to that user.}}<br /><br />
{{begin-hl2msg|callvote|string}}<br />
{{hl2msg|string|type|"Kick", "RestartGame", "ChangeLevel", "NextLevel", "ScrambleTeams", or "ChangeMission". Should return VoteSetup Usermessage if left blank.}}<br />
{{hl2msg|string|param1|"userid_short kicktype_string" for Kick, map name for Changelevel/NextLevel, or popfile for ChangeMission. Otherwise, left blank}}<br />
{{end-hl2msg}}<br />
<br />
kicktype_string is one of these (at least in the English version):<br />
* other<br />
* cheating<br />
* idle<br />
* scamming<br />
<br />
This data is primarily there if you want to intercept these messages in a plugin.<br />
<br />
CallVoteFailed UserMessage is sent back if a vote cannot be called unless a vote is already being displayed.<br />
<br />
=== vote ===<br />
{{qnotice|This command is only valid when a vote is ongoing.}}<br /><br />
{{begin-hl2msg|vote|string}}<br />
{{hl2msg|string|option|F1 is "option1", F2 is "option2", F3 is "option3", F4 is "option4", F5 is "option5"}}<br />
{{end-hl2msg}}<br />
<br />
Note that these are 1-based, but the vote_cast event is 0-based.<br />
<br />
== User Messages ==<br />
{{qnotice|These user messages use unsigned bytes for the team number. Since the October 15, 2014 update, all teams is now represented by 0 (it used to be -1 / 255)}}<br><br />
<br />
<br />
The User Messages that exist in the TF2 Voting system are:<br />
<br />
===VoteSetup===<br />
{{qnotice|Sent to a player when they load the Call Vote screen (which sends the callvote command to the server), lists what votes are allowed on the server}}<br><br />
{{qnotice|This changed in the October 15, 2014 update}}<br><br />
<br />
{{begin-hl2msg|VoteSetup|string}}<br />
{{hl2msg|byte|issue_count|Count of vote issues allowed on this server. Not present in protobuf version}}<br />
{{hl2msg|repeated message|issue|issue fields as shown below}}<br />
{{end-hl2msg}}<br />
<br />
{{begin-hl2msg|issue|string}}<br />
{{hl2msg|string|potential_issue|Vote issue name that will be sent for the callvote command}}<br />
{{hl2msg|string|translation name|Translation name, such as #TF_RestartGame}}<br />
{{hl2msg|byte|enabled|1 if this issue is enabled, 0 if not}}<br />
{{end-hl2msg}}<br />
<br />
There is one "issue" sent for each supported issue.<br />
Valid potential_issue strings are:<br />
* Kick<br />
* RestartGame<br />
* ChangeLevel<br />
* NextLevel<br />
* ScrambleTeams<br />
* ChangeMission<br />
* TeamAutoBalance<br />
* ClassLimits<br />
<br />
Valid translation name strings are:<br />
* #TF_Kick<br />
* #TF_RestartGame<br />
* #TF_ChangeLevel<br />
* #TF_NextLevel<br />
* #TF_ScrambleTeams<br />
* #TF_ChangeMission<br />
* #TF_TeamAutoBalance_Enable<br />
* #TF_TeamAutoBalance_Disable<br />
* #TF_ClassLimit_Enable<br />
* #TF_ClassLimit_Disable<br />
<br />
By default, disabled issues are omitted (sv_vote_ui_hide_disabled_issues 1).<br />
<br />
===CallVoteFailed===<br />
{{qnotice|Sent to a player when they attempt to call a vote and fail.}}<br><br />
{{begin-hl2msg|CallVoteFailed|string}}<br />
{{hl2msg|byte|reason|Failure reason (1-2, 5-10, 12-19)}}<br />
{{hl2msg|short|time|For failure reasons 2 and 8, time in seconds until client can start another vote. 2 is per user, 8 is per vote type.}}<br />
{{end-hl2msg}}<br />
<br />
Valid Failure Codes are:<br /><br />
1 - VOTE_FAILED_TRANSITIONING_PLAYERS - Cannot call vote while other players are still loading. This appears to be a holdover from L4D2; use code 10 instead.<br /><br />
2 - VOTE_FAILED_RATE_EXCEEDED - You called a vote recently and cannot call another one for X seconds (second argument to CallVoteFailed specifies the number of seconds)<br /><br />
5 - VOTE_FAILED_ISSUE_DISABLED - Server has disabled that issue.<br /><br />
6 - VOTE_FAILED_MAP_NOT_FOUND - That map does not exist.<br /><br />
7 - VOTE_FAILED_MAP_NAME_REQUIRED - You must specify a map name<br /><br />
8 - VOTE_FAILED_FAILED_RECENTLY - This vote failed recently<br /><br />
9 - VOTE_FAILED_TEAM_CANT_CALL - Your team cannot call this vote<br /><br />
10 - VOTE_FAILED_WAITINGFORPLAYERS - Voting not allowed while Waiting for Players<br /><br />
11 - VOTE_FAILED_PLAYERNOTFOUND - Doesn't appear to work<br /><br />
12 - VOTE_FAILED_CANNOT_KICK_ADMIN - Can't Kick Server Admin<br /><br />
13 - VOTE_FAILED_SCRAMBLE_IN_PROGRESS - Vote Scramble is pending<br /><br />
14 - VOTE_FAILED_SPECTATOR - Spectators can't vote<br /><br />
15 - VOTE_FAILED_NEXTLEVEL_SET - Next level already set<br /><br />
16 - VOTE_FAILED_MAP_NOT_VALID - Map is not in the map list<br /><br />
17 - VOTE_FAILED_CANNOT_KICK_FOR_TIME - Cannot kick yet. Used for MvM<br /><br />
18 - VOTE_FAILED_CANNOT_KICK_DURING_ROUND - Cannot kick during round. Used for MvM<br /><br />
19 - VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE - Modification is already active, used by Eternaween<br /><br />
<br />
===VoteStart===<br />
{{qnotice|Sent to all players currently online. The default implementation also sends it to bots.}}<br /><br />
<br />
{{begin-hl2msg|VoteStart|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|byte|ent_idx|Client index of person who started the vote, or 99 for the server.}}<br />
{{hl2msg|string|disp_str|Vote issue translation string}}<br />
{{hl2msg|string|details_str|Vote issue text}}<br />
{{hl2msg|bool|is_yes_no_vote|true for Yes/No, false for Multiple choice}}<br />
{{end-hl2msg}}<br />
<br />
Valid issue strings:<br />
* #TF_vote_kick_player_other - Generic Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_idle - Idler Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_cheating - Cheater Kick vote. param1 is player name.<br />
* #TF_vote_kick_player_scamming - Scammer Kick vote. param1 is player name.<br />
* #TF_vote_restart_game - Restart map vote. param1 ignored.<br />
* #TF_vote_changelevel - Change map vote. param1 is map name.<br />
* #TF_vote_nextlevel - Set next level vote. param1 is map name.<br />
* #TF_vote_nextlevel_choices - End of map map vote. param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_scramble_teams - Scramble teams vote. param1 ignored.<br />
* #TF_vote_should_scramble_round - Scramble teams at round end vote. param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_td_start_round - Start the round? param1 ignored. This vote is not in the user vote menu.<br />
* #TF_vote_changechallenge - Change MvM mission? param1 is mission. This vote is in the menu on MvM maps.<br />
* #TF_vote_eternaween - Activate Halloween mode? param1 ignored. This vote is activated by someone using the Eternaween item.<br />
* #TF_vote_autobalance_enable - Active Autobalance? param1 ignored.<br />
* #TF_vote_autobalance_disable - Disable Autobalance? param1 ignored.<br />
* #TF_vote_classlimits_enable - Enable class limits? param1 ignored.<br />
* #TF_vote_classlimits_disable - Disable class limits? param1 ignored.<br />
* #TF_playerid_noteam - Unofficially used for a custom vote. param1 is custom vote issue.<br />
<br />
===VotePass===<br />
{{qnotice|Sent to all players after a vote passes.}}<br /><br />
<br />
{{begin-hl2msg|VotePass|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|string|disp_str|Vote success translation string}}<br />
{{hl2msg|string|details_str|Vote winner}}<br />
{{end-hl2msg}}<br />
<br />
Valid success strings:<br />
* #TF_vote_passed_kick_player - Kick vote passed. param1 is player name.<br />
* #TF_vote_passed_restart_game - Restart vote passed. param1 ignored.<br />
* #TF_vote_passed_changelevel - Change level vote passed. param1 is map name.<br />
* #TF_vote_passed_nextlevel - Set next level vote passed. param1 is map name.<br />
* #TF_vote_passed_nextlevel_extend - Current map has been extended. param1 ignored.<br />
* #TF_vote_passed_scramble_teams - Team Scramble vote passed. param1 is ignored.<br />
* #TF_vote_passed_td_start_round - Round start vote passed. param1 is ignored.<br />
* #TF_vote_passed_changechallenge - MvM mission change passed. param1 is new mission.<br />
* #TF_vote_passed_eternaween - Eternaween vote passed. param1 ignored.<br />
* #TF_vote_passed_autobalance_enable - Autobalance enable vote passed. param1 ignored.<br />
* #TF_vote_passed_autobalance_disable - Autobalance disable vote passed. param1 ignored.<br />
* #TF_vote_passed_classlimits_enable - Class Limit enable vote passed. param1 ignored.<br />
* #TF_vote_passed_classlimits_disable - Class Limit disable vote passed. param1 ignored.<br />
* #TF_playerid_noteam - Unofficially used for a custom success string. param1 is custom success string.<br />
<br />
===VoteFailed===<br />
{{qnotice|Sent to all players after a vote fails.}}<br /><br />
<br />
{{begin-hl2msg|VoteFailed|string}}<br />
{{hl2msg|byte|team|Team index or 0 for all}}<br />
{{hl2msg|byte|reason|Failure reason code (0, 3-4)}}<br />
{{end-hl2msg}}<br />
<br />
Valid Failure codes are:<br /><br />
0 - VOTE_FAILED_GENERIC - Generic "Vote Failed" message<br /> <br />
3 - VOTE_FAILED_YES_MUST_EXCEED_NO - Yes votes must outnumber No votes<br /><br />
4 - VOTE_FAILED_QUORUM_FAILURE - Not Enough Votes<br />
<br />
== Events ==<br />
<br />
=== Server to Client Events ===<br />
<br />
==== vote_cast ====<br />
{{qnotice|Sent to all players when a player chooses a vote option (or more specifically, the server receives a vote command)}}<br><br />
{{begin-hl2msg|vote_cast|string}}<br />
{{hl2msg|byte|vote_option|which option the player voted on}}<br />
{{hl2msg|short|team|[Ed: Usually -1, but team-specific votes can be 2 for RED or 3 for BLU]}}<br />
{{hl2msg|long|entityid|entity id of the voter}}<br />
{{end-hl2msg}}<br />
<br />
This event is unusual as it uses the client's entity ID instead of its userid.<br />
<br />
==== vote_options ====<br />
{{qnotice|Sent to players before VoteStart UserMessage to populate choices for a multiple choice vote}}<br><br />
{{begin-hl2msg|vote_options|string}}<br />
{{hl2msg|byte|count|Number of options - up to MAX_VOTE_OPTIONS [ed: 5]}}<br />
{{hl2msg|string|option1|}}<br />
{{hl2msg|string|option2|}}<br />
{{hl2msg|string|option3|}}<br />
{{hl2msg|string|option4|}}<br />
{{hl2msg|string|option5|}}<br />
{{end-hl2msg}}<br />
<br />
If sv_vote_issue_nextlevel_allowextend is set to 1, option5 is "Extend current Map"<br />
<br />
=== Client-only Events ===<br />
<br />
These events are passed between the TF2 client and the client's VGUI voting panels. They have no server interaction and the server should not be touching them in any way.<br />
<br />
==== vote_ended ====<br />
<br />
{{begin-hl2msg|vote_ended|string}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_started ====<br />
<br />
{{begin-hl2msg|vote_started|string}}<br />
{{hl2msg|string|issue|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{hl2msg|long|initiator|entity id of the player who initiated the vote}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_changed ====<br />
{{qnotice|These values are read from the vote_controller entity on the server}}<br><br />
<br />
{{begin-hl2msg|vote_changed|string}}<br />
{{hl2msg|byte|option1|}}<br />
{{hl2msg|byte|option2|}}<br />
{{hl2msg|byte|option3|}}<br />
{{hl2msg|byte|option4|}}<br />
{{hl2msg|byte|option5|}}<br />
{{hl2msg|byte|potentialVotes|}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_passed ====<br />
<br />
{{begin-hl2msg|vote_passed|string}}<br />
{{hl2msg|string|details|}}<br />
{{hl2msg|string|param1|}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
==== vote_failed ====<br />
<br />
{{begin-hl2msg|vote_failed|string}}<br />
{{hl2msg|byte|team|}}<br />
{{end-hl2msg}}<br />
<br />
== Example voting plugin ==<br />
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.<br />
<br />
<sourcepawn>#include <sourcemod><br />
// TF2's internal map vote uses client index 99 for the server<br />
#define TF2_SERVER_CLIENT_INDEX 99<br />
#define TF2_TEAM_ALL -1<br />
<br />
int yesvotes;<br />
int novotes;<br />
#define MAX_VOTES 4<br />
<br />
public Plugin myinfo = <br />
{<br />
name = "Test Yes/No Vote",<br />
author = "Powerlord",<br />
description = "A test vote plugin for the AlliedMods wiki",<br />
version = "1.0",<br />
url = "http://wiki.alliedmods.net/TF2_Voting"<br />
}<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("testvote", Callvote_Handler);<br />
RegConsoleCmd("vote", vote);<br />
}<br />
<br />
public Action Callvote_Handler(int client, int args)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
bf.WriteByte(TF2_SERVER_CLIENT_INDEX);<br />
bf.WriteString("#TF_playerid_noteam");<br />
bf.WriteString("Is gaben fat?");<br />
bf.WriteBool(true);<br />
EndMessage();<br />
<br />
yesvotes = 0;<br />
novotes = 0;<br />
<br />
return Plugin_Handled;<br />
}<br />
<br />
void UpdateVotes()<br />
{<br />
if (yesvotes+novotes >= MAX_VOTES)<br />
{<br />
PrintToServer("voting complete!");<br />
if (yesvotes > novotes)<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
bf.WriteString("#TF_playerid_noteam");<br />
bf.WriteString("Gaben is fat");<br />
EndMessage();<br />
}<br />
else<br />
{<br />
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFailed"));<br />
bf.WriteByte(TF2_TEAM_ALL);<br />
// Check list of failure reasons<br />
bf.WriteByte(3);<br />
EndMessage();<br />
}<br />
}<br />
}<br />
<br />
// If the TF2 vote system is running (sv_allow_votes 1), this needs to be a command listener<br />
// because TF2 registers the vote command only when a vote is ongoing, and thus hooking it using RegConsoleCmd doesn't work.<br />
public Action vote(int client, int args)<br />
{<br />
char arg[8];<br />
GetCmdArg(1, arg, 8);<br />
<br />
PrintToServer("Got vote %s from %i", arg, client);<br />
<br />
int option = 0;<br />
if (strcmp(arg, "option1", true) == 0)<br />
{<br />
yesvotes++;<br />
}<br />
else if (strcmp(arg, "option2", true) == 0)<br />
{<br />
novotes++;<br />
option = 1;<br />
}<br />
<br />
Event event = CreateEvent("vote_cast");<br />
event.SetInt("entityid", client);<br />
event.SetInt("team", -1);<br />
event.SetInt("vote_option", option);<br />
event.Fire();<br />
<br />
UpdateVotes();<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
See the following images for examples what this looks like:<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038044135/D38024401C1C8FBA0B78BA6CE8F3FD9CD3A275BE/ Vote started]<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038046803/3DC5FACC1F33696D9340D2DCEE72274E5934D9ED/ Vote passed]<br />
<br />
[http://cloud.steampowered.com/ugc/542899697038049485/C469BD2D358F9524A8B17164AD3FE06A3574E837/ Vote failed]<br />
<br />
==See Also==<br />
* [[Left 4 Voting]]<br />
* [[Left 4 Voting 2]]<br />
* [https://forums.alliedmods.net/showthread.php?t=162164 BuiltinVotes], a SourceMod extension that exposes a voting API that uses this voting system.</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Introduction_to_SourcePawn_1.7&diff=10982Introduction to SourcePawn 1.72020-03-30T03:13:37Z<p>Joinedsenses: Fix syntax for dynamic array function parameter</p>
<hr />
<div>This guide is designed to give you a very basic overview to fundamentals of scripting in SourcePawn. [[Pawn]] is a "scripting" language used to embed functionality in other programs. That means it is not a standalone language, like C++ or Java, and its details will differ based on the application. SourcePawn is the version of Pawn used in [[SourceMod]].<br />
<br />
This guide does not tell you how to write SourceMod plugins; it is intended as an overview of the syntax and semantics of the language instead. Read the separate article, [[Introduction to SourceMod Plugins]] for SourceMod API specifics. <br />
<br />
=Non-Programmer Intro=<br />
This section is intended for non-programmers. If you're still confused, you may want to pick up a book on another language, such as PHP, Python, or Java, to get a better idea of what programming is like.<br />
<br />
==Symbols/Keywords==<br />
A symbol is a series of letters, numbers, and/or underscores, that uniquely represents something. Symbols are case-sensitive, and usually start with a letter.<br />
<br />
There are a few reserved symbols that have special meaning. For example, <tt>if</tt>, <tt>for</tt>, and <tt>return</tt> are special constructs in the language that will explained later. They cannot be used as symbol names.<br />
<br />
==Variables==<br />
There a few important constructs you should know before you begin to script. The first is a '''variable'''. A variable is a symbol, or name, that holds data. For example, the variable "a" could hold the number "2", "16", "0", et cetera. Since a variable holds data, it also allocates the memory needed to store that data.<br />
<br />
In addition to a name, variables have a '''type'''. A type tells the program how to interpret the data, and how much memory the data will use. Pawn has three types of data that are most commonly used:<br />
* Integers, using the <tt>int</tt> type. Integer types may store a whole number from -2147483648 to 2147483647.<br />
* Floats, using the <tt>float</tt> type. Float types may store fractional numbers in a huge range, though they are not as precise as integers.<br />
* Characters, using the <tt>char</tt> type. Character types store one byte of character information, typically an [http://www.asciitable.com/ ASCII] character.<br />
* Booleans, using the <tt>bool</tt> type. Booleans store either true or false.<br />
<br />
Example of creating variables and assigning values:<br />
<br />
<sourcepawn>int money = 5400;<br />
float percent = 67.3;<br />
bool enabled = false;<br />
</sourcepawn><br />
<br />
==Functions==<br />
The next important concept is '''functions'''. Functions are symbols or names that perform an action. When you invoke, or call them, they carry out a specific sequence of code and then return a result. There are a few types of functions, but every function is activated the same way. Example:<br />
<br />
<sourcepawn><br />
show(56); // Calls the "show" function, and gives it the number 56.<br />
enable(); // Calls the "enable" function with no values.<br />
bool visible = show(a); //Calls the "show" function, stores its result in a variable.<br />
</sourcepawn><br />
<br />
Every piece of data passed to a function is called a '''parameter'''. A function can have any number of parameters (there is a "reasonable" limit of 32 in SourceMod). Parameters will be explained further in the article.<br />
<br />
==Comments==<br />
Note any text that appears after a "//" is considered a "comment" and is not actual code. There are two comment styles:<br />
*<tt>//</tt> - Double slash, everything following on that line is ignored.<br />
*<tt>/* */</tt> - Multi-line comment, everything in between the asterisks is ignored. You cannot nest these.<br />
<br />
<br />
==Block Coding==<br />
The next concept is block coding. You can group code into "blocks" separated by { and }. This effectively makes one large block of code act as one statement. For example:<br />
<br />
<sourcepawn>{<br />
here;<br />
is;<br />
some;<br />
code;<br />
}</sourcepawn><br />
<br />
Block coding using braces is used everywhere in programming. Blocks of code can be nested within each other. It is a good idea to adapt a consistent and readable indentation style early on to prevent spaghetti-looking code.<br />
<br />
=Language Paradigms=<br />
Pawn may seem similar to other languages, like C, but it has fundamental differences. It is not important that you immediately understand these differences, but they may be helpful if you're familiar with another language already.<br />
*'''Pawn is sort of typed.''' Before SourceMod 1.7, Pawn did not have types. Older code and older natives will reflect this by using tags and the <tt>new</tt> keyword. As of SourceMod 1.7, we recommend that all code use types. For more information see [[SourcePawn Transitional Syntax]].<br />
*'''Pawn is not garbage collected.''' Pawn, as a language, has no built-in memory allocation, and thus has no garbage. If a function allocates memory, you may be responsible for freeing it.<br />
*'''Pawn is not object oriented.''' Pawn does not have structs or objects. As of SourceMod 1.7, it has limited sugaring for treating some data types as objects, but users cannot create their own objects or classes.<br />
*'''Pawn is single-threaded.''' As of this writing, Pawn is not thread safe. <br />
*'''Pawn is compiled.''' Pawn is compiled to an intermediate, machine-independent code, which is stored in a ".smx" file. When loading .smx files, SourceMod translates this code to machine code for the platform and CPU it's running on.<br />
<br />
Early language design decisions were made by ITB CompuPhase. It is designed for low-level embedded devices and is thus very small and very fast.<br />
<br />
=Variables=<br />
Pawn currently supports the following basic variable types:<br />
*<tt>bool</tt> - true or false.<br />
*<tt>char</tt> - an 8-bit ASCII character.<br />
*<tt>int</tt> - a 32-bit signed integer.<br />
*<tt>float</tt> - a 32-bit IEEE-754 floating point number.<br />
*<tt>Handle</tt> - the base type of a SourceMod object<br />
<br />
Other types may exist when defined in include files - for example, enums create new types for named integers, and many types derive from <tt>Handle</tt>.<br />
<br />
Strings, currently, are 0-terminated arrays of <tt>char</tt>s. They're described a little further ahead.<br />
<br />
==Declaration==<br />
Below we include some examples of variable declarations, both valid and invalid. Keep in mind that SourcePawn has recently added new syntax, and that's what's documented below. Older code may use older declaration syntax, which is no longer supported.<br />
<br />
<sourcepawn><br />
int a = 5;<br />
float b = 5.0;<br />
bool c = true;<br />
bool d = false;<br />
</sourcepawn><br />
<br />
Invalid variable usage:<br />
<sourcepawn><br />
int a = 5.0; // Type mismatch. 5.0 is a float.<br />
float b = 5; // Type mismatch. 5 is an integer.<br />
</sourcepawn><br />
<br />
If a variable is not assigned upon declaration, it will be set to 0. For example:<br />
<sourcepawn><br />
int a; // Set to 0<br />
float b; // Set to 0.0<br />
bool c; // Set to false<br />
</sourcepawn><br />
<br />
==Assignment==<br />
Variables can be re-assigned data after they are created. For example:<br />
<sourcepawn>int a;<br />
float b;<br />
bool c;<br />
<br />
a = 5;<br />
b = 5.0;<br />
c = true;<br />
</sourcepawn><br />
<br />
=Arrays=<br />
An array is a sequence of data in a list. Arrays are useful for storing multiple pieces of data in one variable, or mapping one type of data to another.<br />
<br />
==Declaration==<br />
An array is declared using brackets. Some examples of arrays:<br />
<sourcepawn><br />
int players[32]; // Stores 32 integers.<br />
float origin[3]; // Stores 3 floating point numbers<br />
</sourcepawn><br />
<br />
By default, arrays are initialized to 0. You can assign them different default values, however:<br />
<sourcepawn><br />
int numbers[5] = {1, 2, 3, 4, 5}; // Stores 1, 2, 3, 4, 5.<br />
float origin[3] = {1.0, 2.0, 3.0}; // Stores 1.0, 2.0, 3.0.<br />
</sourcepawn><br />
<br />
You can leave out the array size if you're going to pre-assign data to it. For example:<br />
<sourcepawn><br />
int numbers[] = {1, 3, 5, 7, 9};<br />
</sourcepawn><br />
<br />
The compiler will automatically deduce that you intended an array of size 5.<br />
<br />
When array is declared with brackets after its name, Pawn considers that array to have a '''fixed size'''. The size of a fixed-size array is always known. Some arrays can be '''dynamically sized''', by putting the brackets before the name. For example,<br />
<br />
<sourcepawn><br />
int[] numbers = new int[MaxClients]<br />
</sourcepawn><br />
<br />
This creates an array of size <tt>MaxClients</tt>, which could be anything, so the size of the array is not known until the array is allocated.<br />
<br />
==Usage==<br />
Using an array is just like using a normal variable. The only difference is the array must be '''indexed'''. Indexing an array means choosing the element which you wish to use.<br />
<br />
For example, here is an example of the above code using indexes:<br />
<sourcepawn><br />
int numbers[5];<br />
float origin[3];<br />
<br />
numbers[0] = 1;<br />
numbers[1] = 2;<br />
numbers[2] = 3;<br />
numbers[3] = 4;<br />
numbers[4] = 5;<br />
origin[0] = 1.0;<br />
origin[1] = 2.0;<br />
origin[2] = 3.0;<br />
</sourcepawn><br />
<br />
Note that the '''index''' is what's in between the brackets. The index always starts from 0. That is, if an array has N elements, its valid indexes are from 0 to N-1. Accessing the data at these indexes works like a normal variable.<br />
<br />
Using an incorrect index will cause an error. For example:<br />
<sourcepawn><br />
int numbers[5];<br />
<br />
numbers[5] = 20;</sourcepawn><br />
<br />
This may look correct, but 5 is not a valid index. The highest valid index is 4.<br />
<br />
You can use any expression as an index. For example:<br />
<sourcepawn>int a, numbers[5];<br />
<br />
a = 1; // Set a = 1<br />
numbers[a] = 4; // Set numbers[1] = 4<br />
numbers[numbers[a]] = 2; // Set numbers[4] = 2<br />
</sourcepawn><br />
<br />
Expressions will be discussed in depth later in the article.<br />
<br />
=Enums=<br />
Enums are retyped integers. A new enum can be declared as follows:<br />
<br />
<sourcepawn>enum MyEnum // the name is optional<br />
{<br />
MyFirstValue, // == 0, if not explicitly assigned, the first value is zero<br />
MySecondValue, // == 1, implicitly set to the previous value plus one<br />
MyThirdValue = 1, // == 1, explicitly assign to one -- multiple names can share the same value<br />
MyFourthValue, // == 2<br />
}<br />
</sourcepawn><br />
<br />
To allow implicit conversions of enums back to integers (that is, so functions expecting a value of type 'int' accepts it as one instead of generating a warning), the enum must either be anonymous (unnamed) or must start with a lowercase character.<br />
<br />
=Strings=<br />
Strings are a construct for storing text (or even raw binary data). A string is just an array of characters, except that the final character must be 0 (called the null terminator). Without a null terminator, Pawn would not know where to stop reading the string. All strings are [http://en.wikipedia.org/wiki/UTF-8 UTF-8] in SourcePawn.<br />
<br />
In general, you must have an idea of how large a string will be before you store it. SourcePawn does not yet have the capability of pre-determining storage space for strings.<br />
<br />
==Usage==<br />
Strings are usually declared as arrays. For example:<br />
<sourcepawn><br />
char message[] = "Hello!";<br />
char clams[6] = "Clams";<br />
</sourcepawn><br />
<br />
These are equivalent to doing:<br />
<sourcepawn><br />
char message[7];<br />
char clams[6];<br />
<br />
message[0] = 'H';<br />
message[1] = 'e';<br />
message[2] = 'l';<br />
message[3] = 'l';<br />
message[4] = 'o';<br />
message[5] = '!';<br />
message[6] = 0;<br />
clams[0] = 'C';<br />
clams[1] = 'l';<br />
clams[2] = 'a';<br />
clams[3] = 'm';<br />
clams[4] = 's';<br />
clams[5] = 0;<br />
</sourcepawn><br />
<br />
Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.<br />
<br />
Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.<br />
<br />
==Characters==<br />
A character of text can be used in either a String or a cell. For example:<br />
<sourcepawn>char text[] = "Crab";<br />
char clam;<br />
<br />
clam = 'D'; //Set clam to 'D'<br />
text[0] = 'A'; //Change the 'C' to 'A', it is now 'Arab'<br />
clam = text[0]; //Set clam to 'A'<br />
text[1] = clam; //Change the 'r' to 'A', is is now 'AAab'<br />
</sourcepawn><br />
<br />
What you can't do is mix character arrays with strings. The internal storage is different. For example:<br />
<sourcepawn><br />
int clams[] = "Clams"; // Invalid.<br />
int clams[] = {'C', 'l', 'a', 'm', 's', 0}; // Valid, but NOT A STRING.<br />
</sourcepawn><br />
<br />
==Concatenation==<br />
In programming, concatenation is the operation of joining character strings end-to-end. The benefit of this is to improve the readability of the source code for humans, since the compiler will continue to treat the strings as a single string. String literals can be concatenated with the <tt>...</tt> or <tt>\</tt> operator:<br />
<br />
<sourcepawn><br />
char text[] = "This is a really long string of text that should be split over multiple lines for the sake of readability."<br />
<br />
char text[] = "This is a really long string of text that should be "<br />
... "split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char text[] = "This is a really long string of text that should be \<br />
split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char prefix[] = "What you can't do, however, ";<br />
char moreText[] = prefix ... "is concatenate using a non-literal string."; // Invalid, not a constant expression.<br />
</sourcepawn><br />
<br />
=Functions=<br />
Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or '''called''', with '''parameters''' that give specific options.<br />
<br />
There are two types of ways functions are called:<br />
*'''direct call''' - You specifically call a function in your code.<br />
*'''callback''' - The application calls a function in your code, as if it were an event trigger.<br />
<br />
There are six types of functions:<br />
*'''native''': A direct, internal function provided by the application.<br />
*'''public''': A callback function that is visible to the application and other scripts.<br />
*'''normal''': A normal function that only you can call.<br />
*'''static''': The scope of this function is restricted to the current file, can be used in combination with stock.<br />
*'''stock''': A normal function provided by an include file. If unused, it won't be compiled.<br />
*'''forward''': This function is a global event provided by the application. If you implement it, it will be a callback.<br />
<br />
All code in Pawn must exist in functions. This is in contrast to languages like PHP, Perl, and Python which let you write global code. That is because Pawn is a callback-based language: it responds to actions from a parent application, and functions must be written to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.<br />
<br />
==Declaration==<br />
Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the '''signature''' and the '''body'''. The signature contains the name of your function and the parameters it will accept. The body is the contents of its code.<br />
<br />
Example of a function:<br />
<sourcepawn><br />
int AddTwoNumbers(int first, int second)<br />
{<br />
int sum = first + second;<br />
return sum;<br />
}</sourcepawn><br />
<br />
This is a simple function. The prototype is this line:<br />
<sourcepawn>int AddTwoNumbers(int first, int second)</sourcepawn><br />
<br />
Broken down, it means:<br />
*<tt>int</tt> - Return value type (integer).<br />
*<tt>AddTwoNumbers</tt> - Name of the function.<br />
*<tt>int first</tt> - First parameter, an integer.<br />
*<tt>int second</tt> - Second parameter, an integer.<br />
<br />
The body is a block of code. It creates a new variable, called <tt>sum</tt>, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the <tt>return</tt> statement, which tells the function to end and return a value to the caller of the function. All functions return something on completion, unless they return a special type called <tt>void</tt>.<br />
<br />
A function can accept any type of input, and it can return any non-array type.<br />
<br />
You can, of course, pass variables to functions:<br />
<sourcepawn>int numbers[3] = {1, 2, 0};<br />
<br />
numbers[2] = AddTwoNumbers(numbers[0], numbers[1]);</sourcepawn><br />
<br />
Note that cells are passed '''by value'''. That is, their value cannot be changed by the function. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
ChangeValue(a);<br />
<br />
ChangeValue(b)<br />
{<br />
b = 5;<br />
}</sourcepawn><br />
<br />
This code would not change the value of <tt>a</tt>. That is because a copy of the value in <tt>a</tt> is passed instead of <tt>a</tt> itself. <br />
<br />
More examples of functions will be provided throughout the article.<br />
<br />
==Publics==<br />
Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from <tt>sourcemod.inc</tt>:<br />
<br />
<sourcepawn>forward void OnPluginStart();<br />
forward void OnClientDisconnected(int client);</sourcepawn><br />
<br />
To implement and receive these two events, you would write functions as such:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
/* Code here */<br />
}<br />
<br />
public void OnClientDisconnected(int client)<br />
{<br />
/* Code here */<br />
}</sourcepawn><br />
<br />
The '''public''' keyword signals to the parent application that it should attach the function to the appropriate forwarded event.<br />
<br />
==Natives==<br />
Natives are builtin functions provided by SourceMod. You can call them as if they were a normal function. For example, SourceMod has the following function:<br />
<br />
<sourcepawn>native float FloatRound(float num);</sourcepawn><br />
<br />
It can be called like so:<br />
<sourcepawn>int rounded = FloatRound(5.2); // rounded will be 5</sourcepawn><br />
<br />
==Array Parameters==<br />
You can pass arrays or Strings as parameters. It is important to note that these are passed '''by reference'''. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.<br />
<br />
<sourcepawn><br />
int example[] = {1, 2, 3, 4, 5};<br />
<br />
ChangeArray(example, 2, 29);<br />
<br />
void ChangeArray(int[] array, int index, int value)<br />
{<br />
array[index] = value;<br />
}</sourcepawn><br />
<br />
The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:<br />
<sourcepawn>example[2] = 29;</sourcepawn><br />
<br />
Note two important things here. First, arrays are not copied when they are passed to functions - they are passed ''by reference'', so the view of the array is consistent at all times. Second, the brackets changed position in our function signature. This is because our function accepts an array of any size, and since we don't know the size, we must use the dynamic array syntax.<br />
<br />
To prevent an array from being modified in a function, you can mark it as <tt>const</tt>. This will raise an error on code that attempts to modify it. For example:<br />
<br />
<sourcepawn>void CantChangeArray(const int[] array, int index, int value)<br />
{<br />
array[index] = value; //Won't compile<br />
}</sourcepawn><br />
<br />
It is a good idea to use <tt>const</tt> in array parameters if you know the array won't be modified; this can prevent coding mistakes.<br />
<br />
When a function takes an array as a parameter, you can also pass any indexed array element which will be interpreted as a sub-array that begins from that index. For example,<br />
if you want to get the length of a string, you can use the strlen function like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString); // Set length = 8<br />
</sourcepawn><br />
<br />
And if you pass an indexed array element, it will be interpreted as a sub-array that begins from that index like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString[2]); // Set length = 6<br />
</sourcepawn><br />
<br />
==Named Parameters==<br />
With functions that have many parameters with default values, you can call the function with named parameters to assign a value based on its name instead of which position it is in the argument list. For example:<br />
<br />
<sourcepawn><br />
void SomeFunctionWithDefaultParams(int a, int b = 1, int c = 2, int d = 3, int e = 4);<br />
<br />
// If you want to call it with a different value for `d` -- note the dotted argument assignment<br />
SomeFunctionWithDefaultParams(0, .d = 14);<br />
<br />
// This is effectively the same, but less readable:<br />
SomeFunctionWithDefaultParams(0, _, _, 14, _);<br />
</sourcepawn><br />
<br />
=Expressions=<br />
Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.<br />
<br />
The simplest expression is a single number. For example:<br />
<sourcepawn><br />
0; //Returns the number 0<br />
(0); //Returns the number 0 as well<br />
</sourcepawn><br />
<br />
Although expressions can return any value, they are also said to either return ''zero or non-zero''. In that sense, ''zero'' is ''false'', and ''non-zero'' is ''true''. For example, -1 is '''true''' in Pawn, since it is non-zero. Do not assume negative numbers are false.<br />
<br />
The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:<br />
<sourcepawn><br />
5 + 6; //Evaluates to 11<br />
5 * 6 + 3; //Evaluates to 33<br />
5 * (6 + 3); //Evaluates to 45<br />
5.0 + 2.3; //Evaluates to 7.3<br />
(5 * 6) % 7; //Modulo operator, evaluates to 2<br />
(5 + 3) / 2 * 4 - 9; //Evaluates to -8<br />
</sourcepawn><br />
<br />
As noted, expressions can contain variables, or even functions:<br />
<sourcepawn><br />
int a = 5 * 6;<br />
int b = a * 3; //Evaluates to 90<br />
int c = AddTwoNumbers(a, b) + (a * b);<br />
</sourcepawn><br />
<br />
Note: String manipulation routines may be found in the string.inc file located in the include subdirectory. They may be browsed through the [http://docs.sourcemod.net/api/ API Reference] as well.<br />
<br />
==Operators==<br />
There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
a = a + 5;</sourcepawn><br />
<br />
Can be rewritten as:<br />
<sourcepawn>int a = 5;<br />
a += 5;</sourcepawn><br />
<br />
This is true of the following operators in Pawn:<br />
*Four-function: *, /, -, +<br />
*Bit-wise: |, &, ^, ~, <<, >><br />
<br />
Additionally, there are increment/decrement operators:<br />
<sourcepawn>a = a + 1;<br />
a = a - 1;</sourcepawn><br />
<br />
Can be simplified as:<br />
<sourcepawn>a++;<br />
a--;</sourcepawn><br />
<br />
As an advanced note, the ++ or -- can come before the variable (pre-increment, pre-decrement) or after the variable (post-increment, post-decrement). The difference is in how the rest of the expression containing them sees their result.<br />
<br />
* ''Pre:'' The variable is incremented before evaluation, and the rest of the expression sees the new value.<br />
* ''Post:'' The variable is incremented after evaluation, and the rest of the expression sees the old value.<br />
<br />
In other words, <tt>a++</tt> evaluates to the value of <tt>a</tt> while <tt>++a</tt> evaluates to the value of <tt>a + 1</tt>. In both cases <tt>a</tt> is incremented by <tt>1</tt>.<br />
<br />
For example:<br />
<br />
<sourcepawn>int a = 5;<br />
int b = a++; // b = 5, a = 6 (1)<br />
int c = ++a; // a = 7, c = 7 (2)<br />
</sourcepawn><br />
<br />
In (1) <tt>b</tt> is assigned <tt>a</tt>'s ''old'' value ''before'' it is incremented to <tt>6</tt>, but in (2) <tt>c</tt> is assigned <tt>a</tt>'s ''int'' value ''after'' it is incremented to <tt>7</tt>.<br />
<br />
==Comparison Operators==<br />
There are six operators for comparing two values numerically, and the result is either true (non-zero) or false (zero):<br />
*<tt>a == b</tt> - True if a and b have the same value.<br />
*<tt>a != b</tt> - True if a and b have different values.<br />
*<tt>a &gt; b</tt> - True if a is greater than b<br />
*<tt>a &gt;= b</tt> - True if a is greater than or equal to b<br />
*<tt>a &lt; b</tt> - True if a is less than b<br />
*<tt>a &lt;= b</tt> - True if a is less than or equal to b<br />
<br />
For example:<br />
<sourcepawn><br />
(1 != 3); //Evaluates to true because 1 is not equal to 3.<br />
(3 + 3 == 6); //Evaluates to true because 3+3 is 6.<br />
(5 - 2 >= 4); //Evaluates to false because 3 is less than 4.<br />
</sourcepawn><br />
<br />
Note that these operators do not work on arrays or strings. That is, you cannot compare either using <tt>==</tt>.<br />
<br />
==Truth Operators==<br />
These truth values can be combined using three boolean operators:<br />
*<tt>a && b</tt> - True if both a and b are true. False if a or b (or both) is false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>&&</tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 0<br />
|-<br />
! 1<br />
| 0 || 1<br />
|}<br />
*<tt>a || b</tt> - True if a or b (or both) is true. False if both a and b are false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt><nowiki>||</nowiki></tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 1<br />
|-<br />
! 1<br />
| 1 || 1<br />
|}<br />
*<tt>!a</tt> - True if a is false. False if a is true.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>!</tt> !! 0 !! 1<br />
|- <br />
!<br />
| 1 || 0<br />
|}<br />
<br />
For example:<br />
<sourcepawn><br />
(1 || 0); //Evaluates to true because the expression 1 is true<br />
(1 && 0); //Evaluates to false because the expression 0 is false<br />
(!1 || 0); //Evaluates to false because !1 is false.<br />
</sourcepawn><br />
<br />
==Left/Right Values==<br />
Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.<br />
<br />
For example:<br />
<sourcepawn><br />
int a = 5;</sourcepawn><br />
<br />
In this example <tt>a</tt> is an l-value and <tt>5</tt> is an r-value.<br />
<br />
The rules:<br />
*'''Expressions are never l-values'''.<br />
*'''Variables are both l-values and r-values'''.<br />
<br />
=Conditionals=<br />
Conditional statements let you only run code if a certain condition is matched.<br />
<br />
==If Statements==<br />
If statements test one or more conditions. For example:<br />
<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code that will run if the expression was true */<br />
}</sourcepawn><br />
<br />
They can be extended to handle more cases as well:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 6)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 7)<br />
{<br />
/* Code */<br />
}</sourcepawn><br />
<br />
You can also handle the case of no expression being matched. For example:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else<br />
{<br />
/* Code that will run if no expressions were true */<br />
}</sourcepawn><br />
<br />
==Switch Statements==<br />
Switch statements are restricted if statements. They test one expression for a series of possible values. For example:<br />
<br />
<sourcepawn><br />
switch (a)<br />
{<br />
case 5:<br />
{<br />
/* code */<br />
}<br />
case 6:<br />
{<br />
/* code */<br />
}<br />
case 7:<br />
{<br />
/* code */<br />
}<br />
case 8, 9, 10:<br />
{<br />
/* Code */<br />
}<br />
default:<br />
{<br />
/* will run if no case matched */<br />
}<br />
}</sourcepawn><br />
<br />
Unlike some other languages, switches are not fall-through. That is, multiple cases will never be run. When a case matches its code is executed, and the switch is then immediately terminated.<br />
<br />
=Loops=<br />
Loops allow you to conveniently repeat a block of code while a given condition remains true. <br />
<br />
==For Loops==<br />
For loops are loops which have four parts:<br />
*The '''initialization''' statement - run once before the first loop.<br />
*The '''condition''' statement - checks whether the next loop should run, including the first one. The loop terminates when this expression evaluates to false.<br />
*The '''iteration''' statement - run after each loop.<br />
*The '''body''' block - run each time the '''condition''' statement evaluates to true.<br />
<br />
<sourcepawn><br />
for ( /* initialization */ ; /* condition */ ; /* iteration */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
A simple example is a function to sum an array:<br />
<sourcepawn><br />
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};<br />
int sum = SumArray(array, 10);<br />
<br />
int SumArray(const int[] array, int count)<br />
{<br />
int total;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
total += array[i];<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
Broken down:<br />
*<tt>int i = 0</tt> - Creates a new variable for the loop, sets it to 0.<br />
*<tt>i < count</tt> - Only runs the loop if <tt>i</tt> is less than <tt>count</tt>. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.<br />
*<tt>i++</tt> - Increments <tt>i</tt> by one after each loop. This ensures that the loop doesn't run forever; eventually <tt>i</tt> will become too big and the loop will end.<br />
<br />
Thus, the <tt>SumArray</tt> function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.<br />
<br />
==While Loops==<br />
While loops are less common than for loops but are actually the simplest possible loop. They have only two parts:<br />
*The '''condition''' statement - checked before each loop. The loop terminates when it evaluates to false.<br />
*The '''body''' block - run each time through the loop.<br />
<br />
<sourcepawn><br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
As long as the condition expression remains true, the loop will continue. Every for loop can be rewritten as a while loop:<br />
<br />
<sourcepawn><br />
/* initialization */<br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
/* iteration */<br />
}<br />
</sourcepawn><br />
<br />
Here is the previous for loop rewritten as a while loop:<br />
<sourcepawn><br />
int SumArray(const int[] array, int count)<br />
{<br />
int total, i;<br />
<br />
while (i < count)<br />
{<br />
total += array[i];<br />
i++;<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
There are also '''do...while''' loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:<br />
<br />
<sourcepawn><br />
do<br />
{<br />
/* body */<br />
}<br />
while ( /* condition */ );<br />
</sourcepawn><br />
<br />
==Loop Control==<br />
There are two cases in which you want to selectively control a loop:<br />
*'''skipping''' one iteration of the loop but continuing as normal, or;<br />
*'''breaking''' the loop entirely before it's finished.<br />
<br />
Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:<br />
<sourcepawn><br />
/**<br />
* Returns the array index where the value is, or -1 if not found.<br />
*/<br />
int SearchInArray(const int[] array, int count, int value)<br />
{<br />
int index = -1;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
if (array[i] == value)<br />
{<br />
index = i;<br />
break;<br />
}<br />
}<br />
<br />
return index;<br />
}</sourcepawn><br />
<br />
Certainly, this function could simply <tt>return i</tt> instead, but the example shows how <tt>break</tt> will terminate the loop.<br />
<br />
Similarly, the <tt>continue</tt> keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:<br />
<sourcepawn><br />
int SumEvenNumbers(const int[] array, int count)<br />
{<br />
int sum;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
/* If divisibility by 2 is 1, we know it's odd */<br />
if (array[i] % 2 == 1)<br />
{<br />
/* Skip the rest of this loop iteration */<br />
continue;<br />
}<br />
sum += array[i];<br />
}<br />
<br />
return sum;<br />
}</sourcepawn><br />
<br />
=Scope=<br />
Scope refers to the '''visibility''' of code. That is, code at one level may not be "visible" to code at another level. For example:<br />
<br />
<sourcepawn><br />
int A, B, C;<br />
<br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function2();<br />
}<br />
<br />
void Function2()<br />
{<br />
int C;<br />
}</sourcepawn><br />
<br />
In this example, <tt>A</tt>, <tt>B</tt>, and <tt>C</tt> exist at '''global scope'''. They can be seen by any function. However, the <tt>B</tt> in <tt>Function1</tt> is not the same variable as the <tt>B</tt> at the global level. Instead, it is at '''local scope''', and is thus a '''local variable'''.<br />
<br />
Similarly, <tt>Function1</tt> and <tt>Function2</tt> know nothing about each other's variables.<br />
<br />
Not only is the variable private to <tt>Function1</tt>, but it is re-created each time the function is invoked. Imagine this:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function1();<br />
}</sourcepawn><br />
<br />
In the above example, <tt>Function1</tt> calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of <tt>B</tt>. When the function ends, <tt>B</tt> is destroyed, and the value is lost.<br />
<br />
This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:<br />
<br />
<sourcepawn>void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
A = 5;<br />
}<br />
}</sourcepawn><br />
<br />
The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
int B = 5;<br />
}<br />
<br />
B = 5;<br />
}</sourcepawn><br />
<br />
Notice that <tt>B</tt> is declared in a new code block. That means <tt>B</tt> is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, <tt>B</tt> is no longer valid.<br />
<br />
=Dynamic Arrays=<br />
Dynamic arrays are arrays which don't have a hardcoded size. For example:<br />
<br />
<sourcepawn>void Function1(int size)<br />
{<br />
int[] array = new int[size];<br />
<br />
/* Code */<br />
}</sourcepawn><br />
<br />
Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.<br />
<br />
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.<br />
<br />
=Extended Variable Declarations=<br />
Variables can be declared in more ways than simply <tt>int float or char</tt>.<br />
<br />
==static==<br />
The <tt>static</tt> keyword is available at global and local scope. It has different meanings in each.<br />
<br />
===Global static===<br />
A global static variable can only be accessed from within the same file. For example:<br />
<br />
<sourcepawn>//file1.inc<br />
static float g_value1 = 0.15f;<br />
<br />
//file2.inc<br />
static float g_value2 = 0.15f;</sourcepawn><br />
<br />
If a plugin includes both of these files, it will not be able to use either <tt>g_value1</tt> or <tt>g_value2</tt>. This is a simple information hiding mechanism, and is similar to declaring member variables as <tt>private</tt> in languages like C++, Java, or C#.<br />
<br />
===Local static===<br />
A local static variable is a global variable that is only visible from its local lexical scope. For example:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
static int counter = -1;<br />
<br />
counter += inc;<br />
<br />
return counter;<br />
}</sourcepawn><br />
<br />
In this example, <tt>counter</tt> is technically a global variable -- it is initialized once to -1 and is never initialized again. It does not exist on the stack. That means each time <tt>MyFunction</tt> runs, the <tt>counter</tt> variable and its storage in memory is the same.<br />
<br />
Take this example:<br />
<sourcepawn>MyFunction(5);<br />
MyFunction(6);<br />
MyFunction(10);</sourcepawn><br />
<br />
In this example, <tt>counter</tt> will be <tt>-1 + 5 + 6 + 10</tt>, or <tt>20</tt>, because it persists beyond the frame of the function. Note this may pose problems for recursive functions: if your function may be recursive, then <tt>static</tt> is usually not a good idea unless your code is re-entrant. <br />
<br />
The benefit of a local static variable is that you don't have to clutter your script with global variables. As long as the variable doesn't need to be read by another function, you can squirrel it inside the function and its persistence will be guaranteed.<br />
<br />
Note that statics can exist in any local scope:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
if (inc > 0)<br />
{<br />
static int counter;<br />
return (counter += inc);<br />
}<br />
return -1;<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Introduction_to_SourcePawn_1.7&diff=10981Introduction to SourcePawn 1.72020-03-30T03:08:25Z<p>Joinedsenses: /* Array Parameters */ Updated syntax</p>
<hr />
<div>This guide is designed to give you a very basic overview to fundamentals of scripting in SourcePawn. [[Pawn]] is a "scripting" language used to embed functionality in other programs. That means it is not a standalone language, like C++ or Java, and its details will differ based on the application. SourcePawn is the version of Pawn used in [[SourceMod]].<br />
<br />
This guide does not tell you how to write SourceMod plugins; it is intended as an overview of the syntax and semantics of the language instead. Read the separate article, [[Introduction to SourceMod Plugins]] for SourceMod API specifics. <br />
<br />
=Non-Programmer Intro=<br />
This section is intended for non-programmers. If you're still confused, you may want to pick up a book on another language, such as PHP, Python, or Java, to get a better idea of what programming is like.<br />
<br />
==Symbols/Keywords==<br />
A symbol is a series of letters, numbers, and/or underscores, that uniquely represents something. Symbols are case-sensitive, and usually start with a letter.<br />
<br />
There are a few reserved symbols that have special meaning. For example, <tt>if</tt>, <tt>for</tt>, and <tt>return</tt> are special constructs in the language that will explained later. They cannot be used as symbol names.<br />
<br />
==Variables==<br />
There a few important constructs you should know before you begin to script. The first is a '''variable'''. A variable is a symbol, or name, that holds data. For example, the variable "a" could hold the number "2", "16", "0", et cetera. Since a variable holds data, it also allocates the memory needed to store that data.<br />
<br />
In addition to a name, variables have a '''type'''. A type tells the program how to interpret the data, and how much memory the data will use. Pawn has three types of data that are most commonly used:<br />
* Integers, using the <tt>int</tt> type. Integer types may store a whole number from -2147483648 to 2147483647.<br />
* Floats, using the <tt>float</tt> type. Float types may store fractional numbers in a huge range, though they are not as precise as integers.<br />
* Characters, using the <tt>char</tt> type. Character types store one byte of character information, typically an [http://www.asciitable.com/ ASCII] character.<br />
* Booleans, using the <tt>bool</tt> type. Booleans store either true or false.<br />
<br />
Example of creating variables and assigning values:<br />
<br />
<sourcepawn>int money = 5400;<br />
float percent = 67.3;<br />
bool enabled = false;<br />
</sourcepawn><br />
<br />
==Functions==<br />
The next important concept is '''functions'''. Functions are symbols or names that perform an action. When you invoke, or call them, they carry out a specific sequence of code and then return a result. There are a few types of functions, but every function is activated the same way. Example:<br />
<br />
<sourcepawn><br />
show(56); // Calls the "show" function, and gives it the number 56.<br />
enable(); // Calls the "enable" function with no values.<br />
bool visible = show(a); //Calls the "show" function, stores its result in a variable.<br />
</sourcepawn><br />
<br />
Every piece of data passed to a function is called a '''parameter'''. A function can have any number of parameters (there is a "reasonable" limit of 32 in SourceMod). Parameters will be explained further in the article.<br />
<br />
==Comments==<br />
Note any text that appears after a "//" is considered a "comment" and is not actual code. There are two comment styles:<br />
*<tt>//</tt> - Double slash, everything following on that line is ignored.<br />
*<tt>/* */</tt> - Multi-line comment, everything in between the asterisks is ignored. You cannot nest these.<br />
<br />
<br />
==Block Coding==<br />
The next concept is block coding. You can group code into "blocks" separated by { and }. This effectively makes one large block of code act as one statement. For example:<br />
<br />
<sourcepawn>{<br />
here;<br />
is;<br />
some;<br />
code;<br />
}</sourcepawn><br />
<br />
Block coding using braces is used everywhere in programming. Blocks of code can be nested within each other. It is a good idea to adapt a consistent and readable indentation style early on to prevent spaghetti-looking code.<br />
<br />
=Language Paradigms=<br />
Pawn may seem similar to other languages, like C, but it has fundamental differences. It is not important that you immediately understand these differences, but they may be helpful if you're familiar with another language already.<br />
*'''Pawn is sort of typed.''' Before SourceMod 1.7, Pawn did not have types. Older code and older natives will reflect this by using tags and the <tt>new</tt> keyword. As of SourceMod 1.7, we recommend that all code use types. For more information see [[SourcePawn Transitional Syntax]].<br />
*'''Pawn is not garbage collected.''' Pawn, as a language, has no built-in memory allocation, and thus has no garbage. If a function allocates memory, you may be responsible for freeing it.<br />
*'''Pawn is not object oriented.''' Pawn does not have structs or objects. As of SourceMod 1.7, it has limited sugaring for treating some data types as objects, but users cannot create their own objects or classes.<br />
*'''Pawn is single-threaded.''' As of this writing, Pawn is not thread safe. <br />
*'''Pawn is compiled.''' Pawn is compiled to an intermediate, machine-independent code, which is stored in a ".smx" file. When loading .smx files, SourceMod translates this code to machine code for the platform and CPU it's running on.<br />
<br />
Early language design decisions were made by ITB CompuPhase. It is designed for low-level embedded devices and is thus very small and very fast.<br />
<br />
=Variables=<br />
Pawn currently supports the following basic variable types:<br />
*<tt>bool</tt> - true or false.<br />
*<tt>char</tt> - an 8-bit ASCII character.<br />
*<tt>int</tt> - a 32-bit signed integer.<br />
*<tt>float</tt> - a 32-bit IEEE-754 floating point number.<br />
*<tt>Handle</tt> - the base type of a SourceMod object<br />
<br />
Other types may exist when defined in include files - for example, enums create new types for named integers, and many types derive from <tt>Handle</tt>.<br />
<br />
Strings, currently, are 0-terminated arrays of <tt>char</tt>s. They're described a little further ahead.<br />
<br />
==Declaration==<br />
Below we include some examples of variable declarations, both valid and invalid. Keep in mind that SourcePawn has recently added new syntax, and that's what's documented below. Older code may use older declaration syntax, which is no longer supported.<br />
<br />
<sourcepawn><br />
int a = 5;<br />
float b = 5.0;<br />
bool c = true;<br />
bool d = false;<br />
</sourcepawn><br />
<br />
Invalid variable usage:<br />
<sourcepawn><br />
int a = 5.0; // Type mismatch. 5.0 is a float.<br />
float b = 5; // Type mismatch. 5 is an integer.<br />
</sourcepawn><br />
<br />
If a variable is not assigned upon declaration, it will be set to 0. For example:<br />
<sourcepawn><br />
int a; // Set to 0<br />
float b; // Set to 0.0<br />
bool c; // Set to false<br />
</sourcepawn><br />
<br />
==Assignment==<br />
Variables can be re-assigned data after they are created. For example:<br />
<sourcepawn>int a;<br />
float b;<br />
bool c;<br />
<br />
a = 5;<br />
b = 5.0;<br />
c = true;<br />
</sourcepawn><br />
<br />
=Arrays=<br />
An array is a sequence of data in a list. Arrays are useful for storing multiple pieces of data in one variable, or mapping one type of data to another.<br />
<br />
==Declaration==<br />
An array is declared using brackets. Some examples of arrays:<br />
<sourcepawn><br />
int players[32]; // Stores 32 integers.<br />
float origin[3]; // Stores 3 floating point numbers<br />
</sourcepawn><br />
<br />
By default, arrays are initialized to 0. You can assign them different default values, however:<br />
<sourcepawn><br />
int numbers[5] = {1, 2, 3, 4, 5}; // Stores 1, 2, 3, 4, 5.<br />
float origin[3] = {1.0, 2.0, 3.0}; // Stores 1.0, 2.0, 3.0.<br />
</sourcepawn><br />
<br />
You can leave out the array size if you're going to pre-assign data to it. For example:<br />
<sourcepawn><br />
int numbers[] = {1, 3, 5, 7, 9};<br />
</sourcepawn><br />
<br />
The compiler will automatically deduce that you intended an array of size 5.<br />
<br />
When array is declared with brackets after its name, Pawn considers that array to have a '''fixed size'''. The size of a fixed-size array is always known. Some arrays can be '''dynamically sized''', by putting the brackets before the name. For example,<br />
<br />
<sourcepawn><br />
int[] numbers = new int[MaxClients]<br />
</sourcepawn><br />
<br />
This creates an array of size <tt>MaxClients</tt>, which could be anything, so the size of the array is not known until the array is allocated.<br />
<br />
==Usage==<br />
Using an array is just like using a normal variable. The only difference is the array must be '''indexed'''. Indexing an array means choosing the element which you wish to use.<br />
<br />
For example, here is an example of the above code using indexes:<br />
<sourcepawn><br />
int numbers[5];<br />
float origin[3];<br />
<br />
numbers[0] = 1;<br />
numbers[1] = 2;<br />
numbers[2] = 3;<br />
numbers[3] = 4;<br />
numbers[4] = 5;<br />
origin[0] = 1.0;<br />
origin[1] = 2.0;<br />
origin[2] = 3.0;<br />
</sourcepawn><br />
<br />
Note that the '''index''' is what's in between the brackets. The index always starts from 0. That is, if an array has N elements, its valid indexes are from 0 to N-1. Accessing the data at these indexes works like a normal variable.<br />
<br />
Using an incorrect index will cause an error. For example:<br />
<sourcepawn><br />
int numbers[5];<br />
<br />
numbers[5] = 20;</sourcepawn><br />
<br />
This may look correct, but 5 is not a valid index. The highest valid index is 4.<br />
<br />
You can use any expression as an index. For example:<br />
<sourcepawn>int a, numbers[5];<br />
<br />
a = 1; // Set a = 1<br />
numbers[a] = 4; // Set numbers[1] = 4<br />
numbers[numbers[a]] = 2; // Set numbers[4] = 2<br />
</sourcepawn><br />
<br />
Expressions will be discussed in depth later in the article.<br />
<br />
=Enums=<br />
Enums are retyped integers. A new enum can be declared as follows:<br />
<br />
<sourcepawn>enum MyEnum // the name is optional<br />
{<br />
MyFirstValue, // == 0, if not explicitly assigned, the first value is zero<br />
MySecondValue, // == 1, implicitly set to the previous value plus one<br />
MyThirdValue = 1, // == 1, explicitly assign to one -- multiple names can share the same value<br />
MyFourthValue, // == 2<br />
}<br />
</sourcepawn><br />
<br />
To allow implicit conversions of enums back to integers (that is, so functions expecting a value of type 'int' accepts it as one instead of generating a warning), the enum must either be anonymous (unnamed) or must start with a lowercase character.<br />
<br />
=Strings=<br />
Strings are a construct for storing text (or even raw binary data). A string is just an array of characters, except that the final character must be 0 (called the null terminator). Without a null terminator, Pawn would not know where to stop reading the string. All strings are [http://en.wikipedia.org/wiki/UTF-8 UTF-8] in SourcePawn.<br />
<br />
In general, you must have an idea of how large a string will be before you store it. SourcePawn does not yet have the capability of pre-determining storage space for strings.<br />
<br />
==Usage==<br />
Strings are usually declared as arrays. For example:<br />
<sourcepawn><br />
char message[] = "Hello!";<br />
char clams[6] = "Clams";<br />
</sourcepawn><br />
<br />
These are equivalent to doing:<br />
<sourcepawn><br />
char message[7];<br />
char clams[6];<br />
<br />
message[0] = 'H';<br />
message[1] = 'e';<br />
message[2] = 'l';<br />
message[3] = 'l';<br />
message[4] = 'o';<br />
message[5] = '!';<br />
message[6] = 0;<br />
clams[0] = 'C';<br />
clams[1] = 'l';<br />
clams[2] = 'a';<br />
clams[3] = 'm';<br />
clams[4] = 's';<br />
clams[5] = 0;<br />
</sourcepawn><br />
<br />
Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.<br />
<br />
Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.<br />
<br />
==Characters==<br />
A character of text can be used in either a String or a cell. For example:<br />
<sourcepawn>char text[] = "Crab";<br />
char clam;<br />
<br />
clam = 'D'; //Set clam to 'D'<br />
text[0] = 'A'; //Change the 'C' to 'A', it is now 'Arab'<br />
clam = text[0]; //Set clam to 'A'<br />
text[1] = clam; //Change the 'r' to 'A', is is now 'AAab'<br />
</sourcepawn><br />
<br />
What you can't do is mix character arrays with strings. The internal storage is different. For example:<br />
<sourcepawn><br />
int clams[] = "Clams"; // Invalid.<br />
int clams[] = {'C', 'l', 'a', 'm', 's', 0}; // Valid, but NOT A STRING.<br />
</sourcepawn><br />
<br />
==Concatenation==<br />
In programming, concatenation is the operation of joining character strings end-to-end. The benefit of this is to improve the readability of the source code for humans, since the compiler will continue to treat the strings as a single string. String literals can be concatenated with the <tt>...</tt> or <tt>\</tt> operator:<br />
<br />
<sourcepawn><br />
char text[] = "This is a really long string of text that should be split over multiple lines for the sake of readability."<br />
<br />
char text[] = "This is a really long string of text that should be "<br />
... "split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char text[] = "This is a really long string of text that should be \<br />
split over multiple lines for the sake of readability."; // Valid.<br />
<br />
char prefix[] = "What you can't do, however, ";<br />
char moreText[] = prefix ... "is concatenate using a non-literal string."; // Invalid, not a constant expression.<br />
</sourcepawn><br />
<br />
=Functions=<br />
Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or '''called''', with '''parameters''' that give specific options.<br />
<br />
There are two types of ways functions are called:<br />
*'''direct call''' - You specifically call a function in your code.<br />
*'''callback''' - The application calls a function in your code, as if it were an event trigger.<br />
<br />
There are six types of functions:<br />
*'''native''': A direct, internal function provided by the application.<br />
*'''public''': A callback function that is visible to the application and other scripts.<br />
*'''normal''': A normal function that only you can call.<br />
*'''static''': The scope of this function is restricted to the current file, can be used in combination with stock.<br />
*'''stock''': A normal function provided by an include file. If unused, it won't be compiled.<br />
*'''forward''': This function is a global event provided by the application. If you implement it, it will be a callback.<br />
<br />
All code in Pawn must exist in functions. This is in contrast to languages like PHP, Perl, and Python which let you write global code. That is because Pawn is a callback-based language: it responds to actions from a parent application, and functions must be written to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.<br />
<br />
==Declaration==<br />
Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the '''signature''' and the '''body'''. The signature contains the name of your function and the parameters it will accept. The body is the contents of its code.<br />
<br />
Example of a function:<br />
<sourcepawn><br />
int AddTwoNumbers(int first, int second)<br />
{<br />
int sum = first + second;<br />
return sum;<br />
}</sourcepawn><br />
<br />
This is a simple function. The prototype is this line:<br />
<sourcepawn>int AddTwoNumbers(int first, int second)</sourcepawn><br />
<br />
Broken down, it means:<br />
*<tt>int</tt> - Return value type (integer).<br />
*<tt>AddTwoNumbers</tt> - Name of the function.<br />
*<tt>int first</tt> - First parameter, an integer.<br />
*<tt>int second</tt> - Second parameter, an integer.<br />
<br />
The body is a block of code. It creates a new variable, called <tt>sum</tt>, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the <tt>return</tt> statement, which tells the function to end and return a value to the caller of the function. All functions return something on completion, unless they return a special type called <tt>void</tt>.<br />
<br />
A function can accept any type of input, and it can return any non-array type.<br />
<br />
You can, of course, pass variables to functions:<br />
<sourcepawn>int numbers[3] = {1, 2, 0};<br />
<br />
numbers[2] = AddTwoNumbers(numbers[0], numbers[1]);</sourcepawn><br />
<br />
Note that cells are passed '''by value'''. That is, their value cannot be changed by the function. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
ChangeValue(a);<br />
<br />
ChangeValue(b)<br />
{<br />
b = 5;<br />
}</sourcepawn><br />
<br />
This code would not change the value of <tt>a</tt>. That is because a copy of the value in <tt>a</tt> is passed instead of <tt>a</tt> itself. <br />
<br />
More examples of functions will be provided throughout the article.<br />
<br />
==Publics==<br />
Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from <tt>sourcemod.inc</tt>:<br />
<br />
<sourcepawn>forward void OnPluginStart();<br />
forward void OnClientDisconnected(int client);</sourcepawn><br />
<br />
To implement and receive these two events, you would write functions as such:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
/* Code here */<br />
}<br />
<br />
public void OnClientDisconnected(int client)<br />
{<br />
/* Code here */<br />
}</sourcepawn><br />
<br />
The '''public''' keyword signals to the parent application that it should attach the function to the appropriate forwarded event.<br />
<br />
==Natives==<br />
Natives are builtin functions provided by SourceMod. You can call them as if they were a normal function. For example, SourceMod has the following function:<br />
<br />
<sourcepawn>native float FloatRound(float num);</sourcepawn><br />
<br />
It can be called like so:<br />
<sourcepawn>int rounded = FloatRound(5.2); // rounded will be 5</sourcepawn><br />
<br />
==Array Parameters==<br />
You can pass arrays or Strings as parameters. It is important to note that these are passed '''by reference'''. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.<br />
<br />
<sourcepawn><br />
int example[] = {1, 2, 3, 4, 5};<br />
<br />
ChangeArray(example, 2, 29);<br />
<br />
void ChangeArray(int[] array, int index, int value)<br />
{<br />
array[index] = value;<br />
}</sourcepawn><br />
<br />
The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:<br />
<sourcepawn>example[2] = 29;</sourcepawn><br />
<br />
Note two important things here. First, arrays are not copied when they are passed to functions - they are passed ''by reference'', so the view of the array is consistent at all times. Second, the brackets changed position in our function signature. This is because our function accepts an array of any size, and since we don't know the size, we must use the dynamic array syntax.<br />
<br />
To prevent an array from being modified in a function, you can mark it as <tt>const</tt>. This will raise an error on code that attempts to modify it. For example:<br />
<br />
<sourcepawn>void CantChangeArray(const int[] array, int index, int value)<br />
{<br />
array[index] = value; //Won't compile<br />
}</sourcepawn><br />
<br />
It is a good idea to use <tt>const</tt> in array parameters if you know the array won't be modified; this can prevent coding mistakes.<br />
<br />
When a function takes an array as a parameter, you can also pass any indexed array element which will be interpreted as a sub-array that begins from that index. For example,<br />
if you want to get the length of a string, you can use the strlen function like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString); // Set length = 8<br />
</sourcepawn><br />
<br />
And if you pass an indexed array element, it will be interpreted as a sub-array that begins from that index like so:<br />
<br />
<sourcepawn><br />
char myString[] = "myString";<br />
int length = strlen(myString[2]); // Set length = 6<br />
</sourcepawn><br />
<br />
==Named Parameters==<br />
With functions that have many parameters with default values, you can call the function with named parameters to assign a value based on its name instead of which position it is in the argument list. For example:<br />
<br />
<sourcepawn><br />
void SomeFunctionWithDefaultParams(int a, int b = 1, int c = 2, int d = 3, int e = 4);<br />
<br />
// If you want to call it with a different value for `d` -- note the dotted argument assignment<br />
SomeFunctionWithDefaultParams(0, .d = 14);<br />
<br />
// This is effectively the same, but less readable:<br />
SomeFunctionWithDefaultParams(0, _, _, 14, _);<br />
</sourcepawn><br />
<br />
=Expressions=<br />
Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.<br />
<br />
The simplest expression is a single number. For example:<br />
<sourcepawn><br />
0; //Returns the number 0<br />
(0); //Returns the number 0 as well<br />
</sourcepawn><br />
<br />
Although expressions can return any value, they are also said to either return ''zero or non-zero''. In that sense, ''zero'' is ''false'', and ''non-zero'' is ''true''. For example, -1 is '''true''' in Pawn, since it is non-zero. Do not assume negative numbers are false.<br />
<br />
The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:<br />
<sourcepawn><br />
5 + 6; //Evaluates to 11<br />
5 * 6 + 3; //Evaluates to 33<br />
5 * (6 + 3); //Evaluates to 45<br />
5.0 + 2.3; //Evaluates to 7.3<br />
(5 * 6) % 7; //Modulo operator, evaluates to 2<br />
(5 + 3) / 2 * 4 - 9; //Evaluates to -8<br />
</sourcepawn><br />
<br />
As noted, expressions can contain variables, or even functions:<br />
<sourcepawn><br />
int a = 5 * 6;<br />
int b = a * 3; //Evaluates to 90<br />
int c = AddTwoNumbers(a, b) + (a * b);<br />
</sourcepawn><br />
<br />
Note: String manipulation routines may be found in the string.inc file located in the include subdirectory. They may be browsed through the [http://docs.sourcemod.net/api/ API Reference] as well.<br />
<br />
==Operators==<br />
There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:<br />
<sourcepawn>int a = 5;<br />
<br />
a = a + 5;</sourcepawn><br />
<br />
Can be rewritten as:<br />
<sourcepawn>int a = 5;<br />
a += 5;</sourcepawn><br />
<br />
This is true of the following operators in Pawn:<br />
*Four-function: *, /, -, +<br />
*Bit-wise: |, &, ^, ~, <<, >><br />
<br />
Additionally, there are increment/decrement operators:<br />
<sourcepawn>a = a + 1;<br />
a = a - 1;</sourcepawn><br />
<br />
Can be simplified as:<br />
<sourcepawn>a++;<br />
a--;</sourcepawn><br />
<br />
As an advanced note, the ++ or -- can come before the variable (pre-increment, pre-decrement) or after the variable (post-increment, post-decrement). The difference is in how the rest of the expression containing them sees their result.<br />
<br />
* ''Pre:'' The variable is incremented before evaluation, and the rest of the expression sees the new value.<br />
* ''Post:'' The variable is incremented after evaluation, and the rest of the expression sees the old value.<br />
<br />
In other words, <tt>a++</tt> evaluates to the value of <tt>a</tt> while <tt>++a</tt> evaluates to the value of <tt>a + 1</tt>. In both cases <tt>a</tt> is incremented by <tt>1</tt>.<br />
<br />
For example:<br />
<br />
<sourcepawn>int a = 5;<br />
int b = a++; // b = 5, a = 6 (1)<br />
int c = ++a; // a = 7, c = 7 (2)<br />
</sourcepawn><br />
<br />
In (1) <tt>b</tt> is assigned <tt>a</tt>'s ''old'' value ''before'' it is incremented to <tt>6</tt>, but in (2) <tt>c</tt> is assigned <tt>a</tt>'s ''int'' value ''after'' it is incremented to <tt>7</tt>.<br />
<br />
==Comparison Operators==<br />
There are six operators for comparing two values numerically, and the result is either true (non-zero) or false (zero):<br />
*<tt>a == b</tt> - True if a and b have the same value.<br />
*<tt>a != b</tt> - True if a and b have different values.<br />
*<tt>a &gt; b</tt> - True if a is greater than b<br />
*<tt>a &gt;= b</tt> - True if a is greater than or equal to b<br />
*<tt>a &lt; b</tt> - True if a is less than b<br />
*<tt>a &lt;= b</tt> - True if a is less than or equal to b<br />
<br />
For example:<br />
<sourcepawn><br />
(1 != 3); //Evaluates to true because 1 is not equal to 3.<br />
(3 + 3 == 6); //Evaluates to true because 3+3 is 6.<br />
(5 - 2 >= 4); //Evaluates to false because 3 is less than 4.<br />
</sourcepawn><br />
<br />
Note that these operators do not work on arrays or strings. That is, you cannot compare either using <tt>==</tt>.<br />
<br />
==Truth Operators==<br />
These truth values can be combined using three boolean operators:<br />
*<tt>a && b</tt> - True if both a and b are true. False if a or b (or both) is false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>&&</tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 0<br />
|-<br />
! 1<br />
| 0 || 1<br />
|}<br />
*<tt>a || b</tt> - True if a or b (or both) is true. False if both a and b are false.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt><nowiki>||</nowiki></tt> !! 0 !! 1<br />
|-<br />
! 0<br />
| 0 || 1<br />
|-<br />
! 1<br />
| 1 || 1<br />
|}<br />
*<tt>!a</tt> - True if a is false. False if a is true.<br />
{| border="1" cellpadding="2" cellspacing="0" align="center"<br />
! <tt>!</tt> !! 0 !! 1<br />
|- <br />
!<br />
| 1 || 0<br />
|}<br />
<br />
For example:<br />
<sourcepawn><br />
(1 || 0); //Evaluates to true because the expression 1 is true<br />
(1 && 0); //Evaluates to false because the expression 0 is false<br />
(!1 || 0); //Evaluates to false because !1 is false.<br />
</sourcepawn><br />
<br />
==Left/Right Values==<br />
Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.<br />
<br />
For example:<br />
<sourcepawn><br />
int a = 5;</sourcepawn><br />
<br />
In this example <tt>a</tt> is an l-value and <tt>5</tt> is an r-value.<br />
<br />
The rules:<br />
*'''Expressions are never l-values'''.<br />
*'''Variables are both l-values and r-values'''.<br />
<br />
=Conditionals=<br />
Conditional statements let you only run code if a certain condition is matched.<br />
<br />
==If Statements==<br />
If statements test one or more conditions. For example:<br />
<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code that will run if the expression was true */<br />
}</sourcepawn><br />
<br />
They can be extended to handle more cases as well:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 6)<br />
{<br />
/* Code */<br />
}<br />
else if (a == 7)<br />
{<br />
/* Code */<br />
}</sourcepawn><br />
<br />
You can also handle the case of no expression being matched. For example:<br />
<sourcepawn><br />
if (a == 5)<br />
{<br />
/* Code */<br />
}<br />
else<br />
{<br />
/* Code that will run if no expressions were true */<br />
}</sourcepawn><br />
<br />
==Switch Statements==<br />
Switch statements are restricted if statements. They test one expression for a series of possible values. For example:<br />
<br />
<sourcepawn><br />
switch (a)<br />
{<br />
case 5:<br />
{<br />
/* code */<br />
}<br />
case 6:<br />
{<br />
/* code */<br />
}<br />
case 7:<br />
{<br />
/* code */<br />
}<br />
case 8, 9, 10:<br />
{<br />
/* Code */<br />
}<br />
default:<br />
{<br />
/* will run if no case matched */<br />
}<br />
}</sourcepawn><br />
<br />
Unlike some other languages, switches are not fall-through. That is, multiple cases will never be run. When a case matches its code is executed, and the switch is then immediately terminated.<br />
<br />
=Loops=<br />
Loops allow you to conveniently repeat a block of code while a given condition remains true. <br />
<br />
==For Loops==<br />
For loops are loops which have four parts:<br />
*The '''initialization''' statement - run once before the first loop.<br />
*The '''condition''' statement - checks whether the next loop should run, including the first one. The loop terminates when this expression evaluates to false.<br />
*The '''iteration''' statement - run after each loop.<br />
*The '''body''' block - run each time the '''condition''' statement evaluates to true.<br />
<br />
<sourcepawn><br />
for ( /* initialization */ ; /* condition */ ; /* iteration */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
A simple example is a function to sum an array:<br />
<sourcepawn><br />
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};<br />
int sum = SumArray(array, 10);<br />
<br />
int SumArray(const int array[], int count)<br />
{<br />
int total;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
total += array[i];<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
Broken down:<br />
*<tt>int i = 0</tt> - Creates a new variable for the loop, sets it to 0.<br />
*<tt>i < count</tt> - Only runs the loop if <tt>i</tt> is less than <tt>count</tt>. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.<br />
*<tt>i++</tt> - Increments <tt>i</tt> by one after each loop. This ensures that the loop doesn't run forever; eventually <tt>i</tt> will become too big and the loop will end.<br />
<br />
Thus, the <tt>SumArray</tt> function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.<br />
<br />
==While Loops==<br />
While loops are less common than for loops but are actually the simplest possible loop. They have only two parts:<br />
*The '''condition''' statement - checked before each loop. The loop terminates when it evaluates to false.<br />
*The '''body''' block - run each time through the loop.<br />
<br />
<sourcepawn><br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
}<br />
</sourcepawn><br />
<br />
As long as the condition expression remains true, the loop will continue. Every for loop can be rewritten as a while loop:<br />
<br />
<sourcepawn><br />
/* initialization */<br />
while ( /* condition */ )<br />
{<br />
/* body */<br />
/* iteration */<br />
}<br />
</sourcepawn><br />
<br />
Here is the previous for loop rewritten as a while loop:<br />
<sourcepawn><br />
int SumArray(const int array[], int count)<br />
{<br />
int total, i;<br />
<br />
while (i < count)<br />
{<br />
total += array[i];<br />
i++;<br />
}<br />
<br />
return total;<br />
}</sourcepawn><br />
<br />
There are also '''do...while''' loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:<br />
<br />
<sourcepawn><br />
do<br />
{<br />
/* body */<br />
}<br />
while ( /* condition */ );<br />
</sourcepawn><br />
<br />
==Loop Control==<br />
There are two cases in which you want to selectively control a loop:<br />
*'''skipping''' one iteration of the loop but continuing as normal, or;<br />
*'''breaking''' the loop entirely before it's finished.<br />
<br />
Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:<br />
<sourcepawn><br />
/**<br />
* Returns the array index where the value is, or -1 if not found.<br />
*/<br />
int SearchInArray(const int array[], int count, int value)<br />
{<br />
int index = -1;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
if (array[i] == value)<br />
{<br />
index = i;<br />
break;<br />
}<br />
}<br />
<br />
return index;<br />
}</sourcepawn><br />
<br />
Certainly, this function could simply <tt>return i</tt> instead, but the example shows how <tt>break</tt> will terminate the loop.<br />
<br />
Similarly, the <tt>continue</tt> keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:<br />
<sourcepawn><br />
int SumEvenNumbers(const int array[], int count)<br />
{<br />
int sum;<br />
<br />
for (int i = 0; i < count; i++)<br />
{<br />
/* If divisibility by 2 is 1, we know it's odd */<br />
if (array[i] % 2 == 1)<br />
{<br />
/* Skip the rest of this loop iteration */<br />
continue;<br />
}<br />
sum += array[i];<br />
}<br />
<br />
return sum;<br />
}</sourcepawn><br />
<br />
=Scope=<br />
Scope refers to the '''visibility''' of code. That is, code at one level may not be "visible" to code at another level. For example:<br />
<br />
<sourcepawn><br />
int A, B, C;<br />
<br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function2();<br />
}<br />
<br />
void Function2()<br />
{<br />
int C;<br />
}</sourcepawn><br />
<br />
In this example, <tt>A</tt>, <tt>B</tt>, and <tt>C</tt> exist at '''global scope'''. They can be seen by any function. However, the <tt>B</tt> in <tt>Function1</tt> is not the same variable as the <tt>B</tt> at the global level. Instead, it is at '''local scope''', and is thus a '''local variable'''.<br />
<br />
Similarly, <tt>Function1</tt> and <tt>Function2</tt> know nothing about each other's variables.<br />
<br />
Not only is the variable private to <tt>Function1</tt>, but it is re-created each time the function is invoked. Imagine this:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int B;<br />
<br />
Function1();<br />
}</sourcepawn><br />
<br />
In the above example, <tt>Function1</tt> calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of <tt>B</tt>. When the function ends, <tt>B</tt> is destroyed, and the value is lost.<br />
<br />
This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:<br />
<br />
<sourcepawn>void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
A = 5;<br />
}<br />
}</sourcepawn><br />
<br />
The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:<br />
<sourcepawn><br />
void Function1()<br />
{<br />
int A;<br />
<br />
if (A)<br />
{<br />
int B = 5;<br />
}<br />
<br />
B = 5;<br />
}</sourcepawn><br />
<br />
Notice that <tt>B</tt> is declared in a new code block. That means <tt>B</tt> is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, <tt>B</tt> is no longer valid.<br />
<br />
=Dynamic Arrays=<br />
Dynamic arrays are arrays which don't have a hardcoded size. For example:<br />
<br />
<sourcepawn>void Function1(int size)<br />
{<br />
int[] array = new int[size];<br />
<br />
/* Code */<br />
}</sourcepawn><br />
<br />
Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.<br />
<br />
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.<br />
<br />
=Extended Variable Declarations=<br />
Variables can be declared in more ways than simply <tt>int float or char</tt>.<br />
<br />
==static==<br />
The <tt>static</tt> keyword is available at global and local scope. It has different meanings in each.<br />
<br />
===Global static===<br />
A global static variable can only be accessed from within the same file. For example:<br />
<br />
<sourcepawn>//file1.inc<br />
static float g_value1 = 0.15f;<br />
<br />
//file2.inc<br />
static float g_value2 = 0.15f;</sourcepawn><br />
<br />
If a plugin includes both of these files, it will not be able to use either <tt>g_value1</tt> or <tt>g_value2</tt>. This is a simple information hiding mechanism, and is similar to declaring member variables as <tt>private</tt> in languages like C++, Java, or C#.<br />
<br />
===Local static===<br />
A local static variable is a global variable that is only visible from its local lexical scope. For example:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
static int counter = -1;<br />
<br />
counter += inc;<br />
<br />
return counter;<br />
}</sourcepawn><br />
<br />
In this example, <tt>counter</tt> is technically a global variable -- it is initialized once to -1 and is never initialized again. It does not exist on the stack. That means each time <tt>MyFunction</tt> runs, the <tt>counter</tt> variable and its storage in memory is the same.<br />
<br />
Take this example:<br />
<sourcepawn>MyFunction(5);<br />
MyFunction(6);<br />
MyFunction(10);</sourcepawn><br />
<br />
In this example, <tt>counter</tt> will be <tt>-1 + 5 + 6 + 10</tt>, or <tt>20</tt>, because it persists beyond the frame of the function. Note this may pose problems for recursive functions: if your function may be recursive, then <tt>static</tt> is usually not a good idea unless your code is re-entrant. <br />
<br />
The benefit of a local static variable is that you don't have to clutter your script with global variables. As long as the variable doesn't need to be read by another function, you can squirrel it inside the function and its persistence will be guaranteed.<br />
<br />
Note that statics can exist in any local scope:<br />
<br />
<sourcepawn><br />
int MyFunction(int inc)<br />
{<br />
if (inc > 0)<br />
{<br />
static int counter;<br />
return (counter += inc);<br />
}<br />
return -1;<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Function_Calling_API_(SourceMod_Scripting)&diff=10980Function Calling API (SourceMod Scripting)2020-03-30T01:34:50Z<p>Joinedsenses: Update formatting for readability and consistency</p>
<hr />
<div>SourceMod provides plugins with an API for calling functions. This API can be used to call public functions in any plugin, including public functions in the same plugin. <br />
<br />
This article is split into two sections. The first is on generic function calling, which is used for single function calls. The second is on Forwards, which is used for calling multiple functions in one operation.<br />
<br />
For more information on forwards, readers should see [[Writing_Extensions#Creating_Events.2FForwards|forwards in extensions]].<br />
<br />
=Generic Calling=<br />
There are four steps to calling a function in a plugin:<br />
<ol><br />
<li>Obtaining a "call Handle." This is either in the form of a function ID, tagged with <tt>Function</tt>, or a Forward Handle, tagged with <tt>Handle</tt>.</li><br />
<li>Starting the call.<br />
<li>Pushing parameters in increasing order in a way that matches the function prototype.</li><br />
<li>Ending the the call, which performs the call operation and returns the result.</li><br />
</ol><br />
<br />
For simplicity, let's consider calling a function in our own plugin. We have the following function:<br />
<sourcepawn>public void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot)<br />
{<br />
char name[MAX_NAME_LENGTH];<br />
GetClientName(victim, name, sizeof(name));<br />
<br />
if (attacker != victim)<br />
{<br />
char other[MAX_NAME_LENGTH];<br />
GetClientName(attacker, other, sizeof(other));<br />
PrintToServer("<\"%s\"> killed by <\"%s\"> with \"%s\" (headshot: %d)", name, other, weapon, headshot);<br />
}<br />
else if (!attacker)<br />
{<br />
PrintToServer("<\"%s\"> killed by \"world\" with \"%s\" (headshot: %d)", name, weapon, headshot);<br />
}<br />
else<br />
{<br />
PrintToServer("<\"%s\"> killed by \"self\" with \"%s\" (headshot: %d)", name, weapon, headshot);<br />
}<br />
}</sourcepawn><br />
<br />
An indirect way to call this function would be:<br />
<sourcepawn><br />
public void EventHandler(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
if (StrEqual(name, "player_death"))<br />
{<br />
char weapon[64];<br />
int result;<br />
<br />
event.GetString("weapon", weapon, sizeof(weapon));<br />
<br />
/* Start function call */<br />
Call_StartFunction(null, OnClientDied);<br />
<br />
/* Push parameters one at a time */<br />
Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));<br />
Call_PushCell(GetClientOfUserId(event.GetInt("userid")));<br />
Call_PushString(weapon);<br />
Call_PushCell(GetEventInt(event, "headshot"));<br />
<br />
/* Finish the call, get the result */<br />
Call_Finish(result);<br />
}<br />
}</sourcepawn><br />
<br />
This basic example shows starting and completing a function call. However, the real use of function calling is with forwards, which is covered in the next section.<br />
<br />
=Forwards=<br />
Forwards are much more advantageous over single function calls. They are expandable containers, so you can store and complete many calls with very little action. Furthermore, they also adjust themselves when contained plugins are unloaded. Lastly, they are type-checked; each forward's parameter types must be known in advance, and if you push a mismatching type, the call will not complete.<br />
<br />
Forwards must be created using the following types:<br />
*<tt>Param_Any</tt> - Any parameter type can be pushed<br />
*<tt>Param_Cell</tt> - A non-Float cell can be pushed<br />
*<tt>Param_Float</tt> - A Float cell can be pushed<br />
*<tt>Param_String</tt> - A string can be pushed<br />
*<tt>Param_Array</tt> - An array can be pushed<br />
*<tt>Param_VarArgs</tt> - This and all further parameters can be any type, but will be by reference. This cannot be the first parameter type, and if it is used, it must be the last parameter type.<br />
*<tt>Param_CellByRef</tt> - A non-Float cell by reference<br />
*<tt>Param_FloatByRef</tt> - A Float cell by reference<br />
<br />
Strings and arrays are implicitly by-reference. When pushing variable argument parameters, if anything is pushed by-value, it will be internally automatically converted to by-reference.<br />
<br />
Since Forwards will call multiple functions in a row, it needs to know how to interpret the return values of functions. There are four predefined methods:<br />
*<tt>ET_Ignore</tt> - All return values will be ignored; 0 will be returned at the end.<br />
*<tt>ET_Single</tt> - Only the last return value will be returned.<br />
*<tt>ET_Event</tt> - Function should return an <tt>Action</tt> value (<tt>core.inc</tt>). <tt>Plugin_Stop</tt> acts as <tt>Plugin_Handled</tt>. The highest value is returned.<br />
*<tt>ET_Hook</tt> - Function should return an <tt>Action</tt> value. <tt>Plugin_Stop</tt> ends the forward call immediately.<br />
<br />
Let's write a simple example. Our plugin, Plugin A, wants to tell other plugins when a player dies. It has two ways of doing this, either via a ''global'' forward or a ''private'' forward. A global forward acts upon all functions in all plugins that match a single name. A private forward lets you explicitly manage which functions are in the container.<br />
<br />
==Global Forwards==<br />
Global forwards are very simple to use. After creation, they do not need to be maintained. An example plugin below creates a global forward with the following prototype:<br />
<sourcepawn>forward void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot);</sourcepawn><br />
<br />
Implementation:<br />
<sourcepawn>GlobalForward g_DeathForward;<br />
<br />
public void OnPluginStart()<br />
{<br />
g_DeathForward = new GlobalForward("OnClientDied", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);<br />
HookEvent("player_death", EventHandler);<br />
}<br />
<br />
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)<br />
{<br />
RegPluginLibrary("my_plugin");<br />
}<br />
<br />
public Action EventHandler(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
char weapon[64];<br />
Action result;<br />
<br />
event.GetString("weapon", weapon, sizeof(weapon));<br />
<br />
/* Start function call */<br />
Call_StartForward(g_DeathForward);<br />
<br />
/* Push parameters one at a time */<br />
Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));<br />
Call_PushCell(GetClientOfUserId(event.GetInt("userid")));<br />
Call_PushString(weapon);<br />
Call_PushCell(GetEventInt(event, "headshot"));<br />
<br />
/* Finish the call, get the result */<br />
Call_Finish(result);<br />
<br />
return result;<br />
}</sourcepawn><br />
<br />
==Private Forwards==<br />
Private forwards require you to manually add functions to its container. This can leave you with much more flexibility. Like global forwards, they automatically remove functions from unloaded plugins. <br />
<br />
Usually, this is done using dynamic natives; a plugin will expose a function to add to its own forwards. For example:<br />
<sourcepawn>typedef OnClientDiedFunc = function Action (int attacker, int victim, const char[] weapon, bool headshot);<br />
<br />
/**<br />
* Calls the target function when a client dies.<br />
*<br />
* @param func OnClientDiedFunc function.<br />
* @noreturn<br />
*/<br />
native void HookClientDeath(OnClientDiedFunc func);</sourcepawn><br />
<br />
An implementation of this might look like:<br />
<sourcepawn>PrivateForward g_DeathForward;<br />
<br />
public void OnPluginStart()<br />
{<br />
g_DeathForward = new PrivateForward(ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);<br />
<br />
CreateNative("HookClientDeath", Native_HookClientDeath);<br />
<br />
HookEvent("player_death", EventHandler);<br />
}<br />
<br />
public int Native_HookClientDeath(Handle plugin, int numParams)<br />
{<br />
g_DeathForward.AddFunction(plugin, GetNativeFunction(1));<br />
}</sourcepawn><br />
<br />
Note that the code to call the forward does not need to change at all.<br />
<br />
A complete implementation of a private forward may look like this:<br />
<sourcepawn>typedef MyFunction = function void (int client);<br />
native void My_NativeEx(MyFunction func);</sourcepawn><br />
<br />
<sourcepawn>PrivateForward g_hDeathFwd;<br />
<br />
<br />
// typedef MyFunction = function void (int client);<br />
// native void My_NativeEx(MyFunction func);<br />
public APLRes AskPluginLoad2(Handle plugin, bool late, const char[] error, int err_max)<br />
{<br />
RegPluginLibrary("MyPlugin");<br />
<br />
CreateNative("My_NativeEx", My_Native);<br />
<br />
return APLRes_Success;<br />
}<br />
<br />
public void OnPluginStart()<br />
{<br />
HookEvent("player_death", Event_Death);<br />
g_hDeathFwd = new PrivateForward(ET_Ignore, Param_Cell);<br />
}<br />
<br />
public int My_Native(Handle plugin, int numParams)<br />
{<br />
g_hDeathFwd.AddFunction(plugin, GetNativeFunction(1));<br />
}<br />
<br />
public Action Event_Death(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
int client = GetClientOfUserId(event.GetInt("userid"));<br />
<br />
Call_StartForward(g_hDeathFwd);<br />
Call_PushCell(client);<br />
Call_Finish();<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Writing_Multi-Game_SourceMod_Plugins&diff=10979Writing Multi-Game SourceMod Plugins2020-03-30T01:29:07Z<p>Joinedsenses: Update highlighting</p>
<hr />
<div>Different Source games tend to have different quirks. This page discusses the more common quirks you'll run into.<br />
<br />
== Detecting the current game ==<br />
<br />
In order to use what you learn from this document, you will need to detect which game the plugin is running on.<br />
<br />
To detect the current game, use the GetEngineVersion() function, like this:<br />
<br />
<sourcepawn><br />
EngineVersion g_EngineVersion;<br />
<br />
public void OnPluginStart()<br />
{<br />
g_EngineVersion = GetEngineVersion();<br />
}<br />
</sourcepawn><br />
<br />
This isn't always necessary for games that use different events, but it's still a good idea.<br />
<br />
== Round Start ==<br />
<br />
Most plugin writers assume that hooking [[Generic Source Events#round_start|round_start]] will make things work on all games. Those plugin writers would be wrong.<br />
<br />
Several games use other events instead or have behavior that needs to be taken into account.<br />
<br />
{| border="1"<br />
|-<br />
! Game<br />
! Fires round_start?<br />
! Alternate Event<br />
| Extra Quirks<br />
|-<br />
| Half-Life 2: DeathMatch<br />
| Yes<br />
| [[Half-Life 2: Deathmatch Events#teamplay_round_start|teamplay_round_start]]<br />
| teamplay_round_start is only fired for Team Deathmatch<br />
|-<br />
| Day of Defeat: Source<br />
| No<br />
| [[Day of Defeat: Source Events#dod_round_start|dod_round_start]]<br />
|<br />
|-<br />
| Team Fortress 2<br />
| No<br />
| [[Team Fortress 2 Events#teamplay_round_start|teamplay_round_start]]<br />
| '''full_reset''' will be false if this is not the first round of a multi-round map. In Arena mode, the [[Team Fortress 2 Events#arena_round_start|arena_round_start]] event fires when players can start moving.<br />
|-<br />
| Counter-Strike: Global Offensive<br />
| Yes<br />
|<br />
| round_start is fired during warmup round<br />
|}<br />
<br />
== Round End ==<br />
<br />
[[Generic Source Events#round_end|round_end]], like round_start, isn't the same across all games.<br />
<br />
Most of the time, '''winner''' is the team ID of the winning team: 0 for stalemate, 2 for team 1 (Combine, Allies, Terrorists, RED, or Survivors), or 3 for team 2 (Rebels, Axis, Counter-Terrorists, BLU, or Infected).<br />
<br />
{| border="1"<br />
|-<br />
! Game<br />
! Fires round_end?<br />
! Alternate Event<br />
! Extra Quirks<br />
|-<br />
| Half-Life 2: DeathMatch<br />
| Yes<br />
|<br />
| winner is a userid for Deathmatch or a team ID for Team Deathmatch<br />
|-<br />
| Day of Defeat: Source<br />
| No<br />
| [[Day of Defeat: Source Events#dod_round_win|dod_round_win]]<br />
| '''team''' is the winning team.<br />
|-<br />
| Team Fortress 2<br />
| No<br />
| See next section<br />
| See next section<br />
|-<br />
| Nuclear Dawn<br />
| No<br />
| [[Nuclear Dawn Events#round_win|round_win]]<br />
| '''team''' is the winning team. '''type''' is the win reason.<br />
|}<br />
<br />
=== Team Fortress 2 ===<br />
<br />
Round End is so complicated in Team Fortress 2 that I'm giving it its own section. TF2 has not just one, but ''four'' different events for round end.<br />
<br />
{| border="1"<br />
|-<br />
! Game Mode<br />
! Event<br />
| Quirks<br />
|-<br />
| All<br />
| [[Team Fortress 2 Events#teamplay_round_win|teamplay_round_win]]<br />
| '''team''' is the winning team. '''winreason''' was only [https://github.com/ValveSoftware/Source-1-Games/issues/1269#issuecomment-24618786 recently fixed] and its meaning depends on the game mode.<br />
|-<br />
| All but Arena or MvM<br />
| [[Team Fortress 2 Events#teamplay_win_panel|teamplay_win_panel]]<br />
| Win Panel screen, includes each team's score and the scores of the top 3 players. '''winning_team''' is the winning team. '''winreason''' includes captured all points, time ran out, captured flags, etc...<br />
|-<br />
| Arena<br />
| [[Team Fortress 2 Events#arena_win_panel|arena_win_panel]]<br />
| Arena Win Panel screen, includes each team's score and the scores of the top 3 players for each team. '''winning_team''' is the winning team. '''winreason''' can ''only'' be killed all players on the other team and captured point.<br />
|-<br />
| MvM<br />
| [[Team Fortress 2 Events#pve_win_panel|pve_win_panel]]<br />
| MvM Win Panel screen. '''winning_team''' is the winning team. Most data is not present in this event and is instead interpolated from the CTFPlayerResource netprops.<br />
|}<br />
<br />
== player_death ==<br />
All games appear to use the player_death event, but the fields available to each game vary radically.<br />
<br />
The quirks you need to know about are:<br />
<br />
=== Detecting Fake Deaths ===<br />
<br />
Team Fortress 2 introduced the concept of fake deaths. In order to detect if a death was fake, you need to do something like this:<br />
<br />
<sourcepawn><br />
#include <tf2_stocks><br />
<br />
// Hook the "player_death" event during OnPluginStart and pass in the following callback:<br />
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
bool bFake = g_EngineVersion == Engine_TF2 && (event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER);<br />
if (bFake)<br />
{<br />
// Do stuff on faked death<br />
}<br />
// do other stuff<br />
}<br />
</sourcepawn><br />
<br />
== Detecting Headshots ==<br />
<br />
Not all games support headshots, but those that do have them done differently.<br />
<br />
<br />
=== player_death ===<br />
<br />
Headshots can be detected in player_death similarly to this:<br />
<br />
<sourcepawn><br />
#include <tf2_stocks><br />
<br />
// Hook the "player_death" event during OnPluginStart and pass in the following callback:<br />
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
bool bHeadshot;<br />
<br />
switch (g_EngineVersion)<br />
{<br />
case Engine_CSS, Engine_CSGO, Engine_Left4Dead, Engine_Left4Dead2:<br />
{<br />
bHeadshot = event.GetBool("headshot");<br />
}<br />
<br />
case Engine_TF2:<br />
{<br />
bHeadshot = event.GetInt("customkill") == TF_CUSTOM_HEADSHOT;<br />
}<br />
}<br />
<br />
if (bHeadshot)<br />
{<br />
// This was a headshot, do something<br />
}<br />
}<br />
</sourcepawn><br />
<br />
Unfortunately, some games don't appear to track headshots for death (Half-Life 2: DeathMatch for example).<br />
<br />
=== player_hurt ===<br />
player_hurt is a bit trickier.<br />
<br />
<sourcepawn><br />
#include <tf2_stocks><br />
<br />
#define HITGROUP_HEAD 1<br />
<br />
// Hook "player_hurt", etc.<br />
public Action Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
bool bHeadshot;<br />
<br />
switch (g_EngineVersion)<br />
{<br />
case Engine_CSS, Engine_CSGO, Engine_DODS, Engine_Left4Dead, Engine_Left4Dead2:<br />
{<br />
bHeadshot = event.GetInt("hitgroup") == HITGROUP_HEAD;<br />
}<br />
<br />
case Engine_TF2:<br />
{<br />
bHeadshot = event.GetInt("custom") == TF_CUSTOM_HEADSHOT;<br />
}<br />
}<br />
<br />
if (bHeadshot)<br />
{<br />
// This was a headshot, do something<br />
}<br />
}<br />
</sourcepawn><br />
<br />
As you can see, more games support detecting if a shot was a headshot in player_hurt than player_death.<br />
<br />
=== SDKHooks TraceAttack and OnTakeDamage ===<br />
<br />
Headshots can be detected by TraceAttack or OnTakeDamage depending on the game.<br />
<br />
Note: Changing damage and returning Plugin_Changed for either event will change the damage the player takes.<br />
<br />
<sourcepawn><br />
#include <sdkhooks><br />
<br />
#define HITGROUP_HEAD 1<br />
<br />
public void OnClientPutInServer(int client)<br />
{<br />
switch (g_EngineVersion)<br />
{<br />
case Engine_CSS, Engine_CSGO, Engine_DODS, Engine_Left4Dead, Engine_Left4Dead2:<br />
{<br />
SDKHook(client, SDKHook_TraceAttack, TraceAttack);<br />
}<br />
case Engine_TF2:<br />
{<br />
SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);<br />
}<br />
}<br />
<br />
}<br />
<br />
public Action TraceAttack(int victim, int &attacker, int &inflictor, float &damage, <br />
int &damagetype, int &ammotype, int hitbox, int hitgroup)<br />
{<br />
bool bHeadshot = hitgroup == HITGROUP_HEAD;<br />
<br />
if (bHeadshot)<br />
{<br />
// This was a headshot, do something.<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
<br />
public Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, <br />
int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom)<br />
{<br />
bool bHeadshot = damagecustom == TF_CUSTOM_HEADSHOT;<br />
<br />
if (bHeadshot)<br />
{<br />
// This was a headshot, do something<br />
}<br />
<br />
return Plugin_Continue;<br />
}<br />
</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Events_(SourceMod_Scripting)&diff=10978Events (SourceMod Scripting)2020-03-30T01:27:27Z<p>Joinedsenses: Update highlighting</p>
<hr />
<div>:''To view all the events, click [[Game Events (Source)|here]].''<br />
<br />
Events are short, named messages sent by the server. Although they are used for internal message passing, they are also networked to clients.<br />
<br />
All event natives are found in <tt>scripting/include/events.inc</tt>.<br />
<br />
=Introduction=<br />
Events are documented in <tt>.res</tt> files under a mod's <tt>resource</tt> folder. The "default" events are located in <tt>hl2/resource/gameevents.res</tt> and <tt>hl2/resource/serverevents.res</tt>. Mods can extend these events with their own. <br />
<br />
For example, let's look at <tt>player_death</tt> from <tt>hl2/resource/gameevents.res</tt>:<br />
<pre>"player_death"<br />
{<br />
"userid" "short" // user ID who died <br />
"attacker" "short" // user ID who killed<br />
}</pre><br />
<br />
Counter-Strike:Source extends this definition in <tt>cstrike/resource/modevents.res</tt>:<br />
<pre>"player_death"<br />
{<br />
"userid" "short" // user ID who died <br />
"attacker" "short" // user ID who killed<br />
"weapon" "string" // weapon name killer used <br />
"headshot" "bool" // signals a headshot<br />
}</pre><br />
<br />
Note that the event is structured in the following format:<br />
<pre>"name"<br />
{<br />
"key1" "valueType1"<br />
"key2" "valueType2"<br />
...<br />
}</pre><br />
<br />
=Sending Events=<br />
Events are very easy to send. For example, let's say we want to send a death message using the <tt>player_death</tt> event from above. For Counter-Strike:Source, this would look like:<br />
<br />
<sourcepawn>void SendDeathMessage(int attacker, int victim, const char[] weapon, bool headshot)<br />
{<br />
Event event = CreateEvent("player_death");<br />
if (event == null)<br />
{<br />
return;<br />
}<br />
<br />
event.SetInt("userid", GetClientUserId(victim));<br />
event.SetInt("attacker", GetClientUserId(attacker));<br />
event.SetString("weapon", weapon);<br />
event.SetBool("headshot", headshot);<br />
event.Fire();<br />
}</sourcepawn><br />
<br />
Notes:<br />
*You don't need to call <tt>CloseHandle()</tt>, <tt>FireEvent()</tt> does this for us.<br />
*Even though "userid" and "attacker" are shorts, we set them as ints. The term "short" is only used to tell the engine how many bytes of the integer are needed to be networked.<br />
*It is possible for event creation to fail; this can happen if the event does not exist, or nothing is hooking the event. Thus, you should always make sure <tt>CreateEvent</tt> calls return a valid Event handle.<br />
*Most events use client userids instead of client indexes.<br />
*By default, <tt>FireEvent()</tt> broadcasts messages to clients. This can be prevented by passing <tt>dontBroadcast</tt> as true.<br />
<br />
=Hooking Events=<br />
When hooking an event, there are three modes to choose from:<br />
*<tt>Pre</tt> - Hook the event before it is fired.<br />
*<tt>Post</tt> - Hook the event after it is fired.<br />
*<tt>Post_NoCopy</tt> - Hook the event, but do not save any of its information (special optimization).<br />
<br />
Hooking an event is usually done for one of the following goals. To get an idea of which mode to use, see the list below each goal:<br />
*Blocking the event (preventing it from being fired)<br />
**'''Always <tt>Pre</tt>'''<br />
*Rewriting the event (changing its parameters)<br />
**'''Always <tt>Pre</tt>'''<br />
*Acting upon the event (doing something once the event is completed)<br />
**'''<tt>Pre</tt>''' if your action must come before the mod's action.<br />
**'''<tt>Post</tt>''' if your action must come after the mod's action.<br />
**'''<tt>PostNoCopy</tt>''' if your action is <tt>Post</tt> and only requires the event name.<br />
<br />
As always, you do not need to unhook events when your plugin unloads. They are automatically removed.<br />
<br />
==Blocking Events==<br />
Blocking events is the easiest thing to do. Let's say we want to block death events that are headshots:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);<br />
}<br />
<br />
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
if (event.GetBool("headshot"))<br />
{<br />
return Plugin_Handled;<br />
}<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
'''Note:''' Blocking events does not necessarily block actions like damaging players.<br />
<br />
==Rewriting Events==<br />
Rewriting events is just as easy -- events can be modified in pre hooks. For example, say we want to remove headshots from all events:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);<br />
}<br />
<br />
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
event.SetBool("headshot", false);<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
==Post Hooks==<br />
Post hooks are default, and will usually be the most common usage. For example, say we want to print a message to every client that dies:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
HookEvent("player_death", Event_PlayerDeath);<br />
}<br />
<br />
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
char weapon[64];<br />
int victimId = event.GetInt("userid");<br />
int attackerId = event.GetInt("attacker");<br />
bool headshot = event.GetBool("headshot");<br />
event.GetString("weapon", weapon, sizeof(weapon));<br />
<br />
char name[64];<br />
int victim = GetClientOfUserId(victimId);<br />
int attacker = GetClientOfUserId(attackerId);<br />
GetClientName(attacker, name, sizeof(name));<br />
<br />
PrintToConsole(victim,<br />
"You were killed by \"%s\" (weapon \"%s\") (headshot \"%d\")",<br />
name,<br />
weapon,<br />
headshot);<br />
}</sourcepawn><br />
<br />
This will print a message to a player's console telling them who killed them, with what weapon, and whether it was a headshot or not.<br />
<br />
Note that the return value for post hooks is ignored, so the <tt>Action</tt> tag is not needed.<br />
<br />
==PostNoCopy Hooks==<br />
Lastly, there are some hooks where the only piece of information needed is the name of the event. <tt>PostNoCopy</tt> is a special optimization for this case. When transitioning from <tt>Pre</tt> to <tt>Post</tt>, SourceMod must duplicate the event and all of its key/value pairs. <tt>PostNoCopy</tt> prevents that sequence from happening.<br />
<br />
For example, let's say we want to find when a certain sequence of events is called.<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
HookEvent("game_newmap", GameEvents, EventHookMode_PostNoCopy);<br />
HookEvent("game_start", GameEvents, EventHookMode_PostNoCopy);<br />
HookEvent("game_end", GameEvents, EventHookMode_PostNoCopy);<br />
HookEvent("game_message", GameEvents, EventHookMode_PostNoCopy);<br />
}<br />
<br />
public void GameEvents(Event event, const char[] name, bool dontBroadcast)<br />
{<br />
PrintToServer("Event has been fired (event \"%s\") (nobcast \"%d\")", name, dontBroadcast);<br />
}</sourcepawn><br />
<br />
Note that like normal <tt>Post</tt> hooks, there is no return value needed. However, the <tt>event</tt> parameter for <tt>PostNoCopy</tt> will '''always''' be equal to <tt>null</tt>. Thus, the <tt>name</tt> parameter must be used instead of <tt>event.GetName</tt>.<br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Commands_(SourceMod_Scripting)&diff=10977Commands (SourceMod Scripting)2020-03-30T01:21:33Z<p>Joinedsenses: Update formatting for readability and consistency</p>
<hr />
<div>[[SourceMod]] allows you to create console commands similar to [[AMX Mod X]]. There are two main types of console commands:<br />
*'''Server Commands''' - Fired from one of the following input methods:<br />
**the server console itself<br />
**the remote console (RCON)<br />
**the ServerCommand() function, either from SourceMod or the [[Half-Life 2]] engine<br />
*'''Console Commands''' - Fired from one of the following input methods:<br />
**a client's console<br />
**any of the server command input methods<br />
<br />
For server commands, there is no client index. For console/client commands, there is a client index, but it may be 0 to indicate that the command is from the server.<br />
<br />
Note that button-bound "commands," such as +attack and +duck, are not actually console commands. They are a separate command stream sent over the network, as they need to be tied to a specific frame. <br />
<br />
=Server Commands=<br />
As noted above, server commands are fired through the server console, whether remote, local, or through the Source engine. There is no client index associated with a server command. <br />
<br />
Server commands are registered through the <tt>RegServerCmd()</tt> function defined in <tt>console.inc</tt>. When registering a server command, you may be hooking an already existing command, and thus the return value is important.<br />
*<b><tt>Plugin_Continue</tt></b> - The original server command will be processed, if there was one. If the server command was created by a plugin, this has no effect.<br />
*<b><tt>Plugin_Handled</tt></b> - The original server command will not be processed, if there was one. If the server command was created by a plugin, this has no effect.<br />
*<b><tt>Plugin_Stop</tt></b> - The original server command will not be processed, if there was one. Additionally, no further hooks will be called for this command until it is fired again.<br />
<br />
==Adding Server Commands==<br />
Let's say we want to add a test command to show how Half-Life 2 breaks down command arguments. A possible implementation might be<br />
<sourcepawn><br />
public void OnPluginStart()<br />
{<br />
RegServerCmd("test_command", Command_Test);<br />
}<br />
<br />
public Action Command_Test(int args)<br />
{<br />
char arg[128];<br />
char full[256];<br />
<br />
GetCmdArgString(full, sizeof(full));<br />
<br />
PrintToServer("Argument string: %s", full);<br />
PrintToServer("Argument count: %d", args);<br />
<br />
for (int i = 1; i <= args; i++)<br />
{<br />
GetCmdArg(i, arg, sizeof(arg));<br />
PrintToServer("Argument %d: %s", i, arg);<br />
}<br />
<br />
return Plugin_Handled;<br />
}<br />
</sourcepawn><br />
<br />
==Blocking Server Commands==<br />
Let's say we wanted to disable the "kickid" command on the server. There's no real good reason to do this, but for example's sake:<br />
<sourcepawn><br />
public void OnPluginStart()<br />
{<br />
RegServerCmd("kickid", Command_KickId);<br />
}<br />
<br />
public Action Command_Kickid(int args)<br />
{<br />
return Plugin_Handled;<br />
}<br />
</sourcepawn><br />
<br />
=Console Commands=<br />
Unlike server commands, console commands can be triggered by either the server or the client, so the callback function receives a client index as well as the argument count. If the server fired the command, the client index will be 0. <br />
<br />
When returning the <tt>Action</tt> from the callback, the following effects will happen:<br />
*<tt>Plugin_Continue</tt>: The original functionality of the command (if any) will still be processed. If there was no original functionality, the client will receive "Unknown command" in their console.<br />
*<tt>Plugin_Handled</tt>: The original functionality of the command (if any) will be blocked. If there was no functionality originally, this prevents clients from seeing "Unknown command" in their console.<br />
*<tt>Plugin_Stop</tt>: Same as <tt>Plugin_Handled</tt>, except that this will be the last hook called.<br />
*<tt>Plugin_Changed</tt>: Inputs or outputs have been overridden with new values.<br />
<br />
Note that, unlike AMX Mod X, SourceMod does not allow you to register command filters. I.e., there is no equivalent to this notation:<br />
<sourcepawn>register_clcmd("say /ff", "Command_SayFF");</sourcepawn><br />
<br />
This notation was removed to make our internal code simpler and faster. Writing the same functionality is easy, and demonstrated below.<br />
<br />
==Adding Commands==<br />
Adding client commands is very simple. Let's port our earlier testing command to display information about the client as well.<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
RegConsoleCmd("test_command", Command_Test);<br />
}<br />
<br />
public Action Command_Test(int client, int args)<br />
{<br />
char arg[128];<br />
char full[256];<br />
<br />
GetCmdArgString(full, sizeof(full));<br />
<br />
if (client)<br />
{<br />
PrintToServer("Command from client: %N", name);<br />
}<br />
else<br />
{<br />
PrintToServer("Command from server.");<br />
}<br />
<br />
PrintToServer("Argument string: %s", full);<br />
PrintToServer("Argument count: %d", args);<br />
<br />
for (int i = 1; i <= args; i++)<br />
{<br />
GetCmdArg(i, arg, sizeof(arg));<br />
PrintToServer("Argument %d: %s", i, arg);<br />
}<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
==Hooking Commands==<br />
A common example is hooking the say command. Let's say we want to tell players whether FF is enabled when they say '/ff' in game. <br />
<br />
Before we implement this, a common point of confusion with the 'say' command is that Half-Life 2 (and Half-Life 1), by default, send it with the text in one big quoted string. This means if you say "I like hot dogs," your command will be broken down as such:<br />
*Argument string: "I like hot dogs"<br />
*Argument count: 1<br />
*Argument #1: I like hot dogs<br />
<br />
However, if a player types this in their console: <tt>say I like yams</tt>, it will be broken up as:<br />
*Argument string: I like yams<br />
*Argument count: 3<br />
*Argument #1: I<br />
*Argument #2: like<br />
*Argument #3: yams<br />
<br />
<sourcepawn>ConVar g_cvarFF = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
g_cvarFF = FindConVar("mp_friendlyfire");<br />
}<br />
<br />
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs)<br />
{<br />
if (strcmp(sArgs, "ff", false) == 0)<br />
{<br />
if (g_cvarFF != null)<br />
{<br />
if (g_cvarFF.BoolValue)<br />
{<br />
PrintToChat(client, "Friendly fire is enabled.");<br />
} <br />
else <br />
{<br />
PrintToChat(client, "Friendly fire is disabled.");<br />
}<br />
<br />
/* Block the client's messsage from broadcasting */<br />
return Plugin_Handled;<br />
}<br />
}<br />
<br />
/* Let say continue normally */<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
Here we use the OnClientSayCommand forward where as <tt>command</tt> contains the type (example: <tt>say</tt> or <tt>say_team</tt>) and <tt>sArgs</tt> containing the users input<br />
<br />
==Creating Admin Commands==<br />
Let's create a simple admin command which kicks another player by their full name. <br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
RegAdminCmd("admin_kick", Command_Kick, ADMFLAG_KICK, "Kicks a player by name");<br />
}<br />
<br />
public Action Command_Kick(int client, int args)<br />
{<br />
if (args < 1)<br />
{<br />
PrintToConsole(client, "Usage: admin_kick <name>");<br />
return Plugin_Handled;<br />
}<br />
<br />
char name[32];<br />
int target = -1;<br />
GetCmdArg(1, name, sizeof(name));<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (!IsClientConnected(i))<br />
{<br />
continue;<br />
}<br />
<br />
char other[32];<br />
GetClientName(i, other, sizeof(other));<br />
<br />
if (StrEqual(name, other))<br />
{<br />
target = i;<br />
}<br />
}<br />
<br />
if (target == -1)<br />
{<br />
PrintToConsole(client, "Could not find any player with the name: \"%s\"", name);<br />
return Plugin_Handled;<br />
}<br />
<br />
KickClient(target);<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
==Immunity==<br />
In our previous example, we did not take immunity into account. Immunity is a much more complex system in SourceMod than it was in AMX Mod X, and there is no simple flag to denote its permissions. Instead, two functions are provided:<br />
*<tt>CanAdminTarget</tt>: Tests raw AdminId values for immunity.<br />
*<tt>CanUserTarget</tt>: Tests in-game clients for immunity.<br />
<br />
While immunity is generally tested ''player versus player'', it is possible you might want to check for immunity and not have a targetting client. While there is no convenience function for this yet, a good idea might be to check for either ''default'' or ''global'' immunity on the player's groups (these can be user-defined for non-player targeted scenarios).<br />
<br />
When checking for immunity, the following heuristics are performed in this exact order:<br />
<ol><li>If the targeting AdminId is <tt>INVALID_ADMIN_ID</tt>, targeting fails.</li><br />
<li>If the targetted AdminId is <tt>INVALID_ADMIN_ID</tt>, targeting succeeds.</li><br />
<li>If the targeting admin has <tt>Admin_Root</tt> (<tt>ADMFLAG_ROOT</tt>), targeting succeeds.</li><br />
<li>If the targetted admin has global immunity, targeting fails.</li><br />
<li>If the targetted admin has default immunity, and the targeting admin belongs to no groups, targeting fails.</li><br />
<li>If the targetted admin has specific immunity from the targeting admin via group immunities, targeting fails.</li><br />
<li>If no conclusion is reached via the previous steps, targeting succeeds.</li><br />
</ol><br />
<br />
So, how can we adapt our function about to use immunity?<br />
<br />
<sourcepawn>public Action Command_Kick(int client, int args)<br />
{<br />
if (args < 1)<br />
{<br />
PrintToConsole(client, "Usage: admin_kick <name>");<br />
return Plugin_Handled;<br />
}<br />
<br />
char name[32];<br />
int target = -1;<br />
GetCmdArg(1, name, sizeof(name));<br />
<br />
for (int i = 1; i <= MaxClients; i++)<br />
{<br />
if (!IsClientConnected(i))<br />
{<br />
continue;<br />
}<br />
<br />
char other[32];<br />
GetClientName(i, other, sizeof(other));<br />
<br />
if (StrEqual(name, other))<br />
{<br />
target = i;<br />
}<br />
}<br />
<br />
if (target == -1)<br />
{<br />
PrintToConsole(client, "Could not find any player with the name: \"%s\"", name);<br />
return Plugin_Handled;<br />
}<br />
<br />
if (!CanUserTarget(client, target))<br />
{<br />
PrintToConsole(client, "You cannot target this client.");<br />
return Plugin_Handled;<br />
}<br />
<br />
KickClient(target);<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
=Client-Only Commands=<br />
SourceMod exposes a forward that is called whenever a client executes any command string in their console, called <tt>OnClientCommand</tt>. An example of this looks like:<br />
<br />
<sourcepawn>public Action OnClientCommand(int client, int args)<br />
{<br />
char cmd[16];<br />
GetCmdArg(0, cmd, sizeof(cmd)); /* Get command name */<br />
<br />
if (StrEqual(cmd, "test_command"))<br />
{<br />
/* Got the client command! Block it... */<br />
return Plugin_Handled;<br />
}<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
It is worth noting that not everything a client sends will be available through this command. Command registered via external sources in C++ may not be available, especially if they are created via <tt>CON_COMMAND</tt> in the game mod itself. For example, "say" is usually implemented this way, because it can be used by both clients and the server, and thus it does not channel through this forward.<br />
<br />
<br />
=Chat Triggers=<br />
SourceMod will automatically create chat triggers for every command you make. For example, if you create a console command called <tt>"sm_megaslap"</tt>, administrators will be able to type any of the following commands in <tt>say</tt>/<tt>say_team</tt> message modes:<br />
<pre>!sm_megaslap<br />
!megaslap<br />
/sm_megaslap<br />
/megaslap</pre><br />
<br />
SourceMod then executes this command and its arguments as if it came from the client console.<br />
*"<tt>!</tt>" is the default ''public'' trigger (<tt>PublicChatTrigger</tt> in <tt>configs/core.cfg</tt>) and your entry will be displayed to all clients as normal.<br />
*"<tt>/</tt>" is the default ''silent'' trigger (<tt>SilentChatTrigger</tt> in <tt>configs/core.cfg</tt>) and your entry will be blocked from being displayed.<br />
<br />
SourceMod will only execute commands registered with <tt>RegConsoleCmd</tt> or <tt>RegAdminCmd</tt>, and only if those commands are not already provided by Half-Life 2 or the game mod. If the command is prefixed with "sm_" then the "sm_" can be omitted from the chat trigger.<br />
<br />
Console commands which wish to support usage as a chat trigger should not use <tt>PrintTo*</tt> natives. Instead, they should use <tt>ReplyToCommand()</tt>, which will automatically print your message either as a chat message or to the client's console, depending on the source of the command.<br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=SQL_(SourceMod_Scripting)&diff=10976SQL (SourceMod Scripting)2020-03-30T01:04:44Z<p>Joinedsenses: Update methodmap, highlighting, and formatting</p>
<hr />
<div>{{Languages|SQL (SourceMod_Scripting)}}<br />
This article is an introduction to using SourceMod's SQL features. It is not an introduction or tutorial about SQL or any specific SQL implementation.<br />
<br />
SourceMod's SQL layer is formally called ''DBI'', or the '''D'''ata'''b'''ase '''I'''nterface. The interface is a generic abstraction of common SQL functions. To connect to a specific database implementation (such as MySQL, or sqLite), a SourceMod DBI "driver" must be loaded. Currently, there are drivers for MySQL and SQLite<br />
<br />
SourceMod automatically detects and loads drivers on demand (if they exist, of course). All database natives are in <tt>scripting/include/dbi.inc</tt>. The C++ API is in <tt>public/IDBDriver.h</tt>.<br />
<br />
=Connecting=<br />
There are two ways to connect to a database. The first is through named configurations. Named configurations are preset configurations listed in <tt>configs/databases.cfg</tt>. SourceMod specifies that if SQL is being used, there should always be one configuration named "default."<br />
<br />
An example of such a configuration looks like:<br />
<pre> "default"<br />
{<br />
"host" "localhost"<br />
"database" "sourcemod"<br />
"user" "root"<br />
"pass" ""<br />
//"timeout" "0"<br />
//"port" "0"<br />
}</pre><br />
<br />
Connections based on named configurations can be instantiated with either <tt>SQL_Connect</tt> or <tt>SQL_DefConnect</tt>. <br />
<br />
The other option is to use <tt>SQL_ConnectCustom</tt> and manually specify all connection parameters by passing a keyvalue handle containing them.<br />
<br />
Example of a typical connection:<br />
<sourcepawn>char error[255];<br />
Database db = SQL_DefConnect(error, sizeof(error));<br />
<br />
if (db == null)<br />
{<br />
PrintToServer("Could not connect: %s", error);<br />
} <br />
else <br />
{<br />
delete db;<br />
}</sourcepawn><br />
<br />
=Queries=<br />
<br />
==No Results==<br />
The simplest queries are ones which do not return results -- for example, CREATE, DROP, UPDATE, INSERT, and DELETE. For such queries it is recommended to use <tt>SQL_FastQuery()</tt>. The name does not imply that the query will be faster, but rather, it is faster to write code using this function. For example, given that <tt>db</tt> is a valid database Handle:<br />
<br />
<sourcepawn>if (!SQL_FastQuery(db, "UPDATE stats SET players = players + 1"))<br />
{<br />
char error[255];<br />
SQL_GetError(db, error, sizeof(error));<br />
PrintToServer("Failed to query (error: %s)", error);<br />
}</sourcepawn><br />
<br />
==Results==<br />
If a query returns a result set and the results must be processed, you must use <tt>SQL_Query()</tt>. Unlike <tt>SQL_FastQuery()</tt>, this function returns a Handle which must be closed.<br />
<br />
An example of a query which will produce results is:<br />
<sourcepawn>DBResultSet query = SQL_Query(db, "SELECT userid FROM vb_user WHERE username = 'BAILOPAN'");<br />
if (query == null)<br />
{<br />
char error[255];<br />
SQL_GetError(db, error, sizeof(error));<br />
PrintToServer("Failed to query (error: %s)", error);<br />
} <br />
else <br />
{<br />
/* Process results here! */<br />
<br />
/* Free the Handle */<br />
delete query;<br />
}</sourcepawn><br />
<br />
=Prepared Statements=<br />
Prepared statements are another method of querying. The idea behind a prepared statement is that you construct a query "template" once, and re-use it as many times as needed. Prepared statements have the following benefits:<br />
*A good SQL implementation will be able to cache the query better if it is a prepared statement.<br />
*You don't have to rebuild the entire query string every execution.<br />
*You don't have to allocate a new query structure on every execution.<br />
*Input is "always" secure (more on this later).<br />
<br />
A prepared statement has "markers" for inputs. For example, let's consider a function that takes in a database Handle and a name, and retrieves some info from a table:<br />
<sourcepawn>int GetSomeInfo(Database db, const char[] name)<br />
{<br />
DBResultSet hQuery;<br />
char query[100];<br />
<br />
/* Create enough space to make sure our string is quoted properly */<br />
int buffer_len = strlen(name) * 2 + 1;<br />
char[] new_name = new char[buffer_len];<br />
<br />
/* Ask the SQL driver to make sure our string is safely quoted */<br />
db.Escape(name, new_name, buffer_len);<br />
<br />
/* Build the query */<br />
Format(query, sizeof(query), "SELECT userid FROM vb_user WHERE username = '%s'", new_name);<br />
<br />
/* Execute the query */<br />
if ((hQuery = SQL_Query(query)) == null)<br />
{<br />
return 0;<br />
}<br />
<br />
/* Get some info here */<br />
<br />
delete hQuery;<br />
<br />
/* return info; */<br />
}</sourcepawn><br />
<br />
Observe a version with prepared statements:<br />
<sourcepawn>DBStatement hUserStmt = null;<br />
int GetSomeInfo(Database db, const char[] name)<br />
{<br />
/* Check if we haven't already created the statement */<br />
if (hUserStmt == null)<br />
{<br />
char error[255];<br />
hUserStmt = SQL_PrepareQuery(db, "SELECT userid FROM vb_user WHERE username = ?", error, sizeof(error));<br />
if (hUserStmt == null)<br />
{<br />
return 0;<br />
}<br />
}<br />
<br />
SQL_BindParamString(hUserStmt, 0, name, false);<br />
if (!SQL_Execute(hUserStmt))<br />
{<br />
return 0;<br />
}<br />
<br />
/* Get some info here */<br />
/* return info; */<br />
}</sourcepawn><br />
<br />
The important differences:<br />
*The input string (<tt>name</tt>) did not need to be backticked (quoted). The SQL engine automatically performs all type safety and insertion checks.<br />
*There was no need for quotation marks around the parameter marker, <tt>?</tt>, even though it accepted a string.<br />
*We only needed to create the statement Handle once; after that it can live for the lifetime of the database connection.<br />
<br />
=Processing Results=<br />
Processing results is done in the same manner for both normal queries and prepared statements. The important functions are:<br />
*<tt>SQL_GetRowCount()</tt> - Returns the number of rows.<br />
*<tt>SQL_FetchRow()</tt> - Fetches the next row if one is available.<br />
*<tt>SQL_Fetch[Int|String|Float]()</tt> - Fetches data from a field in the current row.<br />
<br />
Let's consider a sample table that looks like this:<br />
<sql>CREATE TABLE users (<br />
name VARCHAR(64) NOT NULL PRIMARY KEY,<br />
age INT UNSIGNED NOT NULL<br />
);</sql><br />
<br />
The following example has code that will print out all users matching a certain age. There is an example for both prepared statements and normal queries.<br />
<sourcepawn>void PrintResults(Handle query)<br />
{<br />
/* Even if we have just one row, you must call SQL_FetchRow() first */<br />
char name[MAX_NAME_LENGTH];<br />
while (SQL_FetchRow(query))<br />
{<br />
SQL_FetchString(query, 0, name, sizeof(name));<br />
PrintToServer("Name \"%s\" was found.", name);<br />
}<br />
}<br />
<br />
bool GetByAge_Query(Database db, int age)<br />
{<br />
char query[100];<br />
Format(query, sizeof(query), "SELECT name FROM users WHERE age = %d", age);<br />
<br />
DBResultSet hQuery = SQL_Query(db, query);<br />
if (hQuery == null)<br />
{<br />
return false;<br />
}<br />
<br />
PrintResults(hQuery);<br />
<br />
delete hQuery;<br />
<br />
return true;<br />
}<br />
<br />
DBStatement hAgeStmt = null;<br />
bool GetByAge_Statement(Database db, int age)<br />
{<br />
if (hAgeSmt == null)<br />
{<br />
char error[255];<br />
if ((hAgeStmt = SQL_PrepareQuery(db, <br />
"SELECT name FROM users WHERE age = ?", <br />
error, <br />
sizeof(error))) == null)<br />
{<br />
return false;<br />
}<br />
}<br />
<br />
SQL_BindParamInt(hAgeStmt, 0, age);<br />
if (!SQL_Execute(hAgeStmt))<br />
{<br />
return false;<br />
}<br />
<br />
PrintResults(hAgeStmt);<br />
<br />
return true;<br />
}</sourcepawn><br />
<br />
Note that these examples did not close the statement Handles. These examples assume a global database instance that is only closed when the plugin is unloaded. For plugins which maintain temporary database connections, prepared statement Handles must be freed or else the database connection will never be closed.<br />
<br />
=Threading=<br />
SourceMod supports threaded SQL querying. That is, SQL operations can be completed in a separate thread from main gameplay. If your database server is remote or requires a network connection, queries can cause noticeable gameplay lag, and supporting threading is often a good idea if your queries occur in the middle of gameplay.<br />
<br />
Threaded queries are ''asynchronous''. That is, they are dispatched and you can only find the results through a callback. Although the callback is guaranteed to fire eventually, it may not fire in any specific given timeframe. Certain drivers may not support threading; if this is the case, an RTE will be thrown. If the threader cannot start or the threader is currently disabled, SourceMod will transparently execute the query in the main thread as a fallback.<br />
<br />
==Operations==<br />
All threaded operations (except connecting) use the same callback for result notification: <tt>SQLQueryCallback</tt>. The parameters are:<br />
*<tt>db</tt> - The cloned database handle. If the db handle was not found or was invalidated, <tt>null</tt> is passed.<br />
*<tt>results</tt> - Result object, or null on failure.<br />
*<tt>error</tt> - An error string.<br />
*<tt>data</tt> - Custom data that can be passed via some SQL operations.<br />
<br />
The following operations can be done via threads:<br />
*<b>Connection</b>, via <tt>SQL_TConnect</tt>.<br />
*<b>Querying</b>, via <tt>SQL_TQuery()</tt>.<br />
*''Note: prepared statements are not yet available for the threader.''<br />
<br />
It is always safe to chain one operation from another.<br />
<br />
===Connecting===<br />
It is not necessary to make a threaded connection in order to make threaded queries. However, creating a threaded connection will not pause the game server if a connection cannot be immediately established. <br />
Connecting however uses a different callback: <tt>SQLConnectCallback</tt>.<br />
<br />
The following parameters are used for the threaded connection callback:<br />
*<tt>db</tt>: Handle to the database connection, or <tt>null</tt> if it could not be found.<br />
*<tt>error</tt>: The error string, if any.<br />
*<tt>data</tt>: Unused (0)<br />
<br />
Example: <br />
<sourcepawn>Database hDatabase = null;<br />
<br />
void StartSQL()<br />
{<br />
Database.Connect(GotDatabase);<br />
}<br />
<br />
public void GotDatabase(Database db, const char[] error, any data)<br />
{<br />
if (db == null)<br />
{<br />
LogError("Database failure: %s", error);<br />
} <br />
else <br />
{<br />
hDatabase = db;<br />
}<br />
}</sourcepawn><br />
<br />
===Querying===<br />
Threaded queries can be performed on any database Handle as long as the driver supports threading. All query results for the first result set are retrieved while in the thread. If your query returns more than one set of results (for example, <tt>CALL</tt> on MySQL with certain functions), the behaviour of the threader is undefined at this time. <b>Note that if you want to perform both threaded and non-threaded queries on the same connection, you MUST read the "Locking" section below.</b><br />
<br />
Query operations use the following callback parameters:<br />
*<tt>owner</tt>: A Handle to the database used to query. The Handle is not the same as the Handle originally passed; however, it will test positively with <tt>SQL_IsSameConnection</tt>. The Handle can be cloned but it cannot be closed (it is closed automatically). It may be <tt>null</tt> in the case of a serious error (for example, the driver being unloaded).<br />
*<tt>hndl</tt>: A Handle to the query. It can be cloned, but not closed (it is closed automatically). It may be <tt>null</tt> if there was an error.<br />
*<tt>error</tt>: Error string, if any.<br />
*<tt>data</tt>: Optional user-defined data passed in through <tt>SQL_TQuery()</tt>.<br />
<br />
Example, continuing from above:<br />
<sourcepawn>void CheckSteamID(int userid, const char[] auth)<br />
{<br />
char query[255];<br />
FormatEx(query, sizeof(query), "SELECT userid FROM users WHERE steamid = '%s'", auth);<br />
hdatabase.Query(T_CheckSteamID, query, userid);<br />
}<br />
<br />
public void T_CheckSteamID(Database db, DBResultSet results, const char[] error, any data)<br />
{<br />
int client = 0;<br />
<br />
/* Make sure the client didn't disconnect while the thread was running */<br />
if ((client = GetClientOfUserId(data)) == 0)<br />
{<br />
return;<br />
}<br />
<br />
if (db == null || results == null || error[0] != '\0')<br />
{<br />
LogError("Query failed! %s", error);<br />
KickClient(client, "Authorization failed");<br />
}<br />
else if (results.RowCount == 0)<br />
{<br />
KickClient(client, "You are not a member");<br />
}<br />
}<br />
</sourcepawn><br />
<br />
==Locking==<br />
It is possible to run both threaded and non-threaded queries on the same connection. However, without the proper precautions, you could corrupt the network stream (even if it's local), corrupt memory, or otherwise cause a crash in the SQL driver. To solve this, SourceMod has ''database locking''. Locking is done via <tt>SQL_LockDatabase()</tt> and <tt>SQL_UnlockDatabase</tt>.<br />
<br />
Whenever performing any of the following non-threaded operations on a database, it is absolutely necessary to enclose the entire operation with a lock:<br />
*<tt>SQL_Query()</tt> (and <tt>SQL_FetchMoreResults</tt> pairings)<br />
*<tt>SQL_FastQuery</tt><br />
*<tt>SQL_PrepareQuery</tt><br />
*<tt>SQL_Bind*</tt> and <tt>SQL_Execute</tt> pairings<br />
<br />
The rule of thumb is: if your operation is going to use the database connection, it must be locked until the operation is fully completed. <br />
<br />
Example:<br />
<sourcepawn>bool GetByAge_Query(Database db, int age)<br />
{<br />
char query[100];<br />
FormatEx(query, sizeof(query), "SELECT name FROM users WHERE age = %d", age);<br />
<br />
SQL_LockDatabase(db);<br />
<br />
DBResultSet hQuery = SQL_Query(db, query);<br />
if (hQuery == null)<br />
{<br />
SQL_UnlockDatabase(db);<br />
return false;<br />
}<br />
<br />
SQL_UnlockDatabase(db);<br />
<br />
PrintResults(hQuery);<br />
<br />
delete hQuery;<br />
<br />
return true;<br />
}</sourcepawn><br />
<br />
Note that it was only necessary to lock the query; SourceMod pre-fetches the result set, and thus the network queue is clean.<br />
<br />
===Warnings===<br />
*<b>Never</b> call <tt>SQL_LockDatabase</tt> right before a threaded operation. You will deadlock the server and have to terminate/kill it. <br />
*<b>Always pair every Lock with an Unlock.</b> Otherwise you risk a deadlock.<br />
*If your query returns multiple result sets, for example, a procedure call on MySQL that returns results, you must lock both the query and the entire fetch operation. SourceMod is only able to fetch one result set at a time, and all result sets must be cleared before a new query is started.<br />
<br />
==Priority==<br />
Threaded SQL operations are placed in a simple priority queue. The priority levels are <tt>High</tt>, <tt>Medium</tt>, and <tt>Low</tt>. Connections always have the highest priority. <br />
<br />
Changing the priority can be useful if you have many queries with different purposes. For example, a statistics plugin might execute 10 queries on death, and one query on join. Because the statistics might rely on the join info, the join query might need to be high priority, while the death queries can be low priority.<br />
<br />
You should <b>never</b> simply assign a high priority to all of your queries simply because you want them to get done fast. Not only does it not work that way, but you may be inserting subtle problems into other plugins by being greedy.<br />
<br />
<br />
=SQLite=<br />
==Introduction==<br />
[http://www.sqlite.org/ SQLite] is a fast local-file SQL database engine and SourceMod provides a DBI driver for it. SQLite differs from MySQL, and thus MySQL queries may not work in SQLite. The driver type for connections is <tt>sqlite</tt>.<br />
<br />
==Usage==<br />
Since SQLite is local only, most of the connection parameters can be ignored. The only connection parameter required is <tt>database</tt>, which specifies the file name of the database. Databases are created on demand if they do not already exist, and are stored in <tt>addons/sourcemod/data/sqlite</tt>. An extension of ".sq3" is automatically appended to the file name.<br />
<br />
Additionally, you can specify sub-folders in the database name. For example, "cssdm/players" will become <tt>addons/sourcemod/data/sqlite/cssdm/players.sq3</tt>.<br />
<br />
SQLite supports the threading layer, and requires all of the same rules as the MySQL driver (including locks on shared connections).<br />
<br />
==External Links==<br />
*[http://www.sqlite.org SQLite Homepage]<br />
*[http://sqlitebrowser.sourceforge.net/ SQLite Browser]<br />
<br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Protobuf&diff=10975Protobuf2020-03-30T00:51:36Z<p>Joinedsenses: /* Multi-game usermessages example */ Add newline for readability.</p>
<hr />
<div>==Intro==<br />
Unlike the serial bitbuffer-backed usermessages in older games, newer games such as CS:GO (and DOTA 2) now use Google's [https://code.google.com/p/protobuf/ Protocol Buffers] or "protobuf" to back net messages and usermessages.<br />
<br />
==Differences==<br />
Instead of having to be read or written in order, the protobuf usermessages use defined fields, accessible by name, in any order.<br />
<br />
Starting, ending, and hooking usermessages remains unchanged. Reading and writing values to/from them is done using the Pb* set of natives in protobuf.inc instead of the Bf* natives in bitbuffer.inc.<br />
<br />
You can tell which usermessage system is in use for the current game by checking GetUserMessageType(). Possible returns are UM_BitBuf and UM_Protobuf.<br />
<br />
<br />
Basic fields ("optional" or "required") are single values and use the PbRead*/PbSet* natives.<br />
<br />
Repeated fields are arrays of values, accessible by their 0-based index with the PbReadRepeated* natives or added with the PbAdd* natives. You can get the count of values in a repeated field with PbGetRepeatedFieldCount.<br />
<br />
<br />
For message and field names, see the [https://github.com/alliedmodders/hl2sdk/blob/csgo/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#L68 CS:GO Usermessages] as defined in protobuf's proto format, the [[Counter-Strike: Global Offensive UserMessages]] page, or the [[DOTA 2 UserMessages]] page.<br />
<br />
==Protobuf natives==<br />
See [https://github.com/alliedmodders/sourcemod/blob/master/plugins/include/protobuf.inc protobuf.inc]<br />
<br />
==Multi-game usermessages example==<br />
From funcommands' drug.sp, using the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/9a3c7f5049b6/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l204 Fade] usermessage:<br />
<sourcepawn><br />
int clients[2];<br />
clients[0] = client; <br />
<br />
int duration = 255;<br />
int holdtime = 255;<br />
int flags = 0x0002;<br />
int color[4] = { 0, 0, 0, 128 };<br />
color[0] = GetRandomInt(0,255);<br />
color[1] = GetRandomInt(0,255);<br />
color[2] = GetRandomInt(0,255);<br />
<br />
Handle msg = StartMessageEx(g_FadeUserMsgId, clients, 1);<br />
<br />
if (GetUserMessageType() == UM_Protobuf)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(msg);<br />
<br />
pb.SetInt("duration", duration);<br />
pb.SetInt("hold_time", holdtime);<br />
pb.SetInt("flags", flags);<br />
pb.SetColor("clr", color);<br />
}<br />
else<br />
{<br />
BrWrite bf = UserMessageToBfWrite(msg);<br />
<br />
bf.WriteShort(duration);<br />
bf.WriteShort(holdtime);<br />
bf.WriteShort(flags);<br />
bf.WriteByte(color[0]);<br />
bf.WriteByte(color[1]);<br />
bf.WriteByte(color[2]);<br />
bf.WriteByte(color[3]);<br />
}<br />
<br />
EndMessage();<br />
</sourcepawn><br />
<br />
==Embedded message example==<br />
<br />
This example sends a VGUIMenu usermessage, adding values to a repeated field "subkeys" that uses the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l137 Subkey message type] defined in the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l132 VGUIMenu message].<br />
<br />
Once an embedded message handle is retrieved with ReadMessage, you can read or write to its fields with the normal PbRead/Set natives. GetRepeatedMessage or AddMessage will retrieve the handle for reading or writing respectively if it is a repeated field.<br />
<br />
<sourcepawn>void SendSourceModMOTD(int client)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(StartMessageOne("VGUIMenu", client));<br />
<br />
pb.SetString("name", "info");<br />
pb.SetBool("show", true);<br />
<br />
Protobuf subkey = PbAddMessage(pb, "subkeys");<br />
subkey.SetString("name", "type");<br />
subkey.SetString("str", "2"); // MOTDPANEL_TYPE_URL<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "title");<br />
subkey.SetString("str", "TESTING");<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "msg");<br />
subkey.SetString("str", "http://www.sourcemod.net");<br />
<br />
EndMessage();<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Menu_API_(SourceMod)&diff=10974Menu API (SourceMod)2020-03-30T00:49:15Z<p>Joinedsenses: Update formatting for readability and consistency</p>
<hr />
<div>SourceMod has an extensive API for building and displaying menus to clients. Unlike AMX Mod X, this API is highly state driven. Menus are based on callbacks which are guaranteed to be fired.<br />
<br />
For C++, the Menu API can be found in <tt>public/IMenuManager.h</tt>. For SourcePawn, it is in <tt>scripting/include/menus.inc</tt>.<br />
<br />
=Objects=<br />
The SourceMod Menu System is based on an object oriented hierarchy. Understanding this hierarchy, even for scripting, is critical to using menus effectively.<br />
<br />
==Styles==<br />
The top level object is a ''MenuStyle'' (<tt>IMenuStyle</tt> in C++). Styles describe a unique menu system. There are two such styles built into SourceMod:<br />
*Valve Style, also called "ESC" menus; 8 items per page, no raw/disabled text can be rendered<br />
*Radio Style, also called "AMX" menus; 10 items per page, raw/disabled text can be rendered<br />
<br />
Each MenuStyle has its own rules and properties. You can think of them as existing on separate "channels." For example, two different menus can exist on a player's screen as both a Valve menu and a Radio menu at the same time, and SourceMod will be able to manage both without any problems. This is because each style keeps track of its own menus separately.<br />
<br />
==Panels==<br />
Menu displays are drawn with a lower level interface called ''Panels'' (<tt>IMenuPanel</tt> in C++). Panels describe exactly one chunk of display text. Both selectable items and raw text can be added to a panel as long as its parent style supports the contents you're trying to draw. For example, the Valve style does not support drawing raw text or disabled items. But with a Radio-style Panel, you can display a large amount of on-screen data in your own format.<br />
<br />
Panels are considered temporary objects. They are created, rendered, displayed, and destroyed. Although they can be saved indefinitely, it is not necessary to do so.<br />
<br />
Valve Style drawing rules/limitations:<br />
*Max items per page is 8.<br />
*Disabled items cannot be drawn.<br />
*Raw text cannot be drawn.<br />
*Spacers do not add a space/newline, giving a "cramped" feel.<br />
*Users must press "ESC" or be at their console to view the menu.<br />
<br />
Radio Style drawing rules/limitations:<br />
*Max items per page is 10.<br />
*Titles appear white; items appear yellow, unless disabled, in which case they are white.<br />
*The 0th item is always white. For consistency, this means navigational controls explained in the next section are always white, and simply not drawn if disabled.<br />
<br />
==Menus==<br />
Lastly, there are plain ''Menus'' (<tt>IBaseMenu</tt> in C++). These are helper objects designed for storing a menu based on selectable items. Unlike low-level panels, menus are containers for '''items''', and can only contain items which are selectable (i.e., do not contain raw text). They fall into two categories:<br />
*Non-paginated: The menu can only have a certain number of items on it, and no control/navigation options will be added, except for an "Exit" button which will always be in the last position supported by the style.<br />
**Valve Style maximum items: 8<br />
**Radio Style maximum items: 10<br />
*Paginated: The menu can have any number of items. When displayed, only a certain number of items will be drawn at a time. Automatic navigation controls are added so players can easily move back and forth to different "pages" of items in the menu.<br />
**"Previous" is always drawn as the first navigation item, third from the last supported position. This will not be drawn if the menu only contains one page. If there are no previous pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.<br />
***Valve Style position: 6<br />
***Radio Style position: 8<br />
**"Next" is always drawn as the second navigation item, second from the last supported position. This will not be drawn if the menu only contains one page. If there are no further pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.<br />
***Valve Style position: 7<br />
***Radio Style position: 9<br />
**"Exit" is drawn if the menu has the exit button property set. It is always the last supported item position.<br />
***Valve Style position: 8<br />
***Radio Style position: 10<br />
<br />
The purpose of Menus is to simplify the procedure of storing, drawing, and calculating the selection of items. Thus, menus do not allow for adding raw text, as that would considerably complicate the drawing algorithm. ''Note: The C++ API supports hooking <tt>IBaseMenu</tt> drawing procedures and adding raw text; this will be added to the scripting API soon.''<br />
<br />
Internally, Menus are drawn via a ''RenderMenu'' algorithm. This algorithm creates a temporary panel and fills it with items from menus. This panel is then displayed to a client. The algorithm attempts to create a consistent feel across all menus, and across all styles. Thus any menu displayed via the <tt>IBaseMenu</tt> class, or <tt>Menu</tt> Handles, will look and act the same, and the Menu API is based off the Panel API.<br />
<br />
<br />
=Callbacks=<br />
==Overview==<br />
Menus are a callback based system. Each callback represents an action that occurs during a ''menu display cycle''. A cycle consists of a number of notifications:<br />
*Start notification.<br />
**Display notification if the menu can be displayed to the client.<br />
**Either an item select or menu cancel notification.<br />
*End notification.<br />
<br />
Since ''End'' signifies the end of a full display cycle, it is usually used to destroy temporary menus.<br />
<br />
==Specification==<br />
A detailed explanation of these events is below. For C++, an <tt>IBaseMenu</tt> pointer is always available. For SourcePawn, a <tt>Menu</tt> Handle and a <tt>MenuAction</tt> are always set in the <tt>MenuHandler</tt> callback. Unlike C++, the SourcePawn API allows certain actions to only be called if they are requested at menu creation time. This is an optimization. However, certain actions cannot be prevented from being called.<br />
<br />
*'''Start'''. The menu has been acknowledged. This does not mean it will be displayed; however, it guarantees that "OnMenuEnd" will be called.<br />
**<tt>OnMenuStart()</tt> in C++.<br />
**<tt>MenuAction_Start</tt> in SourcePawn. This action is not triggered unless requested.<br />
***<tt>param1</tt>: Ignored (always 0).<br />
***<tt>param2</tt>: Ignored (always 0).<br />
*'''Display'''. The menu is being displayed to a client.<br />
**<tt>OnMenuDisplay()</tt> in C++. An <tt>IMenuPanel</tt> pointer and client index are available.<br />
**<tt>MenuAction_Display</tt> in SourcePawn. This action is not triggered unless requested.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A Handle to a menu panel.<br />
*'''Select'''. An item on the menu has been selected. The item position given will be the position in the menu, rather than the key pressed (unless the menu is a raw panel). <br />
**<tt>OnMenuSelect()</tt> in C++. A client index and item position are passed.<br />
**<tt>MenuAction_Select</tt> in SourcePawn. This action is always triggerable, whether requested or not.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: An item position.<br />
*'''Cancel'''. The menu's display to one client has been cancelled.<br />
**<tt>OnMenuCancel()</tt> in C++. A reason for cancellation is provided.<br />
**<tt>MenuAction_Cancel</tt> in SourcePawn. This action is always triggerable, whether requested or not.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A menu cancellation reason code.<br />
*'''End'''. The menu's display cycle has finished; this means that the "Start" action has occurred, and either "Select" or "Cancel" has occurred thereafter. This is typically where menu resources are removed/deleted.<br />
**<tt>OnMenuEnd()</tt> in C++.<br />
**<tt>MenuAction_End</tt> in SourcePawn. This action is always triggered, whether requested or not.<br />
***<tt>param1</tt>: A menu end reason code.<br />
***<tt>param2</tt>: If param1 was MenuEnd_Cancelled, this contains a menu cancellation reason code.<br />
<br />
==Panels==<br />
For panels, the callback rules change. Panels only receive two of the above callbacks, and it is guaranteed that only one of them will be called for a given display cycle. For C++, the <tt>IBaseMenu</tt> pointer will always be <tt>NULL</tt>. For SourcePawn, the menu Handle will always be <tt>INVALID_HANDLE</tt> or <tt>null</tt>.<br />
<br />
*'''Select'''. A key has been pressed. This can be any number and should not be considered as reliably in bounds. For example, even if you only had 2 items in your panel, a client could trigger a key press of "43."<br />
**<tt>OnMenuSelect()</tt> in C++. A client index and key number pressed are passed.<br />
**<tt>MenuAction_Select</tt> in SourcePawn.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: Number of the key pressed.<br />
*'''Cancel'''. The menu's display to one client has been cancelled.<br />
**<tt>OnMenuCancel()</tt> in C++. A reason for cancellation is provided.<br />
**<tt>MenuAction_Cancel</tt> in SourcePawn.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A menu cancellation reason code.<br />
<br />
<br />
=Examples=<br />
First, let's start off with a very basic menu. We want the menu to look like this:<br />
<br />
<pre>Do you like apples?<br />
1. Yes<br />
2. No</pre><br />
<br />
We'll draw this menu with both a basic Menu and a Panel to show the API differences.<br />
<br />
==Basic Menu==<br />
First, let's write our example using the Menu building API. For a more in-depth guide, see [[Menus Step By Step (SourceMod Scripting)]].<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
RegConsoleCmd("menu_test1", Menu_Test1);<br />
}<br />
<br />
public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
/* If an option was selected, tell the client about the item. */<br />
if (action == MenuAction_Select)<br />
{<br />
char info[32];<br />
bool found = menu.GetItem(param2, info, sizeof(info));<br />
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info);<br />
}<br />
/* If the menu was cancelled, print a message to the server about it. */<br />
else if (action == MenuAction_Cancel)<br />
{<br />
PrintToServer("Client %d's menu was cancelled. Reason: %d", param1, param2);<br />
}<br />
/* If the menu has ended, destroy it */<br />
else if (action == MenuAction_End)<br />
{<br />
delete menu;<br />
}<br />
}<br />
<br />
public Action Menu_Test1(int client, int args)<br />
{<br />
Menu menu = new Menu(MenuHandler1);<br />
menu.SetTitle("Do you like apples?");<br />
menu.AddItem("yes", "Yes");<br />
menu.AddItem("no", "No");<br />
menu.ExitButton = false;<br />
menu.Display(client, 20);<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
Note a few very important points from this example:<br />
*One of either <tt>Select</tt> or <tt>Cancel</tt> will always be sent to the action handler.<br />
*<tt>End</tt> will always be sent to the action handler.<br />
*We destroy our Menu in the <tt>End</tt> action, because our Handle is no longer needed. If we had destroyed the Menu after <tt>DisplayMenu</tt>, it would have canceled the menu's display to the client.<br />
*Menus, by default, have an exit button. We disabled this in our example.<br />
*Our menu is set to display for 20 seconds. That means that if the client does not select an item within 20 seconds, the menu will be canceled. This is usually desired for menus that are for voting. Note that unlike AMX Mod X, you do not need to set a timer to make sure the menu will be ended.<br />
*Although we created and destroyed a new Menu Handle, we didn't need to. It is perfectly acceptable to create the Handle once for the lifetime of the plugin.<br />
<br />
Our finished menu and attached console output looks like this (I selected "Yes"):<br />
<br />
[[Image:Basic_menu_1.PNG]]<br />
<br />
==Basic Panel==<br />
Now, let's rewrite our example to use Panels instead.<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
RegConsoleCmd("panel_test1", Panel_Test1);<br />
}<br />
<br />
public int PanelHandler1(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
if (action == MenuAction_Select)<br />
{<br />
PrintToConsole(param1, "You selected item: %d", param2);<br />
}<br />
else if (action == MenuAction_Cancel)<br />
{<br />
PrintToServer("Client %d's menu was cancelled. Reason: %d", param1, param2);<br />
}<br />
}<br />
<br />
public Action Panel_Test1(int client, int args)<br />
{<br />
Panel panel = new Panel();<br />
panel.SetTitle("Do you like apples?");<br />
panel.DrawItem("Yes");<br />
panel.DrawItem("No");<br />
<br />
panel.Send(client, PanelHandler1, 20);<br />
<br />
delete panel;<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
As you can see, Panels are significantly different.<br />
*We can destroy the Panel as soon as we're done displaying it. We can create the Panel once and keep re-using it, but we can destroy it at any time without interrupting client menus.<br />
*The Handler function gets much less data. Since panels are designed as a raw display, no "item" information is saved internally. Thus, the handler function only knows whether the display was canceled or whether (and what) numerical key was pressed.<br />
*There is no automation. You cannot add more than a certain amount of selectable items to a Panel and get pagination. Automated control functionality requires using the heftier Menu object API.<br />
<br />
Our finished display and console output looks like this (I selected "Yes"):<br />
<br />
[[Image:Basic_panel_1.PNG]]<br />
<br />
==Basic Paginated Menu==<br />
Now, let's take a more advanced example -- pagination. Let's say we want to build a menu for changing the map. An easy way to do this is to read the <tt>maplist.txt</tt> file at the start of a plugin and build a menu out of it.<br />
<br />
Since reading and parsing a file is an expensive operation, we only want to do this once per map. Thus we'll build the menu in <tt>OnMapStart</tt>, and we won't call <tt>CloseHandle</tt> until <tt>OnMapEnd</tt>.<br />
<br />
Source code:<br />
<sourcepawn>Menu g_MapMenu = null;<br />
<br />
public void OnPluginStart()<br />
{<br />
RegConsoleCmd("menu_changemap", Command_ChangeMap);<br />
}<br />
<br />
public void OnMapStart()<br />
{<br />
g_MapMenu = BuildMapMenu();<br />
}<br />
<br />
public void OnMapEnd()<br />
{<br />
delete g_MapMenu;<br />
}<br />
<br />
Menu BuildMapMenu()<br />
{<br />
/* Open the file */<br />
File file = OpenFile("maplist.txt", "rt");<br />
if (file == null)<br />
{<br />
return null;<br />
}<br />
<br />
/* Create the menu Handle */<br />
Menu menu = new Menu(Menu_ChangeMap);<br />
char mapname[255];<br />
while (!file.EndOfFile() && file.ReadLine(mapname, sizeof(mapname)))<br />
{<br />
if (mapname[0] == ';' || !IsCharAlpha(mapname[0]))<br />
{<br />
continue;<br />
}<br />
<br />
/* Cut off the name at any whitespace */<br />
int len = strlen(mapname);<br />
for (int i = 0; i < len; i++)<br />
{<br />
if (IsCharSpace(mapname[i]))<br />
{<br />
mapname[i] = '\0';<br />
break;<br />
}<br />
}<br />
<br />
/* Check if the map is valid */<br />
if (!IsMapValid(mapname))<br />
{<br />
continue;<br />
}<br />
<br />
/* Add it to the menu */<br />
menu.AddItem(mapname, mapname);<br />
}<br />
<br />
/* Make sure we close the file! */<br />
file.Close();<br />
<br />
/* Finally, set the title */<br />
menu.SetTitle("Please select a map:");<br />
<br />
return menu;<br />
}<br />
<br />
public int Menu_ChangeMap(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
if (action == MenuAction_Select)<br />
{<br />
char info[32];<br />
<br />
/* Get item info */<br />
bool found = menu.GetItem(param2, info, sizeof(info));<br />
<br />
/* Tell the client */<br />
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info);<br />
<br />
/* Change the map */<br />
ServerCommand("changelevel %s", info);<br />
}<br />
}<br />
<br />
public Action Command_ChangeMap(int client, int args)<br />
{<br />
if (g_MapMenu == null)<br />
{<br />
PrintToConsole(client, "The maplist.txt file was not found!");<br />
return Plugin_Handled;<br />
} <br />
<br />
g_MapMenu.Display(client, MENU_TIME_FOREVER);<br />
<br />
return Plugin_Handled;<br />
}</sourcepawn><br />
<br />
This menu results in many selections (my <tt>maplist.txt</tt> file had around 18 maps). So, our final menu has 3 pages, which side by side, look like:<br />
<br />
[[Image:Basic_menu_2_page1.PNG]]<br />
[[Image:Basic_menu_2_page2.PNG]]<br />
[[Image:Basic_menu_2_page3.PNG]]<br />
<br />
Finally, the console output printed this before the map changed to my selection, <tt>cs_office</tt>:<br />
<pre>You selected item: 8 (found? 1 info: cs_office)</pre><br />
<br />
Displaying and designing this Menu with a raw <tt>ShowMenu</tt> message or <tt>Panel</tt> API would be very time consuming and difficult. We would have to keep track of all the items in an array of hardcoded size, pages which the user is viewing, and write a function which calculated item selection based on current page and key press. The Menu system, thankfully, handles all of this for you.<br />
<br />
'''Notes:'''<br />
*Control options which are not available are not drawn. For example, in the first page, you cannot go "back," and in the last page, you cannot go "next." Despite this, the menu API tries to keep each the interface as consistent as possible. Thus, visually, each navigational control is always in the same position. <br />
*Although we specified no time out for our menu, if we had placed a timeout, flipping through pages does not affect the overall time. For example, if we had a timeout of 20, each successive page flip would continue to detract from the overall display time, rather than restart the allowed hold time back to 20.<br />
*If we had disabled the Exit button, options 8 and 9 would still be "Back" and "Next," respectively.<br />
*Again, we did not free the Menu Handle in <tt>MenuAction_End</tt>. This is because our menu is global/static, and we don't want to rebuild it every time.<br />
*These images show "Back." In SourceMod revisions 1011 and higher, "Back" is changed to "Previous," and "Back" is reserved for the special "ExitBack" functionality.<br />
<br />
=Voting=<br />
SourceMod also has API for displaying menus as votable choices to more than one client. SourceMod automatically handles selecting an item and randomly picking a tie-breaker. The voting API adds two new <tt>MenuAction</tt> values, which for vote displays, are '''always''' passed:<br />
<br />
*<tt>MenuAction_VoteStart</tt>: Fired after <tt>MenuAction_Start</tt> when the voting has officially started.<br />
*<tt>MenuAction_VoteEnd</tt>: Fired when all clients have either voted or cancelled their vote menu. The chosen item is passed through <tt>param1</tt>. This is fired '''before''' <tt>MenuAction_End</tt>. It is important to note that it does not supercede <tt>MenuAction_End</tt>, nor is it the same thing. Menus should never be destroyed in <tt>MenuAction_VoteEnd</tt>. '''Note:''' This is not called if <tt>SetVoteResultCallback</tt>() is used.<br />
*<tt>MenuAction_VoteCancel</tt>: Fired if the menu is cancelled while the vote is in progress. If this is called, <tt>MenuAction_VoteEnd</tt> or the result callback will not be called, but <tt>MenuAction_End</tt> will be afterwards. A vote cancellation reason is passed in <tt>param1</tt>. <br />
<br />
The voting system extends overall menus with two additional properties:<br />
*Only one vote can be active at a time. You must call <tt>IsVoteInProgress</tt>() or else <tt>VoteMenu</tt>() will fail.<br />
*If a client votes and then disconnects while the vote is still active, the client's vote will be invalidated.<br />
<br />
The example below shows has to create a function called <tt>DoVoteMenu()</tt> which will ask all clients whether or not they would like to change to the given map.<br />
<br />
==Simple Vote==<br />
<sourcepawn>public int Handle_VoteMenu(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
delete menu;<br />
}<br />
else if (action == MenuAction_VoteEnd)<br />
{<br />
/* 0=yes, 1=no */<br />
if (param1 == 0)<br />
{<br />
char map[64];<br />
menu.GetItem(param1, map, sizeof(map));<br />
ServerCommand("changelevel %s", map);<br />
}<br />
}<br />
}<br />
<br />
void DoVoteMenu(const char[] map)<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
Menu menu = new Menu(Handle_VoteMenu);<br />
menu.SetTitle("Change map to: %s?", map);<br />
menu.AddItem(map, "Yes");<br />
menu.AddItem("no", "No");<br />
menu.ExitButton = false;<br />
menu.DisplayVoteToAll(20);<br />
}</sourcepawn><br />
<br />
==Advanced Voting==<br />
If you need more information about voting results than <tt>MenuAction_VoteEnd</tt> gives you, you can choose to have a different callback invoked. The new callback will provide much more information, but at a price: <tt>MenuAction_VoteEnd</tt> will not be called, and you will have to decide how to interpret the results. This is done via <tt>SetVoteResultCallback</tt>().<br />
<br />
Example:<br />
<sourcepawn>public int Handle_VoteMenu(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
delete menu;<br />
}<br />
}<br />
<br />
public void Handle_VoteResults(Menu menu, <br />
int num_votes, <br />
int num_clients, <br />
const int[][] client_info, <br />
int num_items, <br />
const int[][] item_info)<br />
{<br />
/* See if there were multiple winners */<br />
int winner = 0;<br />
if (num_items > 1<br />
&& (item_info[0][VOTEINFO_ITEM_VOTES] == item_info[1][VOTEINFO_ITEM_VOTES]))<br />
{<br />
winner = GetRandomInt(0, 1);<br />
}<br />
<br />
char map[64];<br />
menu.GetItem(item_info[winner][VOTEINFO_ITEM_INDEX], map, sizeof(map));<br />
ServerCommand("changelevel %s", map);<br />
}<br />
<br />
void DoVoteMenu(const char[] map)<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
Menu menu = new Menu(Handle_VoteMenu);<br />
menu.VoteResultCallback = Handle_VoteResults;<br />
menu.SetTitle("Change map to: %s?", map);<br />
menu.AddItem(map, "Yes");<br />
menu.AddItem("no", "No");<br />
menu.ExitButton = false;<br />
menu.DisplayVoteToAll(20);<br />
}</sourcepawn><br />
<br />
=ExitBack=<br />
ExitBack is a special term to refer to the "ExitBack Button." This button is disabled by default. Normally, paginated menus have no "Previous" item for the first page. If the "ExitBack" button is enabled, the "Previous" item will show up as "Back." <br />
<br />
Selecting the "ExitBack" option will exit the menu with <tt>MenuCancel_ExitBack</tt> and <tt>MenuEnd_ExitBack</tt>. The functionality of this is the same as a normal menu exit internally; extra functionality must be defined through the callbacks.<br />
<br />
=Closing Menu Handles=<br />
It is only necessary to close a menu handle on <tt>MenuAction_End</tt>. The <tt>MenuAction_End</tt> is done every time a menu is closed and no longer needed.<br />
<br />
=Translations=<br />
It is possible to dynamically translate menus to each player through the <tt>MenuAction_DisplayItem</tt> callback. A special native, <tt>RedrawMenuItem</tt>, is used to transform the text while inside the callback. Let's redo the vote example from earlier to be translated:<br />
<br />
<sourcepawn>public int Handle_VoteMenu(Menu menu, MenuAction action, int param1, int param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
delete menu;<br />
}<br />
else if (action == MenuAction_VoteEnd)<br />
{<br />
/* 0=yes, 1=no */<br />
if (param1 == 0)<br />
{<br />
char map[64];<br />
menu.GetItem(param1, map, sizeof(map));<br />
ServerCommand("changelevel %s", map);<br />
}<br />
}<br />
else if (action == MenuAction_DisplayItem)<br />
{<br />
/* Get the display string, we'll use it as a translation phrase */<br />
char display[64];<br />
menu.GetItem(param2, "", 0, _, display, sizeof(display));<br />
<br />
/* Translate the string to the client's language */<br />
char buffer[255];<br />
Format(buffer, sizeof(buffer), "%T", display, param1);<br />
<br />
/* Override the text */<br />
return RedrawMenuItem(buffer);<br />
}<br />
else if (action == MenuAction_Display)<br />
{<br />
/* Panel Handle is the second parameter */<br />
Panel panel = view_as<Panel>(param2);<br />
<br />
/* Get the map name we're changing to from the first item */<br />
char map[64];<br />
menu.GetItem(0, map, sizeof(map));<br />
<br />
/* Translate to our phrase */<br />
char buffer[255];<br />
Format(buffer, sizeof(buffer), "%T", "Change map to?", client, map);<br />
<br />
panel.SetTitle(buffer);<br />
}<br />
}<br />
<br />
void DoVoteMenu(const char[] map)<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
Menu menu = new Menu(Handle_VoteMenu,MenuAction_DisplayItem|MenuAction_Display);<br />
menu.SetTitle("Change map to: %s?", map);<br />
menu.AddItem(map, "Yes");<br />
menu.AddItem("no", "No");<br />
menu.ExitButton = false;<br />
menu.DisplayVoteToAll(20);<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod Development]]<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Optional_Requirements_(SourceMod_Scripting)&diff=10973Optional Requirements (SourceMod Scripting)2020-03-30T00:41:36Z<p>Joinedsenses: Update highlighting and formatting</p>
<hr />
<div>Normally, if you use natives from an extension or another plugin, your plugin will not load unless those natives exist. However, it is possible to make your dependencies "optional." This article details how.<br />
<br />
=Disabling Requirements=<br />
==Extensions==<br />
To disable an extension being marked as "required," remove the <tt>REQUIRE_EXTENSIONS</tt> define. For example:<br />
<br />
<sourcepawn>#include <sourcemod><br />
#undef REQUIRE_EXTENSIONS<br />
#include <sdktools></sourcepawn><br />
<br />
Note that any extensions included after the <tt>#undef</tt> will also be marked as not required. Thus, you may need to move the include down, or do something like:<br />
<br />
<sourcepawn>#include <sourcemod><br />
#undef REQUIRE_EXTENSIONS<br />
#include <sdktools><br />
#define REQUIRE_EXTENSIONS</sourcepawn><br />
<br />
==Plugins==<br />
To disable an plugin being marked as "required," remove the <tt>REQUIRE_PLUGIN</tt> define. For example:<br />
<br />
<sourcepawn>#include <sourcemod><br />
#undef REQUIRE_PLUGIN<br />
#include <ircrelay></sourcepawn><br />
<br />
Note that any plugins included after the <tt>#undef</tt> will also be marked as not required. Thus, you may need to move the include down, or do something like:<br />
<br />
<sourcepawn>#include <sourcemod><br />
#undef REQUIRE_PLUGIN<br />
#include <ircrelay><br />
#define REQUIRE_PLUGIN</sourcepawn><br />
<br />
==Optional Natives==<br />
To mark a native as optional, use <tt>MarkNativeAsOptional</tt>. It should be called in <tt>AskPluginLoad2</tt>. For example:<br />
<br />
<sourcepawn>public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)<br />
{<br />
MarkNativeAsOptional("SDKCall");<br />
return APLRes_Success;<br />
}</sourcepawn><br />
<br />
=Checking Optional Dependencies=<br />
If you use a plugin or extension as an optional dependency, you may need to check whether it exists. For example, let's say we're relying on a plugin with the library name of "ircrelay." The way to always know whether ircrelay is loaded (and working) is:<br />
<br />
<sourcepawn>bool ircrelay = false;<br />
<br />
public void OnAllPluginsLoaded()<br />
{<br />
ircrelay = LibraryExists("ircrelay");<br />
}<br />
<br />
public void OnLibraryRemoved(const char[] name)<br />
{<br />
if (StrEqual(name, "ircrelay"))<br />
{<br />
ircrelay = false;<br />
}<br />
}<br />
<br />
public void OnLibraryAdded(const char[] name)<br />
{<br />
if (StrEqual(name, "ircrelay"))<br />
{<br />
ircrelay = true;<br />
}<br />
}</sourcepawn><br />
<br />
<br />
=Creating a Dependency=<br />
Allowing other plugins to use your plugin as a library requires making an include file with two structures (the second of which is optional). The first structure must look like this:<br />
<br />
<sourcepawn>public SharedPlugin __pl_myfile = <br />
{<br />
name = "myfile",<br />
file = "myfile.smx",<br />
#if defined REQUIRE_PLUGIN<br />
required = 1,<br />
#else<br />
required = 0,<br />
#endif<br />
};</sourcepawn><br />
<br />
The basic format is:<br />
*The variable name MUST start with <tt>__pl_</tt> and must end with a unique string.<br />
*The "name" portion is treated as the library name and must be unique.<br />
*The filename must match the filename of the plugin implementing the library.<br />
*The requirement portion should remain unchanged in order to maintain standards.<br />
<br />
Additionally, you should expose a function which marks all of your natives as optional. You can do this by:<br />
<sourcepawn>#if !defined REQUIRE_PLUGIN<br />
public void __pl_myfile_SetNTVOptional()<br />
{<br />
MarkNativeAsOptional("native1");<br />
MarkNativeAsOptional("native2");<br />
MarkNativeAsOptional("native3");<br />
}<br />
#endif</sourcepawn><br />
<br />
This function will be secretly called before the plugin loads (if and only if the requirement is optional), thus allowing seamless optional usage by third party developers. Note that the <tt>__pl_</tt> and <tt>_SetNTVOptional</tt> portions <b>must</b> be present, and that everything in between must match the ending of <tt>__pl_</tt> for the <tt>SharedPlugin</tt> structure.<br />
<br />
You also must register the library name with SourceMod -- again this should be the unique string. This should be done inside the <code>AskPluginLoad2</code> function.<br />
<br />
<sourcepawn><br />
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)<br />
{<br />
//... code here ...<br />
RegPluginLibrary("myfile");<br />
<br />
return APLRes_Success;<br />
}<br />
</sourcepawn><br />
<br />
= Full Example =<br />
;''Bounty: bounty.sp''<br />
<sourcepawn><br />
#include <sourcemod><br />
#undef REQUIRE_PLUGIN<br />
#include <ircrelay><br />
</sourcepawn><br />
;''Bounty: bounty.config.sp''<br />
<sourcepawn><br />
bool plugin_IrcRelay = LibraryExists("ircrelay");<br />
if ((BountyIRC) && (plugin_IrcRelay))<br />
{<br />
RegisterIrcCommand("!bounty", "x", Irc_ViewBounty);<br />
IrcMessage(CHAN_MASTER, "IRC Bounty Running!");<br />
}<br />
else<br />
{<br />
if ((BountyIRC) && (!plugin_IrcRelay))<br />
{<br />
BountyConsole_Debug("%t", "Bounty IRC Relay failed");<br />
}<br />
}<br />
</sourcepawn><br />
;''IRC Relay: ircrelay.sp''<br />
<sourcepawn><br />
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)<br />
{<br />
//... code here ...<br />
RegPluginLibrary("ircrelay");<br />
<br />
return APLRes_Success;<br />
}<br />
</sourcepawn><br />
;''IRC Relay: ircrelay.inc''<br />
<sourcepawn><br />
public SharedPlugin __pl_ircrelay = <br />
{<br />
name = "ircrelay",<br />
file = "ircrelay.smx",<br />
#if defined REQUIRE_PLUGIN<br />
required = 1,<br />
#else<br />
required = 0,<br />
#endif<br />
};<br />
<br />
#if !defined REQUIRE_PLUGIN<br />
public void __pl_ircrelay_SetNTVOptional()<br />
{<br />
MarkNativeAsOptional("IrcMessage");<br />
MarkNativeAsOptional("RegisterIrcCommand");<br />
MarkNativeAsOptional("IrcGetCmdArgc");<br />
MarkNativeAsOptional("IrcGetCmdArgv");<br />
}<br />
#endif<br />
</sourcepawn><br />
The <code>name</code> value is what will be checked when you run <code>LibraryExists</code>. This allows the bounty script to fully work, even if the IRC relay plugin is not installed and/or running correctly on your server.<br />
<br />
<br />
[[Category:SourceMod Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Timers_(SourceMod_Scripting)&diff=10972Timers (SourceMod Scripting)2020-03-30T00:38:16Z<p>Joinedsenses: Update syntax, highlighting, formatting</p>
<hr />
<div>Timers in [[SourceMod]] are timed events that occur once or repeatedly at a given interval.<br />
<br />
=Introduction=<br />
Timers allow you to set an interval, a function to use as the event callback, and an optional Handle to pass through the callback. This is useful for saving data asynchronously.<br />
<br />
All timer functions are in <tt>plugins/include/timers.inc</tt>. <br />
<br />
=Basic Usage=<br />
==One-Time Timers==<br />
One-time timers are timers that only execute once. An example of a one-time timer might look like:<br />
<br />
<sourcepawn>public void OnPluginStart()<br />
{<br />
CreateTimer(5.0, LoadStuff);<br />
}<br />
<br />
public Action LoadStuff(Handle timer)<br />
{<br />
PrintToServer("Loading stuff!");<br />
}</sourcepawn><br />
<br />
This code will print "Loading stuff!" to the server console five seconds after the map loads. The return value has no meaning for one-time timers.<br />
<br />
==Repeatable Timers==<br />
Repeatable timers execute infinitely many times, once every interval. Based on the return value, they either continue or cancel.<br />
<br />
For example, say you want to display a message five times, once every three seconds:<br />
<sourcepawn><br />
void someFunction()<br />
{<br />
CreateTimer(3.0, Timer_PrintMessageFiveTimes, _, TIMER_REPEAT);<br />
}<br />
<br />
public Action Timer_PrintMessageFiveTimes(Handle timer)<br />
{<br />
// Create a global variable visible only in the local scope (this function).<br />
static int numPrinted = 0;<br />
<br />
if (numPrinted >= 5) <br />
{<br />
numPrinted = 0;<br />
return Plugin_Stop;<br />
}<br />
<br />
PrintToServer("Warning! This is a message.");<br />
numPrinted++;<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
<br />
Note that <tt>Plugin_Stop</tt> stops the timer, and <tt>Plugin_Continue</tt> allows it to continue repeating.<br />
<br />
=Passing Data=<br />
==Simple Values==<br />
As mentioned earlier, timers let you pass values on to the callback function. This value can be any type. For example, let's say we want to print a message to a player fifteen seconds after they connect. However, we want to cancel the timer if the player disconnects. Implementing this is very easy:<br />
<br />
<sourcepawn><br />
Handle WelcomeTimers[MAXPLAYERS+1];<br />
<br />
public void OnClientPutInServer(int client)<br />
{<br />
WelcomeTimers[client] = CreateTimer(15.0, WelcomePlayer, client);<br />
}<br />
<br />
public void OnClientDisconnect(int client)<br />
{<br />
delete WelcomeTimers[client];<br />
<br />
/**<br />
* 'delete handle;' equates to:<br />
* if (handle != null) {<br />
* CloseHandle(handle);<br />
* handle = null;<br />
* } <br />
*/<br />
}<br />
<br />
public Action WelcomePlayer(Handle timer, int client)<br />
{<br />
PrintToConsole(client, "Welcome to the server!");<br />
WelcomeTimers[client] = null;<br />
}</sourcepawn><br />
<br />
Another example without using handles is to use either UserID or ClientSerial (which is unique to each player):<br />
<br />
<sourcepawn><br />
public void OnClientPutInServer(int client)<br />
{<br />
CreateTimer(15.0, WelcomePlayer, GetClientSerial(client)); // You could also use GetClientUserId(client)<br />
}<br />
<br />
public Action WelcomePlayer(Handle timer, int serial)<br />
{<br />
int client = GetClientFromSerial(serial); // Validate the client serial<br />
<br />
if (client == 0) // The serial is no longer valid, the player must have disconnected<br />
{<br />
return Plugin_Stop;<br />
}<br />
<br />
PrintToConsole(client, "Welcome to the server!");<br />
<br />
return Plugin_Continue;<br />
}</sourcepawn><br />
If you want to close or cancel a timer when a client disconnects, then use the first example with handles.<br />
<br />
==Handles==<br />
If you want to pass a Handle as a value, you have the option of using <tt>TIMER_DATA_HNDL_CLOSE</tt>, which will automatically call <tt>CloseHandle()</tt> for you once the timer dies. <br />
<br />
==Data Packs==<br />
Data packs are packable structures that can be used to hold asynchronous data (data that must be saved and unpacked for later). They are especially useful for timers, and thus there exists a helper function, called <tt>CreateDataTimer()</tt>, which creates a timer using a data pack handle. The handle is created and closed automatically for you.<br />
<br />
The above example could be rewritten as:<br />
<sourcepawn><br />
Handle WelcomeTimers[MAXPLAYERS+1];<br />
<br />
public void OnClientPutInServer(int client)<br />
{<br />
DataPack pack;<br />
WelcomeTimers[client] = CreateDataTimer(15.0, WelcomePlayer, pack);<br />
pack.WriteCell(client);<br />
pack.WriteString("Welcome to the server!");<br />
}<br />
<br />
public void OnClientDisconnect(int client)<br />
{<br />
delete WelcomeTimers[client];<br />
}<br />
<br />
public Action WelcomePlayer(Handle timer, DataPack pack)<br />
{<br />
char str[128];<br />
int client;<br />
<br />
/* Set to the beginning and unpack it */<br />
pack.Reset();<br />
client = pack.ReadCell();<br />
pack.ReadString(str, sizeof(str));<br />
PrintToConsole(client, "%s", str);<br />
WelcomeTimers[client] = null;<br />
}</sourcepawn><br />
<br />
=Notes=<br />
==Accuracy==<br />
The smallest possible interval is 0.1 seconds. Timers have high precision (floating point) but low accuracy, as the current time is based on the in-game tick count, not the system clock. This has two implications:<br />
*If the server is paused (not ticking or hibernating), timers will not run.<br />
*The server will not always tick at an exact interval. <br />
<br />
For example, a 1.234 second interval timer starting from time <tt>t</tt> might not tick until <tt>t+1.242</tt> at a tickrate of 66 ticks per second.<br />
<br />
==Timer Death==<br />
All timers are guaranteed to die either by:<br />
*<tt>CloseHandle()</tt> being called (or on plugin unload);<br />
*<tt>KillTimer()</tt> being called;<br />
*<tt>Plugin_Stop</tt> being returned from a repeatable timer;<br />
*<tt>TriggerTimer()</tt> being called on a one-time timer;<br />
*Execution of a one-time timer finishes.<br />
<br />
When a timer dies, if <tt>TIMER_DATA_HNDL_CLOSE</tt> is set, the Handle will always be closed with the permissions that <tt>CloseHandle()</tt> uses by default. Since timers cannot be cloned, there should be no ownership issues.<br />
<br />
==Changing Intervals==<br />
The interval of a timer cannot be changed as of this writing. The timer must be killed and a new one created.<br />
<br />
==AMX Mod X==<br />
Unlike [[AMX Mod X]]'s <tt>set_task</tt> function, you cannot pass arrays using <tt>CreateTimer</tt>. To accomplish this, you should use the data pack functionality explained above.<br />
<br />
Similarly, there are no flags for counted repeats (non-infinite loops) or timers based on the map start or end. These must be done manually.<br />
<br />
=External Links=<br />
*[http://www.sourcemod.net/devlog/?p=130 On Timer Design, Part 3] (SourceMod DevLog)<br />
*[http://www.sourcemod.net/devlog/?p=119 On Timer Design, Part 2] (SourceMod DevLog)<br />
*[http://www.sourcemod.net/devlog/?p=118 On Timer Design, Part 1] (SourceMod DevLog)<br />
<br />
[[Category:SourceMod Scripting]]<br />
<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Protobuf&diff=10971Protobuf2020-03-30T00:28:17Z<p>Joinedsenses: Update syntax</p>
<hr />
<div>==Intro==<br />
Unlike the serial bitbuffer-backed usermessages in older games, newer games such as CS:GO (and DOTA 2) now use Google's [https://code.google.com/p/protobuf/ Protocol Buffers] or "protobuf" to back net messages and usermessages.<br />
<br />
==Differences==<br />
Instead of having to be read or written in order, the protobuf usermessages use defined fields, accessible by name, in any order.<br />
<br />
Starting, ending, and hooking usermessages remains unchanged. Reading and writing values to/from them is done using the Pb* set of natives in protobuf.inc instead of the Bf* natives in bitbuffer.inc.<br />
<br />
You can tell which usermessage system is in use for the current game by checking GetUserMessageType(). Possible returns are UM_BitBuf and UM_Protobuf.<br />
<br />
<br />
Basic fields ("optional" or "required") are single values and use the PbRead*/PbSet* natives.<br />
<br />
Repeated fields are arrays of values, accessible by their 0-based index with the PbReadRepeated* natives or added with the PbAdd* natives. You can get the count of values in a repeated field with PbGetRepeatedFieldCount.<br />
<br />
<br />
For message and field names, see the [https://github.com/alliedmodders/hl2sdk/blob/csgo/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#L68 CS:GO Usermessages] as defined in protobuf's proto format, the [[Counter-Strike: Global Offensive UserMessages]] page, or the [[DOTA 2 UserMessages]] page.<br />
<br />
==Protobuf natives==<br />
See [https://github.com/alliedmodders/sourcemod/blob/master/plugins/include/protobuf.inc protobuf.inc]<br />
<br />
==Multi-game usermessages example==<br />
From funcommands' drug.sp, using the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/9a3c7f5049b6/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l204 Fade] usermessage:<br />
<sourcepawn><br />
int clients[2];<br />
clients[0] = client; <br />
<br />
int duration = 255;<br />
int holdtime = 255;<br />
int flags = 0x0002;<br />
int color[4] = { 0, 0, 0, 128 };<br />
color[0] = GetRandomInt(0,255);<br />
color[1] = GetRandomInt(0,255);<br />
color[2] = GetRandomInt(0,255);<br />
<br />
Handle msg = StartMessageEx(g_FadeUserMsgId, clients, 1);<br />
<br />
if (GetUserMessageType() == UM_Protobuf)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(msg);<br />
pb.SetInt("duration", duration);<br />
pb.SetInt("hold_time", holdtime);<br />
pb.SetInt("flags", flags);<br />
pb.SetColor("clr", color);<br />
}<br />
else<br />
{<br />
BrWrite bf = UserMessageToBfWrite(msg);<br />
bf.WriteShort(duration);<br />
bf.WriteShort(holdtime);<br />
bf.WriteShort(flags);<br />
bf.WriteByte(color[0]);<br />
bf.WriteByte(color[1]);<br />
bf.WriteByte(color[2]);<br />
bf.WriteByte(color[3]);<br />
}<br />
<br />
EndMessage();<br />
</sourcepawn><br />
<br />
==Embedded message example==<br />
<br />
This example sends a VGUIMenu usermessage, adding values to a repeated field "subkeys" that uses the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l137 Subkey message type] defined in the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l132 VGUIMenu message].<br />
<br />
Once an embedded message handle is retrieved with ReadMessage, you can read or write to its fields with the normal PbRead/Set natives. GetRepeatedMessage or AddMessage will retrieve the handle for reading or writing respectively if it is a repeated field.<br />
<br />
<sourcepawn>void SendSourceModMOTD(int client)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(StartMessageOne("VGUIMenu", client));<br />
<br />
pb.SetString("name", "info");<br />
pb.SetBool("show", true);<br />
<br />
Protobuf subkey = PbAddMessage(pb, "subkeys");<br />
subkey.SetString("name", "type");<br />
subkey.SetString("str", "2"); // MOTDPANEL_TYPE_URL<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "title");<br />
subkey.SetString("str", "TESTING");<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "msg");<br />
subkey.SetString("str", "http://www.sourcemod.net");<br />
<br />
EndMessage();<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=Protobuf&diff=10970Protobuf2020-03-30T00:27:18Z<p>Joinedsenses: Update syntax, methodmap, highlighting</p>
<hr />
<div>==Intro==<br />
Unlike the serial bitbuffer-backed usermessages in older games, newer games such as CS:GO (and DOTA 2) now use Google's [https://code.google.com/p/protobuf/ Protocol Buffers] or "protobuf" to back net messages and usermessages.<br />
<br />
==Differences==<br />
Instead of having to be read or written in order, the protobuf usermessages use defined fields, accessible by name, in any order.<br />
<br />
Starting, ending, and hooking usermessages remains unchanged. Reading and writing values to/from them is done using the Pb* set of natives in protobuf.inc instead of the Bf* natives in bitbuffer.inc.<br />
<br />
You can tell which usermessage system is in use for the current game by checking GetUserMessageType(). Possible returns are UM_BitBuf and UM_Protobuf.<br />
<br />
<br />
Basic fields ("optional" or "required") are single values and use the PbRead*/PbSet* natives.<br />
<br />
Repeated fields are arrays of values, accessible by their 0-based index with the PbReadRepeated* natives or added with the PbAdd* natives. You can get the count of values in a repeated field with PbGetRepeatedFieldCount.<br />
<br />
<br />
For message and field names, see the [https://github.com/alliedmodders/hl2sdk/blob/csgo/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#L68 CS:GO Usermessages] as defined in protobuf's proto format, the [[Counter-Strike: Global Offensive UserMessages]] page, or the [[DOTA 2 UserMessages]] page.<br />
<br />
==Protobuf natives==<br />
See [https://github.com/alliedmodders/sourcemod/blob/master/plugins/include/protobuf.inc protobuf.inc]<br />
<br />
==Multi-game usermessages example==<br />
From funcommands' drug.sp, using the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/9a3c7f5049b6/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l204 Fade] usermessage:<br />
<sourcepawn><br />
int clients[2];<br />
clients[0] = client; <br />
<br />
int duration = 255;<br />
int holdtime = 255;<br />
int flags = 0x0002;<br />
int color[4] = { 0, 0, 0, 128 };<br />
color[0] = GetRandomInt(0,255);<br />
color[1] = GetRandomInt(0,255);<br />
color[2] = GetRandomInt(0,255);<br />
<br />
Handle msg = StartMessageEx(g_FadeUserMsgId, clients, 1);<br />
<br />
if (GetUserMessageType() == UM_Protobuf)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(msg);<br />
pb.SetInt("duration", duration);<br />
pb.SetInt("hold_time", holdtime);<br />
pb.SetInt("flags", flags);<br />
pb.SetColor("clr", color);<br />
}<br />
else<br />
{<br />
BrWrite bf = UserMessageToBfWrite(msg);<br />
bf.WriteShort(duration);<br />
bf.WriteShort(holdtime);<br />
bf.WriteShort(flags);<br />
bf.WriteByte(color[0]);<br />
bf.WriteByte(color[1]);<br />
bf.WriteByte(color[2]);<br />
bf.WriteByte(color[3]);<br />
}<br />
<br />
EndMessage();<br />
</sourcepawn><br />
<br />
==Embedded message example==<br />
<br />
This example sends a VGUIMenu usermessage, adding values to a repeated field "subkeys" that uses the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l137 Subkey message type] defined in the [http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/game/shared/csgo/protobuf/cstrike15_usermessages.proto#l132 VGUIMenu message].<br />
<br />
Once an embedded message handle is retrieved with ReadMessage, you can read or write to its fields with the normal PbRead/Set natives. GetRepeatedMessage or AddMessage will retrieve the handle for reading or writing respectively if it is a repeated field.<br />
<br />
<sourcepawn>SendSourceModMOTD(client)<br />
{<br />
Protobuf pb = UserMessageToProtobuf(StartMessageOne("VGUIMenu", client));<br />
<br />
pb.SetString("name", "info");<br />
pb.SetBool("show", true);<br />
<br />
Protobuf subkey = PbAddMessage(pb, "subkeys");<br />
subkey.SetString("name", "type");<br />
subkey.SetString("str", "2"); // MOTDPANEL_TYPE_URL<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "title");<br />
subkey.SetString("str", "TESTING");<br />
<br />
subkey = pb.AddMessage("subkeys");<br />
subkey.SetString("name", "msg");<br />
subkey.SetString("str", "http://www.sourcemod.net");<br />
<br />
EndMessage();<br />
}</sourcepawn><br />
<br />
[[Category:SourceMod_Scripting]]</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=KeyValues_(SourceMod_Scripting)&diff=10969KeyValues (SourceMod Scripting)2020-03-30T00:14:34Z<p>Joinedsenses: Modified intents for readability and consistency.</p>
<hr />
<div>KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the [http://developer.valvesoftware.com/wiki/KeyValues_class Valve Developer Wiki]. <br />
<br />
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.<br />
<br />
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''<br />
<br />
=Introduction=<br />
KeyValues consist of a ''nodes'', or ''sections'', which contain pairs of keys and values. A section looks like this:<br />
<pre>"section"<br />
{<br />
"key" "value"<br />
}</pre><br />
<br />
The <tt>"section"</tt> string denotes the section's name. The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.<br />
<br />
KeyValues structures are created with <tt>new KeyValues()</tt>. The Handle must be freed when finished in order to avoid a memory leak.<br />
<br />
=Files=<br />
KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:<br />
<pre>"MyFile"<br />
{<br />
"STEAM_0:0:7"<br />
{<br />
"name" "crab"<br />
}<br />
}</pre><br />
<br />
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node. <br />
<br />
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt> To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.<br />
<br />
=Traversal=<br />
SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:<br />
*The root node<br />
*A traversal stack<br />
<br />
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The ''top'' of this stack is where all operations take place. <br />
<br />
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as <tt>kv.GetString</tt> will use this new position.<br />
<br />
There are natives which change the position but do not change the traversal stack. For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.<br />
<br />
More traversal natives:<br />
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.<br />
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).<br />
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.<br />
<br />
==Basic Lookup==<br />
Let's take our <tt>MyFile</tt> example from above. How could we retrieve the name "crab" given the Steam ID?<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
<br />
return true;<br />
}</sourcepawn><br />
<br />
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack. However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.<br />
<br />
==Iterative Lookup==<br />
Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
// Jump into the first subsection<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
// Iterate over subsections at the same nesting level<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer));<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
<br />
return false;<br />
}</sourcepawn><br />
<br />
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.<br />
<br />
==Full Traversal==<br />
Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:<br />
<sourcepawn>void BrowseKeyValues(KeyValues kv)<br />
{<br />
do<br />
{<br />
// You can read the section/key name by using kv.GetSectionName here.<br />
<br />
if (kv.GotoFirstSubKey(false))<br />
{<br />
// Current key is a section. Browse it recursively.<br />
BrowseKeyValues(kv);<br />
kv.GoBack(kv);<br />
}<br />
else<br />
{<br />
// Current key is a regular key, or an empty section.<br />
if (kv.GetDataType(NULL_STRING) != KvData_None)<br />
{<br />
// Read value of key here (use NULL_STRING as key name). You can<br />
// also get the key name by using kv.GetSectionName here.<br />
}<br />
else<br />
{<br />
// Found an empty sub section. It can be handled here if necessary.<br />
}<br />
}<br />
} while (kv.GotoNextKey(false));<br />
}</sourcepawn><br />
<br />
This function will browse an entire KeyValues structure. Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.<br />
<br />
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sections. Because of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.<br />
<br />
=Deletion=<br />
There are two ways to delete sections from a KeyValues structure:<br />
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.<br />
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.<br />
<br />
==Simple Deletion==<br />
First, let's use our "basic lookup" example to delete a Steam ID section:<br />
<sourcepawn><br />
void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
kv.DeleteThis();<br />
kv.Rewind();<br />
kv.ExportToFile("myfile.txt");<br />
delete kv;<br />
}<br />
</sourcepawn><br />
<br />
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.<br />
<br />
==Iterative Deletion==<br />
Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.<br />
<br />
<sourcepawn>void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer))<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.DeleteThis();<br />
delete kv;<br />
return;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
==Full Deletion==<br />
Now, let's take a look at how we would delete all (or some) keys. <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would. If it can't find any more keys, it simply pops the traversal stack. <br />
<br />
An example of deleting all SteamIDs that have blank names:<br />
<sourcepawn>void DeleteAll(KeyValues kv)<br />
{<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
for (;;)<br />
{<br />
char name[4];<br />
kv.GetString("name", name, sizeof(name));<br />
if (name[0] == '\0')<br />
{<br />
if (kv.DeleteThis() < 1)<br />
{<br />
break;<br />
}<br />
}<br />
else if (!kv.GotoNextKey()) <br />
{<br />
break;<br />
} <br />
}<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
While at first this loop looks infinite, it is not. If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.<br />
<br />
==KeyValue Creation==<br />
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API<br />
<sourcepawn>KeyValues kv = new KeyValues("MyFile");<br />
kv.JumpToKey("STEAM_0:0:7", true);<br />
kv.SetString("name", "crab");<br />
kv.Rewind();<br />
kv.ExportToFile("C:\\javalia.txt");<br />
delete kv;</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=KeyValues_(SourceMod_Scripting)&diff=10968KeyValues (SourceMod Scripting)2020-03-30T00:08:06Z<p>Joinedsenses: Undo revision 10967 by Joinedsenses (talk)</p>
<hr />
<div>KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the [http://developer.valvesoftware.com/wiki/KeyValues_class Valve Developer Wiki]. <br />
<br />
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.<br />
<br />
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''<br />
<br />
=Introduction=<br />
KeyValues consist of a ''nodes'', or ''sections'', which contain pairs of keys and values. A section looks like this:<br />
<pre>"section"<br />
{<br />
"key" "value"<br />
}</pre><br />
<br />
The <tt>"section"</tt> string denotes the section's name. The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.<br />
<br />
KeyValues structures are created with <tt>new KeyValues()</tt>. The Handle must be freed when finished in order to avoid a memory leak.<br />
<br />
=Files=<br />
KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:<br />
<pre>"MyFile"<br />
{<br />
"STEAM_0:0:7"<br />
{<br />
"name" "crab"<br />
}<br />
}</pre><br />
<br />
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node. <br />
<br />
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt> To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.<br />
<br />
=Traversal=<br />
SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:<br />
*The root node<br />
*A traversal stack<br />
<br />
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The ''top'' of this stack is where all operations take place. <br />
<br />
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as <tt>kv.GetString</tt> will use this new position.<br />
<br />
There are natives which change the position but do not change the traversal stack. For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.<br />
<br />
More traversal natives:<br />
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.<br />
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).<br />
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.<br />
<br />
==Basic Lookup==<br />
Let's take our <tt>MyFile</tt> example from above. How could we retrieve the name "crab" given the Steam ID?<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}</sourcepawn><br />
<br />
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack. However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.<br />
<br />
==Iterative Lookup==<br />
Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
// Jump into the first subsection<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
// Iterate over subsections at the same nesting level<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer));<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
return false;<br />
}</sourcepawn><br />
<br />
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.<br />
<br />
==Full Traversal==<br />
Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:<br />
<sourcepawn>void BrowseKeyValues(KeyValues kv)<br />
{<br />
do<br />
{<br />
// You can read the section/key name by using kv.GetSectionName here.<br />
<br />
if (kv.GotoFirstSubKey(false))<br />
{<br />
// Current key is a section. Browse it recursively.<br />
BrowseKeyValues(kv);<br />
kv.GoBack(kv);<br />
}<br />
else<br />
{<br />
// Current key is a regular key, or an empty section.<br />
if (kv.GetDataType(NULL_STRING) != KvData_None)<br />
{<br />
// Read value of key here (use NULL_STRING as key name). You can<br />
// also get the key name by using kv.GetSectionName here.<br />
}<br />
else<br />
{<br />
// Found an empty sub section. It can be handled here if necessary.<br />
}<br />
}<br />
} while (kv.GotoNextKey(false));<br />
}</sourcepawn><br />
<br />
This function will browse an entire KeyValues structure. Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.<br />
<br />
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sections. Because of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.<br />
<br />
=Deletion=<br />
There are two ways to delete sections from a KeyValues structure:<br />
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.<br />
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.<br />
<br />
==Simple Deletion==<br />
First, let's use our "basic lookup" example to delete a Steam ID section:<br />
<sourcepawn><br />
void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
kv.DeleteThis();<br />
kv.Rewind();<br />
kv.ExportToFile("myfile.txt");<br />
delete kv;<br />
}<br />
</sourcepawn><br />
<br />
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.<br />
<br />
==Iterative Deletion==<br />
Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.<br />
<br />
<sourcepawn>void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer))<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.DeleteThis();<br />
delete kv;<br />
return;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
==Full Deletion==<br />
Now, let's take a look at how we would delete all (or some) keys. <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would. If it can't find any more keys, it simply pops the traversal stack. <br />
<br />
An example of deleting all SteamIDs that have blank names:<br />
<sourcepawn>void DeleteAll(KeyValues kv)<br />
{<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
for (;;)<br />
{<br />
char name[4];<br />
kv.GetString("name", name, sizeof(name));<br />
if (name[0] == '\0')<br />
{<br />
if (kv.DeleteThis() < 1)<br />
{<br />
break;<br />
}<br />
}<br />
else if (!kv.GotoNextKey()) <br />
{<br />
break;<br />
} <br />
}<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
While at first this loop looks infinite, it is not. If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.<br />
<br />
==KeyValue Creation==<br />
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API<br />
<sourcepawn>KeyValues kv = new KeyValues("MyFile");<br />
kv.JumpToKey("STEAM_0:0:7", true);<br />
kv.SetString("name", "crab");<br />
kv.Rewind();<br />
kv.ExportToFile("C:\\javalia.txt");<br />
delete kv;</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=KeyValues_(SourceMod_Scripting)&diff=10967KeyValues (SourceMod Scripting)2020-03-30T00:07:26Z<p>Joinedsenses: /* Full Deletion */ Changed from two-space indent to four-space for readability and consistency</p>
<hr />
<div>KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the [http://developer.valvesoftware.com/wiki/KeyValues_class Valve Developer Wiki]. <br />
<br />
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.<br />
<br />
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''<br />
<br />
=Introduction=<br />
KeyValues consist of a ''nodes'', or ''sections'', which contain pairs of keys and values. A section looks like this:<br />
<pre>"section"<br />
{<br />
"key" "value"<br />
}</pre><br />
<br />
The <tt>"section"</tt> string denotes the section's name. The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.<br />
<br />
KeyValues structures are created with <tt>new KeyValues()</tt>. The Handle must be freed when finished in order to avoid a memory leak.<br />
<br />
=Files=<br />
KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:<br />
<pre>"MyFile"<br />
{<br />
"STEAM_0:0:7"<br />
{<br />
"name" "crab"<br />
}<br />
}</pre><br />
<br />
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node. <br />
<br />
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt> To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.<br />
<br />
=Traversal=<br />
SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:<br />
*The root node<br />
*A traversal stack<br />
<br />
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The ''top'' of this stack is where all operations take place. <br />
<br />
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as <tt>kv.GetString</tt> will use this new position.<br />
<br />
There are natives which change the position but do not change the traversal stack. For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.<br />
<br />
More traversal natives:<br />
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.<br />
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).<br />
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.<br />
<br />
==Basic Lookup==<br />
Let's take our <tt>MyFile</tt> example from above. How could we retrieve the name "crab" given the Steam ID?<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}</sourcepawn><br />
<br />
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack. However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.<br />
<br />
==Iterative Lookup==<br />
Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
// Jump into the first subsection<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
// Iterate over subsections at the same nesting level<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer));<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
return false;<br />
}</sourcepawn><br />
<br />
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.<br />
<br />
==Full Traversal==<br />
Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:<br />
<sourcepawn>void BrowseKeyValues(KeyValues kv)<br />
{<br />
do<br />
{<br />
// You can read the section/key name by using kv.GetSectionName here.<br />
<br />
if (kv.GotoFirstSubKey(false))<br />
{<br />
// Current key is a section. Browse it recursively.<br />
BrowseKeyValues(kv);<br />
kv.GoBack(kv);<br />
}<br />
else<br />
{<br />
// Current key is a regular key, or an empty section.<br />
if (kv.GetDataType(NULL_STRING) != KvData_None)<br />
{<br />
// Read value of key here (use NULL_STRING as key name). You can<br />
// also get the key name by using kv.GetSectionName here.<br />
}<br />
else<br />
{<br />
// Found an empty sub section. It can be handled here if necessary.<br />
}<br />
}<br />
} while (kv.GotoNextKey(false));<br />
}</sourcepawn><br />
<br />
This function will browse an entire KeyValues structure. Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.<br />
<br />
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sections. Because of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.<br />
<br />
=Deletion=<br />
There are two ways to delete sections from a KeyValues structure:<br />
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.<br />
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.<br />
<br />
==Simple Deletion==<br />
First, let's use our "basic lookup" example to delete a Steam ID section:<br />
<sourcepawn><br />
void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
kv.DeleteThis();<br />
kv.Rewind();<br />
kv.ExportToFile("myfile.txt");<br />
delete kv;<br />
}<br />
</sourcepawn><br />
<br />
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.<br />
<br />
==Iterative Deletion==<br />
Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.<br />
<br />
<sourcepawn>void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer))<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.DeleteThis();<br />
delete kv;<br />
return;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
==Full Deletion==<br />
Now, let's take a look at how we would delete all (or some) keys. <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would. If it can't find any more keys, it simply pops the traversal stack. <br />
<br />
An example of deleting all SteamIDs that have blank names:<br />
<sourcepawn>void DeleteAll(KeyValues kv)<br />
{<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
for (;;)<br />
{<br />
char name[4];<br />
kv.GetString("name", name, sizeof(name));<br />
if (name[0] == '\0')<br />
{<br />
if (kv.DeleteThis() < 1)<br />
{<br />
break;<br />
}<br />
}<br />
else if (!kv.GotoNextKey()) <br />
{<br />
break;<br />
} <br />
}<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
While at first this loop looks infinite, it is not. If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.<br />
<br />
==KeyValue Creation==<br />
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API<br />
<sourcepawn>KeyValues kv = new KeyValues("MyFile");<br />
kv.JumpToKey("STEAM_0:0:7", true);<br />
kv.SetString("name", "crab");<br />
kv.Rewind();<br />
kv.ExportToFile("C:\\javalia.txt");<br />
delete kv;</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=KeyValues_(SourceMod_Scripting)&diff=10966KeyValues (SourceMod Scripting)2020-03-30T00:05:00Z<p>Joinedsenses: /* Full Deletion */ Modified bracket formatting for consistency</p>
<hr />
<div>KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the [http://developer.valvesoftware.com/wiki/KeyValues_class Valve Developer Wiki]. <br />
<br />
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.<br />
<br />
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''<br />
<br />
=Introduction=<br />
KeyValues consist of a ''nodes'', or ''sections'', which contain pairs of keys and values. A section looks like this:<br />
<pre>"section"<br />
{<br />
"key" "value"<br />
}</pre><br />
<br />
The <tt>"section"</tt> string denotes the section's name. The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.<br />
<br />
KeyValues structures are created with <tt>new KeyValues()</tt>. The Handle must be freed when finished in order to avoid a memory leak.<br />
<br />
=Files=<br />
KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:<br />
<pre>"MyFile"<br />
{<br />
"STEAM_0:0:7"<br />
{<br />
"name" "crab"<br />
}<br />
}</pre><br />
<br />
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node. <br />
<br />
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt> To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.<br />
<br />
=Traversal=<br />
SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:<br />
*The root node<br />
*A traversal stack<br />
<br />
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The ''top'' of this stack is where all operations take place. <br />
<br />
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as <tt>kv.GetString</tt> will use this new position.<br />
<br />
There are natives which change the position but do not change the traversal stack. For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.<br />
<br />
More traversal natives:<br />
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.<br />
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).<br />
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.<br />
<br />
==Basic Lookup==<br />
Let's take our <tt>MyFile</tt> example from above. How could we retrieve the name "crab" given the Steam ID?<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}</sourcepawn><br />
<br />
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack. However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.<br />
<br />
==Iterative Lookup==<br />
Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
// Jump into the first subsection<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
// Iterate over subsections at the same nesting level<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer));<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
return false;<br />
}</sourcepawn><br />
<br />
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.<br />
<br />
==Full Traversal==<br />
Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:<br />
<sourcepawn>void BrowseKeyValues(KeyValues kv)<br />
{<br />
do<br />
{<br />
// You can read the section/key name by using kv.GetSectionName here.<br />
<br />
if (kv.GotoFirstSubKey(false))<br />
{<br />
// Current key is a section. Browse it recursively.<br />
BrowseKeyValues(kv);<br />
kv.GoBack(kv);<br />
}<br />
else<br />
{<br />
// Current key is a regular key, or an empty section.<br />
if (kv.GetDataType(NULL_STRING) != KvData_None)<br />
{<br />
// Read value of key here (use NULL_STRING as key name). You can<br />
// also get the key name by using kv.GetSectionName here.<br />
}<br />
else<br />
{<br />
// Found an empty sub section. It can be handled here if necessary.<br />
}<br />
}<br />
} while (kv.GotoNextKey(false));<br />
}</sourcepawn><br />
<br />
This function will browse an entire KeyValues structure. Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.<br />
<br />
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sections. Because of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.<br />
<br />
=Deletion=<br />
There are two ways to delete sections from a KeyValues structure:<br />
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.<br />
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.<br />
<br />
==Simple Deletion==<br />
First, let's use our "basic lookup" example to delete a Steam ID section:<br />
<sourcepawn><br />
void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
kv.DeleteThis();<br />
kv.Rewind();<br />
kv.ExportToFile("myfile.txt");<br />
delete kv;<br />
}<br />
</sourcepawn><br />
<br />
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.<br />
<br />
==Iterative Deletion==<br />
Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.<br />
<br />
<sourcepawn>void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer))<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.DeleteThis();<br />
delete kv;<br />
return;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
==Full Deletion==<br />
Now, let's take a look at how we would delete all (or some) keys. <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would. If it can't find any more keys, it simply pops the traversal stack. <br />
<br />
An example of deleting all SteamIDs that have blank names:<br />
<sourcepawn>void DeleteAll(KeyValues kv)<br />
{<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
for (;;)<br />
{<br />
char name[4];<br />
kv.GetString("name", name, sizeof(name));<br />
if (name[0] == '\0')<br />
{<br />
if (kv.DeleteThis() < 1)<br />
{<br />
break;<br />
}<br />
}<br />
else if (!kv.GotoNextKey()) <br />
{<br />
break;<br />
} <br />
}<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
While at first this loop looks infinite, it is not. If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.<br />
<br />
==KeyValue Creation==<br />
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API<br />
<sourcepawn>KeyValues kv = new KeyValues("MyFile");<br />
kv.JumpToKey("STEAM_0:0:7", true);<br />
kv.SetString("name", "crab");<br />
kv.Rewind();<br />
kv.ExportToFile("C:\\javalia.txt");<br />
delete kv;</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
{{LanguageSwitch}}</div>Joinedsenseshttps://wiki.alliedmods.net/index.php?title=KeyValues_(SourceMod_Scripting)&diff=10965KeyValues (SourceMod Scripting)2020-03-30T00:04:01Z<p>Joinedsenses: /* KeyValue Creation */ Fix unescaped slash</p>
<hr />
<div>KeyValues are simple, tree-based structures used for storing nested sections containing key/value pairs. Detailed information on KeyValues can be seen at the [http://developer.valvesoftware.com/wiki/KeyValues_class Valve Developer Wiki]. <br />
<br />
All KeyValues specific functions in this document are from <tt>public/include/keyvalues.inc</tt>.<br />
<br />
''Note: While the following examples are correct code-wise, over the years they have occasionally led people to use KeyValues in cases where they really should be using a [https://wiki.alliedmods.net/SQL_%28SourceMod_Scripting%29 database] instead.''<br />
<br />
=Introduction=<br />
KeyValues consist of a ''nodes'', or ''sections'', which contain pairs of keys and values. A section looks like this:<br />
<pre>"section"<br />
{<br />
"key" "value"<br />
}</pre><br />
<br />
The <tt>"section"</tt> string denotes the section's name. The <tt>"key"</tt> string is the key name, and the <tt>"value"</tt>" string is the value.<br />
<br />
KeyValues structures are created with <tt>new KeyValues()</tt>. The Handle must be freed when finished in order to avoid a memory leak.<br />
<br />
=Files=<br />
KeyValues can be exported and imported via KeyValues files. These files always consist of a root node and any number of sub-keys or sub-sections. For example, a file might look like this:<br />
<pre>"MyFile"<br />
{<br />
"STEAM_0:0:7"<br />
{<br />
"name" "crab"<br />
}<br />
}</pre><br />
<br />
In this example, <tt>STEAM_0:0:7</tt> is a section under <tt>MyFile</tt>, the root node. <br />
<br />
To load KeyValues from a file, use <tt>kv.ImportFromFile</tt> To save KeyValues to a file, use <tt>kv.ExportToFile</tt>, where <tt>kv</tt> is a Handle to a KeyValues structure.<br />
<br />
=Traversal=<br />
SourceMod provides natives for traversing over a KeyValues structure. However, it is important to understand how this traversal works. Internally, SourceMod keeps track of two pieces of information:<br />
*The root node<br />
*A traversal stack<br />
<br />
Since a KeyValues structure is inherently recursive (it's a tree), the ''traversal stack'' is used to save a history of each ''traversal'' made (a traversal being a recursive dive into the tree, where the current nesting level deepens by one section). The ''top'' of this stack is where all operations take place. <br />
<br />
For example, <tt>kv.JumpToKey</tt> will attempt to find a sub-key under the current section. If it succeeds, the position in the tree has changed by moving down one level, and thus this position is pushed onto the traversal stack. Now, operations such as <tt>kv.GetString</tt> will use this new position.<br />
<br />
There are natives which change the position but do not change the traversal stack. For example, <tt>kv.GotoNextKey</tt> will change the current section being viewed, but there are no push/pop operations to the traversal stack. This is because the nesting level did not change; it advances to the next section at the same level, rather than finding a section a level deeper.<br />
<br />
More traversal natives:<br />
*<tt>kv.GotoFirstSubKey</tt> - Finds the first sub-section under the current section. This pushes the section onto the traversal stack.<br />
*<tt>kv.GoBack</tt> - Pops the traversal stack (moves up one level).<br />
*<tt>kv.Rewind</tt> - Clears the traversal stack so the current position is the root node.<br />
<br />
==Basic Lookup==<br />
Let's take our <tt>MyFile</tt> example from above. How could we retrieve the name "crab" given the Steam ID?<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}</sourcepawn><br />
<br />
'''Note:''' <tt>kv.JumpToKey</tt> is a traversal native that changes the nesting level of the traversal stack. However, <tt>delete</tt> will not accidentally close only the current level of the KeyValues structure. It will close the entire thing.<br />
<br />
==Iterative Lookup==<br />
Let's modify our previous example to use iteration. This has the same functionality, but demonstrates how to browse over many sections.<br />
<br />
<sourcepawn>bool GetNameFromSteamID(const char[] steamid, char[] name, int maxlength)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
<br />
// Jump into the first subsection<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return false;<br />
}<br />
<br />
// Iterate over subsections at the same nesting level<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer));<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.GetString("name", name, maxlength);<br />
delete kv;<br />
return true;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
return false;<br />
}</sourcepawn><br />
<br />
'''Note:''' In this example, note that <tt>kv.GotoNextKey</tt> is an iterative function, not a traversal one. Since it does not change the nesting level, we don't need to call <tt>kv.GoBack</tt> to return and continue iterating.<br />
<br />
==Full Traversal==<br />
Let's say we wanted to browse every section of a KeyValues file. An example of this might look like:<br />
<sourcepawn>void BrowseKeyValues(KeyValues kv)<br />
{<br />
do<br />
{<br />
// You can read the section/key name by using kv.GetSectionName here.<br />
<br />
if (kv.GotoFirstSubKey(false))<br />
{<br />
// Current key is a section. Browse it recursively.<br />
BrowseKeyValues(kv);<br />
kv.GoBack(kv);<br />
}<br />
else<br />
{<br />
// Current key is a regular key, or an empty section.<br />
if (kv.GetDataType(NULL_STRING) != KvData_None)<br />
{<br />
// Read value of key here (use NULL_STRING as key name). You can<br />
// also get the key name by using kv.GetSectionName here.<br />
}<br />
else<br />
{<br />
// Found an empty sub section. It can be handled here if necessary.<br />
}<br />
}<br />
} while (kv.GotoNextKey(false));<br />
}</sourcepawn><br />
<br />
This function will browse an entire KeyValues structure. Note that every successful call to <tt>kv.GotoFirstSubKey</tt> is paired with a call to <tt>kv.GoBack</tt>. This is because the former pushes onto the traversal stack. We must pop the position off so we can continue browsing from our old position.<br />
<br />
Also note that <tt>keyOnly</tt> is set to false in both <tt>kv.GotoFirstSubKey</tt> and <tt>kv.GotoNextKey</tt> so that it will jump to regular keys and not just between sections. Because of this we also need to check if <tt>kv.GotoFirstSubKey</tt> succeeded moving to a regular key before we read the value. This is simply done by getting the data type of the current key. If <tt>kv.GotoFirstSubKey</tt> failed to move to any key (the section is empty), the cursor is still on the section, which doesn't have a data type. If there is a data type, we've confirmed that it's a regular key.<br />
<br />
=Deletion=<br />
There are two ways to delete sections from a KeyValues structure:<br />
*<tt>kv.DeleteKey</tt> - Safely removes a named sub-section and any of its child sections/keys.<br />
*<tt>kv.DeleteThis</tt> - Safely removes the current position if possible.<br />
<br />
==Simple Deletion==<br />
First, let's use our "basic lookup" example to delete a Steam ID section:<br />
<sourcepawn><br />
void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
kv.DeleteThis();<br />
kv.Rewind();<br />
kv.ExportToFile("myfile.txt");<br />
delete kv;<br />
}<br />
</sourcepawn><br />
<br />
'''Note:''' We called <tt>kv.Rewind</tt> so the file would be dumped from the root position, instead of the current one.<br />
<br />
==Iterative Deletion==<br />
Likewise, let's show how our earlier iterative example could be adapted for deleting a section. For this we can use <tt>kv.DeleteThis</tt>, which deletes the current position from the previous position in the traversal stack.<br />
<br />
<sourcepawn>void RemoveSteamID(const char[] steamid)<br />
{<br />
KeyValues kv = new KeyValues("MyFile");<br />
kv.ImportFromFile("myfile.txt");<br />
if (!kv.JumpToKey(steamid))<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
char buffer[255];<br />
do<br />
{<br />
kv.GetSectionName(buffer, sizeof(buffer))<br />
if (StrEqual(buffer, steamid))<br />
{<br />
kv.DeleteThis();<br />
delete kv;<br />
return;<br />
}<br />
} while (kv.GotoNextKey());<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
==Full Deletion==<br />
Now, let's take a look at how we would delete all (or some) keys. <tt>kv.DeleteThis</tt> has the special property that it can act as an automatic iterator. When it deletes a key, it automatically attempts to advance to the next one, as <tt>kv.GotoNextKey</tt> would. If it can't find any more keys, it simply pops the traversal stack. <br />
<br />
An example of deleting all SteamIDs that have blank names:<br />
<sourcepawn>void DeleteAll(KeyValues kv)<br />
{<br />
if (!kv.GotoFirstSubKey())<br />
{<br />
delete kv;<br />
return;<br />
}<br />
<br />
for (;;)<br />
{<br />
char name[4];<br />
kv.GetString("name", name, sizeof(name));<br />
if (name[0] == '\0')<br />
{<br />
if (kv.DeleteThis() < 1)<br />
{<br />
break;<br />
}<br />
} else if (!kv.GotoNextKey()) {<br />
break;<br />
} <br />
}<br />
<br />
delete kv;<br />
}</sourcepawn><br />
<br />
While at first this loop looks infinite, it is not. If <tt>kv.DeleteThis</tt> fails to find a subsequent key, it will break out of the loop. Similarly, if <tt>kv.GotoNextKey</tt> fails, the loop will end.<br />
<br />
==KeyValue Creation==<br />
This is how you would create the <tt>MyFile</tt> example from above with the KeyValue API<br />
<sourcepawn>KeyValues kv = new KeyValues("MyFile");<br />
kv.JumpToKey("STEAM_0:0:7", true);<br />
kv.SetString("name", "crab");<br />
kv.Rewind();<br />
kv.ExportToFile("C:\\javalia.txt");<br />
delete kv;</sourcepawn><br />
<br />
[[Category:SourceMod Scripting]]<br />
{{LanguageSwitch}}</div>Joinedsenses