Skip to content
This repository was archived by the owner on Sep 24, 2023. It is now read-only.

Commit d347d72

Browse files
committed
Initial plugin commit
Here we go.
1 parent f3d2304 commit d347d72

4 files changed

Lines changed: 250 additions & 0 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# ---> SourceMod
2+
# Ignore compiled SourceMod plugins
3+
4+
*.smx
5+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#if defined _level_keyvalues_included
2+
#endinput
3+
#endif
4+
#define _level_keyvalues_included
5+
6+
/**
7+
* Returns a copy of the KeyValues associated with the entity of a specified Hammer ID.
8+
* The KeyValues handle returned by the native is read-only; any changes made do not propagate
9+
* up to the shared plugin.
10+
*/
11+
native KeyValues LevelEntity_GetKeysByHammerID(int iHammerID);
12+
13+
/**
14+
* Returns a copy of the KeyValues associated with a given entity.
15+
*/
16+
stock KeyValues LevelEntity_GetKeysByEntity(int entref) {
17+
int entity = EntRefToEntIndex(entref);
18+
return LevelEntity_GetKeysByHammerID(GetEntProp(entity, Prop_Data, "m_iHammerID"));
19+
}
20+
21+
/**
22+
* Parses an entity's output value (as formatted in the entity string).
23+
*
24+
* Refer to https://developer.valvesoftware.com/wiki/AddOutput for the format.
25+
*/
26+
stock bool ParseEntityOutputString(const char[] output, char[] targetName, int targetNameLength,
27+
char[] inputName, int inputNameLength, char[] variantValue, int variantValueLength,
28+
float &delay, int &nFireCount) {
29+
int delimiter;
30+
char buffer[32];
31+
32+
delimiter = SplitString(output, ",", targetName, targetNameLength);
33+
delimiter += SplitString(output[delimiter], ",", inputName, inputNameLength);
34+
delimiter += SplitString(output[delimiter], ",", variantValue, variantValueLength);
35+
36+
delimiter += SplitString(output[delimiter], ",", buffer, sizeof(buffer));
37+
delay = StringToFloat(buffer);
38+
39+
nFireCount = StringToInt(output[delimiter]);
40+
41+
return true;
42+
}
43+
44+
45+
public SharedPlugin __pl_level_keyvalues = {
46+
name = "level-keyvalues",
47+
file = "level_keyvalues.smx",
48+
#if defined REQUIRE_PLUGIN
49+
required = 1,
50+
#else
51+
required = 0,
52+
#endif
53+
};
54+
55+
public void __pl_level_keyvalues_SetNTVOptional() {
56+
#if !defined REQUIRE_PLUGIN
57+
MarkNativeAsOptional("LevelEntity_GetKeysByHammerID");
58+
#endif
59+
}

