add SMJSONAPI

This commit is contained in:
BotoX 2018-08-07 22:22:27 +02:00
parent ab4e9de4a4
commit 2262559464
8 changed files with 2014 additions and 0 deletions

View File

@ -0,0 +1,927 @@
//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//=============================================================================
// No spaces in event names, max length 32
// All strings are case sensitive
// total game event byte length must be < 1024
//
// valid data key types are:
// none : value is not networked
// string : a zero terminated string
// bool : unsigned int, 1 bit
// byte : unsigned int, 8 bit
// short : signed int, 16 bit
// long : signed int, 32 bit
// float : float, 32 bit
// gameevents + hltvevents + replayevents + engineevents
"allevents"
{
//////////////////////////////////////////////////////////////////////
// Game events
//////////////////////////////////////////////////////////////////////
"team_info" // info about team
{
"teamid" "byte" // unique team id
"teamname" "string" // team name eg "Team Blue"
}
"team_score" // team score changed
{
"teamid" "byte" // team id
"score" "short" // total team score
}
"teamplay_broadcast_audio" // emits a sound to everyone on a team
{
"team" "byte" // unique team id
"sound" "string" // name of the sound to emit
}
//////////////////////////////////////////////////////////////////////
// Player events
//////////////////////////////////////////////////////////////////////
// "player_team" // player change his team
// {
// "userid" "short" // user ID on server
// "team" "byte" // team id
// "oldteam" "byte" // old team id
// "disconnect" "bool" // team change because player disconnects
// "autoteam" "bool" // true if the player was auto assigned to the team
// "silent" "bool" // if true wont print the team join messages
// "name" "string" // player's name
// }
"player_class" // a player changed his class
{
"userid" "short" // user ID on server
"class" "string" // new player class / model
}
// "player_death" // a game event, name may be 32 charaters long
// {
// "userid" "short" // user ID who died
// "attacker" "short" // user ID who killed
// }
// "player_hurt"
// {
// "userid" "short" // player index who was hurt
// "attacker" "short" // player index who attacked
// "health" "byte" // remaining health points
// }
"player_chat" // a public player chat
{
"teamonly" "bool" // true if team only chat
"userid" "short" // chatting player
"text" "string" // chat text
}
"player_score" // players scores changed
{
"userid" "short" // user ID on server
"kills" "short" // # of kills
"deaths" "short" // # of deaths
"score" "short" // total game score
}
"player_spawn" // player spawned in game
{
"userid" "short" // user ID on server
}
"player_shoot" // player shoot his weapon
{
"userid" "short" // user ID on server
"weapon" "byte" // weapon ID
"mode" "byte" // weapon mode
}
"player_use"
{
"userid" "short" // user ID on server
"entity" "short" // entity used by player
}
"player_changename"
{
"userid" "short" // user ID on server
"oldname" "string" // players old (current) name
"newname" "string" // players new name
}
"player_hintmessage"
{
"hintmessage" "string" // localizable string of a hint
}
"base_player_teleported"
{
"entindex" "short"
}
//////////////////////////////////////////////////////////////////////
// Game events
//////////////////////////////////////////////////////////////////////
"game_init" // sent when a new game is started
{
}
"game_newmap" // send when new map is completely loaded
{
"mapname" "string" // map name
}
"game_start" // a new game starts
{
"roundslimit" "long" // max round
"timelimit" "long" // time limit
"fraglimit" "long" // frag limit
"objective" "string" // round objective
}
"game_end" // a game ended
{
"winner" "byte" // winner team/user id
}
"round_start"
{
"timelimit" "long" // round time limit in seconds
"fraglimit" "long" // frag limit in seconds
"objective" "string" // round objective
}
"round_end"
{
"winner" "byte" // winner team/user i
"reason" "byte" // reson why team won
"message" "string" // end round message
}
"game_message" // a message send by game logic to everyone
{
"target" "byte" // 0 = console, 1 = HUD
"text" "string" // the message text
}
"break_breakable"
{
"entindex" "long"
"userid" "short"
"material" "byte" // BREAK_GLASS, BREAK_WOOD, etc
}
"break_prop"
{
"entindex" "long"
"userid" "short"
}
"entity_killed"
{
"entindex_killed" "long"
"entindex_attacker" "long"
"entindex_inflictor" "long"
"damagebits" "long"
}
"bonus_updated"
{
"numadvanced" "short"
"numbronze" "short"
"numsilver" "short"
"numgold" "short"
}
"achievement_event"
{
"achievement_name" "string" // non-localized name of achievement
"cur_val" "short" // # of steps toward achievement
"max_val" "short" // total # of steps in achievement
}
// sent whenever an achievement that's tracked on the HUD increases
"achievement_increment"
{
"achievement_id" "long" // ID of achievement that went up
"cur_val" "short" // # of steps toward achievement
"max_val" "short" // total # of steps in achievement
}
"physgun_pickup"
{
"entindex" "long" // entity picked up
}
"flare_ignite_npc"
{
"entindex" "long" // entity ignited
}
"helicopter_grenade_punt_miss"
{
}
"user_data_downloaded" // fired when achievements/stats are downloaded from Steam or XBox Live
{
}
"ragdoll_dissolved"
{
"entindex" "long"
}
"hltv_changed_mode"
{
"oldmode" "short"
"newmode" "short"
"obs_target" "short"
}
"hltv_changed_target"
{
"mode" "short"
"old_target" "short"
"obs_target" "short"
}
// Client side VoteController talking to HUD
"vote_ended"
{
}
"vote_started"
{
"issue" "string"
"param1" "string"
"team" "byte"
"initiator" "long" // entity id of the player who initiated the vote
}
"vote_changed"
{
"vote_option1" "byte"
"vote_option2" "byte"
"vote_option3" "byte"
"vote_option4" "byte"
"vote_option5" "byte"
"potentialVotes" "byte"
}
"vote_passed"
{
"details" "string"
"param1" "string"
"team" "byte"
}
"vote_failed"
{
"team" "byte"
}
"vote_cast"
{
"vote_option" "byte" // which option the player voted on
"team" "short"
"entityid" "long" // entity id of the voter
}
"vote_options"
{
"count" "byte" // Number of options - up to MAX_VOTE_OPTIONS
"option1" "string"
"option2" "string"
"option3" "string"
"option4" "string"
"option5" "string"
}
//////////////////////////////////////////////////////////////////////
// Replay events
//////////////////////////////////////////////////////////////////////
"replay_saved"
{
}
"entered_performance_mode"
{
}
"browse_replays"
{
}
"replay_youtube_stats"
{
"views" "long"
"likes" "long"
"favorited" "long"
}
//////////////////////////////////////////////////////////////////////
// Economy events
//////////////////////////////////////////////////////////////////////
"inventory_updated"
{
}
"cart_updated"
{
}
"store_pricesheet_updated"
{
}
"gc_connected"
{
}
"item_schema_initialized"
{
}
//////////////////////////////////////////////////////////////////////
// HLTV specific events
//////////////////////////////////////////////////////////////////////
"hltv_status" // general HLTV status
{
"clients" "long" // number of HLTV spectators
"slots" "long" // number of HLTV slots
"proxies" "short" // number of HLTV proxies
"master" "string" // disptach master IP:port
}
"hltv_cameraman" // a spectator/player is a cameraman
{
"index" "short" // camera man entity index
}
"hltv_rank_camera" // a camera ranking
{
"index" "byte" // fixed camera index
"rank" "float" // ranking, how interesting is this camera view
"target" "short" // best/closest target entity
}
"hltv_rank_entity" // an entity ranking
{
"index" "short" // entity index
"rank" "float" // ranking, how interesting is this entity to view
"target" "short" // best/closest target entity
}
"hltv_fixed" // show from fixed view
{
"posx" "long" // camera position in world
"posy" "long"
"posz" "long"
"theta" "short" // camera angles
"phi" "short"
"offset" "short"
"fov" "float"
"target" "short" // follow this entity or 0
}
"hltv_chase" // shot of a single entity
{
"target1" "short" // primary traget index
"target2" "short" // secondary traget index or 0
"distance" "short" // camera distance
"theta" "short" // view angle horizontal
"phi" "short" // view angle vertical
"inertia" "byte" // camera inertia
"ineye" "byte" // diretcor suggests to show ineye
}
"hltv_message" // a HLTV message send by moderators
{
"text" "string"
}
"hltv_title"
{
"text" "string"
}
"hltv_chat" // a HLTV chat msg send by spectators
{
"text" "string"
}
//////////////////////////////////////////////////////////////////////
// replay specific events
//////////////////////////////////////////////////////////////////////
"replay_startrecord" // Sent when the server begins recording - only used to display UI
{
}
"replay_sessioninfo" // Sent when the server begins recording, or when a client first connects - only sent once per recording session
{
"sn" "string" // session name
"di" "byte" // dump interval
"cb" "long" // current block
"st" "long" // session start tick
}
"replay_endrecord"
{
}
"replay_replaysavailable"
{
}
"replay_servererror"
{
"error" "string"
}
//////////////////////////////////////////////////////////////////////
// Server events
//////////////////////////////////////////////////////////////////////
"server_spawn" // send once a server starts
{
"hostname" "string" // public host name
"address" "string" // hostame, IP or DNS name
"ip" "long"
"port" "short" // server port
"game" "string" // game dir
"mapname" "string" // map name
"maxplayers" "long" // max players
"os" "string" // WIN32, LINUX
"dedicated" "bool" // true if dedicated server
"password" "bool" // true if password protected
}
"server_changelevel_failed"
{
"levelname" "string" // The level name that failed changelevel
}
"server_shutdown" // server shut down
{
"reason" "string" // reason why server was shut down
}
"server_cvar" // a server console var has changed
{
"cvarname" "string" // cvar name, eg "mp_roundtime"
"cvarvalue" "string" // new cvar value
}
"server_message" // a generic server message
{
"text" "string" // the message text
}
"server_addban"
{
"name" "string" // player name
"userid" "short" // user ID on server
"networkid" "string" // player network (i.e steam) id
"ip" "string" // IP address
"duration" "string" // length of the ban
"by" "string" // banned by...
"kicked" "bool" // whether the player was also kicked
}
"server_removeban"
{
"networkid" "string" // player network (i.e steam) id
"ip" "string" // IP address
"by" "string" // removed by...
}
"player_connect" // a new client connected - we should only really have server listeners for this, due to the IP being exposed
{
"name" "string" // player name
"index" "byte" // player slot (entity index-1)
"userid" "short" // user ID on server (unique on server)
"networkid" "string" // player network (i.e steam) id
"address" "string" // ip:port
"bot" "short" // is a bot
}
"player_connect_client" // a new client connected
{
"name" "string" // player name
"index" "byte" // player slot (entity index-1)
"userid" "short" // user ID on server (unique on server)
"networkid" "string" // player network (i.e steam) id
"bot" "short" // is a bot
}
"player_info" // a player changed his name
{
"name" "string" // player name
"index" "byte" // player slot (entity index-1)
"userid" "short" // user ID on server (unique on server)
"networkid" "string" // player network (i.e steam) id
"bot" "bool" // true if player is a AI bot
}
"player_disconnect" // a client was disconnected
{
"userid" "short" // user ID on server
"reason" "string" // "self", "kick", "ban", "cheat", "error"
"name" "string" // player name
"networkid" "string" // player network (i.e steam) id
"bot" "short" // is a bot
}
"player_activate"
{
"userid" "short" // user ID on server
}
"player_say"
{
"userid" "short" // user ID on server
"text" "string" // the say text
}
"client_disconnect" // client side disconnect message
{
"message" "string" // Why are we disconnecting? This could be a localization token or an English-language string
}
"client_beginconnect" // client tries to connect to server
{
"address" "string" // Name we used to connect to the server
"ip" "long"
"port" "short" // server port
"source" "string" // what caused us to attempt this connection? (blank for general command line, "serverbrowser", "quickplay", etc)
}
"client_connected" // client has completed the challenge / handshake process and is in SIGNONSTATE_CONNECTED
{
"address" "string" // Name we used to connect to the server
"ip" "long"
"port" "short" // server port
}
"client_fullconnect"
{
"address" "string" // Name we used to connect to the server
"ip" "long"
"port" "short" // server port
}
"host_quit"
{
}
//////////////////////////////////////////////////////////////////////
// CStrike events
//////////////////////////////////////////////////////////////////////
"player_death" // a game event, name may be 32 characters long
{
// this extents the original player_death by a new fields
"userid" "short" // user ID who died
"attacker" "short" // user ID who killed
"weapon" "string" // weapon name killer used
"headshot" "bool" // singals a headshot
"dominated" "short" // did killer dominate victim with this kill
"revenge" "short" // did killer get revenge on victim with this kill
}
"player_hurt"
{
"userid" "short" // player index who was hurt
"attacker" "short" // player index who attacked
"health" "byte" // remaining health points
"armor" "byte" // remaining armor points
"weapon" "string" // weapon name attacker used, if not the world
"dmg_health" "byte" // damage done to health
"dmg_armor" "byte" // damage done to armor
"hitgroup" "byte" // hitgroup that was damaged
}
"bomb_beginplant"
{
"userid" "short" // player who is planting the bomb
"site" "short" // bombsite index
}
"bomb_abortplant"
{
"userid" "short" // player who is planting the bomb
"site" "short" // bombsite index
}
"bomb_planted"
{
"userid" "short" // player who planted the bomb
"site" "short" // bombsite index
"posx" "short" // position x
"posy" "short" // position y
}
"bomb_defused"
{
"userid" "short" // player who defused the bomb
"site" "short" // bombsite index
}
"bomb_exploded"
{
"userid" "short" // player who planted the bomb
"site" "short" // bombsite index
}
"bomb_dropped"
{
"userid" "short" // player who dropped the bomb
}
"bomb_pickup"
{
"userid" "short" // player who picked up the bomb
}
"bomb_begindefuse"
{
"userid" "short" // player who is defusing
"haskit" "bool"
}
"bomb_abortdefuse"
{
"userid" "short" // player who was defusing
}
"hostage_follows"
{
"userid" "short" // player who touched the hostage
"hostage" "short" // hostage entity index
}
"hostage_hurt"
{
"userid" "short" // player who hurt the hostage
"hostage" "short" // hostage entity index
}
"hostage_killed"
{
"userid" "short" // player who killed the hostage
"hostage" "short" // hostage entity index
}
"hostage_rescued"
{
"userid" "short" // player who rescued the hostage
"hostage" "short" // hostage entity index
"site" "short" // rescue site index
}
"hostage_stops_following"
{
"userid" "short" // player who rescued the hostage
"hostage" "short" // hostage entity index
}
"hostage_rescued_all"
{
}
"hostage_call_for_help"
{
"hostage" "short" // hostage entity index
}
"vip_escaped"
{
"userid" "short" // player who was the VIP
}
"vip_killed"
{
"userid" "short" // player who was the VIP
"attacker" "short" // user ID who killed the VIP
}
"player_radio"
{
"userid" "short"
"slot" "short"
}
"bomb_beep"
{
"entindex" "long" // c4 entity
}
"weapon_fire"
{
"userid" "short"
"weapon" "string" // weapon name used
}
"weapon_fire_on_empty"
{
"userid" "short"
"weapon" "string" // weapon name used
}
"weapon_reload"
{
"userid" "short"
}
"weapon_zoom"
{
"userid" "short"
}
"item_pickup"
{
"userid" "short"
"item" "string" // either a weapon such as 'tmp' or 'hegrenade', or an item such as 'nvgs'
}
"grenade_bounce"
{
"userid" "short"
}
"hegrenade_detonate"
{
"userid" "short"
"x" "float"
"y" "float"
"z" "float"
}
"flashbang_detonate"
{
"userid" "short"
"x" "float"
"y" "float"
"z" "float"
}
"smokegrenade_detonate"
{
"userid" "short"
"x" "float"
"y" "float"
"z" "float"
}
"bullet_impact"
{
"userid" "short"
"x" "float"
"y" "float"
"z" "float"
}
"player_footstep"
{
"userid" "short"
}
"player_jump"
{
"userid" "short"
}
"player_blind"
{
"userid" "short"
}
"player_falldamage"
{
"userid" "short"
"damage" "float"
}
"door_moving"
{
"entindex" "long"
"userid" "short"
}
"round_freeze_end"
{
}
"nav_blocked"
{
"area" "long"
"blocked" "bool"
}
"nav_generate"
{
}
"player_stats_updated"
{
"forceupload" "bool"
}
"spec_target_updated"
{
}
"cs_win_panel_round"
{
"show_timer_defend" "bool"
"show_timer_attack" "bool"
"timer_time" "short"
"final_event" "byte" //define in cs_gamerules.h
"funfact_token" "string"
"funfact_player" "short"
"funfact_data1" "long"
"funfact_data2" "long"
"funfact_data3" "long"
}
"cs_win_panel_match"
{
"t_score" "short"
"ct_score" "short"
"t_kd" "float"
"ct_kd" "float"
"t_objectives_done" "short"
"ct_objectives_done" "short"
"t_money_earned" "long"
"ct_money_earned" "long"
}
"show_freezepanel"
{
"killer" "short" // entindex of the killer entity
}
"hide_freezepanel"
{
}
"freezecam_started"
{
}
"player_avenged_teammate"
{
"avenger_id" "short"
"avenged_player_id" "short"
}
"achievement_earned"
{
"player" "byte" // entindex of the player
"achievement" "short" // achievement ID
}
"achievement_earned_local"
{
"achievement" "short" // achievement ID
}
"match_end_conditions"
{
"frags" "long"
"max_rounds" "long"
"win_rounds" "long"
"time" "long"
}
"round_mvp"
{
"userid" "short"
"reason" "short"
}
"player_decal"
{
"userid" "short"
}
"teamplay_round_start" // round restart
{
"full_reset" "bool" // is this a full reset of the map
}
"christmas_gift_grab"
{
"userid" "short"
}
}

