Skip to content

Commit 0638890

Browse files
committed
2 parents 01f6cb3 + 531b053 commit 0638890

File tree

17 files changed

+572
-22
lines changed

17 files changed

+572
-22
lines changed

.github/workflows/mocktest.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: hl2sdk-mock tests
2+
on:
3+
push:
4+
branches:
5+
- master
6+
- '[0-9]+.[0-9]+-dev'
7+
pull_request:
8+
branches:
9+
- master
10+
- '[0-9]+.[0-9]+-dev'
11+
jobs:
12+
mock:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v3
16+
name: Clone sourcemod
17+
with:
18+
submodules: recursive
19+
path: sourcemod
20+
21+
- uses: actions/checkout@v3
22+
name: Clone metamod-source
23+
with:
24+
repository: alliedmodders/metamod-source
25+
submodules: recursive
26+
path: metamod-source
27+
28+
- uses: actions/checkout@v3
29+
name: Clone hl2sdk-mock
30+
with:
31+
repository: alliedmodders/hl2sdk-mock
32+
submodules: recursive
33+
path: hl2sdk-mock
34+
35+
- uses: actions/setup-python@v4
36+
name: Setup Python 3.10
37+
with:
38+
python-version: "3.10"
39+
40+
- name: Install AMBuild
41+
run: |
42+
python -m pip install --upgrade pip setuptools wheel
43+
pip install git+https://github.com/alliedmodders/ambuild
44+
45+
- name: Build MetaMod:Source
46+
working-directory: metamod-source
47+
run: |
48+
python configure.py --enable-optimize --sdks=mock --targets=x86_64
49+
ambuild objdir
50+
51+
- name: Build SourceMod
52+
working-directory: sourcemod
53+
run: |
54+
python configure.py --no-mysql --enable-optimize --sdks=mock --targets=x86_64
55+
ambuild objdir
56+
57+
- name: Build hl2sdk-mock
58+
working-directory: hl2sdk-mock
59+
run: |
60+
python configure.py --enable-optimize --targets=x86_64
61+
ambuild objdir
62+
63+
- name: Setup gamedir
64+
working-directory: hl2sdk-mock
65+
shell: bash
66+
run: |
67+
mkdir ../gamedir
68+
./build_gamedir.sh ../gamedir ../metamod-source/objdir/package
69+
./build_gamedir.sh ../gamedir ../sourcemod/objdir/package
70+
71+
- name: Compile testsuite
72+
working-directory: hl2sdk-mock
73+
shell: bash
74+
run: |
75+
mkdir ../gamedir/addons/sourcemod/plugins/optional
76+
77+
for f in ../sourcemod/plugins/testsuite/mock/*.sp; do
78+
echo "Compiling $(basename $f)"
79+
../gamedir/addons/sourcemod/scripting/spcomp64 -i ../gamedir/addons/sourcemod/scripting/include -o "../gamedir/addons/sourcemod/plugins/optional/$(basename $f .sp).smx" -E "$f"
80+
done
81+
82+
- name: Test
83+
working-directory: hl2sdk-mock
84+
shell: bash
85+
run: |
86+
for f in ../gamedir/addons/sourcemod/plugins/optional/*.smx; do
87+
echo "==================================="
88+
echo "Running $(basename $f)..."
89+
echo "==================================="
90+
timeout 60 ./objdir/dist/x86_64/srcds -game_dir ../gamedir +map de_thunder -command "sm plugins load optional/$(basename $f)" -run -run-ticks 20 |
91+
{
92+
failed=0
93+
while IFS= read -r line; do
94+
echo "$line"
95+
if [[ "$line" == *"FAIL"* ]]; then
96+
failed=1
97+
fi
98+
done
99+
if [ "$failed" = "1" ]; then
100+
echo "$(basename $f) failed."
101+
exit 1
102+
fi
103+
}
104+
done

core/NextMap.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ bool NextMapManager::SetNextMap(const char *map)
109109
return true;
110110
}
111111

112+
static char g_nextMap[PLATFORM_MAX_PATH];
113+
112114
#if SOURCE_ENGINE != SE_DARKMESSIAH
113115
void NextMapManager::HookChangeLevel(const char *map, const char *unknown)
114116
#else
@@ -122,8 +124,16 @@ void NextMapManager::HookChangeLevel(const char *map, const char *unknown, const
122124
}
123125

124126
const char *newmap = sm_nextmap.GetString();
127+
if (newmap[0] != '\0') {
128+
ke::SafeStrcpy(g_nextMap, sizeof(g_nextMap), newmap);
129+
newmap = g_nextMap;
130+
131+
// Clear the value so that if the map load fails later we don't get stuck in a loop.
132+
// This might cause us to go off-cycle for a map, but nextmap will get us back on track.
133+
sm_nextmap.SetValue("");
134+
}
125135

126-
if (newmap[0] == 0 || !g_HL2.IsMapValid(newmap))
136+
if (newmap[0] == '\0' || !g_HL2.IsMapValid(newmap))
127137
{
128138
RETURN_META(MRES_IGNORED);
129139
}
@@ -142,6 +152,15 @@ void NextMapManager::HookChangeLevel(const char *map, const char *unknown, const
142152

143153
void NextMapManager::OnSourceModLevelChange( const char *mapName )
144154
{
155+
// If we were controlling the map change, reset sm_nextmap to be the name of the map we successfully changed to.
156+
// This maintains an old API contract on the plugin side. We use the real map name even if it was different from
157+
// the expected map name as if the expected map failed to load we let the game take over instead, but the nextmap
158+
// plugin compares the sm_nextmap value to the current map to decide if it should advance the mapcycle.
159+
if (g_nextMap[0] != '\0') {
160+
sm_nextmap.SetValue(mapName);
161+
g_nextMap[0] = '\0';
162+
}
163+
145164
/* Skip the first 'mapchange' when the server starts up */
146165
if (m_tempChangeInfo.startTime != 0)
147166
{

core/logic/smn_core.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
#include <time.h>
3333
#include <string.h>
3434
#include <stdlib.h>
35+
#include <iomanip>
36+
#include <sstream>
3537
#include <list>
3638
#include "common_logic.h"
3739
#include "Logger.h"
@@ -273,6 +275,49 @@ static cell_t FormatTime(IPluginContext *pContext, const cell_t *params)
273275
return 1;
274276
}
275277