scripting/level_keyvalues.sp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* [ANY?] Level KeyValues
3+
*
4+
* Parses the level entity string (from SDKHooks' `OnLevelInit` forward) into a KeyValues
5+
* handle, indexed by Hammer ID.
6+
*/
7+
#pragma semicolon 1
8+
#pragma dynamic 1048576
9+
#include <sourcemod>
10+
11+
#include <sdkhooks>
12+
#include <regex>
13+
14+
#pragma newdecls required
15+
16+
#define PLUGIN_VERSION "0.0.0"
17+
public Plugin myinfo = {
18+
name = "Level KeyValues",
19+
author = "nosoop",
20+
description = "Parses the map entity string into a KeyValues structure.",
21+
version = PLUGIN_VERSION,
22+
url = "https://github.com/nosoop/SM-LevelKeyValues/"
23+
}
24+
25+
KeyValues g_MapEntities;
26+
27+
public APLRes AskPluginLoad2(Handle self, bool late, char[] error, int err_max) {
28+
RegPluginLibrary("level-keyvalues");
29+
30+
CreateNative("LevelEntity_GetKeysByHammerID", Native_GetKeysByHammerID);
31+
32+
return APLRes_Success;
33+
}
34+
35+
public Action OnLevelInit(const char[] mapName, char mapEntities[2097152]) {
36+
if (g_MapEntities) {
37+
delete g_MapEntities;
38+
}
39+
40+
g_MapEntities = ParseEntityList(mapEntities);
41+
42+
/**
43+
* create a forward to allow manipulation of the KV? create a cloned copy?
44+
* could open up possibilities for dynamically filtering / adding new entities in ways that
45+
* Stripper:Source doesn't support natively
46+
*/
47+
48+
return Plugin_Continue;
49+
}
50+
51+
public int Native_GetKeysByHammerID(Handle plugin, int argc) {
52+
// native KeyValues LevelEntity_GetKeysByHammerID(int iHammerID);
53+
int iHammerID = GetNativeCell(1);
54+
55+
char hammerID[64];
56+
IntToString(iHammerID, hammerID, sizeof(hammerID));
57+
58+
if (g_MapEntities && g_MapEntities.JumpToKey(hammerID, false)) {
59+
// create and transfer ownership of KeyValues
60+
KeyValues kv = new KeyValues(hammerID);
61+
KeyValues retval = view_as<KeyValues>(CloneHandle(kv, plugin));
62+
63+
kv.Import(g_MapEntities);
64+
g_MapEntities.GoBack();
65+
66+
delete kv;
67+
68+
return view_as<int>(retval);
69+
}
70+
return 0;
71+
}
72+
73+
/**
74+
* Parses the level entity string into a KeyValues struct.
75+
* The KeyValues struct organizes keys using the `hammerid` as the section names.
76+
*/
77+
static KeyValues ParseEntityList(const char mapEntities[2097152]) {
78+
static Regex s_KeyValueLine;
79+
80+
if (!s_KeyValueLine) {
81+
// Pattern copied from alliedmodders/stripper-source/master/parser.cpp
82+
s_KeyValueLine = new Regex("\"([^\"]+)\"\\s+\"([^\"]+)\"");
83+
}
84+
85+
KeyValues mapKeyValues = new KeyValues("map_entities");
86+
87+
int nKeys;
88+
89+
char key[256], value[256];
90+
91+
int i, n;
92+
char lineBuffer[4096];
93+
while ((n = SplitString(mapEntities[i], "\n", lineBuffer, sizeof(lineBuffer))) != -1) {
94+
switch(lineBuffer[0]) {
95+
case '{': {
96+
char sectionValue[128];
97+
Format(sectionValue, sizeof(sectionValue), "__parsed_unknown_%d", nKeys);
98+
99+
mapKeyValues.JumpToKey(sectionValue, true);
100+
nKeys++;
101+
}
102+
case '}': {
103+
mapKeyValues.GoBack();
104+
105+
// next open bracket starts on same line
106+
if (lineBuffer[1] == '{') {
107+
char sectionValue[128];
108+
Format(sectionValue, sizeof(sectionValue), "__parsed_unknown_%d", nKeys);
109+
110+
mapKeyValues.JumpToKey(sectionValue, true);
111+
nKeys++;
112+
}
113+
}
114+
default: {
115+
if (s_KeyValueLine.Match(lineBuffer)) {
116+
s_KeyValueLine.GetSubString(1, key, sizeof(key));
117+
s_KeyValueLine.GetSubString(2, value, sizeof(value));
118+
119+
KeyValues_AddString(mapKeyValues, key, value);
120+
121+
// change section name to hammerid for quick lookup (`m_iHammerID` dataprop)
122+
if (StrEqual(key, "hammerid")) {
123+
mapKeyValues.SetSectionName(value);
124+
}
125+
}
126+
}
127+
}
128+
i += n;
129+
}
130+
mapKeyValues.GoBack();
131+
mapKeyValues.SetNum("num_entities", nKeys);
132+
133+
return mapKeyValues;
134+
}
135+
136+
/**
137+
* Adds a new key/value pair to the KeyValues structure in a way that allows for duplicate key
138+
* names.
139+
*/
140+
static void KeyValues_AddString(KeyValues kv, const char[] key, const char[] value) {
141+
static int s_nTempKey;
142+
143+
char tempKey[64];
144+
Format(tempKey, sizeof(tempKey), "__levelkv_temp_buffer_%d", s_nTempKey++);
145+
146+
kv.SetString(tempKey, value);
147+
148+
kv.JumpToKey(tempKey);
149+
kv.GotoFirstSubKey(false);
150+
kv.SetSectionName(key);
151+
kv.GotoNextKey();
152+
153+
kv.GoBack();
154+
}

scripting/level_keyvalues_test.sp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Sourcemod 1.7 Plugin Template
3+
*/
4+
#pragma semicolon 1
5+
#include <sourcemod>
6+
7+
#include <sdktools>
8+
9+
#pragma newdecls required
10+
#include <level_keyvalues>
11+
12+
public void OnMapStart() {
13+
int logicAuto = FindEntityByClassname(-1, "team_control_point");
14+
15+
if (logicAuto != -1) {
16+
LogMessage("%s", "Found a team_control_point with keys:");
17+
18+
KeyValues entityKeys = LevelEntity_GetKeysByEntity(logicAuto);
19+
20+
if (entityKeys) {
21+
char keyBuffer[128], valueBuffer[128];
22+
entityKeys.GotoFirstSubKey(false);
23+
do {
24+
entityKeys.GetSectionName(keyBuffer, sizeof(keyBuffer));
25+
entityKeys.GetString(NULL_STRING, valueBuffer, sizeof(valueBuffer));
26+
27+
LogMessage("%s -> %s", keyBuffer, valueBuffer);
28+
} while (entityKeys.GotoNextKey(false));
29+
delete entityKeys;
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)