127
SMJSONAPI/example.txt Normal file
View File

@ -0,0 +1,127 @@
request:
{
"method": "subscribe",
"module": "gameevents",
"events": [
"player_connect",
"player_disconnect",
"player_say",
"invalid"
]
}
response:
{
"method": "subscribe",
"module": "gameevents",
"error": 0,
"events": [
0,
1,
2,
-3
]
}
-----
events:
{
"method": "publish",
"module": "gameevents",
"event": {
"name": "player_connect",
"data": {
"name": "BotoX",
"index": 0,
"userid": 4,
"networkid": "[U:1:51174697]",
"address": "192.168.1.4:27006",
"bot": 0
}
}
}
{
"method": "publish",
"module": "gameevents",
"event": {
"name": "player_say",
"data": {
"text": "hello",
"userid": 4
}
}
}
{
"method": "publish",
"module": "gameevents",
"event": {
"name": "player_disconnect",
"data": {
"userid": 4,
"reason": "Disconnect by user.",
"name": "BotoX",
"networkid": "[U:1:51174697]",
"bot": 0
}
}
}
-----
request:
{
"method": "unsubscribe",
"module": "gameevents",
"events": [
"player_connect",
"player_disconnect",
"player_say",
"invalid"
]
}
response:
{
"method": "unsubscribe",
"module": "gameevents",
"error": 0,
"events": [
0,
0,
0,
0
]
}
-----
request:
{
"method": "function",
"function": "KickClient",
"parameters": [
1,
"%d hello %s",
1337,
"cat"
]
}
Other examples:
{ "method": "function", "function": "PrintToServer", "parameters": ["1: %s, 2: %d, 3: %s, 4: %d", "1test1", [2222], "3test3", [4444]] }
{ "method": "function", "function": "TeleportEntity", "parameters": [1, [206.472443, 2418.516357, -62.058395], [26.994024, -14.119852, 0.000000], [["NULL_VECTOR"]] ] }
{ "method": "function", "function": "GetClientName", "parameters": [ 1, "", 1024 ] }
{ "method": "function", "function": "GetClientAbsOrigin", "parameters": [ 1, [0.0,0,0] ] }
{ "method": "function", "function": "ServerCommandEx", "parameters": [ "", 1024, "echo hello mates %d %f %s", [123], [123.456], "test" ] }
{ "method": "subscribe", "module": "gameevents", "events": [ "player_say" ] }
{ "method": "function", "function": "GetUserFlagBits", "args": [ 9 ] }