278+
static int ParseTime(IPluginContext *pContext, const cell_t *params)
279+
{
280+
char *datetime;
281+
char *format;
282+
pContext->LocalToStringNULL(params[1], &datetime);
283+
pContext->LocalToStringNULL(params[2], &format);
284+
285+
if (format == NULL)
286+
{
287+
format = const_cast<char *>(bridge->GetCvarString(g_datetime_format));
288+
}
289+
else if (!format[0])
290+
{
291+
return pContext->ThrowNativeError("Time format string cannot be empty.");
292+
}
293+
if (!datetime || !datetime[0])
294+
{
295+
return pContext->ThrowNativeError("Date/time string cannot be empty.");
296+
}
297+
298+
// https://stackoverflow.com/a/33542189
299+
std::tm t{};
300+
std::istringstream input(datetime);
301+
302+
auto previousLocale = input.imbue(std::locale::classic());
303+
input >> std::get_time(&t, format);
304+
bool failed = input.fail();
305+
input.imbue(previousLocale);
306+
307+
if (failed)
308+
{
309+
return pContext->ThrowNativeError("Invalid date/time string or time format.");
310+
}
311+
312+
#if defined PLATFORM_WINDOWS
313+
return _mkgmtime(&t);
314+
#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE
315+
return timegm(&t);
316+
#else
317+
return pContext->ThrowNativeError("Platform has no implemented UTC conversion for std::tm to std::time_t");
318+
#endif
319+
}
320+
276321
static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params)
277322
{
278323
IPluginIterator *iter = scripts->GetPluginIterator();
@@ -1088,6 +1133,7 @@ REGISTER_NATIVES(coreNatives)
10881133
{"ThrowError", ThrowError},
10891134
{"GetTime", GetTime},
10901135
{"FormatTime", FormatTime},
1136+
{"ParseTime", ParseTime},
10911137
{"GetPluginIterator", GetPluginIterator},
10921138
{"MorePlugins", MorePlugins},
10931139
{"ReadPlugin", ReadPlugin},

gamedata/sm-tf2.games.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"StunPlayer"
7575
{
7676
"library" "server"
77-
"windows" "\x55\x8B\xEC\x83\xEC\x20\x57\x8B\xF9\x8B\x87\x54\x04\x00\x00"
77+
"windows" "\x55\x8B\xEC\x83\xEC\x20\x57\x8B\xF9\x8B\x87\xDC\x04\x00\x00"
7878
"linux" "@_ZN15CTFPlayerShared10StunPlayerEffiP9CTFPlayer"
7979
"mac" "@_ZN15CTFPlayerShared10StunPlayerEffiP9CTFPlayer"
8080
}

plugins/include/dhooks.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ native int DHookGamerules(Handle setup, bool post, DHookRemovalCB removalcb=INVA
764764
*
765765
* @param setup Setup handle to use to add the hook.
766766
* @param post true to make the hook a post hook. (If you need to change the return value or need the return
767-
* alue use a post hook! If you need to change params and return use a pre and post hook!)
767+
* value use a post hook! If you need to change params and return use a pre and post hook!)
768768
* @param addr This pointer address.
769769
* @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and
770770
* will call this callback)

