From f40ae82df4235b7c8aa2a5eece93c162d2f2c238 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 3 Jul 2023 19:48:01 +0100 Subject: [PATCH 1/8] Clear sm_nextmap so we don't get stuck in a loop (#1545) * Pure C++ solution * Pure SourcePawn solution --- core/NextMap.cpp | 21 ++++++++++++++++++++- plugins/nextmap.sp | 21 ++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/core/NextMap.cpp b/core/NextMap.cpp index 41dcb91e52..16b6b25a3d 100644 --- a/core/NextMap.cpp +++ b/core/NextMap.cpp @@ -109,6 +109,8 @@ bool NextMapManager::SetNextMap(const char *map) return true; } +static char g_nextMap[PLATFORM_MAX_PATH]; + #if SOURCE_ENGINE != SE_DARKMESSIAH void NextMapManager::HookChangeLevel(const char *map, const char *unknown) #else @@ -122,8 +124,16 @@ void NextMapManager::HookChangeLevel(const char *map, const char *unknown, const } const char *newmap = sm_nextmap.GetString(); + if (newmap[0] != '\0') { + ke::SafeStrcpy(g_nextMap, sizeof(g_nextMap), newmap); + newmap = g_nextMap; + + // Clear the value so that if the map load fails later we don't get stuck in a loop. + // This might cause us to go off-cycle for a map, but nextmap will get us back on track. + sm_nextmap.SetValue(""); + } - if (newmap[0] == 0 || !g_HL2.IsMapValid(newmap)) + if (newmap[0] == '\0' || !g_HL2.IsMapValid(newmap)) { RETURN_META(MRES_IGNORED); } @@ -142,6 +152,15 @@ void NextMapManager::HookChangeLevel(const char *map, const char *unknown, const void NextMapManager::OnSourceModLevelChange( const char *mapName ) { + // If we were controlling the map change, reset sm_nextmap to be the name of the map we successfully changed to. + // This maintains an old API contract on the plugin side. We use the real map name even if it was different from + // the expected map name as if the expected map failed to load we let the game take over instead, but the nextmap + // plugin compares the sm_nextmap value to the current map to decide if it should advance the mapcycle. + if (g_nextMap[0] != '\0') { + sm_nextmap.SetValue(mapName); + g_nextMap[0] = '\0'; + } + /* Skip the first 'mapchange' when the server starts up */ if (m_tempChangeInfo.startTime != 0) { diff --git a/plugins/nextmap.sp b/plugins/nextmap.sp index e793ad6b76..bbd0efb0b1 100644 --- a/plugins/nextmap.sp +++ b/plugins/nextmap.sp @@ -88,7 +88,9 @@ public void OnPluginStart() RegAdminCmd("sm_maphistory", Command_MapHistory, ADMFLAG_CHANGEMAP, "Shows the most recent maps played"); RegConsoleCmd("listmaps", Command_List); - // Set to the current map so OnMapStart() will know what to do + HookEventEx("server_changelevel_failed", OnChangelevelFailed, EventHookMode_Pre); + + // Set to the current map so OnConfigsExecuted() will know what to do char currentMap[PLATFORM_MAX_PATH]; GetCurrentMap(currentMap, sizeof(currentMap)); SetNextMap(currentMap); @@ -110,10 +112,18 @@ public void OnConfigsExecuted() // not in mapcyclefile. So we keep it set to the last expected nextmap. - ferret if (strcmp(lastMap, currentMap) == 0) { - FindAndSetNextMap(); + FindAndSetNextMap(currentMap); } } +public void OnChangelevelFailed(Event event, const char[] name, bool dontBroadcast) +{ + char failedMap[PLATFORM_MAX_PATH]; + event.GetString("levelname", failedMap, sizeof(failedMap)); + + FindAndSetNextMap(failedMap); +} + public Action Command_List(int client, int args) { PrintToConsole(client, "Map Cycle:"); @@ -129,7 +139,7 @@ public Action Command_List(int client, int args) return Plugin_Handled; } -void FindAndSetNextMap() +void FindAndSetNextMap(char[] currentMap) { if (ReadMapList(g_MapList, g_MapListSerial, @@ -149,14 +159,11 @@ void FindAndSetNextMap() if (g_MapPos == -1) { - char current[PLATFORM_MAX_PATH]; - GetCurrentMap(current, sizeof(current)); - for (int i = 0; i < mapCount; i++) { g_MapList.GetString(i, mapName, sizeof(mapName)); if (FindMap(mapName, mapName, sizeof(mapName)) != FindMap_NotFound && - strcmp(current, mapName, false) == 0) + strcmp(currentMap, mapName, false) == 0) { g_MapPos = i; break; From 5addaffa5665f353c874f45505914ab692535c24 Mon Sep 17 00:00:00 2001 From: Corey D Date: Tue, 4 Jul 2023 21:42:45 +1000 Subject: [PATCH 2/8] Add ParseTime (strptime) native (#1697) * Add GetTimeStamp (strptime) native * Fix description * Fix typos * Fix issues * Rewrite ParseTime * Fix docstrings * Fix ParseTime * Clarify docs --- core/logic/smn_core.cpp | 46 +++++++++++++++++++++++++++++++++++ plugins/include/sourcemod.inc | 18 ++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index 490a6a00da..8e90d40310 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include "common_logic.h" #include "Logger.h" @@ -273,6 +275,49 @@ static cell_t FormatTime(IPluginContext *pContext, const cell_t *params) return 1; } +static int ParseTime(IPluginContext *pContext, const cell_t *params) +{ + char *datetime; + char *format; + pContext->LocalToStringNULL(params[1], &datetime); + pContext->LocalToStringNULL(params[2], &format); + + if (format == NULL) + { + format = const_cast(bridge->GetCvarString(g_datetime_format)); + } + else if (!format[0]) + { + return pContext->ThrowNativeError("Time format string cannot be empty."); + } + if (!datetime || !datetime[0]) + { + return pContext->ThrowNativeError("Date/time string cannot be empty."); + } + + // https://stackoverflow.com/a/33542189 + std::tm t{}; + std::istringstream input(datetime); + + auto previousLocale = input.imbue(std::locale::classic()); + input >> std::get_time(&t, format); + bool failed = input.fail(); + input.imbue(previousLocale); + + if (failed) + { + return pContext->ThrowNativeError("Invalid date/time string or time format."); + } + +#if defined PLATFORM_WINDOWS + return _mkgmtime(&t); +#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE + return timegm(&t); +#else + return pContext->ThrowNativeError("Platform has no implemented UTC conversion for std::tm to std::time_t"); +#endif +} + static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params) { IPluginIterator *iter = scripts->GetPluginIterator(); @@ -1088,6 +1133,7 @@ REGISTER_NATIVES(coreNatives) {"ThrowError", ThrowError}, {"GetTime", GetTime}, {"FormatTime", FormatTime}, + {"ParseTime", ParseTime}, {"GetPluginIterator", GetPluginIterator}, {"MorePlugins", MorePlugins}, {"ReadPlugin", ReadPlugin}, diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 876a6d6834..1927015680 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -412,6 +412,24 @@ native int GetTime(int bigStamp[2]={0,0}); */ native void FormatTime(char[] buffer, int maxlength, const char[] format, int stamp=-1); +/** + * Parses a string representing a date and/or time into a unix timestamp. + * The timezone is always interpreted as UTC/GMT. + * + * See this URL for valid parameters: + * https://en.cppreference.com/w/cpp/io/manip/get_time + * + * Note that available parameters depends on support from your operating system. + * In particular, ones highlighted in yellow on that page are not currently + * available on Windows and should be avoided for portable plugins. + * + * @param dateTime Date and/or time string. + * @param format Formatting rules (passing NULL_STRING will use the rules defined in sm_datetime_format). + * @return 32bit timestamp (number of seconds since unix epoch). + * @error Invalid date/time string or time format. + */ +native int ParseTime(const char[] dateTime, const char[] format); + /** * Loads a game config file. * From 46e3de8d24fc8f57177361f52e0efb17fa0c9ac8 Mon Sep 17 00:00:00 2001 From: peace-maker Date: Wed, 12 Jul 2023 13:38:12 +0200 Subject: [PATCH 3/8] Fix minor translation typos (#2017) No need to know the languages for those. --- translations/cze/mapchooser.phrases.txt | 2 +- translations/jp/plugin.basecommands.txt | 2 +- translations/ko/common.phrases.txt | 2 +- translations/lv/nominations.phrases.txt | 1 - translations/ro/common.phrases.txt | 8 ++++---- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/translations/cze/mapchooser.phrases.txt b/translations/cze/mapchooser.phrases.txt index 2f76ca58ea..18e77e345a 100644 --- a/translations/cze/mapchooser.phrases.txt +++ b/translations/cze/mapchooser.phrases.txt @@ -42,7 +42,7 @@ "Starting Runoff" { - "cze" "Zadna mapa neziskala {1]%% hlasu ({2} [{3}%%] & {4} [{5}%%]), zacina rozhodujici hlasovani." + "cze" "Zadna mapa neziskala {1}%% hlasu ({2} [{3}%%] & {4} [{5}%%]), zacina rozhodujici hlasovani." } } diff --git a/translations/jp/plugin.basecommands.txt b/translations/jp/plugin.basecommands.txt index 300d1861fc..292b83c5cb 100644 --- a/translations/jp/plugin.basecommands.txt +++ b/translations/jp/plugin.basecommands.txt @@ -12,7 +12,7 @@ "Value of cvar" { - "jp" "CVar \"{1]\" の値: \"{2}\"" + "jp" "CVar \"{1}\" の値: \"{2}\"" } "Cvar changed" diff --git a/translations/ko/common.phrases.txt b/translations/ko/common.phrases.txt index 82073e0c63..dedcd7a1a6 100644 --- a/translations/ko/common.phrases.txt +++ b/translations/ko/common.phrases.txt @@ -232,7 +232,7 @@ "Say all" { - "ko" "(전체 채팅) { 1 }" + "ko" "(전체 채팅) {1}" } "Chat admins" diff --git a/translations/lv/nominations.phrases.txt b/translations/lv/nominations.phrases.txt index 67f5800e8b..84ec5d8066 100644 --- a/translations/lv/nominations.phrases.txt +++ b/translations/lv/nominations.phrases.txt @@ -67,7 +67,6 @@ "Map Not In Pool" { - "#format" "{1:s}" "lv" "Karte '{1}' nav pieejama nomināciju sarakstā." } diff --git a/translations/ro/common.phrases.txt b/translations/ro/common.phrases.txt index b3c0c0bfc5..2bed676aab 100644 --- a/translations/ro/common.phrases.txt +++ b/translations/ro/common.phrases.txt @@ -232,22 +232,22 @@ "Say all" { - "ro" "Say tuturor" + "ro" "(Say tuturor) {1}" } "Chat admins" { - "ro" "Chat admins" + "ro" "(Chat admins) {1}" } "Chat to admins" { - "ro" "Chat cu admins" + "ro" "(Chat cu admins) {1}" } "Private say to" { - "ro" "Say privat lui" + "ro" "(Say privat lui {1}) {2}" } } From cb8541548b83cd8e292a93d75fe8bc68dba8542d Mon Sep 17 00:00:00 2001 From: peace-maker Date: Wed, 12 Jul 2023 22:55:40 +0200 Subject: [PATCH 4/8] Update sourcepawn (#2011) * Update sourcepawn * Remove #pragma rational Float --- plugins/include/float.inc | 1 - sourcepawn | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/include/float.inc b/plugins/include/float.inc index 9650489d7e..4a769767e7 100644 --- a/plugins/include/float.inc +++ b/plugins/include/float.inc @@ -275,7 +275,6 @@ stock int RoundFloat(float value) * User defined operators. */ #if !defined __sourcepawn2__ -#pragma rational Float // Internal aliases for backwards compatibility. native float __FLOAT_MUL__(float a, float b) = FloatMul; diff --git a/sourcepawn b/sourcepawn index 9cf9f31d45..508a64c72a 160000 --- a/sourcepawn +++ b/sourcepawn @@ -1 +1 @@ -Subproject commit 9cf9f31d4560fe7e76c6be75a2245088d03a3937 +Subproject commit 508a64c72ab31a3c23e9dec80f812a75ce1ee129 From 2ef874dbc98b4f04ba9a6078e8db48ec17852556 Mon Sep 17 00:00:00 2001 From: peace-maker Date: Wed, 12 Jul 2023 22:58:28 +0200 Subject: [PATCH 5/8] Run basic native tests using hl2sdk-mock in CI (#2015) This tests the sorting.inc API similar to the existing test plugin in the plugins/testsuite folder. Tests fail if the srcds output contains "FAIL". --- .github/workflows/mocktest.yml | 104 +++++++++ plugins/include/testing.inc | 50 +++- plugins/testsuite/mock/test_sorting.sp | 309 +++++++++++++++++++++++++ 3 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/mocktest.yml create mode 100644 plugins/testsuite/mock/test_sorting.sp diff --git a/.github/workflows/mocktest.yml b/.github/workflows/mocktest.yml new file mode 100644 index 0000000000..32cd66397d --- /dev/null +++ b/.github/workflows/mocktest.yml @@ -0,0 +1,104 @@ +name: hl2sdk-mock tests +on: + push: + branches: + - master + - '[0-9]+.[0-9]+-dev' + pull_request: + branches: + - master + - '[0-9]+.[0-9]+-dev' +jobs: + mock: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: Clone sourcemod + with: + submodules: recursive + path: sourcemod + + - uses: actions/checkout@v3 + name: Clone metamod-source + with: + repository: alliedmodders/metamod-source + submodules: recursive + path: metamod-source + + - uses: actions/checkout@v3 + name: Clone hl2sdk-mock + with: + repository: alliedmodders/hl2sdk-mock + submodules: recursive + path: hl2sdk-mock + + - uses: actions/setup-python@v4 + name: Setup Python 3.10 + with: + python-version: "3.10" + + - name: Install AMBuild + run: | + python -m pip install --upgrade pip setuptools wheel + pip install git+https://github.com/alliedmodders/ambuild + + - name: Build MetaMod:Source + working-directory: metamod-source + run: | + python configure.py --enable-optimize --sdks=mock --targets=x86_64 + ambuild objdir + + - name: Build SourceMod + working-directory: sourcemod + run: | + python configure.py --no-mysql --enable-optimize --sdks=mock --targets=x86_64 + ambuild objdir + + - name: Build hl2sdk-mock + working-directory: hl2sdk-mock + run: | + python configure.py --enable-optimize --targets=x86_64 + ambuild objdir + + - name: Setup gamedir + working-directory: hl2sdk-mock + shell: bash + run: | + mkdir ../gamedir + ./build_gamedir.sh ../gamedir ../metamod-source/objdir/package + ./build_gamedir.sh ../gamedir ../sourcemod/objdir/package + + - name: Compile testsuite + working-directory: hl2sdk-mock + shell: bash + run: | + mkdir ../gamedir/addons/sourcemod/plugins/optional + + for f in ../sourcemod/plugins/testsuite/mock/*.sp; do + echo "Compiling $(basename $f)" + ../gamedir/addons/sourcemod/scripting/spcomp64 -i ../gamedir/addons/sourcemod/scripting/include -o "../gamedir/addons/sourcemod/plugins/optional/$(basename $f .sp).smx" -E "$f" + done + + - name: Test + working-directory: hl2sdk-mock + shell: bash + run: | + for f in ../gamedir/addons/sourcemod/plugins/optional/*.smx; do + echo "===================================" + echo "Running $(basename $f)..." + echo "===================================" + timeout 60 ./objdir/dist/x86_64/srcds -game_dir ../gamedir +map de_thunder -command "sm plugins load optional/$(basename $f)" -run -run-ticks 20 | + { + failed=0 + while IFS= read -r line; do + echo "$line" + if [[ "$line" == *"FAIL"* ]]; then + failed=1 + fi + done + if [ "$failed" = "1" ]; then + echo "$(basename $f) failed." + exit 1 + fi + } + done diff --git a/plugins/include/testing.inc b/plugins/include/testing.inc index d9db40d103..6ff6d73201 100644 --- a/plugins/include/testing.inc +++ b/plugins/include/testing.inc @@ -38,7 +38,7 @@ stock void SetTestContext(const char[] context) strcopy(TestContext, sizeof(TestContext), context); } -stock void AssertEq(const char[] text, int cell1, int cell2) +stock void AssertEq(const char[] text, any cell1, any cell2) { TestNumber++; if (cell1 == cell2) @@ -52,6 +52,39 @@ stock void AssertEq(const char[] text, int cell1, int cell2) } } +stock void AssertArrayEq(const char[] text, const any[] value, const any[] expected, int len) +{ + TestNumber++; + for (int i = 0; i < len; ++i) + { + if (value[i] != expected[i]) + { + PrintToServer("[%d] %s FAIL: %s should be %d at index %d, got %d", TestNumber, TestContext, text, expected[i], i, value[i]); + ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext); + break; + } + } + PrintToServer("[%d] %s: '%s' arrays are equal OK", TestNumber, TestContext, text); +} + +stock void AssertArray2DEq(const char[] text, const any[][] value, const any[][] expected, int len, int innerlen) +{ + TestNumber++; + for (int i=0; i < len; ++i) + { + for (int j=0; j < innerlen; ++j) + { + if (value[i][j] != expected[i][j]) + { + 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]); + ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext); + break; + } + } + } + PrintToServer("[%d] %s: '%s' 2D arrays are equal OK", TestNumber, TestContext, text); +} + stock void AssertFalse(const char[] text, bool value) { TestNumber++; @@ -93,3 +126,18 @@ stock void AssertStrEq(const char[] text, const char[] value, const char[] expec ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext); } } + +stock void AssertStrArrayEq(const char[] text, const char[][] value, const char[][] expected, int len) +{ + TestNumber++; + for (int i = 0; i < len; ++i) + { + if (!StrEqual(value[i], expected[i])) + { + PrintToServer("[%d] %s FAIL: %s should be '%s' at index %d, got '%s'", TestNumber, TestContext, text, expected[i], i, value[i]); + ThrowError("test %d (%s in %s) failed", TestNumber, text, TestContext); + break; + } + } + PrintToServer("[%d] %s: '%s' arrays are equal OK", TestNumber, TestContext, text); +} diff --git a/plugins/testsuite/mock/test_sorting.sp b/plugins/testsuite/mock/test_sorting.sp new file mode 100644 index 0000000000..a875c23ca1 --- /dev/null +++ b/plugins/testsuite/mock/test_sorting.sp @@ -0,0 +1,309 @@ +#pragma semicolon 1 +#pragma newdecls required +#include + +public Plugin myinfo = +{ + name = "Sorting Test", + author = "AlliedModders LLC", + description = "Tests sorting functions", + version = "1.0.0.0", + url = "https://www.sourcemod.net/" +}; + +public void OnPluginStart() { + SetTestContext("Sorting Test"); + Test_SortIntegers(); + Test_SortFloats(); + Test_SortStrings(); + Test_Sort1D(); + Test_Sort2D(); + Test_SortSortADTArrayIntegers(); + Test_SortSortADTArrayFloats(); + Test_SortSortADTArrayStrings(); + Test_SortADTCustom(); +} + +void Test_SortIntegers() { + int array[] = {6, 7, 3, 2, 8, 5, 0, 1, 4, 9}; + SortIntegers(array, sizeof(array), Sort_Ascending); + AssertArrayEq("SortIntegers Sort_Ascending", array, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, sizeof(array)); + SortIntegers(array, sizeof(array), Sort_Descending); + AssertArrayEq("SortIntegers Sort_Descending", array, {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, sizeof(array)); +} + +void Test_SortFloats() { + float array[] = {6.3, 7.6, 3.2, 2.1, 8.5, 5.2, 0.4, 1.7, 4.8, 8.2}; + SortFloats(array, sizeof(array), Sort_Ascending); + AssertArrayEq("SortFloats Sort_Ascending", array, {0.4, 1.7, 2.1, 3.2, 4.8, 5.2, 6.3, 7.6, 8.2, 8.5}, sizeof(array)); + SortFloats(array, sizeof(array), Sort_Descending); + AssertArrayEq("SortFloats Sort_Descending", array, {8.5, 8.2, 7.6, 6.3, 5.2, 4.8, 3.2, 2.1, 1.7, 0.4}, sizeof(array)); +} + +void Test_SortStrings() { + char strarray[][] = { + "faluco", + "bailopan", + "pm onoto", + "damaged soul", + "sniperbeamer", + "sidluke", + "johnny got his gun", + "gabe newell", + "pRED*'s awesome", + "WHAT?!" + }; + char expected_ascending[][] = { + "WHAT?!", + "bailopan", + "damaged soul", + "faluco", + "gabe newell", + "johnny got his gun", + "pRED*'s awesome", + "pm onoto", + "sidluke", + "sniperbeamer" + }; + SortStrings(strarray, sizeof(strarray), Sort_Ascending); + AssertStrArrayEq("SortStrings Sort_Ascending", strarray, expected_ascending, sizeof(strarray)); + char expected_descending[][] = { + "sniperbeamer", + "sidluke", + "pm onoto", + "pRED*'s awesome", + "johnny got his gun", + "gabe newell", + "faluco", + "damaged soul", + "bailopan", + "WHAT?!", + }; + SortStrings(strarray, sizeof(strarray), Sort_Descending); + AssertStrArrayEq("SortStrings Sort_Descending", strarray, expected_descending, sizeof(strarray)); +} + +int Custom1DSort(int elem1, int elem2, const int[] array, Handle hndl) { + return elem1 - elem2; +} + +int Custom1DSortFloat(int elem1, int elem2, const int[] array, Handle hndl) { + float f1 = view_as(elem1); + float f2 = view_as(elem2); + if (f1 > f2) + { + return 1; + } else if (f1 < f2) { + return -1; + } + return 0; +} + +void Test_Sort1D() { + int intArray[10] = {6, 7, 3, 2, 8, 5, 0, 1, 4, 9}; + SortCustom1D(intArray, sizeof(intArray), Custom1DSort); + AssertArrayEq("SortCustom1D Integers Ascending", intArray, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, sizeof(intArray)); + + float floatArray[10] = {6.3, 7.6, 3.2, 2.1, 8.5, 5.2, 0.4, 1.7, 4.8, 8.2}; + SortCustom1D(view_as(floatArray), sizeof(floatArray), Custom1DSortFloat); + AssertArrayEq("SortCustom1D Floats Ascending", floatArray, {0.4, 1.7, 2.1, 3.2, 4.8, 5.2, 6.3, 7.6, 8.2, 8.5}, sizeof(floatArray)); +} + +#define SUB_ARRAY_SIZE 4 +int Custom2DSortInteger(int[] elem1, int[] elem2, const int[][] array, Handle hndl) +{ + int sum1, sum2; + for (int i = 0; i < SUB_ARRAY_SIZE; ++i) { + sum1 += elem1[i]; + sum2 += elem2[i]; + } + return sum1 - sum2; +} + +int Custom2DSortString(char[] elem1, char[] elem2, const char[][] array, Handle hndl) +{ + return strcmp(elem1, elem2); +} + +void Test_Sort2D() { + int array[][SUB_ARRAY_SIZE] = { + { 5, 9, 2, 3 }, + { 10, 1, 24, 5 }, + { 1, 2, 3, 0} + }; + int expected[][SUB_ARRAY_SIZE] = { + { 1, 2, 3, 0}, + { 5, 9, 2, 3 }, + { 10, 1, 24, 5 } + }; + SortCustom2D(array, sizeof(array), Custom2DSortInteger); + AssertArray2DEq("SortCustom2D int[][] Ascending", array, expected, sizeof(array), sizeof(array[])); + + char strarray[][] = { + "faluco", + "bailopan", + "pm onoto", + "damaged soul", + "sniperbeamer", + "sidluke", + "johnny got his gun", + "gabe newell", + "pRED*'s awesome", + "WHAT?!" + }; + char expected_ascending[][] = { + "WHAT?!", + "bailopan", + "damaged soul", + "faluco", + "gabe newell", + "johnny got his gun", + "pRED*'s awesome", + "pm onoto", + "sidluke", + "sniperbeamer" + }; + SortCustom2D(view_as(strarray), sizeof(strarray), Custom2DSortString); + AssertStrArrayEq("SortCustom2D char[][] Ascending", strarray, expected_ascending, sizeof(strarray)); +} + +void Test_SortSortADTArrayIntegers() { + ArrayList arraylist = new ArrayList(); + arraylist.Push(6); + arraylist.Push(7); + arraylist.Push(3); + arraylist.Push(2); + arraylist.Push(8); + arraylist.Push(5); + arraylist.Push(0); + arraylist.Push(4); + arraylist.Push(9); + arraylist.Push(1); + + arraylist.Sort(Sort_Ascending, Sort_Integer); + int[] array1 = new int[arraylist.Length]; + for (int i = 0; i < arraylist.Length; ++i) { + array1[i] = arraylist.Get(i); + } + AssertArrayEq("SortADTArray Integers Ascending", array1, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, arraylist.Length); + + arraylist.Sort(Sort_Descending, Sort_Integer); + int[] array2 = new int[arraylist.Length]; + for (int i = 0; i < arraylist.Length; ++i) { + array2[i] = arraylist.Get(i); + } + AssertArrayEq("SortADTArray Integers Descending", array2, {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, arraylist.Length); +} + +void Test_SortSortADTArrayFloats() { + ArrayList arraylist = new ArrayList(); + arraylist.Push(6.3); + arraylist.Push(7.2); + arraylist.Push(3.9); + arraylist.Push(2.0); + arraylist.Push(8.1); + arraylist.Push(5.1); + arraylist.Push(0.6); + arraylist.Push(4.2); + arraylist.Push(9.5); + arraylist.Push(1.7); + + arraylist.Sort(Sort_Ascending, Sort_Float); + float[] array1 = new float[arraylist.Length]; + for (int i = 0; i < arraylist.Length; ++i) { + array1[i] = arraylist.Get(i); + } + AssertArrayEq("SortADTArray Float Ascending", array1, {0.6, 1.7, 2.0, 3.9, 4.2, 5.1, 6.3, 7.2, 8.1, 9.5}, arraylist.Length); + + arraylist.Sort(Sort_Descending, Sort_Float); + float[] array2 = new float[arraylist.Length]; + for (int i = 0; i < arraylist.Length; ++i) { + array2[i] = arraylist.Get(i); + } + AssertArrayEq("SortADTArray Float Descending", array2, {9.5, 8.1, 7.2, 6.3, 5.1, 4.2, 3.9, 2.0, 1.7, 0.6}, arraylist.Length); +} + +void Test_SortSortADTArrayStrings() { + ArrayList arraylist = new ArrayList(ByteCountToCells(64)); + arraylist.PushString("faluco"); + arraylist.PushString("bailopan"); + arraylist.PushString("pm onoto"); + arraylist.PushString("damaged soul"); + arraylist.PushString("sniperbeamer"); + arraylist.PushString("sidluke"); + arraylist.PushString("johnny got his gun"); + arraylist.PushString("gabe newell"); + arraylist.PushString("Hello pRED*"); + arraylist.PushString("WHAT?!"); + + char expected_ascending[][] = { + "Hello pRED*", + "WHAT?!", + "bailopan", + "damaged soul", + "faluco", + "gabe newell", + "johnny got his gun", + "pm onoto", + "sidluke", + "sniperbeamer" + }; + arraylist.Sort(Sort_Ascending, Sort_String); + char[][] array1 = new char[arraylist.Length][64]; + for (int i = 0; i < arraylist.Length; ++i) { + arraylist.GetString(i, array1[i], 64); + } + AssertStrArrayEq("SortADTArray String Ascending", array1, expected_ascending, arraylist.Length); + + char expected_descending[][] = { + "sniperbeamer", + "sidluke", + "pm onoto", + "johnny got his gun", + "gabe newell", + "faluco", + "damaged soul", + "bailopan", + "WHAT?!", + "Hello pRED*" + }; + arraylist.Sort(Sort_Descending, Sort_String); + char[][] array2 = new char[arraylist.Length][64]; + for (int i = 0; i < arraylist.Length; ++i) { + arraylist.GetString(i, array2[i], 64); + } + AssertStrArrayEq("SortADTArray String Descending", array2, expected_descending, arraylist.Length); +} + +enum struct Testy { + int val; + float fal; +} + +int ArrayADTCustomCallback(int index1, int index2, ArrayList array, Handle hndl) { + Testy testy1, testy2; + array.GetArray(index1, testy1); + array.GetArray(index2, testy2); + return testy1.val - testy2.val; +} + +void Test_SortADTCustom() { + ArrayList arraylist = new ArrayList(sizeof(Testy)); + Testy testy1 = {11, 2.9}; + Testy testy2 = {5995, 0.0}; + Testy testy3 = {20, 66.0001}; + Testy testy4 = {185, -205.2}; + arraylist.PushArray(testy1); + arraylist.PushArray(testy2); + arraylist.PushArray(testy3); + arraylist.PushArray(testy4); + + arraylist.SortCustom(ArrayADTCustomCallback); + int[] array = new int[arraylist.Length]; + Testy temptesty; + for (int i = 0; i < arraylist.Length; ++i) { + arraylist.GetArray(i, temptesty); + array[i] = temptesty.val; + } + AssertArrayEq("SortADTCustom enum struct Ascending", array, {11, 20, 185, 5995}, arraylist.Length); +} From 7170a8f11cf67d396cce047515f873ec7cd29c38 Mon Sep 17 00:00:00 2001 From: rtldg <55846624+rtldg@users.noreply.github.com> Date: Fri, 21 Jul 2023 07:58:14 +0000 Subject: [PATCH 6/8] Fix typo in DHookRaw docs (#2020) --- plugins/include/dhooks.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/include/dhooks.inc b/plugins/include/dhooks.inc index 76ff8c4de3..99f17e7e8f 100644 --- a/plugins/include/dhooks.inc +++ b/plugins/include/dhooks.inc @@ -764,7 +764,7 @@ native int DHookGamerules(Handle setup, bool post, DHookRemovalCB removalcb=INVA * * @param setup Setup handle to use to add the hook. * @param post true to make the hook a post hook. (If you need to change the return value or need the return - * alue use a post hook! If you need to change params and return use a pre and post hook!) + * value use a post hook! If you need to change params and return use a pre and post hook!) * @param addr This pointer address. * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and * will call this callback) From 0dafe0489967ca3ca26e493329d64e203425da08 Mon Sep 17 00:00:00 2001 From: Mikusch Date: Wed, 26 Jul 2023 04:08:48 +0200 Subject: [PATCH 7/8] TF2: Update StunPlayer signature (#2024) (fixes #2022) --- gamedata/sm-tf2.games.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamedata/sm-tf2.games.txt b/gamedata/sm-tf2.games.txt index c00e993c8e..695e3b05a3 100644 --- a/gamedata/sm-tf2.games.txt +++ b/gamedata/sm-tf2.games.txt @@ -74,7 +74,7 @@ "StunPlayer" { "library" "server" - "windows" "\x55\x8B\xEC\x83\xEC\x20\x57\x8B\xF9\x8B\x87\x54\x04\x00\x00" + "windows" "\x55\x8B\xEC\x83\xEC\x20\x57\x8B\xF9\x8B\x87\xDC\x04\x00\x00" "linux" "@_ZN15CTFPlayerShared10StunPlayerEffiP9CTFPlayer" "mac" "@_ZN15CTFPlayerShared10StunPlayerEffiP9CTFPlayer" } From 531b0530969236d888938c9f41746a4ef7f47788 Mon Sep 17 00:00:00 2001 From: Mikusch Date: Wed, 26 Jul 2023 04:10:08 +0200 Subject: [PATCH 8/8] TF2: Add TFCond_ImmuneToPushback condition (#2023) --- plugins/include/tf2.inc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/include/tf2.inc b/plugins/include/tf2.inc index 16c8bcbe5a..d05e1dc68b 100644 --- a/plugins/include/tf2.inc +++ b/plugins/include/tf2.inc @@ -208,7 +208,8 @@ enum TFCond TFCond_LostFooting, //126: Less ground friction TFCond_AirCurrent, //127: Reduced air control and friction TFCond_HalloweenHellHeal, // 128: Used when a player gets teleported to hell - TFCond_PowerupModeDominant // 129: Reduces effects of certain powerups + TFCond_PowerupModeDominant, // 129: Reduces effects of certain powerups + TFCond_ImmuneToPushback // 130: Player is immune to pushback effects }; const float TFCondDuration_Infinite = -1.0;