View File

@ -0,0 +1,49 @@
public void API_PrintToChatAll(const char[] format, any ...)
{
char buffer[254];
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
SetGlobalTransTarget(i);
VFormat(buffer, sizeof(buffer), format, 2);
PrintToChat(i, "%s", buffer);
}
}
}
public void API_PrintCenterTextAll(const char[] format, any ...)
{
char buffer[254];
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
SetGlobalTransTarget(i);
VFormat(buffer, sizeof(buffer), format, 2);
PrintCenterText(i, "%s", buffer);
}
}
}
public void API_PrintHintTextToAll(const char[] format, any ...)
{
char buffer[254];
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
SetGlobalTransTarget(i);
VFormat(buffer, sizeof(buffer), format, 2);
PrintHintText(i, "%s", buffer);
}
}
}
public int API_GetMaxClients()
{
return MaxClients;
}

View File

@ -0,0 +1,158 @@
static KeyValues g_Config;
bool GameEvents_Init()
{
char sGame[32];
GetGameFolderName(sGame, sizeof(sGame));
char sConfigFile[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/Events.%s.cfg", sGame);
if(!FileExists(sConfigFile))
{
SetFailState("Could not find config: \"%s\"", sConfigFile);
}
g_Config = new KeyValues("allevents");
if(!g_Config.ImportFromFile(sConfigFile))
{
delete g_Config;
SetFailState("ImportFromFile() failed!");
}
return true;
}
static void GameEvents_OnHook(Event event, const char[] name, bool dontBroadcast)
{
static char sEventName[32];
event.GetName(sEventName, sizeof(sEventName));
g_Config.JumpToKey(sEventName);
JSONObject jEvent = new JSONObject();
jEvent.SetString("name", sEventName);
JSONObject jEventData = new JSONObject();
if(g_Config.GotoFirstSubKey(false))
{
do
{
static char sKey[32];
static char sType[8];
g_Config.GetSectionName(sKey, sizeof(sKey));
g_Config.GetString(NULL_STRING, sType, sizeof(sType));
if(StrEqual(sKey, "_hooked"))
continue;
if(StrEqual(sType, "short") || StrEqual(sType, "long") || StrEqual(sType, "byte"))
{
int iValue = event.GetInt(sKey);
jEventData.SetInt(sKey, iValue);
}
else if(StrEqual(sType, "float"))
{
float fValue = event.GetFloat(sKey);
jEventData.SetFloat(sKey, fValue);
}
else if(StrEqual(sType, "bool"))
{
bool bValue = event.GetBool(sKey);
jEventData.SetBool(sKey, bValue);
}
else if(StrEqual(sType, "string"))
{
static char sValue[1024];
event.GetString(sKey, sValue, sizeof(sValue));
jEventData.SetString(sKey, sValue);
}
} while(g_Config.GotoNextKey(false));
g_Config.Rewind();
}
jEvent.Set("data", jEventData);
Subscribe_GameEvents_Publish(sEventName, jEvent);
}
int GameEvents_Hook(const char[] sEventName)
{
if(!g_Config.JumpToKey(sEventName))
return -1;
int Hooked = g_Config.GetNum("_hooked", 0);
if(Hooked)
{
g_Config.SetNum("_hooked", Hooked + 1);
return Hooked + 1;
}
Hooked = HookEventEx(sEventName, GameEvents_OnHook, EventHookMode_Post);
if(Hooked)
g_Config.SetNum("_hooked", 1);
g_Config.Rewind();
return Hooked ? 1 : -1;
}
int GameEvents_Unhook(const char[] sEventName)
{
if(!g_Config.JumpToKey(sEventName))
return -1;
int Hooked = g_Config.GetNum("_hooked", 0);
if(!Hooked)
return 0;
if(Hooked == 1)
UnhookEvent(sEventName, GameEvents_OnHook, EventHookMode_Post);
g_Config.SetNum("_hooked", Hooked - 1);
g_Config.Rewind();
return Hooked - 1;
}
stock void GameEvents_HookAll()
{
g_Config.GotoFirstSubKey(false);
do
{
static char sKey[32];
g_Config.GetSectionName(sKey, sizeof(sKey));
int Hooked = g_Config.GetNum("_hooked", 0);
if(!Hooked)
{
if(HookEventEx(sKey, GameEvents_OnHook, EventHookMode_Post))
g_Config.SetNum("_hooked", 1);
}
else
g_Config.SetNum("_hooked", Hooked + 1);
} while(g_Config.GotoNextKey(true));
g_Config.Rewind();
}
stock void GameEvents_UnhookAll()
{
g_Config.GotoFirstSubKey(false);
do
{
static char sKey[32];
g_Config.GetSectionName(sKey, sizeof(sKey));
int Hooked = g_Config.GetNum("_hooked", 0);
if(Hooked)
{
UnhookEvent(sKey, GameEvents_OnHook, EventHookMode_Post);
g_Config.SetNum("_hooked", 0);
}
} while(g_Config.GotoNextKey(true));
g_Config.Rewind();
}

View File

@ -0,0 +1,472 @@
#pragma semicolon 1
#pragma newdecls required
#pragma dynamic 65535
#include <sourcemod>
#include <AsyncSocket>
#include <smjansson>
#define MAX_CLIENTS 16
#include "API.inc"
#include "Subscribe.inc"
static AsyncSocket g_ServerSocket;
static AsyncSocket g_Client_Socket[MAX_CLIENTS] = { null, ... };
static int g_Client_Subscriber[MAX_CLIENTS] = { -1, ... };
ConVar g_ListenAddr;
ConVar g_ListenPort;
public Plugin myinfo =
{
name = "SM JSON API",
author = "SourceMod TCP JSON API",
description = "",
version = "1.0",
url = ""
}
public void OnPluginStart()
{
Subscribe_OnPluginStart();
g_ListenAddr = CreateConVar("sm_jsonapi_addr", "127.0.0.1", "SM JSON API listen ip address", FCVAR_PROTECTED);
g_ListenPort = CreateConVar("sm_jsonapi_port", "27021", "SM JSON API listen ip address", FCVAR_PROTECTED, true, 1025.0, true, 65535.0);
AutoExecConfig(true, "plugin.SMJSONAPI");
}
public void OnConfigsExecuted()
{
if(g_ServerSocket)
return;
g_ServerSocket = new AsyncSocket();
g_ServerSocket.SetConnectCallback(OnAsyncConnect);
g_ServerSocket.SetErrorCallback(OnAsyncServerError);
char sAddr[32];
g_ListenAddr.GetString(sAddr, sizeof(sAddr));
int Port = g_ListenPort.IntValue;
g_ServerSocket.Listen(sAddr, Port);
LogMessage("Listening on %s:%d", sAddr, Port);
}
static void OnAsyncConnect(AsyncSocket socket)
{
int Client = GetFreeClientIndex();
LogMessage("OnAsyncConnect(Client=%d)", Client);
if(Client == -1)
{
delete socket;
return;
}
g_Client_Socket[Client] = socket;
socket.SetErrorCallback(OnAsyncClientError);
socket.SetDataCallback(OnAsyncData);
}
static void OnAsyncServerError(AsyncSocket socket, int error, const char[] errorName)
{
SetFailState("OnAsyncServerError(): %d, \"%s\"", error, errorName);
}
static void OnAsyncClientError(AsyncSocket socket, int error, const char[] errorName)
{
int Client = ClientFromSocket(socket);
LogMessage("OnAsyncClientError(Client=%d): %d, \"%s\"", Client, error, errorName);
if(Client == -1)
{
delete socket;
return;
}
if(g_Client_Subscriber[Client] != -1)
{
Subscriber_Destroy(g_Client_Subscriber[Client]);
g_Client_Subscriber[Client] = -1;
}
g_Client_Socket[Client] = null;
delete socket;
}
static void OnAsyncData(AsyncSocket socket, const char[] data, const int size)
{
int Client = ClientFromSocket(socket);
int iLine;
int iColumn;
static char sError[256];
JSONValue jRequest = json_load_ex(data, sError, sizeof(sError), iLine, iColumn);
if(jRequest == null)
{
JSONObject jResponse = new JSONObject();
JSONObject jError = new JSONObject();
jError.SetString("type", "json");
jError.SetString("error", sError);
jError.SetInt("line", iLine);
jError.SetInt("column", iColumn);
jResponse.Set("error", jError);
jResponse.ToString(sError, sizeof(sError));
socket.WriteNull(sError);
delete jResponse;
return;
}
if(jRequest.IsObject)
{
//view_as<JSONObject>(jRequest).DumpToServer();
JSONObject jResponse = new JSONObject();
// Negative values and objects indicate errors
// 0 and positive integers indicate success
jResponse.SetInt("error", 0);
HandleRequest(Client, view_as<JSONObject>(jRequest), jResponse);
static char sResponse[4096];
jResponse.ToString(sResponse, sizeof(sResponse));
socket.WriteNull(sResponse);
delete jResponse;
}
delete jRequest;
}
static int HandleRequest(int Client, JSONObject jRequest, JSONObject jResponse)
{
static char sMethod[32];
if(jRequest.GetString("method", sMethod, sizeof(sMethod)) < 0)
{
JSONObject jError = new JSONObject();
jError.SetString("error", "Request has no 'method' string-value.");
jResponse.Set("error", jError);
return -1;
}
jResponse.SetString("method", sMethod);
if(StrEqual(sMethod, "subscribe") || StrEqual(sMethod, "unsubscribe") || StrEqual(sMethod, "replay"))
{
int Subscriber = g_Client_Subscriber[Client];
if(Subscriber == -1)
{
Subscriber = Subscriber_Create(Client);
if(Subscriber < 0)
{
JSONObject jError = new JSONObject();
jError.SetString("type", "subscribe");
jError.SetString("error", "Could not allocate a subscriber.");
jError.SetInt("code", Subscriber);
jResponse.Set("error", jError);
return -1;
}
g_Client_Subscriber[Client] = Subscriber;
}
return Subscriber_HandleRequest(Subscriber, jRequest, jResponse);
}
else if(StrEqual(sMethod, "function"))
{
static char sFunction[64];
if(jRequest.GetString("function", sFunction, sizeof(sFunction)) < 0)
{
JSONObject jError = new JSONObject();
jError.SetString("error", "Request has no 'function' string-value.");
jResponse.Set("error", jError);
return -1;
}
jResponse.SetString("function", sFunction);
static char sAPIFunction[64];
Format(sAPIFunction, sizeof(sAPIFunction), "API_%s", sFunction);
Function Fun = GetFunctionByName(INVALID_HANDLE, sAPIFunction);
if(Fun == INVALID_FUNCTION)
{
int Res = Call_StartNative(sFunction);
if(!Res)
{
JSONObject jError = new JSONObject();
jError.SetString("error", "Invalid function specified.");
jError.SetInt("code", Res);
jResponse.Set("error", jError);
return -1;
}
}
else
Call_StartFunction(INVALID_HANDLE, Fun);
JSONArray jArgsArray = view_as<JSONArray>(jRequest.Get("args"));
if(jArgsArray == null || !jArgsArray.IsArray)
{
delete jArgsArray;
Call_Cancel();
JSONObject jError = new JSONObject();
jError.SetString("error", "Request has no 'args' array-value.");
jResponse.Set("error", jError);
return -1;
}
int aiValues[32];
int iValues = 0;
float afValues[32];
int fValues = 0;
static char asValues[16][1024];
int sValues = 0;
for(int i = 0; i < jArgsArray.Length; i++)
{
bool Fail = false;
JSONValue jValue;
jValue = jArgsArray.Get(i);
char sType1[32];
jValue.TypeToString(sType1, sizeof(sType1));
if(jValue.IsArray)
{
JSONArray jValueArray = view_as<JSONArray>(jValue);
JSONValue jArrayValue = jValueArray.Get(0);
char sType2[32];
jArrayValue.TypeToString(sType2, sizeof(sType2));
if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
{
int iValues_ = iValues;
for(int j = 0; j < jValueArray.Length; j++)
{
JSONInteger jInteger = view_as<JSONInteger>(jValueArray.Get(j));
aiValues[iValues_++] = jInteger.Value;
delete jInteger;
}
Call_PushArrayEx(aiValues[iValues], jValueArray.Length, SM_PARAM_COPYBACK);
iValues += jValueArray.Length;
}
else if(jArrayValue.IsFloat)
{
int fValues_ = fValues;
for(int j = 0; j < jValueArray.Length; j++)
{
JSONFloat jFloat = view_as<JSONFloat>(jValueArray.Get(j));
afValues[fValues_++] = jFloat.Value;
delete jFloat;
}
Call_PushArrayEx(afValues[fValues], jValueArray.Length, SM_PARAM_COPYBACK);
fValues += jValueArray.Length;
}
/*else if(jArrayValue.IsString)
{
int sValues_ = sValues;
for(int j = 0; j < jValueArray.Length; j++)
{
JSONString jString = view_as<JSONString>(jValueArray.Get(j));
jString.GetString(asValues[sValues_++], sizeof(asValues[]));
delete jString;
}
Call_PushArrayEx(view_as<int>(asValues[sValues]), jValueArray.Length, SM_PARAM_COPYBACK);
sValues += jValueArray.Length;
}*/
else if(jArrayValue.IsArray) // Special
{
static char sSpecial[32];
view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
if(StrEqual(sSpecial, "NULL_VECTOR"))
Call_PushArrayEx(NULL_VECTOR, 3, 0);
else if(StrEqual(sSpecial, "NULL_STRING"))
Call_PushString(NULL_STRING);
else
Fail = true;
}
else
Fail = true;
if(Fail)
{
Call_Cancel();
delete jArrayValue;
delete jValue;
delete jArgsArray;
static char sType[16];
jValue.TypeToString(sType, sizeof(sType));
char sError[128];
FormatEx(sError, sizeof(sError), "Unsupported parameter in list %d of type '%s'", i, sType);
JSONObject jError = new JSONObject();
jError.SetString("error", sError);
jResponse.Set("error", jError);
return -1;
}
delete jArrayValue;
}
else if(jValue.IsInteger || jValue.IsBoolean)
{
aiValues[iValues] = view_as<JSONInteger>(jValue).Value;
Call_PushCell(aiValues[iValues++]);
}
else if(jValue.IsFloat)
{
afValues[fValues] = view_as<JSONFloat>(jValue).Value;
Call_PushFloat(afValues[fValues++]);
}
else if(jValue.IsString)
{
// Broken: view_as<JSONString>(jValue).GetString(asValues[sValues], sizeof(asValues[]));
JSONString jString = view_as<JSONString>(jValue);
jString.GetString(asValues[sValues], sizeof(asValues[]));
Call_PushStringEx(asValues[sValues++], sizeof(asValues[]), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
}
else
{
Call_Cancel();
delete jValue;
delete jArgsArray;
static char sType[16];
jValue.TypeToString(sType, sizeof(sType));
char sError[128];
FormatEx(sError, sizeof(sError), "Unsupported parameter %d of type '%s'", i, sType);
JSONObject jError = new JSONObject();
jError.SetString("error", sError);
jResponse.Set("error", jError);
return -1;
}
delete jValue;
}
int Result;
static char sException[1024];
int Error = Call_FinishEx(Result, sException, sizeof(sException));
if(Error != SP_ERROR_NONE)
{
delete jArgsArray;
JSONObject jError = new JSONObject();
jError.SetInt("error", Error);
jError.SetString("exception", sException);
jResponse.Set("error", jError);
return -1;
}
jResponse.SetInt("result", Result);
JSONArray jArgsResponse = new JSONArray();
iValues = 0;
fValues = 0;
sValues = 0;
for(int i = 0; i < jArgsArray.Length; i++)
{
JSONValue jValue;
jValue = jArgsArray.Get(i);
if(jValue.IsArray)
{
JSONArray jArrayResponse = new JSONArray();
JSONArray jValueArray = view_as<JSONArray>(jValue);
JSONValue jArrayValue = jValueArray.Get(0);
if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
{
for(int j = 0; j < jValueArray.Length; j++)
jArrayResponse.AppendInt(aiValues[iValues++]);
}
else if(jArrayValue.IsFloat)
{
for(int j = 0; j < jValueArray.Length; j++)
jArrayResponse.AppendFloat(afValues[fValues++]);
}
else if(jArrayValue.IsString)
{
for(int j = 0; j < jValueArray.Length; j++)
jArrayResponse.AppendString(asValues[sValues++]);
}
else if(jArrayValue.IsArray) // Special
{
static char sSpecial[32];
view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
jArrayResponse.AppendString(sSpecial);
}
delete jArrayValue;
jArgsResponse.Append(jArrayResponse);
}
else if(jValue.IsInteger || jValue.IsBoolean)
{
jArgsResponse.AppendInt(aiValues[iValues++]);
}
else if(jValue.IsFloat)
{
jArgsResponse.AppendFloat(afValues[fValues++]);
}
else if(jValue.IsString)
{
jArgsResponse.AppendString(asValues[sValues++]);
}
delete jValue;
}
jResponse.Set("args", jArgsResponse);
delete jArgsArray;
return 0;
}
JSONObject jError = new JSONObject();
jError.SetString("error", "No handler found for requested method.");
jResponse.Set("error", jError);
return -1;
}
int PublishEvent(int Client, JSONObject Object)
{
if(Client < 0 || Client > MAX_CLIENTS || g_Client_Socket[Client] == null)
return -1;
static char sEvent[4096];
Object.ToString(sEvent, sizeof(sEvent));
g_Client_Socket[Client].WriteNull(sEvent);
return 0;
}
static int GetFreeClientIndex()
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!g_Client_Socket[i])
return i;
}
return -1;
}
static int ClientFromSocket(AsyncSocket socket)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(g_Client_Socket[i] == socket)
return i;
}
return -1;
}