plugins/include/float.inc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ stock int RoundFloat(float value)
275275
* User defined operators.
276276
*/
277277
#if !defined __sourcepawn2__
278-
#pragma rational Float
279278
280279
// Internal aliases for backwards compatibility.
281280
native float __FLOAT_MUL__(float a, float b) = FloatMul;

plugins/include/sourcemod.inc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,24 @@ native int GetTime(int bigStamp[2]={0,0});
412412
*/
413413
native void FormatTime(char[] buffer, int maxlength, const char[] format, int stamp=-1);
414414

415+
/**
416+
* Parses a string representing a date and/or time into a unix timestamp.
417+
* The timezone is always interpreted as UTC/GMT.
418+
*
419+
* See this URL for valid parameters:
420+
* https://en.cppreference.com/w/cpp/io/manip/get_time
421+
*
422+
* Note that available parameters depends on support from your operating system.
423+
* In particular, ones highlighted in yellow on that page are not currently
424+
* available on Windows and should be avoided for portable plugins.
425+
*
426+
* @param dateTime Date and/or time string.
427+
* @param format Formatting rules (passing NULL_STRING will use the rules defined in sm_datetime_format).
428+
* @return 32bit timestamp (number of seconds since unix epoch).
429+
* @error Invalid date/time string or time format.
430+
*/
431+
native int ParseTime(const char[] dateTime, const char[] format);
432+
415433
/**
416434
* Loads a game config file.
417435
*

plugins/include/testing.inc

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ stock void SetTestContext(const char[] context)
3838
strcopy(TestContext, sizeof(TestContext), context);
3939
}
4040

41-
stock void AssertEq(const char[] text, int cell1, int cell2)
41+
stock void AssertEq(const char[] text, any cell1, any cell2)
4242
{
4343
TestNumber++;
4444
if (cell1 == cell2)
@@ -52,6 +52,39 @@ stock void AssertEq(const char[] text, int cell1, int cell2)
5252
}
5353
}
5454

55+
stock void AssertArrayEq(const char[] text, const any[] value, const any[] expected, int len)
56+
{
57+
TestNumber++;
58+
for (int i = 0; i < len; ++i)
59+
{
60+
if (value[i] != expected[i])
61+
{
62+
PrintToServer("[%d] %s FAIL: %s should be %d at index %d, got %d", TestNumber, TestContext, text, expected[i], i, value[i]);
63+
ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext);
64+
break;
65+
}
66+
}
67+
PrintToServer("[%d] %s: '%s' arrays are equal OK", TestNumber, TestContext, text);
68+
}
69+
70+
stock void AssertArray2DEq(const char[] text, const any[][] value, const any[][] expected, int len, int innerlen)
71+
{
72+
TestNumber++;
73+
for (int i=0; i < len; ++i)
74+
{
75+
for (int j=0; j < innerlen; ++j)
76+
{
77+
if (value[i][j] != expected[i][j])
78+
{
79+
PrintToServer("[%d] %s FAIL: %s should be %d at index [%d][%d], got %d", TestNumber, TestContext, text, expected[i][j], i, j, value[i][j]);
80+
ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext);
81+
break;
82+
}
83+
}
84+
}
85+
PrintToServer("[%d] %s: '%s' 2D arrays are equal OK", TestNumber, TestContext, text);
86+
}
87+
5588
stock void AssertFalse(const char[] text, bool value)
5689
{
5790
TestNumber++;
@@ -93,3 +126,18 @@ stock void AssertStrEq(const char[] text, const char[] value, const char[] expec
93126
ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext);
94127
}
95128
}
129+
130+
stock void AssertStrArrayEq(const char[] text, const char[][] value, const char[][] expected, int len)
131+
{
132+
TestNumber++;
133+
for (int i = 0; i < len; ++i)
134+
{
135+
if (!StrEqual(value[i], expected[i]))
136+
{
137+
PrintToServer("[%d] %s FAIL: %s should be '%s' at index %d, got '%s'", TestNumber, TestContext, text, expected[i], i, value[i]);
138+
ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext);
139+
break;
140+
}
141+
}
142+
PrintToServer("[%d] %s: '%s' arrays are equal OK", TestNumber, TestContext, text);
143+
}

plugins/include/tf2.inc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ enum TFCond
208208
TFCond_LostFooting, //126: Less ground friction
209209
TFCond_AirCurrent, //127: Reduced air control and friction
210210
TFCond_HalloweenHellHeal, // 128: Used when a player gets teleported to hell
211-
TFCond_PowerupModeDominant // 129: Reduces effects of certain powerups
211+
TFCond_PowerupModeDominant, // 129: Reduces effects of certain powerups
212+
TFCond_ImmuneToPushback // 130: Player is immune to pushback effects
212213
};
213214

214215
const float TFCondDuration_Infinite = -1.0;

plugins/nextmap.sp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ public void OnPluginStart()
8888
RegAdminCmd("sm_maphistory", Command_MapHistory, ADMFLAG_CHANGEMAP, "Shows the most recent maps played");
8989
RegConsoleCmd("listmaps", Command_List);
9090

91-
// Set to the current map so OnMapStart() will know what to do
91+
HookEventEx("server_changelevel_failed", OnChangelevelFailed, EventHookMode_Pre);
92+
93+
// Set to the current map so OnConfigsExecuted() will know what to do
9294
char currentMap[PLATFORM_MAX_PATH];
9395
GetCurrentMap(currentMap, sizeof(currentMap));
9496
SetNextMap(currentMap);
@@ -110,10 +112,18 @@ public void OnConfigsExecuted()
110112
// not in mapcyclefile. So we keep it set to the last expected nextmap. - ferret
111113
if (strcmp(lastMap, currentMap) == 0)
112114
{
113-
FindAndSetNextMap();
115+
FindAndSetNextMap(currentMap);
114116
}
115117
}
116118

119+
public void OnChangelevelFailed(Event event, const char[] name, bool dontBroadcast)
120+
{
121+
char failedMap[PLATFORM_MAX_PATH];
122+
event.GetString("levelname", failedMap, sizeof(failedMap));
123+
124+
FindAndSetNextMap(failedMap);
125+
}
126+
117127
public Action Command_List(int client, int args)
118128
{
119129
PrintToConsole(client, "Map Cycle:");
@@ -129,7 +139,7 @@ public Action Command_List(int client, int args)
129139
return Plugin_Handled;
130140
}
131141

132-
void FindAndSetNextMap()
142+
void FindAndSetNextMap(char[] currentMap)
133143
{
134144
if (ReadMapList(g_MapList,
135145
g_MapListSerial,
@@ -149,14 +159,11 @@ void FindAndSetNextMap()
149159

150160
if (g_MapPos == -1)
151161
{
152-
char current[PLATFORM_MAX_PATH];
153-
GetCurrentMap(current, sizeof(current));
154-
155162
for (int i = 0; i < mapCount; i++)
156163
{
157164
g_MapList.GetString(i, mapName, sizeof(mapName));
158165
if (FindMap(mapName, mapName, sizeof(mapName)) != FindMap_NotFound &&
159-
strcmp(current, mapName, false) == 0)
166+
strcmp(currentMap, mapName, false) == 0)
160167
{
161168
g_MapPos = i;
162169
break;

0 commit comments

Comments
 (0)