View File

@ -0,0 +1,279 @@
#include "GameEvents.inc"
#define MAX_SUBSCRIBERS MAX_CLIENTS
enum
{
eSubscribeError_OutOfRange = -1,
eSubscribeError_Inactive = -2,
eSubscribeError_GameEvent = -3,
}
static bool g_Subscriber_Active[MAX_SUBSCRIBERS] = { false, ... };
static int g_Subscriber_Client[MAX_SUBSCRIBERS] = { -1, ... };
static ArrayList g_Subscriber_GameEvents[MAX_SUBSCRIBERS];
bool Subscribe_OnPluginStart()
{
GameEvents_Init();
}
int Subscriber_Create(int Client)
{
int Index = Client;
if(Index < 0 || Index >= MAX_SUBSCRIBERS || g_Subscriber_Active[Index])
{
Index = GetFreeSubscriberIndex();
if(Index == eSubscribeError_OutOfRange)
return eSubscribeError_OutOfRange;
}
g_Subscriber_Active[Index] = true;
g_Subscriber_Client[Index] = Client;
g_Subscriber_GameEvents[Index] = new ArrayList(ByteCountToCells(32));
return Index;
}
int Subscriber_Destroy(int Index)
{
if(Index < 0 || Index >= MAX_SUBSCRIBERS)
return eSubscribeError_OutOfRange;
if(!g_Subscriber_Active[Index])
return eSubscribeError_Inactive;
for(int i = 0; i < g_Subscriber_GameEvents[Index].Length; i++)
{
static char sEventName[32];
g_Subscriber_GameEvents[Index].GetString(i, sEventName, sizeof(sEventName));
GameEvents_Unhook(sEventName);
}
delete g_Subscriber_GameEvents[Index];
g_Subscriber_Active[Index] = false;
g_Subscriber_Client[Index] = -1;
return 0;
}
int Subscriber_HandleRequest(int Index, JSONObject Request, JSONObject Response)
{
static char sMethod[32];
Request.GetString("method", sMethod, sizeof(sMethod));
static char sModule[32];
if(Request.GetString("module", sModule, sizeof(sModule)) < 0)
{
JSONObject jError = new JSONObject();
jError.SetString("error", "Request has no 'module' string-value.");
Response.Set("error", jError);
return -1;
}
Response.SetString("module", sModule);
if(StrEqual(sModule, "gameevents"))
{
JSONArray EventArray = view_as<JSONArray>(Request.Get("events"));
if(EventArray == null || !EventArray.IsArray)
{
delete EventArray;
JSONObject jError = new JSONObject();
jError.SetString("error", "Request has no 'events' array-value.");
Response.Set("error", jError);
return -1;
}
int Method = 0;
if(StrEqual(sMethod, "subscribe"))
Method = 1;
else if(StrEqual(sMethod, "unsubscribe"))
Method = 2;
else if(StrEqual(sMethod, "replay"))
Method = 3;
JSONArray EventArrayResponse = new JSONArray();
for(int i = 0; i < EventArray.Length; i++)
{
static char sEventName[32];
if(EventArray.GetString(i, sEventName, sizeof(sEventName)) < 0)
{
JSONObject jError = new JSONObject();
jError.SetString("error", "not a string-value");
EventArrayResponse.Append(jError);
continue;
}
int Res;
if(Method == 1)
Res = Subscribe_GameEvents_Subscribe(Index, sEventName);
else if(Method == 2)
Res = Subscribe_GameEvents_Unsubscribe(Index, sEventName);
else if(Method == 3)
Res = Subscribe_GameEvents_Replay(Index, sEventName);
EventArrayResponse.AppendInt(Res);
}
delete EventArray;
Response.Set("events", EventArrayResponse);
return 0;
}
JSONObject jError = new JSONObject();
jError.SetString("error", "No handler found for requested module.");
Response.Set("error", jError);
return -1;
}
static int GetFreeSubscriberIndex()
{
for(int i = 0; i < MAX_SUBSCRIBERS; i++)
{
if(!g_Subscriber_Active[i])
return i;
}
return eSubscribeError_OutOfRange;
}
/* GameEvents */
static int Subscribe_GameEvents_Subscribe(int Index, const char[] sEventName)
{
if(Index < 0 || Index >= MAX_SUBSCRIBERS)
return eSubscribeError_OutOfRange;
if(!g_Subscriber_Active[Index])
return eSubscribeError_Inactive;
int Find = g_Subscriber_GameEvents[Index].FindString(sEventName);
if(Find != -1)
return Find;
if(GameEvents_Hook(sEventName) < 0)
return eSubscribeError_GameEvent;
return g_Subscriber_GameEvents[Index].PushString(sEventName);
}
static int Subscribe_GameEvents_Unsubscribe(int Index, const char[] sEventName)
{
if(Index < 0 || Index >= MAX_SUBSCRIBERS)
return eSubscribeError_OutOfRange;
if(!g_Subscriber_Active[Index])
return eSubscribeError_Inactive;
int Find = g_Subscriber_GameEvents[Index].FindString(sEventName);
if(Find == -1)
return 0;
if(GameEvents_Unhook(sEventName) < 0)
return eSubscribeError_GameEvent;
g_Subscriber_GameEvents[Index].Erase(Find);
return Find;
}
static int Subscribe_GameEvents_Replay(int Index, const char[] sEventName)
{
if(Index < 0 || Index >= MAX_SUBSCRIBERS)
return eSubscribeError_OutOfRange;
if(!g_Subscriber_Active[Index])
return eSubscribeError_Inactive;
if(StrEqual(sEventName, "player_connect"))
{
for(int client = 1; client <= MaxClients; client++)
{
if(!IsClientConnected(client))
continue;
char sName[MAX_NAME_LENGTH];
GetClientName(client, sName, sizeof(sName));
char sSteamID[32];
GetClientAuthId(client, AuthId_Engine, sSteamID, sizeof(sSteamID), false);
char sAddress[32];
GetClientIP(client, sAddress, sizeof(sAddress), false);
JSONObject jEventData = new JSONObject();
jEventData.SetString("name", sName);
char sBuf[MAX_NAME_LENGTH];
jEventData.GetString("name", sBuf, sizeof(sBuf));
jEventData.SetInt("index", client - 1);
jEventData.SetInt("userid", GetClientUserId(client));
jEventData.SetString("networkid", sSteamID);
jEventData.SetString("address", sAddress);
jEventData.SetBool("bot", IsFakeClient(client));
Subscribe_GameEvents_FakePublish(g_Subscriber_Client[Index], sEventName, jEventData);
}
}
else if(StrEqual(sEventName, "player_activate"))
{
for(int client = 1; client <= MaxClients; client++)
{
if(!IsClientInGame(client))
continue;
JSONObject jEventData = new JSONObject();
jEventData.SetInt("userid", GetClientUserId(client));
Subscribe_GameEvents_FakePublish(g_Subscriber_Client[Index], sEventName, jEventData);
}
}
else
return eSubscribeError_GameEvent;
return 0;
}
static int Subscribe_GameEvents_FakePublish(int Index, const char[] sEventName, JSONObject jEventData)
{
if(Index < 0 || Index >= MAX_SUBSCRIBERS)
return eSubscribeError_OutOfRange;
if(!g_Subscriber_Active[Index])
return eSubscribeError_Inactive;
JSONObject jEvent = new JSONObject();
jEvent.SetString("name", sEventName);
jEvent.Set("data", jEventData);
JSONObject jPublish = new JSONObject();
jPublish.SetString("method", "publish");
jPublish.SetString("module", "gameevents");
jPublish.Set("event", jEvent);
PublishEvent(g_Subscriber_Client[Index], jPublish);
delete jPublish;
return 0;
}
void Subscribe_GameEvents_Publish(const char[] sEventName, JSONObject jEvent)
{
JSONObject jPublish = new JSONObject();
jPublish.SetString("method", "publish");
jPublish.SetString("module", "gameevents");
jPublish.Set("event", jEvent);
for(int Index = 0; Index < MAX_SUBSCRIBERS; Index++)
{
if(!g_Subscriber_Active[Index])
continue;
if(g_Subscriber_GameEvents[Index].FindString(sEventName) == -1)
continue;
PublishEvent(g_Subscriber_Client[Index], jPublish);
}
delete jPublish;
}
/* GameEvents */

View File

@ -0,0 +1 @@
../../../includes/AsyncSocket.inc

View File

@ -0,0 +1 @@
../../../includes/smjansson.inc