diff --git a/.gitignore b/.gitignore
index 05c064a..04f4e46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -258,3 +258,5 @@ ModelManifest.xml
/dgamexwrapper/generate_version_info.js
/dgamexwrapper/package.json
/dgamexwrapper/version_info.json
+/tests/logbt240.txt
+/tests/logaa111.txt
diff --git a/.gitmodules b/.gitmodules
index a2db05b..0d2dbf1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -19,6 +19,9 @@
[submodule "libs/curlpp"]
path = dgamexwrapper/libs/curlpp
url = https://github.com/jpbarrette/curlpp.git
+[submodule "dgamexwrapper/libs/RebornSecurityHeaders"]
+ path = dgamexwrapper/libs/RebornSecurityHeaders
+ url = https://github.com/mohabhassan/RebornSecurityHeaders.git
[submodule "tests/libs/mohpc"]
path = tests/libs/mohpc
url = https://github.com/mohabhassan/mohpc.git
diff --git a/dgamexwrapper/src/Animate.h b/dgamexwrapper/BaseHeaders/Animate.h
similarity index 100%
rename from dgamexwrapper/src/Animate.h
rename to dgamexwrapper/BaseHeaders/Animate.h
diff --git a/dgamexwrapper/BaseHeaders/BaseHeaders.vcxitems b/dgamexwrapper/BaseHeaders/BaseHeaders.vcxitems
index 09a9ea2..09f9be6 100644
--- a/dgamexwrapper/BaseHeaders/BaseHeaders.vcxitems
+++ b/dgamexwrapper/BaseHeaders/BaseHeaders.vcxitems
@@ -14,6 +14,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dgamexwrapper/src/Class.h b/dgamexwrapper/BaseHeaders/Class.h
similarity index 80%
rename from dgamexwrapper/src/Class.h
rename to dgamexwrapper/BaseHeaders/Class.h
index 589aa96..cb1af3b 100644
--- a/dgamexwrapper/src/Class.h
+++ b/dgamexwrapper/BaseHeaders/Class.h
@@ -1,6 +1,7 @@
#pragma once
-#include "safeptr.h"
#include "ClassMemory.h"
+
+class SafePtrBase;
/*
* Class Class
* Used as a sub class for the patch.
@@ -13,6 +14,9 @@ class Class
public:
Class();
virtual ~Class();
+#ifndef REBORNSECURITYFIXES_EXPORTS
CLASS_CUSTOM_ALLOCATION;
+#endif
};
+#include "safeptr.h"
diff --git a/dgamexwrapper/src/ClassMemory.h b/dgamexwrapper/BaseHeaders/ClassMemory.h
similarity index 100%
rename from dgamexwrapper/src/ClassMemory.h
rename to dgamexwrapper/BaseHeaders/ClassMemory.h
diff --git a/dgamexwrapper/src/ClientAdmin.h b/dgamexwrapper/BaseHeaders/ClientAdmin.h
similarity index 99%
rename from dgamexwrapper/src/ClientAdmin.h
rename to dgamexwrapper/BaseHeaders/ClientAdmin.h
index 2fed345..97a7c7e 100644
--- a/dgamexwrapper/src/ClientAdmin.h
+++ b/dgamexwrapper/BaseHeaders/ClientAdmin.h
@@ -1,6 +1,5 @@
#pragma once
#include "dgamex86.h"
-#include "sv_misc.h"
#include
#include
using std::string, std::vector, std::list;
@@ -113,6 +112,7 @@ class ClientAdmin
bool attemptLogout();
bool hasRight(AccessLevel right);
+ int getRights();
void AddKick(int kickedClientNum, bool hasReason = false, string reason = "");
void AddBan(int bannedClientNum, bool hasReason = false, string reason = "");
diff --git a/dgamexwrapper/src/Entity.h b/dgamexwrapper/BaseHeaders/Entity.h
similarity index 99%
rename from dgamexwrapper/src/Entity.h
rename to dgamexwrapper/BaseHeaders/Entity.h
index e19b466..c48b814 100644
--- a/dgamexwrapper/src/Entity.h
+++ b/dgamexwrapper/BaseHeaders/Entity.h
@@ -28,7 +28,7 @@ class EntityAA : public SimpleEntity
public:
int entnum;//0
int radnum;//1
- gentity_t* edict;//2
+ gentityAA_t* edict;//2
gclientAA_t* client;//3
int spawnflags;//4
str model;//5
@@ -92,7 +92,7 @@ class EntityDSH : public SimpleEntity
public:
int entnum;//0
int radnum;//1
- gentity_t *edict;//2
+ gentityDSH_t *edict;//2
gclientDSH_t *client;//3
int spawnflags;//4
str model;//5
diff --git a/dgamexwrapper/BaseHeaders/GameVersion.h b/dgamexwrapper/BaseHeaders/GameVersion.h
index 7f90f77..113334a 100644
--- a/dgamexwrapper/BaseHeaders/GameVersion.h
+++ b/dgamexwrapper/BaseHeaders/GameVersion.h
@@ -17,13 +17,21 @@ class GameInfo
string dgamex86Path;
string system86Name;
- inline static unordered_map> md5_game_info = {
- {"5e6dbee412397e0a57be8c9b4592ba55", {AA, Server, 1.11}},
+ inline static unordered_map> md5_exe_info = {
+ {"a8b6296979bcad2af6edc6685ef4d06d", {AA, Server, 1.11}},
{"418deeab51331c7a9e1051b60cba23d5", {BT, Server, 2.30}},
{"ac58a6562cc8f53d2f3452b29902b54d", {BT, Server, 2.40}},
{"1db701ce0f5cf29b8fa479f0898f7d08", {BT, Server, 2.40}},
{"1162c8c26b7e0d687cbdaa2e52e5ae78", {SH, Server, 2.15}}
};
+
+ inline static unordered_map> md5_game_info = {
+ {"6c7731b5259ac0da9c68382bbedc1bbc", {AA, Server, 1.11}},
+ {"16f69d0f22537c4c1718d72506efa988", {BT, Server, 2.30}},
+ {"ebf1898c107418d875e9afe89a45c381", {BT, Server, 2.40}},
+ {"f80ed34d942496cb5778206e4f11ee54", {SH, Server, 2.15}}
+ };
+
void InitMini(Expansion game_expansion, Side game_side, double game_version)
{
gameExpansion = game_expansion;
@@ -78,14 +86,27 @@ class GameInfo
bool InitFromMD5(string& md5)
{
using std::get;
- auto it = md5_game_info.find(md5);
- if (it == md5_game_info.end())
+ auto it = md5_exe_info.find(md5);
+ if (it == md5_exe_info.end())
return false;
auto &tpl = it->second;
Init(get(tpl), get(tpl), get(tpl));
return true;
}
+ int ValidateGameDLLMD5(string& md5)
+ {
+ using std::get;
+ auto it = md5_game_info.find(md5);
+ if (it == md5_game_info.end())
+ return 1;
+ auto& tpl = it->second;
+ if (get(tpl) != gameExpansion || get(tpl) != gameSide || get(tpl) != gameVersion)
+ return 2;
+
+ return 0;
+ }
+
const Expansion GetExpansion()
{
return gameExpansion;
diff --git a/dgamexwrapper/src/Item.h b/dgamexwrapper/BaseHeaders/Item.h
similarity index 82%
rename from dgamexwrapper/src/Item.h
rename to dgamexwrapper/BaseHeaders/Item.h
index 35ad480..c53e9db 100644
--- a/dgamexwrapper/src/Item.h
+++ b/dgamexwrapper/BaseHeaders/Item.h
@@ -1,10 +1,10 @@
#pragma once
#include "Trigger.h"
#include "Sentient.h"
-#ifdef MOHAA_TMP
-class Item : public Trigger
+class ItemAA : public TriggerAA
{
- SafePtr owner; /* bitsize 128, bitpos 7968 */
+public:
+ SafePtr owner; /* bitsize 128, bitpos 7968 */
qboolean respawnable; /* bitsize 32, bitpos 8096 */
qboolean playrespawn; /* bitsize 32, bitpos 8128 */
float respawntime; /* bitsize 32, bitpos 8160 */
@@ -16,4 +16,3 @@ class Item : public Trigger
str sPickupSound; /* bitsize 32, bitpos 8352 */
qboolean no_remove; /* bitsize 32, bitpos 8384 */
};
-#endif // MOHAA
\ No newline at end of file
diff --git a/dgamexwrapper/src/Listener.h b/dgamexwrapper/BaseHeaders/Listener.h
similarity index 51%
rename from dgamexwrapper/src/Listener.h
rename to dgamexwrapper/BaseHeaders/Listener.h
index 878563e..ec963cf 100644
--- a/dgamexwrapper/src/Listener.h
+++ b/dgamexwrapper/BaseHeaders/Listener.h
@@ -5,6 +5,29 @@
class Event;
+// entity subclass
+#define ECF_ENTITY (1 << 0)
+#define ECF_ANIMATE (1 << 1)
+#define ECF_SENTIENT (1 << 2)
+#define ECF_PLAYER (1 << 3)
+#define ECF_ACTOR (1 << 4)
+#define ECF_ITEM (1 << 5)
+#define ECF_INVENTORYITEM (1 << 6)
+#define ECF_WEAPON (1 << 7)
+#define ECF_PROJECTILE (1 << 8)
+#define ECF_DOOR (1 << 9)
+#define ECF_CAMERA (1 << 10)
+#define ECF_VEHICLE (1 << 11)
+#define ECF_VEHICLETANK (1 << 12)
+#define ECF_VEHICLETURRET (1 << 13)
+#define ECF_TURRET (1 << 14)
+#define ECF_PATHNODE (1 << 15)
+#define ECF_WAYPOINT (1 << 16)
+#define ECF_TEMPWAYPOINT (1 << 17)
+#define ECF_VEHICLEPOINT (1 << 18)
+#define ECF_SPLINEPATH (1 << 19)
+#define ECF_CRATEOBJECT (1 << 20)
+
/*
* Class Listener
* Used as an important utility class for the patch.
diff --git a/dgamexwrapper/src/Player.h b/dgamexwrapper/BaseHeaders/Player.h
similarity index 96%
rename from dgamexwrapper/src/Player.h
rename to dgamexwrapper/BaseHeaders/Player.h
index ca47877..69c20c8 100644
--- a/dgamexwrapper/src/Player.h
+++ b/dgamexwrapper/BaseHeaders/Player.h
@@ -124,12 +124,17 @@ class PlayerNF : public SentientNF
ScriptEventHandler(GetConnStateEvent);
ScriptEventHandler(GetActiveWeapEvent);
ScriptEventHandler(SecFireHeldEvent);
+ ScriptEventHandler(RunHeldEvent);
+ ScriptEventHandler(LeanLeftHeldEvent);
+ ScriptEventHandler(LeanRightHeldEvent);
ScriptEventHandler(GetUserInfoEvent);
ScriptEventHandler(GetInventoryEvent);
ScriptEventHandler(IsAdminEvent);
+ ScriptEventHandler(AdminRightsEvent);
ScriptEventHandler(AddKillsEventAA);
ScriptEventHandler(AddDeathsEventAA);
+ ScriptEventHandler(BindWeapEventAA);
//The following functions are console(client command) events.
ScriptEventHandler(PatchVersionEvent);
diff --git a/dgamexwrapper/src/Sentient.h b/dgamexwrapper/BaseHeaders/Sentient.h
similarity index 100%
rename from dgamexwrapper/src/Sentient.h
rename to dgamexwrapper/BaseHeaders/Sentient.h
diff --git a/dgamexwrapper/src/SimpleEntity.h b/dgamexwrapper/BaseHeaders/SimpleEntity.h
similarity index 81%
rename from dgamexwrapper/src/SimpleEntity.h
rename to dgamexwrapper/BaseHeaders/SimpleEntity.h
index 6719f57..4b31a4c 100644
--- a/dgamexwrapper/src/SimpleEntity.h
+++ b/dgamexwrapper/BaseHeaders/SimpleEntity.h
@@ -26,5 +26,9 @@ class SimpleEntity : public Listener
public:
SimpleEntity();
~SimpleEntity();
+
+ int IsSubclassOfPlayer() const;
+ int IsSubclassOfTurretGun() const;
+ int IsSubclassOfInventoryItem() const;
};
diff --git a/dgamexwrapper/src/Trigger.h b/dgamexwrapper/BaseHeaders/Trigger.h
similarity index 83%
rename from dgamexwrapper/src/Trigger.h
rename to dgamexwrapper/BaseHeaders/Trigger.h
index 83d6a80..a930d5f 100644
--- a/dgamexwrapper/src/Trigger.h
+++ b/dgamexwrapper/BaseHeaders/Trigger.h
@@ -1,14 +1,13 @@
#pragma once
#include "Animate.h"
-#ifdef MOHAA_TMP
-class ScriptThreadLabel
+class ScriptThreadLabelAA
{
void* m_Script;//FIXME: GameScript*
const_str m_Label;
};
-class Trigger : public sdgfsgAnimate // todo: universalize handle this
+class TriggerAA : public AnimateAA // todo: universalize handle this
{
protected:
float wait; /* bitsize 32, bitpos 7200 */
@@ -18,7 +17,7 @@ class Trigger : public sdgfsgAnimate // todo: universalize handle this
int count; /* bitsize 32, bitpos 7328 */
const_str noise; /* bitsize 32, bitpos 7360 */
const_str message; /* bitsize 32, bitpos 7392 */
- ScriptThreadLabel /* id 260 */ m_Thread; /* bitsize 64, bitpos 7424 */
+ ScriptThreadLabelAA /* id 260 */ m_Thread; /* bitsize 64, bitpos 7424 */
SafePtr activator; /* bitsize 128, bitpos 7488 */
int respondto; /* bitsize 32, bitpos 7616 */
qboolean useTriggerDir; /* bitsize 32, bitpos 7648 */
@@ -30,5 +29,4 @@ class Trigger : public sdgfsgAnimate // todo: universalize handle this
qboolean edgeTriggered; /* bitsize 32, bitpos 7904 */
int multiFaceted; /* bitsize 32, bitpos 7936 */
};
-#endif // MOHAA
diff --git a/dgamexwrapper/BaseHeaders/Version.h b/dgamexwrapper/BaseHeaders/Version.h
index 743a1e9..1e58bf5 100644
--- a/dgamexwrapper/BaseHeaders/Version.h
+++ b/dgamexwrapper/BaseHeaders/Version.h
@@ -1,5 +1,5 @@
#pragma once
#define PATCH_NAME "NightFall"
//don't remove this macro from this file as it's used in building/packaging scripts
-#define PATCH_VERSION "1.1.1"
+#define PATCH_VERSION "1.2.0"
#define PATCH_STAGE "stable"
diff --git a/dgamexwrapper/src/Weapon.h b/dgamexwrapper/BaseHeaders/Weapon.h
similarity index 95%
rename from dgamexwrapper/src/Weapon.h
rename to dgamexwrapper/BaseHeaders/Weapon.h
index cd3278f..70430f9 100644
--- a/dgamexwrapper/src/Weapon.h
+++ b/dgamexwrapper/BaseHeaders/Weapon.h
@@ -1,7 +1,7 @@
#pragma once
#include "Item.h"
-#ifdef MOHAA_TMP
-class Weapon : public Item
+#ifdef MOHAA_TMP //todo: see if we need this
+class WeaponAA : public ItemAA
{
protected:
int m_iAnimSlot; /* bitsize 32, bitpos 8416 */
@@ -28,7 +28,7 @@ class Weapon : public Item
weaponstate_t weaponstate; /* bitsize 32, bitpos 9152 */
int rank; /* bitsize 32, bitpos 9184 */
int order; /* bitsize 32, bitpos 9216 */
- SafePtr last_owner; /* bitsize 128, bitpos 9248 */
+ SafePtr last_owner; /* bitsize 128, bitpos 9248 */
float last_owner_trigger_time; /* bitsize 32, bitpos 9376 */
qboolean notdroppable; /* bitsize 32, bitpos 9408 */
int aimanim; /* bitsize 32, bitpos 9440 */
@@ -94,6 +94,6 @@ class Weapon : public Item
int m_iNumLeftArmShots; /* bitsize 32, bitpos 12960 */
int m_iNumRightArmShots; /* bitsize 32, bitpos 12992 */
AIRanges_t mAIRange; /* bitsize 32, bitpos 13024 */
- SafePtr aim_target; /* bitsize 128, bitpos 13056 */
+ SafePtr aim_target; /* bitsize 128, bitpos 13056 */
};
-#endif // MOHAA
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/dgamexwrapper/BaseHeaders/dgamex86.h b/dgamexwrapper/BaseHeaders/dgamex86.h
index 66ca19b..f16bb7c 100644
--- a/dgamexwrapper/BaseHeaders/dgamex86.h
+++ b/dgamexwrapper/BaseHeaders/dgamex86.h
@@ -4101,6 +4101,96 @@ typedef struct Player_s
*/
+typedef enum serverState_e
+{
+ SS_DEAD,
+ SS_LOADING,
+ SS_LOADING2,
+ SS_GAME
+
+} serverState_t;
+
+typedef struct challenge_s
+{
+ netAdr_t adr;
+ int challenge;
+ int time;
+ int pingTime;
+ int firstTime;
+ qboolean connected;
+
+} challenge_t;
+
+typedef struct sgSfx_s
+{
+ int flags;
+ char name[64];
+
+} sgSfx_t;
+
+typedef struct sgChannelbase_s
+{
+ qboolean isPlaying;
+ int status;
+ sgSfx_t sfx;
+ int entNum;
+ int entChannel;
+ float origin[3];
+ float volume;
+ int baseRate;
+ float newPitchMult;
+ float minDist;
+ float maxDist;
+ int startTime;
+ int time;
+ int nextCheckObstructionTime;
+ int endTime;
+ int flags;
+ int offset;
+ int loopCount;
+
+} sgChannelbase_t;
+
+typedef struct sgSoundSystem_s
+{
+ sgChannelbase_t channels[96];
+
+} sgSoundSystem_t;
+
+
+
+typedef struct serverStaticAA_s
+{
+ qboolean initialized;
+ int snapFlagServerBit;
+ int time;
+ int startTime;
+ int lastTime;
+ int serverLagTime;
+ qboolean autosave;
+ int mapTime;
+ clientAA_t* clients;
+ int iNumClients;
+ int numSnapshotEntities;
+ int nextSnapshotEntities;
+ entityState_t* snapshotEntities;
+ int nextHeartbeatTime;
+ challenge_t challenges[1024];
+ netAdr_t redirectAddress;
+ netAdr_t authorizeAddress;
+ char gameName[64];
+ char mapName[64];
+ char rawServerName[64];
+ int areaBitsWarningTime;
+ qboolean soundsNeedLoad;
+ char tmFileName[64];
+ int tmMoopcount;
+ int tmOffset;
+ sgSoundSystem_t soundSystem;
+
+} serverStaticAA_t;
+
+
typedef struct refImport_s
{
void ( *Printf )( char *format, ... );
@@ -5201,6 +5291,7 @@ class BaseGameImport
IMPORT_FUNCTION(Args, char*, void);
IMPORT_FUNCTION(Milliseconds, int, void);
IMPORT_FUNCTION(Cvar_Get, cvar_t*, const char* varName, const char* varValue, int varFlags);
+ IMPORT_FUNCTION(Cvar_Set, void, const char* varName, const char* varValue);
IMPORT_FUNCTION(MSG_WriteBits, void, int value, int bits);
IMPORT_FUNCTION(MSG_WriteByte, void, int c);
IMPORT_FUNCTION(MSG_WriteShort, void, int c);
@@ -5307,6 +5398,7 @@ class GameImport : public BaseGameImport
IMPORT_FUNCTION_OVERRIDE(Args, char*, ECS(), ECS());
IMPORT_FUNCTION_OVERRIDE(Milliseconds, int, ECS(), ECS());
IMPORT_FUNCTION_OVERRIDE(Cvar_Get, cvar_t*, ECS(const char* varName, const char* varValue, int varFlags), ECS(varName, varValue, varFlags));
+ IMPORT_FUNCTION_OVERRIDE(Cvar_Set, void, ECS(const char* varName, const char* varValue), ECS(varName, varValue));
IMPORT_FUNCTION_OVERRIDE(MSG_WriteBits, void, ECS(int value, int bits), ECS(value, bits));
IMPORT_FUNCTION_OVERRIDE(MSG_WriteByte, void, ECS(int c), ECS(c));
IMPORT_FUNCTION_OVERRIDE(MSG_WriteShort, void, ECS(int c), ECS(c));
@@ -5504,11 +5596,11 @@ class GameExportFactory
};
-#ifndef REBORNSECURITYFIXES_EXPORTS
+//#ifndef REBORNSECURITYFIXES_EXPORTS
extern shared_ptr gi;
extern shared_ptr globals;
-extern shared_ptr globals_backup;
-#endif
+extern shared_ptr globals_backup;
+//#endif
enum INTTYPE_e { TRANS_BSP, TRANS_LEVEL, TRANS_MISSION, TRANS_MISSION_FAILED };
diff --git a/dgamexwrapper/BaseHeaders/nf_misc.h b/dgamexwrapper/BaseHeaders/nf_misc.h
index c1729ee..566c798 100644
--- a/dgamexwrapper/BaseHeaders/nf_misc.h
+++ b/dgamexwrapper/BaseHeaders/nf_misc.h
@@ -25,3 +25,5 @@ char* Info_ValueForKey(const char* s, const char* key);
bool CheckFPSSkin(char* userinfo);
bool CheckCommentSlashesInName(char* name);
+
+char* CopyString(const char* in);
\ No newline at end of file
diff --git a/dgamexwrapper/BaseHeaders/safeptr.h b/dgamexwrapper/BaseHeaders/safeptr.h
new file mode 100644
index 0000000..74dd254
--- /dev/null
+++ b/dgamexwrapper/BaseHeaders/safeptr.h
@@ -0,0 +1,179 @@
+#pragma once
+
+//class Class;
+
+#define LL_Add(rootnode, newnode, next, prev) \
+ { \
+ (newnode)->next = (rootnode); \
+ (newnode)->prev = (rootnode)->prev; \
+ (rootnode)->prev->next = (newnode); \
+ (rootnode)->prev = (newnode); \
+ }
+
+#define LL_Remove(node,next,prev) \
+ { \
+ node->prev->next = node->next; \
+ node->next->prev = node->prev; \
+ node->next = node; \
+ node->prev = node; \
+ }
+#define LL_Reset(list,next,prev) (list)->next = (list)->prev = (list)
+class SafePtrBase
+{
+
+protected:
+ SafePtrBase* prev;
+ SafePtrBase* next;
+ Class* ptr;
+
+public:
+ virtual ~SafePtrBase()
+ {
+ throw "Shouldn't execute nightfall's SafePtrBaSE Destructor.";
+ }
+
+ void AddReference(Class* ptr);
+ void RemoveReference(Class* ptr);
+ void InitSafePtr(Class* newptr);
+
+};
+
+inline void SafePtrBase::AddReference(Class* ptr)
+{
+ if (!ptr->SafePtrList)
+ {
+ ptr->SafePtrList = this;
+ LL_Reset(this, next, prev);
+ }
+ else
+ {
+ LL_Add(ptr->SafePtrList, this, next, prev);
+ }
+}
+
+inline void SafePtrBase::RemoveReference(Class* ptr)
+{
+ if (ptr->SafePtrList == this)
+ {
+ if (ptr->SafePtrList->next == this)
+ {
+ ptr->SafePtrList = nullptr;
+ }
+ else
+ {
+ ptr->SafePtrList = next;
+ LL_Remove(this, next, prev);
+ }
+ }
+ else
+ {
+ LL_Remove(this, next, prev);
+ }
+}
+
+inline void SafePtrBase::InitSafePtr(Class* newptr)
+{
+ if (ptr != newptr)
+ {
+ if (ptr)
+ {
+ RemoveReference(ptr);
+ }
+
+ ptr = newptr;
+ if (ptr == nullptr)
+ {
+ return;
+ }
+
+ AddReference(ptr);
+ }
+}
+
+template
+class SafePtr : public SafePtrBase
+{
+public:
+ SafePtr& operator=(T* const obj);
+
+ template< class U > friend bool operator==(SafePtr& a, U* b);
+ template< class U > friend bool operator!=(SafePtr& a, U* b);
+ template< class U > friend bool operator==(U* a, SafePtr& b);
+ template< class U > friend bool operator!=(U* a, SafePtr& b);
+ template< class U > friend bool operator==(SafePtr& a, SafePtr& b);
+ template< class U > friend bool operator!=(SafePtr& a, SafePtr& b);
+
+ bool operator !() const;
+ operator T* () const;
+ T* operator->() const;
+ T& operator*() const;
+};
+
+template
+inline SafePtr& SafePtr::operator=(T* const obj)
+{
+ InitSafePtr(obj);
+ return *this;
+}
+
+
+template
+inline bool operator==(SafePtr& a, T* b)
+{
+ return a.ptr == b;
+}
+
+template
+inline bool operator!=(SafePtr& a, T* b)
+{
+ return a.ptr != b;
+}
+
+template
+inline bool operator==(T* a, SafePtr& b)
+{
+ return a == b.ptr;
+}
+
+template
+inline bool operator!=(T* a, SafePtr& b)
+{
+ return a != b.ptr;
+}
+
+template
+inline bool operator==(SafePtr& a, SafePtr& b)
+{
+ return a.ptr == b.ptr;
+}
+
+template
+inline bool operator!=(SafePtr& a, SafePtr& b)
+{
+ return a.ptr != b.ptr;
+}
+
+
+template
+inline bool SafePtr::operator !() const
+{
+ return ptr == NULL;
+}
+
+template
+inline SafePtr::operator T* () const
+{
+ return (T*)ptr;
+}
+
+template
+inline T* SafePtr::operator->() const
+{
+ return (T*)ptr;
+}
+
+template
+inline T& SafePtr::operator*() const
+{
+ return *(T*)ptr;
+}
diff --git a/dgamexwrapper/src/sv_misc.h b/dgamexwrapper/BaseHeaders/sv_misc.h
similarity index 73%
rename from dgamexwrapper/src/sv_misc.h
rename to dgamexwrapper/BaseHeaders/sv_misc.h
index db7b2cd..de2407a 100644
--- a/dgamexwrapper/src/sv_misc.h
+++ b/dgamexwrapper/BaseHeaders/sv_misc.h
@@ -12,6 +12,10 @@ client_t * GetClientByClientNum(int clientNum);
void __cdecl NET_OutOfBandPrint(netSrc_t sock, netAdr_t adr, const char *format, ...);
+string NetAdrToIPString(netAdr_t& adr);
+string NetAdrToPortString(netAdr_t& adr);
+string NetAdrToIPPortString(netAdr_t& adr);
+
string GetIPFromClient(client_t* cl);
string GetPortFromClient(client_t* cl);
string GetIPPortFromClient(client_t* cl);
diff --git a/dgamexwrapper/dgamex86.sln b/dgamexwrapper/dgamex86.sln
index cb15fbd..2bd95d3 100644
--- a/dgamexwrapper/dgamex86.sln
+++ b/dgamexwrapper/dgamex86.sln
@@ -25,6 +25,8 @@ Global
Release|x64 = Release|x64
SpearHead|Win32 = SpearHead|Win32
SpearHead|x64 = SpearHead|x64
+ Universal|Win32 = Universal|Win32
+ Universal|x64 = Universal|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.BreakThrough|Win32.ActiveCfg = BreakThrough|Win32
@@ -47,6 +49,9 @@ Global
{6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.SpearHead|Win32.ActiveCfg = SpearHead|Win32
{6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.SpearHead|Win32.Build.0 = SpearHead|Win32
{6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.SpearHead|x64.ActiveCfg = SpearHead|Win32
+ {6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.Universal|Win32.ActiveCfg = Universal|Win32
+ {6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.Universal|Win32.Build.0 = Universal|Win32
+ {6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}.Universal|x64.ActiveCfg = Universal|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dgamexwrapper/dgamex86.vcxproj b/dgamexwrapper/dgamex86.vcxproj
index 94ec386..0129f51 100644
--- a/dgamexwrapper/dgamex86.vcxproj
+++ b/dgamexwrapper/dgamex86.vcxproj
@@ -21,6 +21,10 @@
SpearHead
Win32
+
+ Universal
+ Win32
+
{6C9B194A-CAB0-447C-BC2B-8828C17DFBD8}
@@ -47,6 +51,12 @@
false
MultiByte
+
+ DynamicLibrary
+ v141_xp
+ false
+ MultiByte
+
DynamicLibrary
v141_xp
@@ -77,6 +87,10 @@
+
+
+
+
@@ -111,6 +125,12 @@
false
gamex86
+
+ ./release/universal/
+ ./bin/
+ false
+ gamex86
+
./release/mohsh/
./bin/
@@ -120,6 +140,7 @@
Release
Release
Release
+ Release
Release
@@ -146,7 +167,7 @@
Level3
true
EditAndContinue
- src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;%(AdditionalIncludeDirectories)
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
stdcpp17
@@ -181,7 +202,7 @@
Disabled
Disabled
- src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;%(AdditionalIncludeDirectories)
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
MSVC6;WIN32;NDEBUG;_WINDOWS;_USRDLL;MOHBT;SERVER;NO_FILES;NO_CGI;NO_SSL;CURL_STATICLIB;WC_NO_HARDEN;%(PreprocessorDefinitions)
true
MultiThreaded
@@ -241,7 +262,7 @@
Disabled
Disabled
- src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;%(AdditionalIncludeDirectories)
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
MSVC6;WIN32;NDEBUG;_WINDOWS;_USRDLL;MOHBT;CLIENT;NO_FILES;NO_CGI;NO_SSL;CURL_STATICLIB;WC_NO_HARDEN;%(PreprocessorDefinitions)
true
MultiThreaded
@@ -302,7 +323,7 @@
Disabled
Disabled
- src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;%(AdditionalIncludeDirectories)
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
MSVC6;WIN32;NDEBUG;_WINDOWS;_USRDLL;MOHAA;SERVER;NO_FILES;NO_CGI;NO_SSL;CURL_STATICLIB;WC_NO_HARDEN;%(PreprocessorDefinitions)
true
MultiThreaded
@@ -326,7 +347,7 @@
0x0409
- Ws2_32.lib;Wldap32.lib;Shlwapi.lib;detours.lib;wolfssl.lib;sqlite3.lib;SQLiteCpp.lib;libcurl.lib;libcurlpp.lib;%(AdditionalDependencies)
+ Ws2_32.lib;Wldap32.lib;Shlwapi.lib;detours.lib;wolfssl.lib;sqlite3.lib;SQLiteCpp.lib;libcurl.lib;libcurlpp.lib;RebornSecurityFixes.lib;%(AdditionalDependencies)
$(OutDir)\gamex86.dll
true
@@ -338,7 +359,69 @@
$(OutDir)\gamex86.lib
MachineX86
Windows
- libs/Detours/lib.X86;libs/wolfSSL/build/Win32/VC14.10/LIB Release;libs/SQLiteCpp/build/Release;libs/SQLiteCpp/build/sqlite3/Release;libs/curl/build/Win32/VC14.10/LIB Release - LIB wolfSSL;libs/curlpp/Build/Release;%(AdditionalLibraryDirectories)
+ libs/Detours/lib.X86;libs/wolfSSL/build/Win32/VC14.10/LIB Release;libs/SQLiteCpp/build/Release;libs/SQLiteCpp/build/sqlite3/Release;libs/curl/build/Win32/VC14.10/LIB Release - LIB wolfSSL;libs/curlpp/Build/Release;libs/RebornSecurityHeaders;%(AdditionalLibraryDirectories)
+
+
+ /EXPORT:"GetGameAPI" %(AdditionalOptions)
+
+
+ true
+ .\release/dgamex86.bsc
+
+
+ "$(OutDir)/copy.bat"
+
+
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ Win32
+ .\release/dgamex86.tlb
+
+
+
+
+ Disabled
+ Disabled
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
+ MSVC6;WIN32;NDEBUG;_WINDOWS;_USRDLL;MOHAA;SERVER;NO_FILES;NO_CGI;NO_SSL;CURL_STATICLIB;WC_NO_HARDEN;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ true
+ .\bin/dgamex86.pch
+ .\bin/
+ .\bin/
+ .\bin/
+ true
+ TurnOffAllWarnings
+ true
+ CompileAsCpp
+ stdcpp17
+
+
+ AssemblyAndSourceCode
+ /Zc:threadSafeInit- %(AdditionalOptions)
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ 0x0409
+
+
+ Ws2_32.lib;Wldap32.lib;Shlwapi.lib;detours.lib;wolfssl.lib;sqlite3.lib;SQLiteCpp.lib;libcurl.lib;libcurlpp.lib;RebornSecurityFixes.lib;%(AdditionalDependencies)
+ $(OutDir)\gamex86.dll
+ true
+
+
+ $(OutDir)\gamex86.pdb
+ false
+
+
+ $(OutDir)\gamex86.lib
+ MachineX86
+ Windows
+ libs/Detours/lib.X86;libs/wolfSSL/build/Win32/VC14.10/LIB Release;libs/SQLiteCpp/build/Release;libs/SQLiteCpp/build/sqlite3/Release;libs/curl/build/Win32/VC14.10/LIB Release - LIB wolfSSL;libs/curlpp/Build/Release;libs/RebornSecurityHeaders;%(AdditionalLibraryDirectories)
/EXPORT:"GetGameAPI" %(AdditionalOptions)
@@ -364,7 +447,7 @@
Disabled
Disabled
- src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;%(AdditionalIncludeDirectories)
+ src;libs/Detours/include;libs/wolfSSL;libs/SQLiteCpp/include;libs/civetweb/include;libs/nlohmann-json/single_include;libs/curl/include;libs/curlpp/include;libs/RebornSecurityHeaders;%(AdditionalIncludeDirectories)
MSVC6;WIN32;NDEBUG;_WINDOWS;_USRDLL;MOHSH;SERVER;NO_FILES;NO_CGI;NO_SSL;CURL_STATICLIB;WC_NO_HARDEN;%(PreprocessorDefinitions)
true
MultiThreaded
@@ -470,18 +553,13 @@
-
-
-
-
-
@@ -489,27 +567,18 @@
-
-
-
-
-
-
-
-
-
diff --git a/dgamexwrapper/dgamex86.vcxproj.filters b/dgamexwrapper/dgamex86.vcxproj.filters
index 3994ece..3ab2c0a 100644
--- a/dgamexwrapper/dgamex86.vcxproj.filters
+++ b/dgamexwrapper/dgamex86.vcxproj.filters
@@ -194,12 +194,6 @@
Header Files
-
- Header Files
-
-
- Header Files
-
Header Files
@@ -215,9 +209,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -227,12 +218,6 @@
Header Files
-
- Header Files
-
-
- Header Files
-
Header Files
@@ -242,27 +227,15 @@
Header Files
-
- Header Files
-
Header Files
-
- Header Files
-
Header Files
Header Files
-
- Header Files
-
-
- Header Files
-
Header Files
@@ -299,21 +272,6 @@
Header Files
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
Header Files
diff --git a/dgamexwrapper/gamebins/aa/1.11/MOHAA_server.exe b/dgamexwrapper/gamebins/aa/1.11/MOHAA_server.exe
index 865938c..ea740b2 100644
Binary files a/dgamexwrapper/gamebins/aa/1.11/MOHAA_server.exe and b/dgamexwrapper/gamebins/aa/1.11/MOHAA_server.exe differ
diff --git a/dgamexwrapper/gamebins/aa/1.11/main/gamex86mohaa.dll b/dgamexwrapper/gamebins/aa/1.11/main/gamex86mohaa.dll
index 79c4626..6c7e48b 100644
Binary files a/dgamexwrapper/gamebins/aa/1.11/main/gamex86mohaa.dll and b/dgamexwrapper/gamebins/aa/1.11/main/gamex86mohaa.dll differ
diff --git a/dgamexwrapper/ida/dgamex86mohbt.idb b/dgamexwrapper/ida/dgamex86mohbt.idb
index 9ddbded..ba215fb 100644
Binary files a/dgamexwrapper/ida/dgamex86mohbt.idb and b/dgamexwrapper/ida/dgamex86mohbt.idb differ
diff --git a/dgamexwrapper/ida/moh_Breakthrough_server.idb b/dgamexwrapper/ida/moh_Breakthrough_server.idb
index d4853b0..e127e2d 100644
Binary files a/dgamexwrapper/ida/moh_Breakthrough_server.idb and b/dgamexwrapper/ida/moh_Breakthrough_server.idb differ
diff --git a/dgamexwrapper/libs/RebornSecurityHeaders b/dgamexwrapper/libs/RebornSecurityHeaders
new file mode 160000
index 0000000..fd97df1
--- /dev/null
+++ b/dgamexwrapper/libs/RebornSecurityHeaders
@@ -0,0 +1 @@
+Subproject commit fd97df1f673cf8ec5f1d6b54e01e1ba763fef7ea
diff --git a/dgamexwrapper/src/ClientAdmin.cpp b/dgamexwrapper/src/ClientAdmin.cpp
index 91ffac6..269796b 100644
--- a/dgamexwrapper/src/ClientAdmin.cpp
+++ b/dgamexwrapper/src/ClientAdmin.cpp
@@ -1,4 +1,5 @@
#include "ClientAdmin.h"
+#include "sv_misc.h"
#include
#include "g_misc.h"
#include "IPFilter.h"
@@ -417,6 +418,21 @@ bool ClientAdmin::hasRight(AccessLevel right)
return entry.getRigths() & (1<value;
}
+
+void CustomCvar::SetValue(string v, bool silent)
+{
+ cvar_t* developer = gi->Cvar_Get("developer", "0", 0);
+ char* developer_value = developer->string;
+ if (silent) // set developer to zero
+ {
+ developer->modified = qtrue;
+ developer->modificationCount++;
+ //gi->Free(developer->string);
+ developer->string = CopyString("0");
+ developer->value = 0;
+ developer->integer = 0;
+ }
+ gi->Cvar_Set(name.c_str(), v.c_str());
+ if (silent) // set developer to old value
+ {
+ developer->modified = qtrue;
+ developer->modificationCount++;
+ gi->Free(developer->string);
+ developer->string = developer_value;
+ developer->value = atof(developer->string);
+ developer->integer = atoi(developer->string);
+ }
+}
diff --git a/dgamexwrapper/src/CustomCvar.h b/dgamexwrapper/src/CustomCvar.h
index 90bf1b2..e8cabbf 100644
--- a/dgamexwrapper/src/CustomCvar.h
+++ b/dgamexwrapper/src/CustomCvar.h
@@ -18,5 +18,7 @@ class CustomCvar
int GetIntValue();
float GetFloatValue();
+ void SetValue(std::string v, bool silent = false);
+
};
diff --git a/dgamexwrapper/src/Event.h b/dgamexwrapper/src/Event.h
index 9e11726..ec5c708 100644
--- a/dgamexwrapper/src/Event.h
+++ b/dgamexwrapper/src/Event.h
@@ -129,7 +129,7 @@ class Event : public Class
{
public:
qboolean fromScript;
- short unsigned int eventnum;
+ short unsigned int eventnum; //set by game
short unsigned int dataSize;
ScriptVariable *data;
diff --git a/dgamexwrapper/src/HTTPClient.cpp b/dgamexwrapper/src/HTTPClient.cpp
index 46dadd0..dd94cc3 100644
--- a/dgamexwrapper/src/HTTPClient.cpp
+++ b/dgamexwrapper/src/HTTPClient.cpp
@@ -174,7 +174,9 @@ void HTTPClient::Shutdown()
//shutdown curlpp
curlpp::terminate();
status = CST_STOP;
- lock_guard lock1(cv_mutex);
+ {
+ lock_guard lock1(cv_mutex);
+ }
cv.notify_all();
lock_guard lock2(requests_mutex);
@@ -198,9 +200,10 @@ void HTTPClient::CreateAPIRequest(string url, string method, ScriptVariable & sc
//auto start = chrono::high_resolution_clock::now();
{
+ lock_guard lock1(cv_mutex);
status = CST_NEWREQ;
- cv.notify_all();
}
+ cv.notify_all();
//auto stop = chrono::high_resolution_clock::now();
//auto duration = chrono::duration_cast(stop - start);
//gi->Printf("!!!!!!!!!!!! time = %lld\n", duration.count());
diff --git a/dgamexwrapper/src/PlayerAdminCommands.cpp b/dgamexwrapper/src/PlayerAdminCommands.cpp
index 5d09f4d..60bda72 100644
--- a/dgamexwrapper/src/PlayerAdminCommands.cpp
+++ b/dgamexwrapper/src/PlayerAdminCommands.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include "sv_misc.h"
using namespace std;
void PlayerNF::AdminCommandsInit()
diff --git a/dgamexwrapper/src/PlayerMisc.cpp b/dgamexwrapper/src/PlayerMisc.cpp
index cefc6c7..f3d9630 100644
--- a/dgamexwrapper/src/PlayerMisc.cpp
+++ b/dgamexwrapper/src/PlayerMisc.cpp
@@ -1,8 +1,15 @@
#include "Player.h"
#include "g_misc.h"
#include "ClientAdmin.h"
+#include "Item.h"
+#include "sv_misc.h"
+#define BUTTON_ATTACK1 1 // +/-attackprimary
#define BUTTON_ATTACK2 2 // +/-attacksecondary
+#define BUTTON_RUN 4
+#define BUTTON_USE 8
+#define BUTTON_LEANLEFT 16
+#define BUTTON_LEANRIGHT 32
void PlayerNF::MiscInit()
{
@@ -51,6 +58,35 @@ void PlayerNF::MiscInit()
EV_GETTER
),
&PlayerNF::PreSecFireHeldEvent);
+ cerSet.AddEventResponse(new Event(
+ "runheld",
+ EV_DEFAULT,
+ NULL,
+ NULL,
+ "Returns 1 if player is holding a run (wasd) button.",
+ EV_GETTER
+ ),
+ &PlayerNF::PreRunHeldEvent);
+ cerSet.AddEventResponse(new Event(
+ "leanleftheld",
+ EV_DEFAULT,
+ NULL,
+ NULL,
+ "Returns 1 if player is holding lean left button.",
+ EV_GETTER
+ ),
+ &PlayerNF::PreLeanLeftHeldEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "leanrightheld",
+ EV_DEFAULT,
+ NULL,
+ NULL,
+ "Returns 1 if player is holding lean right button.",
+ EV_GETTER
+ ),
+ &PlayerNF::PreLeanRightHeldEvent);
+
cerSet.AddEventResponse(new Event(
"userinfo",
EV_DEFAULT,
@@ -79,6 +115,17 @@ void PlayerNF::MiscInit()
EV_RETURN
),
&PlayerNF::PreIsAdminEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "adminrights",
+ EV_DEFAULT,
+ NULL,
+ NULL,
+ "Returns integer rights of the admin",
+ EV_GETTER
+ ),
+ &PlayerNF::PreAdminRightsEvent);
+
if (gameInfo.IsAA())
{
cerSet.AddEventResponse(new Event(
@@ -98,6 +145,17 @@ void PlayerNF::MiscInit()
"Adds number of deaths to player. (Can be also negative)"
),
&PlayerNF::PreAddDeathsEventAA);
+
+
+ cerSet.AddEventResponse(new Event(
+ "bindweap",
+ EV_DEFAULT,
+ "e",
+ "weapon",
+ "Binds weapon to player and set player as weapon owner.",
+ EV_NORMAL
+ ),
+ &PlayerNF::PreBindWeapEventAA);
}
}
@@ -140,6 +198,21 @@ void PlayerNF::SecFireHeldEvent(Event *ev)
ev->AddInteger((bool)(buttons & BUTTON_ATTACK2));
}
+void PlayerNF::RunHeldEvent(Event* ev)
+{
+ ev->AddInteger((bool)(buttons & BUTTON_RUN));
+}
+
+void PlayerNF::LeanLeftHeldEvent(Event* ev)
+{
+ ev->AddInteger((bool)(buttons & BUTTON_LEANLEFT));
+}
+
+void PlayerNF::LeanRightHeldEvent(Event* ev)
+{
+ ev->AddInteger((bool)(buttons & BUTTON_LEANRIGHT));
+}
+
void PlayerNF::GetUserInfoEvent(Event *ev)
{
ev->AddString(client->pers.userinfo);
@@ -180,6 +253,20 @@ void PlayerNF::IsAdminEvent(Event * ev)
}
}
+void PlayerNF::AdminRightsEvent(Event* ev)
+{
+ ClientAdmin admin(client->ps.clientNum);
+
+ if (admin.isAdmin())
+ {
+ ev->AddInteger(admin.getRights());
+ }
+ else
+ {
+ ev->AddInteger(-1);
+ }
+}
+
void PlayerNF::AddKillsEventAA(Event* ev)
{
@@ -193,3 +280,16 @@ void PlayerNF::AddDeathsEventAA(Event* ev)
if (current_team)
current_team->AddDeathsAA((Player*)realPlayer, ev->GetInteger(1));
}
+
+void PlayerNF::BindWeapEventAA(Event* ev)
+{
+ ItemAA* itm = (ItemAA*)ev->GetEntity(1);
+ if (itm->owner == (SentientAA*)realPlayer)
+ {
+ itm->owner = nullptr;
+ }
+ else
+ {
+ itm->owner = (SentientAA*)realPlayer;
+ }
+}
\ No newline at end of file
diff --git a/dgamexwrapper/src/ScriptThread.h b/dgamexwrapper/src/ScriptThread.h
index 7bf651e..d0c683f 100644
--- a/dgamexwrapper/src/ScriptThread.h
+++ b/dgamexwrapper/src/ScriptThread.h
@@ -51,6 +51,9 @@ class ScriptThread : public Listener
void FReadPakEvent(Event * ev);
void FListEvent(Event * ev);
+ void FNewDirEvent(Event * ev);
+ void FRemoveDirEvent(Event * ev);
+
static void MathsInit();
void MSinEvent(Event * ev);
@@ -98,6 +101,9 @@ class ScriptThread : public Listener
void RegexParseEvent(Event * ev);
void JsonParseEvent(Event * ev);
void JsonStringifyEvent(Event * ev);
+ void CharToIntEvent(Event* ev);
+ void SetCvarExEvent(Event* ev);
+ void TeamSwitchDelayEvent(Event* ev);
static void ScriptedEventsInit();
diff --git a/dgamexwrapper/src/ScriptThreadFileSystem.cpp b/dgamexwrapper/src/ScriptThreadFileSystem.cpp
index ac9d930..df5c26d 100644
--- a/dgamexwrapper/src/ScriptThreadFileSystem.cpp
+++ b/dgamexwrapper/src/ScriptThreadFileSystem.cpp
@@ -1,6 +1,7 @@
#include "ScriptThread.h"
#include
#include
+#include "CustomCvar.h"
#define MAX_SCRIPTFILES 32
@@ -210,6 +211,27 @@ void ScriptThread::FileSystemInit()
EV_RETURN
),
&ScriptThread::FListEvent);
+
+
+ cerSet.AddEventResponse(new Event(
+ "fnewdir",
+ EV_DEFAULT,
+ "s",
+ "path",
+ "Creates a new directory with the provided path.",
+ EV_RETURN
+ ),
+ &ScriptThread::FNewDirEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "fremovedir",
+ EV_DEFAULT,
+ "s",
+ "path",
+ "Removes the directory with the provided path.",
+ EV_RETURN
+ ),
+ &ScriptThread::FRemoveDirEvent);
}
void ScriptThread::FOpenEvent(Event *ev)
@@ -255,6 +277,8 @@ void ScriptThread::FOpenEvent(Event *ev)
else
{
scriptFiles.push_back(fp);
+ CustomCvar sv_scriptfiles("sv_scriptfiles", "0", 0);
+ sv_scriptfiles.SetValue(std::to_string(scriptFiles.size()), true);
}
ev->AddInteger((int)fp);
@@ -287,6 +311,8 @@ void ScriptThread::FCloseEvent(Event *ev)
{
ret = fclose(fp);
scriptFiles.erase(it);
+ CustomCvar sv_scriptfiles("sv_scriptfiles", "0", 0);
+ sv_scriptfiles.SetValue(std::to_string(scriptFiles.size()), true);
}
ev->AddInteger(ret);
@@ -952,4 +978,49 @@ void ScriptThread::FListEvent(Event *ev)
gi->FS_FreeFileList(list);
ev->AddValue(array);
-}
\ No newline at end of file
+}
+
+void ScriptThread::FNewDirEvent(Event* ev)
+{
+ int argnum = ev->NumArgs();
+
+ if (argnum != 1)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: Wrong number of arguments for fnewdir!\n");
+ return;
+ }
+
+ string dir_path = ev->GetString(1);
+ std::error_code ec;
+ bool success = fs::create_directory(dir_path, ec);
+ if (!success && ec)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: fnewdir error " + std::to_string(ec.value()) + ": " + ec.message() + "\n");
+ ev->AddInteger(ec.value());
+ return;
+ }
+ ev->AddInteger(0);
+}
+
+void ScriptThread::FRemoveDirEvent(Event* ev)
+{
+ int argnum = ev->NumArgs();
+
+ if (argnum != 1)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: Wrong number of arguments for fremovedir!\n");
+ return;
+ }
+
+ string dir_path = ev->GetString(1);
+ std::error_code ec;
+ bool success = fs::remove(dir_path, ec);
+ if (!success && ec)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: fremovedir error " + std::to_string(ec.value()) + ": " + ec.message() + "\n");
+ ev->AddInteger(ec.value());
+ return;
+ }
+
+ ev->AddInteger(0);
+}
diff --git a/dgamexwrapper/src/ScriptThreadMisc.cpp b/dgamexwrapper/src/ScriptThreadMisc.cpp
index 4ff0994..063dac4 100644
--- a/dgamexwrapper/src/ScriptThreadMisc.cpp
+++ b/dgamexwrapper/src/ScriptThreadMisc.cpp
@@ -8,7 +8,6 @@
#include "sv_misc.h"
#include
-
void ScriptThread::MiscInit()
{
////////////////////////////////////////
@@ -228,6 +227,34 @@ void ScriptThread::MiscInit()
EV_RETURN
),
&ScriptThread::JsonStringifyEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "chartoint",
+ EV_DEFAULT,
+ "s",
+ "character",
+ "Converts the given character to a string.",
+ EV_RETURN
+ ),
+ &ScriptThread::CharToIntEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "setcvar_ex",
+ EV_DEFAULT,
+ "ssSI",
+ "cvar value options flags",
+ "Sets cvar to value, using provided options and flags."
+ ),
+ &ScriptThread::SetCvarExEvent);
+
+ cerSet.AddEventResponse(new Event(
+ "teamswitchdelay",
+ EV_DEFAULT,
+ "f",
+ "delay",
+ "Sets minimum allowed delay for players to change between teams."
+ ),
+ &ScriptThread::TeamSwitchDelayEvent);
}
@@ -867,3 +894,49 @@ void ScriptThread::JsonStringifyEvent(Event * ev)
GameVarToJson(var, j);
ev->AddString(j.dump().c_str());
}
+
+void ScriptThread::CharToIntEvent(Event* ev)
+{
+ str char_str = ev->GetString(1);
+
+ int res = char_str[0];
+
+ ev->AddInteger(res);
+}
+
+void ScriptThread::SetCvarExEvent(Event* ev)
+{
+ int numArgs = ev->NumArgs();
+ if (numArgs <2 || numArgs > 4)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: Wrong number of arguments for setcvar_ex!\n");
+ return;
+ }
+ string name = ev->GetString(1);
+ string value = ev->GetString(2);
+ string options = "";
+ if (numArgs >=3)
+ options = ev->GetString(3);
+ int flags = 0;
+ if (numArgs == 4)
+ flags = ev->GetInteger(4);
+ bool silent = false;
+ if (options.find('S') != string::npos || options.find('s') != string::npos)
+ silent = true;
+
+ CustomCvar var(name, value, 0);
+ var.SetValue(value, silent);
+}
+
+void ScriptThread::TeamSwitchDelayEvent(Event* ev)
+{
+ int numArgs = ev->NumArgs();
+ if (numArgs != 1)
+ {
+ gi->Printf(PATCH_NAME " SCRIPT ERROR: Wrong number of arguments for teamswitchdelay!\n");
+ return;
+ }
+ float delay = ev->GetFloat(1);
+ CustomCvar var("g_teamswitchdelay", "15", 0);
+ var.SetValue(std::to_string(delay), true);
+}
diff --git a/dgamexwrapper/src/ScriptThreadScriptedEvents.cpp b/dgamexwrapper/src/ScriptThreadScriptedEvents.cpp
index 507b294..fca136b 100644
--- a/dgamexwrapper/src/ScriptThreadScriptedEvents.cpp
+++ b/dgamexwrapper/src/ScriptThreadScriptedEvents.cpp
@@ -88,7 +88,7 @@ void ScriptThread::RegisterevEvent(Event*ev)
if (exists)
{
- gi->Printf(PATCH_NAME " SCRIPT ERROR: registerev: event %s is already registered !\n", eventname.c_str());
+ gi->Printf(PATCH_NAME " SCRIPT WARNING: registerev: event %s is already registered !\n", eventname.c_str());
ev->AddInteger(SEVR_ALREADEXISTS);
return;
}
@@ -128,7 +128,7 @@ void ScriptThread::UnregisterevEvent(Event*ev)
if (exists)
{
- gi->Printf(PATCH_NAME " SCRIPT ERROR: unregisterev: event %s is already unregistered!\n", eventname.c_str());
+ gi->Printf(PATCH_NAME " SCRIPT WARNING: unregisterev: event %s is already unregistered!\n", eventname.c_str());
ev->AddInteger(SEVR_ALREADEXISTS);
return;
}
diff --git a/dgamexwrapper/src/SimpleEntity.cpp b/dgamexwrapper/src/SimpleEntity.cpp
index f54d903..64ea467 100644
--- a/dgamexwrapper/src/SimpleEntity.cpp
+++ b/dgamexwrapper/src/SimpleEntity.cpp
@@ -1,3 +1,6 @@
+#include "..\BaseHeaders\SimpleEntity.h"
+#include "..\BaseHeaders\SimpleEntity.h"
+#include "..\BaseHeaders\SimpleEntity.h"
#include "SimpleEntity.h"
@@ -11,3 +14,18 @@ SimpleEntity::SimpleEntity()
SimpleEntity::~SimpleEntity()
{
}
+
+int SimpleEntity::IsSubclassOfPlayer() const
+{
+ return (entflags & ECF_PLAYER);
+}
+
+int SimpleEntity::IsSubclassOfTurretGun() const
+{
+ return (entflags & ECF_TURRET);
+}
+
+int SimpleEntity::IsSubclassOfInventoryItem() const
+{
+ return (entflags & ECF_INVENTORYITEM);
+}
\ No newline at end of file
diff --git a/dgamexwrapper/src/dgamex86.cpp b/dgamexwrapper/src/dgamex86.cpp
index 427ed2a..b898535 100644
--- a/dgamexwrapper/src/dgamex86.cpp
+++ b/dgamexwrapper/src/dgamex86.cpp
@@ -21,7 +21,8 @@
#include "UpdateClient.h"
#include "ClientFilter.h"
#include "nf_misc.h"
-
+#include "RebornAPI.h"
+#include "sv_misc.h"
//#include "hooks/script.h"
@@ -65,6 +66,9 @@ int start, middle, end;
extern DirectorClass* Director;
+
+static RebornExports *rbe;
+//RebornImports rbi_back;
/*
//----------------------- End -----------------------\\
*/
@@ -155,34 +159,42 @@ char* G_ClientConnect_Internal(int clientNum, qboolean firstTime)
}
else
{
+ //reborn check:
+ if (gameInfo.IsAA())
+ {
+ reason = rbe->RB_PreClientConnect(clientNum, firstTime);
+ }
+
//you can't use gclient_t here,
//for userinfo: gi->GetUserInfo
char userinfo[MAX_INFOSTRING];
gi->GetUserinfo(clientNum, userinfo, MAX_INFOSTRING);
- //IP related checks
- char* ip = Info_ValueForKey(userinfo, "ip");
- if (ip == "")
- {
- gi->Printf(PATCH_NAME " connect error: empty client ip %d on G_ClientConnect_Internal! Ignoring...\n", clientNum);
- reason = NULL;
- }
- else
+ if (reason == NULL)
{
- IPFilter filter;
- if (!filter.CanConnect(ip))
+ //IP related checks
+ char* ip = Info_ValueForKey(userinfo, "ip");
+ if (ip == "")
{
- reason = "Banned IP.";
- }
- else if (!filter.CanConnectMaxConnections(clientNum))
- {
- reason = "Too many connection attempts from one IP.";
+ gi->Printf(PATCH_NAME " connect error: empty client ip %d on G_ClientConnect_Internal! Ignoring...\n", clientNum);
+ reason = NULL;
}
else
{
- reason = NULL;
+ IPFilter filter;
+ if (!filter.CanConnect(ip))
+ {
+ reason = "Banned IP.";
+ }
+ else if (!filter.CanConnectMaxConnections(clientNum))
+ {
+ reason = "Too many connection attempts from one IP.";
+ }
+ else
+ {
+ reason = NULL;
+ }
}
}
-
//name related checks:
if (reason == NULL)
{
@@ -319,6 +331,10 @@ void G_ClientThink(GEntity &ent, userCmd_t *ucmd, userEyes_t *eyeInfo )
gi->DropClient(GClient(ent->client)->ps.clientNum, "has been kicked for too high ping");
}
+ if (gameInfo.IsAA())
+ if (!rbe->RB_PreClientThink((gentityAA_t*)(gentity_t*)ent, ucmd, eyeInfo))
+ return;
+
globals_backup->ClientThink()( ent, ucmd, eyeInfo);
}
@@ -328,6 +344,17 @@ void G_ClientThink(GEntity &ent, userCmd_t *ucmd, userEyes_t *eyeInfo )
void G_ClientCommand (GEntity &ent ){
//gi->Argv(0) dmmessage Gi.Argv(1) 0 gi,Argv(2) blah gi->Args() 0 blah test
//gi->Printf("\ngi.Argv(0) %s Gi.Argv(1) %s gi,Argv(2) %s gi->Args() %s\n", gi->Argv(0) ,gi->Argv(1) ,gi->Argv(2) ,gi->Args());
+
+
+
+ if (gameInfo.IsAA() && gameInfo.IsServer() && rbe->RB_PreClientCommand)
+ {
+ if (!rbe->RB_PreClientCommand((gentityAA_t*)(gentity_t * )ent))
+ {
+ return;
+ }
+ }
+
char *cmd = gi->Argv(0);
if (!strcmp(cmd, "keyp"))
{
@@ -372,56 +399,56 @@ void G_ClientCommand (GEntity &ent ){
}
else if (!strcmp(cmd, "dmmessage"))
{
- int numArgs = gi->Argc();
- if (numArgs >= 3)
+ int numArgs = gi->Argc();
+ if (numArgs >= 3)
+ {
+ vector args;
+ //sayteam test1 test2 test3 test4 test5 sdfgfsg
+ //args are:
+ //clientnum part1 part2 part3 part4 ...
+ //example:
+ //1 test1 test2 test3 test4 ...
+ //
+ //test1 is arg 2
+ //sayteam is -1
+ //normal say is 0
+ //sayprivate & sayone: clientnum is actualy clientnum (it's a bit fuzzy when using sv_privateclients)
+ for (size_t i = 2; i < numArgs; i++)
{
- vector args;
- //sayteam test1 test2 test3 test4 test5 sdfgfsg
- //args are:
- //clientnum part1 part2 part3 part4 ...
- //example:
- //1 test1 test2 test3 test4 ...
- //
- //test1 is arg 2
- //sayteam is -1
- //normal say is 0
- //sayprivate & sayone: clientnum is actualy clientnum (it's a bit fuzzy when using sv_privateclients)
- for (size_t i = 2; i < numArgs; i++)
- {
- args.push_back(gi->Argv(i));
- }
+ args.push_back(gi->Argv(i));
+ }
- bool shouldKick;
- string reason;
- ChatFilter filter;
- if (!filter.CanSend(args, GClient(ent->client)->ps.clientNum, shouldKick, reason))
+ bool shouldKick;
+ string reason;
+ ChatFilter filter;
+ if (!filter.CanSend(args, GClient(ent->client)->ps.clientNum, shouldKick, reason))
+ {
+ if (shouldKick)
{
- if (shouldKick)
- {
- ClientAdmin admin(internalClientNum);
- admin.AddKick(GClient(ent->client)->ps.clientNum, true, reason.c_str());
- gi->DropClient(GClient(ent->client)->ps.clientNum, ("has been kicked for " + reason).c_str());
- }
- else
- {
- gi->SendServerCommand(GClient(ent->client)->ps.clientNum, "hudprint \"%s\n\"", reason.c_str());
- }
- return;
+ ClientAdmin admin(internalClientNum);
+ admin.AddKick(GClient(ent->client)->ps.clientNum, true, reason.c_str());
+ gi->DropClient(GClient(ent->client)->ps.clientNum, ("has been kicked for " + reason).c_str());
}
-
- try
+ else
{
- int target = std::stoi(gi->Argv(1));
- if (!filter.CheckScriptCallback(args, ent, target))
- {
- return;
- }
+ gi->SendServerCommand(GClient(ent->client)->ps.clientNum, "hudprint \"%s\n\"", reason.c_str());
}
- catch (const std::exception&)
+ return;
+ }
+
+ try
+ {
+ int target = std::stoi(gi->Argv(1));
+ if (!filter.CheckScriptCallback(args, ent, target))
{
- gi->Printf(PATCH_NAME " dmmessage error: invalid chat target %s or invalid script return value ! Ignoring...\n", gi->Argv(1));
+ return;
}
}
+ catch (const std::exception&)
+ {
+ gi->Printf(PATCH_NAME " dmmessage error: invalid chat target %s or invalid script return value ! Ignoring...\n", gi->Argv(1));
+ }
+ }
}
globals_backup->ClientCommand()(ent);
}
@@ -654,6 +681,12 @@ void G_InitGame( int startTime, int randomSeed )
initConsoleCommands();
startCrashReporter();
+
+ if (gameInfo.IsAA() && gameInfo.IsServer() && rbe->RB_InitGame)
+ {
+ rbe->RB_InitGame(startTime, randomSeed);
+ }
+
globals_backup->Init()( startTime, randomSeed );
//gi->Printf("Class count: %d", classcount);
gi->Printf(PATCH_NAME " Wrapper inited \n");
@@ -726,6 +759,10 @@ void G_Shutdown (void)
uc.CheckForUpdate();
}
+ if (gameInfo.IsAA() && gameInfo.IsServer() && rbe->RB_Shutdown)
+ {
+ rbe->RB_Shutdown();
+ }
globals_backup->Shutdown()();
#ifndef _WIN32
@@ -739,6 +776,79 @@ void G_Shutdown (void)
#endif
}
+void InitRebornProgs()
+{
+ //only for AA
+ if (!gameInfo.IsAA())
+ return;
+ RebornImports rbi;
+
+ rbi.MemoryMalloc = MemoryMalloc;
+ rbi.MemoryFree = MemoryFree;
+
+ rbi.Cvar_Get = [](const char* varName, const char* varValue, int varFlags) -> cvar_t* {return gi->Cvar_Get(varName, varValue, varFlags); };
+ rbi.Milliseconds = []() -> int {return gi->Milliseconds(); };
+
+ rbi.KickClient = [](int client_num, const char* reason, const char* public_reason) {
+ ClientAdmin admin(internalClientNum);
+ admin.AddKick(client_num, bool(reason), reason);
+ gi->DropClient(client_num, ("has been kicked for " + string(public_reason)).c_str());
+ };
+
+ rbi.Printf = [](const char* format, ...) {
+ va_list va;
+ char fullstr[4096];
+
+ va_start(va, format);
+ vsprintf(fullstr, format, va);
+ va_end(va);
+
+ gi->Printf(fullstr);
+ };
+
+ rbi.centerprintf = [](gentityAA_t * ent, const char* format, ...) {
+ va_list va;
+ char fullstr[4096];
+
+ va_start(va, format);
+ vsprintf(fullstr, format, va);
+ va_end(va);
+ gi->centerprintf((gentity_t*)ent, fullstr);
+ };
+ rbi.HookFunction = [](_Inout_ PVOID* ppPointerOriginal, _In_ PVOID pDetour) -> bool {
+ bool ok = true;
+ ok = ok && (DetourTransactionBegin() == NO_ERROR);
+ ok = ok && (DetourUpdateThread(GetCurrentThread()) == NO_ERROR);
+ ok = ok && (DetourAttach((ppPointerOriginal), (pDetour)) == NO_ERROR);
+ ok = ok && (DetourTransactionCommit() == NO_ERROR);
+ return ok;
+ };
+ rbi.UnHookFunction = [](_Inout_ PVOID* ppPointerOriginal, _In_ PVOID pDetour) -> bool {
+ bool ok = true;
+ ok = ok && (DetourTransactionBegin() == NO_ERROR);
+ ok = ok && (DetourUpdateThread(GetCurrentThread()) == NO_ERROR);
+ ok = ok && (DetourDetach((ppPointerOriginal), (pDetour)) == NO_ERROR);
+ ok = ok && (DetourTransactionCommit() == NO_ERROR);
+ return ok;
+ };
+ rbi.Trace = [](trace_t* results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask, qboolean cylinder, qboolean traceDeep)
+ {
+ gi->Trace(results, start, mins, maxs, end, passEntityNum, contentMask, cylinder, traceDeep);
+ };
+
+ rbi.AddCommand = [](const char* cmdName, xcommand_t cmdFunction)
+ {
+ SV_Commands_AddCommand(cmdName, cmdFunction);
+ };
+
+ rbi.Argc = []() ->int { return gi->Argc(); };
+ rbi.Argv = [](int arg) -> char*{ return gi->Argv(arg); };
+ rbi.Args = []() -> char*{ return gi->Args(); };
+ rbi.Info_ValueForKey = [](const char* s, const char* key) -> char*{ return Info_ValueForKey(s, key); };
+
+ rbe = GetRebornAPI(&rbi);
+}
+
string dllMainErr_str;
//input: gameImport_T* , output: gameExport_t*
void* __cdecl GetGameAPI( void *import_actual )
@@ -763,7 +873,7 @@ void* __cdecl GetGameAPI( void *import_actual )
if (!dllMainErr_str.empty())
{
gameImportAA_t* tmp_import = (gameImportAA_t*)import_actual; // we only use Printf which is the same for any expansion/side
- tmp_import->Printf(dllMainErr_str.c_str());
+ tmp_import->Printf((dllMainErr_str+"\n").c_str());
return NULL;
}
@@ -871,6 +981,7 @@ void* __cdecl GetGameAPI( void *import_actual )
*/
//TODO: supress DavenExtra for BT.
//sizeof(Player);
+ InitRebornProgs();
return globals->GetRealGameExport();
}
@@ -946,29 +1057,60 @@ BOOL WINAPI DllMain( HINSTANCE hModule, DWORD dwReason, PVOID lpReserved )
- //get version info based on calling exe's hashes.
- char szExeFileName[MAX_PATH];
- GetModuleFileNameA(NULL, szExeFileName, MAX_PATH);
- namespace fs = std::filesystem;
- fs::path p = szExeFileName;
-
- char md5csum[MD5_STR_SIZE];
- if (!md5File(szExeFileName, md5csum))
{
- dllMainErr_str = "NightFall GetGameAPI ERROR: could not calculate md5 checksum for exe: " + string(szExeFileName);
+ //get version info based on calling exe's hashes.
+ //also verify a supported exe.
+ char szExeFileName[MAX_PATH];
+ GetModuleFileNameA(NULL, szExeFileName, MAX_PATH);
+ namespace fs = std::filesystem;
+ fs::path p = szExeFileName;
+
+ char md5csum[MD5_STR_SIZE];
+ if (!md5File(szExeFileName, md5csum))
+ {
+ dllMainErr_str = "NightFall GetGameAPI ERROR: could not calculate md5 checksum for exe: " + string(szExeFileName);
+ }
+ else if (!gameInfo.InitFromMD5(string(md5csum)))
+ {
+ dllMainErr_str = "NightFall GetGameAPI ERROR: no exe match found for md5: " + string(md5csum);
+ }
}
- else if (!gameInfo.InitFromMD5(string(md5csum)))
+ hmod = LoadLibraryA(DGAMEX86_PATH.c_str());
+
+ //verify a supported DLL
+ if (hmod)
{
- dllMainErr_str = "NightFall GetGameAPI ERROR: no exe match found for md5: " + string(md5csum);
- }
+ char szDLLFileName[MAX_PATH];
+ GetModuleFileNameA(hmod, szDLLFileName, MAX_PATH);
+ namespace fs = std::filesystem;
+ fs::path p = szDLLFileName;
- hmod = LoadLibraryA(DGAMEX86_PATH.c_str());
+ char md5csum[MD5_STR_SIZE];
+ if (!md5File(szDLLFileName, md5csum))
+ {
+ dllMainErr_str = "NightFall GetGameAPI ERROR: could not calculate md5 checksum for dll: " + string(szDLLFileName);
+ }
+ else
+ {
+ int ret = gameInfo.ValidateGameDLLMD5(string(md5csum));
+ if (ret == 1)
+ {
+ dllMainErr_str = "NightFall GetGameAPI ERROR: no dll match found for md5: " + string(md5csum);
+ }
+ else if (ret == 2)
+ {
+ dllMainErr_str = "NightFall GetGameAPI ERROR: no dll/exe mismatch found for dll md5: " + string(md5csum);
+ }
+ }
+ }
if(hmod)
{
pGetGameAPI = (pGetGameAPI_spec)GetProcAddress(hmod, "GetGameAPI");
}
+
+
systemHMOD = GetModuleHandleA(SYSTEM86_NAME.c_str());
if (systemHMOD)
diff --git a/dgamexwrapper/src/nf_misc.cpp b/dgamexwrapper/src/nf_misc.cpp
index 1192ff7..2a0e143 100644
--- a/dgamexwrapper/src/nf_misc.cpp
+++ b/dgamexwrapper/src/nf_misc.cpp
@@ -172,4 +172,13 @@ bool CheckCommentSlashesInName(char* name)
{
int len = strlen(name);
return name[len - 2] == '/' && (name[len - 1] == '/' || name[len - 1] == '*');
-}
\ No newline at end of file
+}
+
+//output is read only
+char* CopyString(const char* in)
+{
+ char* out;
+ out = (char*)gi->Malloc(strlen(in) + 1);
+ strcpy(out, in);
+ return out;
+}
diff --git a/dgamexwrapper/src/safeptr.h b/dgamexwrapper/src/safeptr.h
deleted file mode 100644
index c0cd7e6..0000000
--- a/dgamexwrapper/src/safeptr.h
+++ /dev/null
@@ -1,94 +0,0 @@
-#pragma once
-
-class Class;
-class SafePtrBase
-{
-
-protected:
- SafePtrBase* prev;
- SafePtrBase* next;
- Class* ptr;
-
-public:
- virtual ~SafePtrBase()
- {
- throw "Shouldn't execute nightfall's SafePtrBaSE Destructor.";
- }
-};
-template
-class SafePtr : public SafePtrBase
-{
-public:
- template< class U > friend bool operator==(SafePtr a, U* b);
- template< class U > friend bool operator!=(SafePtr a, U* b);
- template< class U > friend bool operator==(U* a, SafePtr b);
- template< class U > friend bool operator!=(U* a, SafePtr b);
- template< class U > friend bool operator==(SafePtr a, SafePtr b);
- template< class U > friend bool operator!=(SafePtr a, SafePtr b);
-
- bool operator !() const;
- operator T* () const;
- T* operator->() const;
- T& operator*() const;
-};
-
-
-template
-inline bool operator==(SafePtr a, T* b)
-{
- return a.ptr == b;
-}
-
-template
-inline bool operator!=(SafePtr a, T* b)
-{
- return a.ptr != b;
-}
-
-template
-inline bool operator==(T* a, SafePtr b)
-{
- return a == b.ptr;
-}
-
-template
-inline bool operator!=(T* a, SafePtr b)
-{
- return a != b.ptr;
-}
-
-template
-inline bool operator==(SafePtr a, SafePtr b)
-{
- return a.ptr == b.ptr;
-}
-
-template
-inline bool operator!=(SafePtr a, SafePtr b)
-{
- return a.ptr != b.ptr;
-}
-
-template
-inline bool SafePtr::operator !() const
-{
- return ptr == NULL;
-}
-
-template
-inline SafePtr::operator T* () const
-{
- return (T*)ptr;
-}
-
-template
-inline T* SafePtr::operator->() const
-{
- return (T*)ptr;
-}
-
-template
-inline T& SafePtr::operator*() const
-{
- return *(T*)ptr;
-}
diff --git a/dgamexwrapper/src/sv_commands.cpp b/dgamexwrapper/src/sv_commands.cpp
index e27b2ee..dc41bd2 100644
--- a/dgamexwrapper/src/sv_commands.cpp
+++ b/dgamexwrapper/src/sv_commands.cpp
@@ -220,6 +220,12 @@ void SV_Commands_Shutdown()
Cmd_UnHookCommand(SV_KickNum_f_original, &SV_ClientKick2);
}
+void SV_Commands_AddCommand(const char* cmd, xcommand_t cmdFunction)
+{
+ ConsoleCommands.emplace_back(cmd, cmdFunction);
+ gi->AddCommand(cmd, NULL);
+}
+
/* same behaviour as normal kicks, but displays to the kicked user that he was kicked. */
/* original game behaviour will display a "server disconnected" message instead of "kicked from server". */
void SV_Kick2()
diff --git a/dgamexwrapper/src/sv_commands.h b/dgamexwrapper/src/sv_commands.h
index 1a06fd2..c100d7c 100644
--- a/dgamexwrapper/src/sv_commands.h
+++ b/dgamexwrapper/src/sv_commands.h
@@ -29,6 +29,7 @@ void SV_Commands_Init();
bool SV_Commands_HandleCommand();
void SV_Commands_Shutdown();
+void SV_Commands_AddCommand(const char* cmd, xcommand_t cmdFunction);
void SV_Kick2();
diff --git a/dgamexwrapper/src/sv_misc.cpp b/dgamexwrapper/src/sv_misc.cpp
index a01ff49..e1cc3dd 100644
--- a/dgamexwrapper/src/sv_misc.cpp
+++ b/dgamexwrapper/src/sv_misc.cpp
@@ -37,17 +37,32 @@ void __cdecl NET_OutOfBandPrint(netSrc_t sock, netAdr_t adr, const char * format
NET_OutOfBandPrint_Real(sock, adr, string);
}
+string NetAdrToIPString(netAdr_t& adr)
+{
+ if (adr.type == NA_LOOPBACK)
+ return "127.0.0.1";
+ else if (adr.type == NA_IP)
+ return string(to_string(adr.ip[0]) + "." + to_string(adr.ip[1]) + "." + to_string(adr.ip[2]) + "." + to_string(adr.ip[3]));
+ else
+ return "";
+}
+
+string NetAdrToPortString(netAdr_t& adr)
+{
+ return to_string(ntohs(adr.port));
+}
+
+string NetAdrToIPPortString(netAdr_t& adr)
+{
+ return NetAdrToIPString(adr) + ":" + NetAdrToPortString(adr);
+}
+
string GetIPFromClient(client_t* cl_actual)
{
if (!cl_actual)
return "";
Client cl(cl_actual);
- if (cl->netchan.remoteAddress.type == NA_LOOPBACK)
- return "127.0.0.1";
- else if (cl->netchan.remoteAddress.type == NA_IP)
- return string(to_string(cl->netchan.remoteAddress.ip[0]) + "." + to_string(cl->netchan.remoteAddress.ip[1]) + "." + to_string(cl->netchan.remoteAddress.ip[2]) + "." + to_string(cl->netchan.remoteAddress.ip[3]));
- else
- return "";
+ return NetAdrToIPString(cl->netchan.remoteAddress);
}
string GetPortFromClient(client_t* cl_actual)
@@ -55,7 +70,7 @@ string GetPortFromClient(client_t* cl_actual)
if (!cl_actual)
return "";
Client cl(cl_actual);
- return to_string(ntohs(cl->netchan.remoteAddress.port));
+ return NetAdrToPortString(cl->netchan.remoteAddress);
}
string GetIPPortFromClient(client_t* cl)
diff --git a/docs/cvars.md b/docs/cvars.md
index f639a01..81f300e 100644
--- a/docs/cvars.md
+++ b/docs/cvars.md
@@ -11,12 +11,24 @@
- [sv\_api\_client](#sv_api_client)
- [sv\_update](#sv_update)
- [sv\_store](#sv_store)
+ - [sv\_scriptfiles](#sv_scriptfiles)
- [sv\_disablechat](#sv_disablechat)
- [sv\_disabletaunt](#sv_disabletaunt)
- [sv\_filterchat](#sv_filterchat)
- [g\_badchatlimit](#g_badchatlimit)
+ - [g\_teamswitchdelay](#g_teamswitchdelay)
- [sv\_maxconnperip](#sv_maxconnperip)
- [sv\_kickping](#sv_kickping)
+ - [sv\_kickbadcmd](#sv_kickbadcmd)
+ - [sv\_packetantiflood](#sv_packetantiflood)
+ - [sv\_packetflooddelay](#sv_packetflooddelay)
+ - [| Supported Games | MOHAA Only |](#-supported-games--mohaa-only-)
+ - [sv\_remotetoolip](#sv_remotetoolip)
+ - [| Supported Games | MOHAA Only |](#-supported-games--mohaa-only--1)
+ - [sv\_antistwh](#sv_antistwh)
+ - [| Supported Games | MOHAA Only |](#-supported-games--mohaa-only--2)
+ - [sv\_rebornloader](#sv_rebornloader)
+ - [| Remarks | Inside the script, self will be Director (ScriptMaster) |](#-remarks--inside-the-script-self-will-be-director-scriptmaster-)
### sv_crashrpt_poll_delay
| Name | sv_crashrpt_poll_delay |
@@ -81,6 +93,14 @@
| Description | Specify database file name inside main for setproperty and getproperty functions. |
| Default value | `mainta/store.bin` or `maintt/store.bin` |
+---
+### sv_scriptfiles
+| Name | sv_scriptfiles |
+|--|--|
+| Description | Specifies the amount of currently open script files (opened with [fopen](scriptallfuncs.md#fopen)). |
+| Default value | `main/store.bin` or `mainta/store.bin` or `maintt/store.bin` |
+| More Info | When you successfully open files with [fopen](scriptallfuncs.md#fopen), sv_scriptfiles increases.
When you close already opened files with [fclose](scriptallfuncs.md#fclose), sv_scriptfiles decreases. |
+
---
### sv_disablechat
| Name | sv_disablechat |
@@ -109,6 +129,14 @@
| Description | Specifies amount of times a use can enter bad chat filter words before being kicked. |
| Allowed values | 0 or more |
+---
+### g_teamswitchdelay
+| Name | g_teamswitchdelay |
+|--|--|
+| Description | Specifies amount of seconds a player must wait before switching to a new team. |
+| More Info | This is equivalent to script function [teamswitchdelay](scriptallfuncs.md#teamswitchdelay) and using the script function changes the value of this cvar silently. |
+| Allowed values | any integer value |
+
---
### sv_maxconnperip
| Name | sv_maxconnperip |
@@ -126,3 +154,63 @@
| Remarks | user is warned 1 time before getting kicked |
---
+
+---
+### sv_kickbadcmd
+| Name | sv_kickbadcmd |
+|--|--|
+| Description | Specifies whether players who enter bad commands should be kicked. |
+| Allowed values | 0 or 1 |
+| Remarks | when someone types a bad command that is used for crashing the server, they will be kicked if the cvar is non-zero |
+| Supported Games | MOHAA Only |
+
+---
+
+---
+### sv_packetantiflood
+| Name | sv_packetantiflood |
+|--|--|
+| Description | Enable/disable packet anti-flood system |
+| Allowed values | 0 or 1 |
+| Supported Games | MOHAA Only |
+
+---
+
+---
+### sv_packetflooddelay
+| Name | sv_packetflooddelay |
+|--|--|
+| Description | Specify packet flood delay in milliseconds. |
+| Allowed values | any non-negative number |
+| Remarks | Specifies the minimum delay in milliseconds between two consecutive packets originating from the same IP to be processed. This is only for connectionless packets (players that already joined the game won't be affected). |
+| Supported Games | MOHAA Only |
+---
+
+---
+### sv_remotetoolip
+| Name | sv_remotetoolip |
+|--|--|
+| Description | Specify IP for whitelisted remote tool IP. |
+| Allowed values | any valid IP address |
+| Remarks | This IP will not be affected by anti-flood system. |
+| Supported Games | MOHAA Only |
+---
+
+---
+### sv_antistwh
+| Name | sv_antistwh |
+|--|--|
+| Description | Enable/Disable anti-STWH. |
+| Allowed values | 0 or 1 |
+| Remarks | Enables/Disables anti-Shoot Through Walls Hack(STWH) system. |
+| Supported Games | MOHAA Only |
+---
+
+---
+### sv_rebornloader
+| Name | sv_rebornloader |
+|--|--|
+| Description | Specifies script that will be loaded at the start of the map. |
+| Allowed values | script path |
+| Remarks | Inside the script, self will be Director (ScriptMaster) |
+---
\ No newline at end of file
diff --git a/docs/rconcmds.md b/docs/rconcmds.md
index e33044a..066dfba 100644
--- a/docs/rconcmds.md
+++ b/docs/rconcmds.md
@@ -155,3 +155,12 @@
| Description | Print current patch version. |
| Example | `patchver` |
---
+### registertool
+| Name | registertool |
+|--|--|
+| Arguments | none |
+| Description | Used to register remote tool IP address as whitelisted from anti-STWH system. Only use this via RCON.|
+| Example | `registertool` |
+| Supported Games | MOHAA Only |
+---
+
diff --git a/docs/scriptallfuncs.md b/docs/scriptallfuncs.md
index 6bb6c75..bd851ad 100644
--- a/docs/scriptallfuncs.md
+++ b/docs/scriptallfuncs.md
@@ -29,6 +29,7 @@
- [floor](#floor)
- [fmod](#fmod)
- [ScriptThread Miscellaneous Functions](#scriptthread-miscellaneous-functions)
+ - [setcvar\_ex](#setcvar_ex)
- [stuffsrv](#stuffsrv)
- [conprintf](#conprintf)
- [md5string](#md5string)
@@ -40,6 +41,8 @@
- [regex\_parse](#regex_parse)
- [json\_parse](#json_parse)
- [json\_stringify](#json_stringify)
+ - [chartoint](#chartoint)
+ - [teamswitchdelay](#teamswitchdelay)
- [getentity](#getentity)
- [netname](#netname)
- [getip](#getip)
@@ -76,6 +79,8 @@
- [fcopy](#fcopy)
- [freadpak](#freadpak)
- [flist](#flist)
+ - [fnewdir](#fnewdir)
+ - [fremovedir](#fremovedir)
- [ScriptThread API Functions](#scriptthread-api-functions)
- [create\_api\_request](#create_api_request)
- [register\_api\_route](#register_api_route)
@@ -85,13 +90,19 @@
- [getkills](#getkills)
- [getdeaths](#getdeaths)
- [.secfireheld](#secfireheld)
+ - [.runheld](#runheld)
+ - [.leanleftheld](#leanleftheld)
+ - [.leanrightheld](#leanrightheld)
- [.inventory](#inventory)
- [getactiveweap](#getactiveweap)
- [getconnstate](#getconnstate)
- [isadmin](#isadmin)
+ - [adminrights](#adminrights)
+ - [bindweap](#bindweap)
- [References](#references)
- [C numerics library](#c-numerics-library)
- [C standard Input/Output library](#c-standard-inputoutput-library)
+ - [Reborn Scripting Commands](#reborn-scripting-commands)
## ScriptThread Date/Time Functions
---
### gettime
@@ -158,7 +169,7 @@ Most of these functions use their [C numerics library](#c-numerics-library) coun
sin(float x)
Returns the sine of an angle of x radians.
-**Warning:** For SH/BT, x is expected to be in degrees.
+> **Warning:** For SH/BT, x is expected to be in degrees.
Example:
@@ -517,6 +528,27 @@ Floating-point remainder of num/denom.
```
---
## ScriptThread Miscellaneous Functions
+### setcvar_ex
+ setcvar_ex(string cvar, string value, [string options="", integer flags=0])
+Sets the `cvar` to `value` using the specified `options` and `flags`.
+Currently supported `options`:
+```
+"s" or "S" set cvar silently (will not print to log even if developer is set)
+```
+`flags` is reserved for future use
+
+Example:
+```
+setcvar_ex "fraglimit" "1" "s" 0
+or
+setcvar_ex "fraglimit" "1" "S" 0
+
+```
+Result:
+```
+Changes fraglimit to 1 without printing "Cvar_Set2: fraglimit 1" to console/logfile
+```
+---
### stuffsrv
stuffsrv(string cmd)
Executes command in server console.
@@ -758,6 +790,34 @@ local.result = {"content":[{"content":"aaa","type":"string"},{"content":"bb","ty
- reference, a reference to array, usually not used in scripts
---
---
+### chartoint
+ chartoint(string ch)
+Return integer (corresponding ASCII value) representation of `ch`.
+
+Example:
+```
+local.result = chartoint "a"
+```
+Result:
+```
+local.result = 97
+```
+---
+---
+### teamswitchdelay
+ teamswitchdelay(integer delay)
+Sets minimum delay `delay` to switch between teams.
+This command is equivalent to `setcvar_ex "g_teamswitchdelay" delay "s" 0`
+Example:
+```
+teamswitchdelay 50
+```
+Result:
+```
+Players will have to wait 50 seconds before switching to a new team.
+```
+---
+---
### getentity
getentity(integer entnum)
Get entity whose entity number is entnum.
@@ -1147,7 +1207,8 @@ Most of the following functions map to their respective cstdio library counterpa
Opens file with file_path using access_type.
share_flags specify whether to allow other processes and threads to open your file for reading/writing.
-
+This command increments value of [sv_scriptfiles](cvars.md#sv_scriptfiles) by one.
+Maximum supported concurrent open files count is 32.
Example:
```
@@ -1172,6 +1233,7 @@ Share Flags:
### fclose
fclose(integer file_handle)
Closes file.
+This command decrements value of [sv_scriptfiles](cvars.md#sv_scriptfiles) by one.
Example:
```
@@ -1470,6 +1532,40 @@ Result:
Array containing list of file/folder names.
```
---
+---
+### fnewdir
+ fnewdir(string dirpath)
+Creates new empty directory with path `dirpath`.
+
+
+Example:
+```
+local.result = fnewdir "main/mynewdir"
+
+```
+Result:
+```
+0 on success or if directory already exists.
+nonzero on failure or if one of dirpath parent directories does not exist.
+```
+---
+---
+### fremovedir
+ fremovedir(string dirpath)
+Removes **empty** directory with path `dirpath`.
+
+
+Example:
+```
+local.result = fremovedir "main/mynewdir"
+
+```
+Result:
+```
+0 on success or if directory does not exist.
+nonzero on failure or if directory is not emtpy.
+```
+---
## ScriptThread API Functions
### create_api_request
create_api_request(string url, string method, string/array scriptname, var userdata)
@@ -1622,6 +1718,55 @@ Returns 1 if player is holding secondary fire button (usually right click), 0 ot
```
---
---
+### .runheld
+ .runheld
+Return player's run button status.
+
+
+Example:
+```
+local.result = local.player.runheld
+
+```
+Result:
+```
+Returns 1 if player is holding run (even if player is not moving), 0 otherwise (when left shift is held/player is walking).
+```
+---
+---
+### .leanleftheld
+ .leanleftheld
+Return player's lean left button status.
+
+
+Example:
+```
+local.result = local.player.leanleftheld
+
+```
+Result:
+```
+Returns 1 if player is holding lean left button (Z), 0 otherwise.
+```
+---
+---
+---
+### .leanrightheld
+ .leanrightheld
+Return player's lean right button status.
+
+
+Example:
+```
+local.result = local.player.leanrightheld
+
+```
+Result:
+```
+Returns 1 if player is holding lean right button (C), 0 otherwise.
+```
+---
+---
### .inventory
.inventory
Return player's inventory as array.
@@ -1693,7 +1838,47 @@ Result:
Returns integer value: 1 if logged in as admin (using ad_login), 0 otherwise.
```
---
+---
+### adminrights
+ .adminrights
+Returns rights for player.
+
+Example:
+```
+local.result = local.player.adminrights
+
+
+```
+Result:
+```
+Returns integer value: -1 if NOT logged in as admin (using ad_login).
+If logged in, returns the integer value for admin rights, which is located inside admins.ini
+```
+---
+---
+### bindweap
+ bindweap(Entity weapon)
+> **NOTE:** This command is supported for AA only.
+
+Binds weapon to player. Sets him as weapon owner.
+2nd use of the command will unbind the weapon from player.
+
+Example:
+```
+$player[1] bindweap local.weapon
+local.weapon anim fire
+$player[1] bindweap local.weapon
+
+```
+Result:
+```
+Sets player as weapon owner.
+```
+
+> **Warning:** This is sort of a hack&trick scripting command. It should only be used by experienced users and only like shown in the example - just before firing the weapon and just after, to unbind it from the player. Otherwise you can have errors, weapon model glued to player, or server crashes. It should be used only for some kind of remote turrets etc.
+---
## References
### [C numerics library](https://cplusplus.com/reference/cmath)
-### [C standard Input/Output library](https://cplusplus.com/reference/cstdio/)
\ No newline at end of file
+### [C standard Input/Output library](https://cplusplus.com/reference/cstdio/)
+### [Reborn Scripting Commands](http://www.x-null.net/wiki/index.php?title=Reborn_Scripting_Commands)
\ No newline at end of file
diff --git a/docs/scriptfuncs.md b/docs/scriptfuncs.md
index 8bb72d4..69819fd 100644
--- a/docs/scriptfuncs.md
+++ b/docs/scriptfuncs.md
@@ -4,6 +4,7 @@
- [NightFall Scripting Functions Documentation](#nightfall-scripting-functions-documentation)
- [Table of Contents](#table-of-contents)
- [New NightFall functions](#new-nightfall-functions)
+ - [setcvar\_ex](#setcvar_ex)
- [register\_api\_route](#register_api_route)
- [unregister\_api\_route](#unregister_api_route)
- [create\_api\_request](#create_api_request)
@@ -12,6 +13,7 @@
- [json\_parse](#json_parse)
- [json\_stringify](#json_stringify)
- [Slightly modified reborn functions](#slightly-modified-reborn-functions)
+ - [bindweap](#bindweap)
- [getactiveweap](#getactiveweap)
- [fopen](#fopen)
- [fclose](#fclose)
@@ -20,6 +22,15 @@
- [fsaveall](#fsaveall)
- [getdate](#getdate)
- [registerev](#registerev)
+ - [Undocumented reborn functions](#undocumented-reborn-functions)
+ - [chartoint](#chartoint)
+ - [teamswitchdelay](#teamswitchdelay)
+ - [fnewdir](#fnewdir)
+ - [fremovedir](#fremovedir)
+ - [runheld](#runheld)
+ - [leanleftheld](#leanleftheld)
+ - [leanrightheld](#leanrightheld)
+ - [adminrights](#adminrights)
- [Reborn original functions](#reborn-original-functions)
- [Date/Time Functions](#datetime-functions)
- [Maths Functions](#maths-functions)
@@ -30,6 +41,7 @@
---
## New NightFall functions
---
+### [setcvar_ex](scriptallfuncs.md#setcvar_ex)
### [register_api_route](scriptallfuncs.md#register_api_route)
### [unregister_api_route](scriptallfuncs.md#unregister_api_route)
### [create_api_request](scriptallfuncs.md#create_api_request)
@@ -40,6 +52,7 @@
---
## Slightly modified reborn functions
---
+### [bindweap](scriptallfuncs.md#bindweap)
### [getactiveweap](scriptallfuncs.md#getactiveweap)
### [fopen](scriptallfuncs.md#fopen)
### [fclose](scriptallfuncs.md#fclose)
@@ -49,6 +62,17 @@
### [getdate](scriptallfuncs.md#getdate)
### [registerev](scriptallfuncs.md#registerev)
---
+## Undocumented reborn functions
+---
+### [chartoint](scriptallfuncs.md#chartoint)
+### [teamswitchdelay](scriptallfuncs.md#teamswitchdelay)
+### [fnewdir](scriptallfuncs.md#fnewdir)
+### [fremovedir](scriptallfuncs.md#fremovedir)
+### [runheld](scriptallfuncs.md#runheld)
+### [leanleftheld](scriptallfuncs.md#leanleftheld)
+### [leanrightheld](scriptallfuncs.md#leanrightheld)
+### [adminrights](scriptallfuncs.md#adminrights)
+---
## Reborn original functions
---
For the sake of not re-inventing the wheel, the rest of the functions in NightFall are exactly identical to reborn ones.
diff --git a/tests/bot_manager.py b/tests/bot_manager.py
index 9807dbd..8194f43 100644
--- a/tests/bot_manager.py
+++ b/tests/bot_manager.py
@@ -181,12 +181,15 @@ def new_muliple_instances(self, n, should_connect=True):
return insts
+ def join_team(self, team=b'axis'):
+ assert self.send_cmd_await_output(b'join_team ' + team, b'mohpc_test' + str(self.instance_id).encode() + b' has joined the ' + team.capitalize())
+ self.send_command(b'primarydmweapon rifle')
+
def spawn(self, team=b'axis'):
print('BotManager: spawn: team:', team.decode(), 'instance_id:', self.instance_id)
self.send_command(b'set dm_playermodel american_army')
self.send_command(b'set dm_playergermanmodel german_wehrmacht_soldier')
assert self.instance_id != -1
- assert self.send_cmd_await_output(b'join_team ' + team, b'mohpc_test' + str(self.instance_id).encode() + b' has joined the ' + team.capitalize())
- self.send_command(b'primarydmweapon rifle')
+ self.join_team(team)
time.sleep(1) #for spawn
print('BotManager: spawned:', team.decode())
\ No newline at end of file
diff --git a/tests/file_manager.py b/tests/file_manager.py
index 2c8adad..e4aede5 100644
--- a/tests/file_manager.py
+++ b/tests/file_manager.py
@@ -60,4 +60,13 @@ def unload(self):
elif os.path.isdir(fpath):
shutil.rmtree(fpath, ignore_errors=True)
- print('unloaded test files')
\ No newline at end of file
+ print('unloaded test files')
+ def delete(self, path, is_main=False):
+ if is_main:
+ fpath = self.game_path / path
+ else:
+ fpath = self.main_path / path
+ if os.path.isfile(fpath):
+ os.remove(fpath)
+ elif os.path.isdir(fpath):
+ shutil.rmtree(fpath, ignore_errors=True)
\ No newline at end of file
diff --git a/tests/testfiles/scriptfilesystem/mainfiles/tests/scriptfilesystem.scr b/tests/testfiles/scriptfilesystem/mainfiles/tests/scriptfilesystem.scr
index 59b8df4..16e1473 100644
--- a/tests/testfiles/scriptfilesystem/mainfiles/tests/scriptfilesystem.scr
+++ b/tests/testfiles/scriptfilesystem/mainfiles/tests/scriptfilesystem.scr
@@ -35,6 +35,9 @@ main:
waitthread test_freadpak
waitthread test_flist
+ waitthread test_fnewdir
+ waitthread test_fremovedir
+
setcvar test_filesystem_score (level.filesystem_testcases_passed + "/" + level.filesystem_testcases_total)
setcvar test_filesystem_failed level.filesystem_failed_testscases
setcvar test_filesystem_status finished
@@ -229,4 +232,23 @@ test_flist:
waitthread validate_test (local.ret[2]) "test_folder/" "(flist test25)"
+end
+
+test_fnewdir:
+ local.ret = fnewdir "test_fnewdir"
+ waitthread validate_test (local.ret==0) 1 "(fnewdir test1)"
+ local.ret = fnewdir "test_fnewdir2/test_fnewdir" // non-existent path
+ waitthread validate_test (local.ret==0) 0 "(fnewdir test2)"
+ local.ret = fnewdir "test_fnewdir3" // already exists
+ waitthread validate_test (local.ret==0) 1 "(fnewdir test3)"
+end
+
+
+test_fremovedir:
+ local.ret = fremovedir "test_fremovedir" // empty
+ waitthread validate_test (local.ret==0) 1 "(fremovedir test1)"
+ local.ret = fremovedir "test_fremovedir2" // non-empty
+ waitthread validate_test (local.ret==0) 0 "(fremovedir test2)"
+ local.ret = fremovedir "test_fremovedir3" //non-existent
+ waitthread validate_test (local.ret==0) 1 "(fremovedir test3)"
end
\ No newline at end of file
diff --git a/tests/testfiles/scriptfilesystem/test_fremovedir2/test.txt b/tests/testfiles/scriptfilesystem/test_fremovedir2/test.txt
new file mode 100644
index 0000000..e69de29
diff --git a/tests/testfiles/scriptmiscall/mainfiles/tests/misc.scr b/tests/testfiles/scriptmiscall/mainfiles/tests/misc.scr
index 45f9efa..ff0c89f 100644
--- a/tests/testfiles/scriptmiscall/mainfiles/tests/misc.scr
+++ b/tests/testfiles/scriptmiscall/mainfiles/tests/misc.scr
@@ -20,6 +20,10 @@ main:
local.t = "ttt"
waitthread validate_test (typeof local.t) "const string" "(typeof local.t=ttt)"
+ waitthread validate_test (chartoint "a") 97 "(chartoint a)"
+ waitthread validate_test (chartoint "A") 65 "(chartoint A)"
+ waitthread validate_test (chartoint "#") 35 "(chartoint #)"
+
waitthread validate_test (getproperty "nightfall_test") -3 "(getproperty nightfall_test)"
waitthread validate_test (setproperty "nightfall_test" "nf_t_1") 0 "(setproperty nightfall_test nf_t_1)"
waitthread validate_test (getproperty "nightfall_test") "nf_t_1" "(getproperty nightfall_test)"
diff --git a/tests/testfiles/scriptplayeradminrights/mainfiles/admins.ini b/tests/testfiles/scriptplayeradminrights/mainfiles/admins.ini
new file mode 100644
index 0000000..89cfaa7
--- /dev/null
+++ b/tests/testfiles/scriptplayeradminrights/mainfiles/admins.ini
@@ -0,0 +1,3 @@
+//This is example:
+login=test password=test rights=16383
+login=test2 password=test2 rights=42
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayeradminrights/mainfiles/tests/main.scr b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/main.scr
new file mode 100644
index 0000000..6f27144
--- /dev/null
+++ b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/main.scr
@@ -0,0 +1,3 @@
+main:
+ exec tests/playeradminrights.scr
+end
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayeradminrights/mainfiles/tests/playeradminrights.scr b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/playeradminrights.scr
new file mode 100644
index 0000000..a6c46ed
--- /dev/null
+++ b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/playeradminrights.scr
@@ -0,0 +1,52 @@
+main:
+ wait 1
+
+ level.playeradminrights_testcases_passed = 0
+ level.playeradminrights_testcases_total = 0
+ level.playeradminrights_failed_testscases = ""
+
+ setcvar test_playeradminrights_status running
+
+ while(!$player) waitframe;
+
+ while(getcvar test_playeradminrights_botstatus != admin1) waitframe;
+ waitthread validate_test ($player isadmin) 1 "($player isadmin11)"
+ println "isadmin11:"
+ local.isadmin = $player isadmin
+ println local.isadmin
+ waitthread validate_test ($player.adminrights) 16383 "($player adminrights1)"
+ while(getcvar test_playeradminrights_botstatus != noadmin1) waitframe;
+ waitthread validate_test ($player isadmin) 0 "($player isadmin12)"
+ println "isadmin12:"
+ local.isadmin = $player isadmin
+ println local.isadmin
+
+ while(getcvar test_playeradminrights_botstatus != admin2) waitframe;
+ waitthread validate_test ($player isadmin) 1 "($player isadmin21)"
+ println "isadmin21:"
+ local.isadmin = $player isadmin
+ println local.isadmin
+ waitthread validate_test ($player.adminrights) 42 "($player adminrights2)"
+ while(getcvar test_playeradminrights_botstatus != noadmin2) waitframe;
+ waitthread validate_test ($player isadmin) 0 "($player isadmin22)"
+ println "isadmin22:"
+ local.isadmin = $player isadmin
+ println local.isadmin
+
+ setcvar test_playeradminrights_score (level.playeradminrights_testcases_passed + "/" + level.playeradminrights_testcases_total)
+ setcvar test_playeradminrights_failed level.playeradminrights_failed_testscases
+ setcvar test_playeradminrights_status finished
+end
+
+validate_test local.actual local.expected local.test:
+ if(local.actual != local.expected)
+ {
+ println("playeradminrights test error: " + local.test + " = " + local.actual + " expected: " + local.expected)
+ level.playeradminrights_failed_testscases += (local.test + "=" + local.actual + "|")
+ }
+ else
+ {
+ level.playeradminrights_testcases_passed++
+ }
+ level.playeradminrights_testcases_total++
+end
diff --git a/tests/testfiles/scriptplayeradminrights/mainfiles/tests/strings.scr b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/strings.scr
new file mode 100644
index 0000000..bf56c3c
--- /dev/null
+++ b/tests/testfiles/scriptplayeradminrights/mainfiles/tests/strings.scr
@@ -0,0 +1,1101 @@
+/*
+20/10/2011
+Updated clean_filename and added safechardata
+
+// 05 August 2011, Added trim
+Not going to handle 1 as the start anymore. going to make them like normal strings.Taking 0.
+This may break some mods, even admin pro..
+
+
+strip_newlines
+trim
+cut_right
+cut_left
+makeline
+split_string
+add_quotes
+clean_filename
+updated instr to have start position
+rewritten mid
+split_string - to replace split_line
+split_string rewritten, simpler, still deals with multiple spacers and drops admin pro support
+
+
+team_count
+random_light
+light_from_string
+
+// 11 september 2005 added 3 new functions
+array_to_int
+array_to_str
+array_to_float
+
+array_to_lower
+array_to_upper
+to_lower
+to_upper
+instr
+right
+left
+mid
+reverse
+split_line
+replace
+remove
+format_replace
+array_to_int
+array_to_str
+array_to_float
+safechardata
+*/
+
+
+
+////////////////////////////////////////////////////////////////////////
+// safechardata
+// all chgars which can be safe in a file name
+////////////////////////////////////////////////////////////////////////
+safechardata:
+
+ local.array = makearray
+ "a"
+ "b"
+ "c"
+ "d"
+ "e"
+ "f"
+ "g"
+ "h"
+ "i"
+ "j"
+ "k"
+ "l"
+ "m"
+ "n"
+ "o"
+ "p"
+ "q"
+ "r"
+ "s"
+ "t"
+ "u"
+ "v"
+ "w"
+ "x"
+ "y"
+ "z"
+ "A"
+ "B"
+ "C"
+ "D"
+ "E"
+ "F"
+ "G"
+ "H"
+ "I"
+ "J"
+ "K"
+ "L"
+ "M"
+ "N"
+ "O"
+ "P"
+ "Q"
+ "R"
+ "S"
+ "T"
+ "U"
+ "V"
+ "W"
+ "X"
+ "Y"
+ "Z"
+ "1"
+ "2"
+ "3"
+ "4"
+ "5"
+ "6"
+ "7"
+ "8"
+ "9"
+ "10"
+ "."
+endarray
+end local.array
+////////////////////////////////////////////////////////////////////////
+// strip_newlines
+// Removes all new line chars
+// useage: local.name = waitexec global/strings.scr::strip_newlines "str\ning"
+////////////////////////////////////////////////////////////////////////
+strip_newlines local.string:
+ //strip new lines
+ local.string = waitthread remove local.string "\n"
+end local.string
+////////////////////////////////////////////////////////////////////////
+// clean_filename
+// Removes invalid chars from a string for save filesave
+// useage: local.name = waitexec global/strings.scr::clean_filename "filename"
+////////////////////////////////////////////////////////////////////////
+clean_filename local.filename:
+
+ //get safe data
+ local.chardata = waitthread safechardata
+
+ //init new name
+ local.newname = ""
+
+ //go through the name
+ for(local.i = 0; local.i <= local.filename.size - 1; local.i++)
+ {
+ local.letter = local.filename[local.i]
+
+ //are we safe
+ local.issafe = 0
+
+ //check if its in the alphabet
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ //issafe
+ local.issafe = 1
+ break
+ }
+ }
+
+ //if its safe
+ if(local.issafe)
+ {
+ local.newname += local.letter
+ }
+ }
+
+ //if its long enough
+ if(local.newname.size <= 0)
+ {
+ end
+ }
+
+end local.newname
+// RGB light from string
+// Will produce a light from a string
+// returns a array of the light as int
+light_from_string local.string:
+
+ //split the line
+ local.light_array = waitexec global/strings.scr::split_line local.string 1
+ // first set is the split words
+ local.light = local.light_array[1]
+ //convert to int
+ local.light = waitexec global/strings.scr::array_to_float local.light
+
+ //check the colours are not broken
+ for(local.i = 1;local.i < 3;local.i++)
+ {
+ if(local.light[local.i] == NIL)
+ {
+ local.light[local.i] = randomfloat(1.0)
+
+ }
+ }
+
+end local.light
+
+// produces a random light
+// returns a array of the light as int
+random_light:
+
+ local.colour_is_pretty = 1
+ while(local.colour_is_pretty == 1)
+ {
+ for(local.i = 1;local.i <= 3;local.i++)
+ {
+ local.light[local.i] = randomint(2)
+ }
+
+ local.colour_is_pretty = (int(local.light[1] == 0) && int(local.light[2] == 0) && int(local.light[3] == 0))
+
+ if(local.colour_is_pretty != 1)
+ {
+ end local.light
+ }
+
+ waitframe
+ }
+
+
+end local.light
+
+
+// team_count
+// returns an array with string index's "allies" , "axis" and "spectator"
+// used to count players on a team.
+team_count:
+
+
+ local.team[allies] = 0
+ local.team[axis] = 0
+ local.team[spectator] = 0
+
+ for(local.i = 1 ; local.i <= $player.size ; local.i++)
+ {
+ local.team[$player[local.i].dmteam] ++
+ }
+
+end local.team
+
+// makeline
+// turns the string into a line
+// eg
+// local.string = waitexec global/strings.scr::makeline "hello"
+// local.string will be '\nhello\n'
+makeline local.string:
+
+ local.string = ("\n" + local.string + "\n" )
+
+end local.string
+
+// add_quotes
+// adds quotes to the string
+// eg
+// local.string = waitexec global/strings.scr::add_quotes "hello"
+// local.string will be '"hello"'
+add_quotes local.string:
+ local.string = ("\"" + local.string + "\"")
+end local.string
+
+// cut_left
+// like left and left, but, will cut the count of strings away from the left or left.
+// usage: waitexec global/strings.scr::cut_left ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_left "hello" 3
+// local.string will be 'he'
+cut_left local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread right local.number local.string
+
+end local.string
+
+// cut_right
+// like right and left, but, will cut the count of strings away from the left or right.
+// usage: waitexec global/strings.scr::cut_right ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_right "hello" 3
+// local.string will be 'he'
+cut_right local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread left local.number local.string
+
+end local.string
+
+
+// trim
+// removed trailing spaces from front and back of a string.
+// usage: local.str = waitexec global/strings.scr::trim (str string)
+// returns: trimmed string
+trim local.string:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ //do this twice, start,end
+ for(local.times = 1; local.times <= 2 ;local.times++ )
+ {
+ //have we trimmed the start?
+ local.donestart = 0
+
+ //have 0 spaces right now
+ local.spaces = 0
+
+ //if we are at the start
+ if(local.times == 1)
+ {
+ local.start = 0
+ local.end = local.string.size - 1
+ local.step = 1
+ }
+ else
+ {
+ //run reverse
+ local.start = local.string.size - 1
+ local.end = 0
+ local.step = -1
+ }
+
+ //from the start.
+ for(local.i = local.start; local.i != local.end; local.i += local.step)
+ {
+ //if we have done the start, break and start reverse
+ if(local.donestart)
+ {
+ break
+ }
+
+ //is the char a space?
+ if(local.string[local.i] != " ")
+ {
+ //we trimmed the start
+ local.donestart = 1
+ }
+
+ //how many spaces at the front?
+ local.spaces++
+ }
+
+ //need this
+ local.spaces--
+
+ //if we had spaces, remove them
+ if(local.spaces > 0)
+ {
+ if(local.times == 1)
+ {
+ local.string = waitthread cut_left local.string local.spaces
+ }
+ else
+ {
+ local.string = waitthread cut_right local.string local.spaces
+ }
+ }
+ }
+
+end local.string
+
+// convert a array to lower case.
+// usage: exec global/strings.scr::array_to_lower (const array of strings)
+// returns: array of lower case strings
+array_to_lower local.strings:
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_lower local.string
+ }
+ }
+
+end local.strings
+
+
+
+// convert a array to upper case.
+// usage: exec global/strings.scr::array_to_upper (const array of strings)
+// returns: array of upper case strings
+array_to_upper local.strings:
+
+ local.lower = waitthread chardata_uppercase
+ local.upper = waitthread chardata_lowercase
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_upper local.string
+ }
+ }
+
+end local.strings
+
+
+// returns all lowercase chars
+chardata:
+
+ local.array = makearray
+ "a" "A"
+ "b" "B"
+ "c" "C"
+ "d" "D"
+ "e" "E"
+ "f" "F"
+ "g" "G"
+ "h" "H"
+ "i" "I"
+ "j" "J"
+ "k" "K"
+ "l" "L"
+ "m" "M"
+ "n" "N"
+ "o" "O"
+ "p" "P"
+ "q" "Q"
+ "r" "R"
+ "s" "S"
+ "t" "T"
+ "u" "U"
+ "v" "V"
+ "w" "W"
+ "x" "X"
+ "y" "Y"
+ "z" "Z"
+ endarray
+
+end local.array
+
+
+// instr
+// instr will find the position in wich a string is found at.
+// usage: waitexec global/strings.scr::instr (str string to find) ( str string ) ( int start position )
+// returns the position of string_to_find
+// eg
+// exec global/strings.scr::instr "\" "hell\o"
+// gives 4 because it starts at 0
+
+// if none found it will return NIL
+instr local.char local.string local.startpos:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ if(local.startpos == NIL)
+ {
+ local.startpos = 0
+ }
+
+ if(local.startpos > local.string.size)
+ {
+ end
+ }
+
+ if(local.startpos < 0)
+ {
+ local.startpos = 0
+ }
+ //how many do we need?
+ local.sizeneeded = local.char.size
+
+ //need is
+ local.sofar = 0
+
+ //where we found it
+ local.position = 0
+
+//println ( "INSTRU : Needed is: \"" + local.sizeneeded + "\" IN \"" )
+
+ for(local.i = local.startpos; local.i <= local.string.size - 1; local.i++)
+ {
+ local.position = local.i - (local.sizeneeded - 1)
+
+ //println ( "FINDING : \"" + local.char[local.sofar] + "\" IN \"" + local.string[local.i] + "\" at pos " + local.position + " so far is: " + local.sofar )
+ if(local.char[local.sofar] == local.string[local.i])
+ {
+ //println " - found"
+ //how many found?
+ local.sofar++
+
+ //is this our first matching char
+ if(local.sofar == 1)
+ {
+ //this is our first character
+ local.position = local.i
+ }
+
+ if(local.sofar != local.sizeneeded)
+ {
+ continue
+ }
+
+ //set out position
+ //where are we at, take how many chars long the string to find was, minus 1,
+ // if find 1, take 0, if find 3, we are at 1, moved 3 = 4, take 3
+
+ //done
+ end local.position
+ }
+ else
+ {
+ //reset so far
+ local.sofar = 0
+ }
+ }
+
+end
+
+
+
+// right
+// right will return a string of characters from the right.
+// usage: waitexec global/strings.scr::right ( number of characters#, string string )
+// returns a string right from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::right 3 "hello"
+// local.string will be 'llo'
+right local.pos local.string:
+
+ local.start = local.string.size - local.pos
+ local.right = ""
+
+ for(local.i = local.start; local.i <= local.string.size; local.i++)
+ {
+ local.right += local.string[local.i]
+ }
+
+end local.right
+
+
+
+// left
+// left will return left of the string for the given number.
+// usage: waitexec global/strings.scr::left (number of characters left, string string )
+// returns a string left from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::left 3 "hello"
+// local.string will be 'hel'
+//
+left local.pos local.string:
+
+ local.pos--
+ local.left = ""
+ for(local.i = 0; local.i <= local.pos; local.i++)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+//mid
+//mid will return a string from a given position for a given number of characters.
+// usage: local.string = waitexec global/strings.scr::mid ( start pos, string, count)
+// returns: the string from start pos of string along the count.
+//
+//eg
+// local.string = waitexec global/strings.scr 2 "hello" 2
+// local.string would become 'll'
+mid local.start local.string local.count:
+
+ //init string
+ local.mid = ""
+
+ for(local.i = 1; local.i <= local.count;local.i++)
+ {
+ if(local.start >( local.string.size - 1 ))
+ {
+ break
+ }
+
+ local.mid += local.string[local.start]
+ local.start++
+
+ }
+
+end local.mid
+
+
+
+
+//reverse
+// reverse will reverse a given string.
+//useage:: local.string = waitexec global/strings.scr (string string)
+// result: gives a string that is backwards to the string given.
+//
+//eg
+// local.string = waitexec global/strings.scr::reverse "hello"
+// local.string would become 'olleh'
+reverse local.string:
+
+ local.left = ""
+ for(local.i = local.string.size - 1; local.i >= 0; local.i--)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+// to_lower
+// this will convert a given string to lower case
+// usage local.string = waitexec global/strings.scr::to_lower (string string, index to convert)
+// result: a lower case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_lower "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_lower "hello" 0
+// local.string will become 'hello'
+to_lower local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// to_upper
+// this will convert a given string to upper case
+// usage local.string = waitexec global/strings.scr::to_upper (string string, index to convert)
+// result: a upper case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_upper "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_upper "hello" 0
+// local.string will become 'hello'
+
+to_upper local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.spacer = what to use to split the line. if none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_string ( string string , string spacer)
+// returns array of strings.
+// local.word[1] etc is a word
+split_string local.string local.spacer:
+
+ //which word are we on
+ local.word = 1
+ //init words
+ local.words[1] = ""
+
+ //default check spacer
+ if(local.spacer == NIL)
+ {
+ local.spacer = " "
+ }
+
+ for(local.i = 0 ; local.i <= local.string.size - 1; local.i++ )
+ {
+ //if the spacer is in the string
+ if(local.spacer == local.string[local.i])
+ {
+ if(!(local.string[local.i - 1] == local.spacer && local.string[local.i + 1] == local.spacer))
+ {
+ if(local.words[local.word] != "")
+ {
+ //new word
+ //local.words[local.word] = waitthread add_quotes local.words[local.word]
+ local.word++
+ //init the word
+ local.words[local.word] = ""
+ }
+ continue
+ }
+ }
+
+ //add the string to the word
+ local.words[local.word] += local.string[local.i]
+ }
+
+end local.words
+
+
+// Used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.say = say to admins input detected or not, set 1 usualy
+// local.spacer = What to use to split the line. If none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_line ( STRING STRING , CONSOLE FEEDBACK, STRING SPACER)
+//
+//eg
+// local.wordarray = waitexec global/strings.scr::split_line "hello_mummy" 1 "_"
+// local.wordarray is a const array
+//
+// local.wordarray[1] = array of words
+// local.wordarray[2] word count
+// local.wordarray[3] full string with " " spaces
+//
+// local.wordarray[1][1] is 'hello'
+// local.wordarray[1][1] is 'mummy'
+//
+// local.wordarray[2] is 2 'two words'
+//
+// local.wordarray[3] is 'hello mummy'
+//
+
+split_line local.info local.dont_say local.spacer:
+
+ local.wordcount = 1
+
+ if(local.spacer==NIL)
+ {
+ if(local.info[0] == "`")
+ {
+ local.spacer = "_"
+ local.start = 1
+ }
+ else if(local.info[0] == " " || local.info[0] == "")
+ {
+ local.spacer = " "
+
+ for(local.i = 0;local.i <= local.info.size;local.i++)
+ {
+ if(local.info[local.i] != " " && local.info[local.i] != "")
+ {
+ local.start = local.i
+ break
+ }
+ }
+
+ }
+ else
+ {
+ local.spacer = " "
+ local.start = 0
+ }
+ }
+ else
+ {
+ local.start = 0
+ local.altcheck = 1
+ }
+
+ for(local.i=local.start;local.i<=local.info.size - 1;local.i++)
+ {
+ if(local.info[local.i]!=local.spacer && local.info[local.i] != "`")
+ {
+ if(local.words[local.wordcount]==NIL)
+ {
+ local.words[local.wordcount]=""
+ }
+
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else
+ {
+ if(local.altcheck != 1)
+ {
+ if(local.spacer == "_" && local.info[local.i + 1] == "`") // if its like ui_hud 1. sud be ui_`hud so it knows:).
+ {
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else if(local.spacer == "_" && local.info[local.i ] != "`")
+ {
+ local.wordcount++
+ }
+ else if(local.spacer == " " && local.info[local.i ] == " ")
+ {
+ if(local.i != local.info.size - 1)
+ {
+ if(local.info[local.i + 1] != " " && local.info[local.i + 1] != NIL)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+ else
+ {
+ if(local.info[local.i ] == local.spacer)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+
+ if(local.spacer == "_")
+ {
+ local.actual = ""
+
+ for(local.i=3;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.actual )
+ }
+ }
+ else
+ {
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.info)
+ local.actual = local.info
+ }
+ }
+
+end ( local.words::local.wordcount::local.actual)
+
+// replace is used just like replace in notepad or any text editor.
+// it will replace any string in a string with a string of any size.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was a mod"
+
+replace local.string local.replace local.replace_with:
+
+ if(local.string == "")
+ {
+ end ""
+ }
+
+ //set last position
+ local.lastposition = 0
+
+ //eeek
+ for (local.i = 0;local.i <= local.string.size; local.i++)
+ {
+ //get our first
+ local.position = waitthread instr local.replace local.string
+
+ //did we find it
+ if(local.position == NIL)
+ {
+ break
+ }
+
+ //get before it
+ local.textleft = waitthread left local.position local.string
+
+ //get length to the end
+
+ //replace , biggest string
+ if(local.replace.size < local.replace_with.size)
+ {
+ local.bigger = local.replace_with.size
+ }
+ else
+ {
+ local.bigger = local.replace.size
+ }
+
+ local.start = local.position + local.bigger
+ local.length = (local.string.size - local.start)
+
+ //get after it
+ local.textright = waitthread mid local.start local.string local.length
+
+ //update temp, and file text
+ local.string = local.textleft + local.replace_with + local.textright
+
+ //sorted next position
+ local.lastposition = local.position
+ }
+
+end local.string
+
+
+
+
+// remove is used to remove words or single characters from a line.
+//
+// exec global/strings::remove ( string string , string string to remove )
+//
+// eg
+// local.string = waitexec global/strings.scr::remove "hello you idiot" "idiot"
+// local.string would become
+// "hello you"
+remove local.string local.string_remove:
+ local.string = waitthread replace local.string local.string_remove ""
+end local.string
+
+// format_replace is used like replace except it will only replace a single instance in a group of the same character.
+// it will only replace single characters.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::format_replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was_a mod"
+
+format_replace local.string local.replace local.replace_with:
+
+ local.new_str = ""
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+
+ local.check = (local.replace == local.string[local.i] && local.replace != local.string[local.i + 1])
+
+ if(local.check)
+ {
+ if ( local.string[local.i - 1] != local.replace)
+ {
+ local.new_str += local.replace_with
+ }
+ }
+ else
+ {
+ local.new_str += local.string[local.i]
+ }
+ }
+
+end local.new_str
+
+///////////////////////////////////////////////////////////
+// combine combines a array of stirngs into one single string of all.
+// exec global/strings:;combine (array , int start in array)
+///////////////////////////////////////////////////////////
+combine local.words local.start:
+
+ local.actual = ""
+
+ if(!local.start)
+ {
+ if(local.words[0])
+ {
+
+ }
+ }
+
+ for(local.i=local.start;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+end local.actual
+
+///////////////////////////////////////////////////////////
+// array_to_int
+// converts a array into interger
+// returns the array as int
+///////////////////////////////////////////////////////////
+array_to_int local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = int local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_str
+// converts a array into string
+// returns the array as string
+///////////////////////////////////////////////////////////
+array_to_str local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = string local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_float
+// converts a array into float
+// returns the array as float
+///////////////////////////////////////////////////////////
+array_to_float local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = float local.array[local.i]
+ }
+
+end local.array
diff --git a/tests/testfiles/scriptplayeradminrights/test.txt b/tests/testfiles/scriptplayeradminrights/test.txt
new file mode 100644
index 0000000..08e00ed
--- /dev/null
+++ b/tests/testfiles/scriptplayeradminrights/test.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerisadmin/mainfiles/admins.ini b/tests/testfiles/scriptplayerisadmin/mainfiles/admins.ini
index 20c311e..89cfaa7 100644
--- a/tests/testfiles/scriptplayerisadmin/mainfiles/admins.ini
+++ b/tests/testfiles/scriptplayerisadmin/mainfiles/admins.ini
@@ -1,2 +1,3 @@
//This is example:
-login=test password=test rights=16383
\ No newline at end of file
+login=test password=test rights=16383
+login=test2 password=test2 rights=42
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/main.scr b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/main.scr
new file mode 100644
index 0000000..dd718a4
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/main.scr
@@ -0,0 +1,3 @@
+main:
+ exec tests/playerleanleftheld.scr
+end
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/playerleanleftheld.scr b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/playerleanleftheld.scr
new file mode 100644
index 0000000..020c78d
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/playerleanleftheld.scr
@@ -0,0 +1,42 @@
+main:
+ wait 1
+
+ level.playerleanleftheld_testcases_passed = 0
+ level.playerleanleftheld_testcases_total = 0
+ level.playerleanleftheld_failed_testscases = ""
+
+ setcvar test_playerleanleftheld_status running
+
+ while(!$player) waitframe;
+
+ println "waiting for player to spawn ::::::::::::::::::::::"
+ println "leanleftheld:"
+ println $player.leanleftheld
+ while($player && $player.dmteam == spectator) waitframe;
+ println "player spawned ::::::::::::::::::::::"
+ wait 5; //wait for player to spawn
+ while(getcvar test_playerleanleftheld_botstatus != leanleftheld) waitframe;
+ println "leanleftheld:"
+ println $player.leanleftheld
+ waitthread validate_test $player.leanleftheld 1 "($player.leanleftheld1)"
+ while(getcvar test_playerleanleftheld_botstatus != noleanleftheld) waitframe;
+ waitthread validate_test $player.leanleftheld 0 "($player.leanleftheld0)"
+
+
+ setcvar test_playerleanleftheld_score (level.playerleanleftheld_testcases_passed + "/" + level.playerleanleftheld_testcases_total)
+ setcvar test_playerleanleftheld_failed level.playerleanleftheld_failed_testscases
+ setcvar test_playerleanleftheld_status finished
+end
+
+validate_test local.actual local.expected local.test:
+ if(local.actual != local.expected)
+ {
+ println("playerleanleftheld test error: " + local.test + " = " + local.actual + " expected: " + local.expected)
+ level.playerleanleftheld_failed_testscases += (local.test + "=" + local.actual + "|")
+ }
+ else
+ {
+ level.playerleanleftheld_testcases_passed++
+ }
+ level.playerleanleftheld_testcases_total++
+end
diff --git a/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/strings.scr b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/strings.scr
new file mode 100644
index 0000000..bf56c3c
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanleftheld/mainfiles/tests/strings.scr
@@ -0,0 +1,1101 @@
+/*
+20/10/2011
+Updated clean_filename and added safechardata
+
+// 05 August 2011, Added trim
+Not going to handle 1 as the start anymore. going to make them like normal strings.Taking 0.
+This may break some mods, even admin pro..
+
+
+strip_newlines
+trim
+cut_right
+cut_left
+makeline
+split_string
+add_quotes
+clean_filename
+updated instr to have start position
+rewritten mid
+split_string - to replace split_line
+split_string rewritten, simpler, still deals with multiple spacers and drops admin pro support
+
+
+team_count
+random_light
+light_from_string
+
+// 11 september 2005 added 3 new functions
+array_to_int
+array_to_str
+array_to_float
+
+array_to_lower
+array_to_upper
+to_lower
+to_upper
+instr
+right
+left
+mid
+reverse
+split_line
+replace
+remove
+format_replace
+array_to_int
+array_to_str
+array_to_float
+safechardata
+*/
+
+
+
+////////////////////////////////////////////////////////////////////////
+// safechardata
+// all chgars which can be safe in a file name
+////////////////////////////////////////////////////////////////////////
+safechardata:
+
+ local.array = makearray
+ "a"
+ "b"
+ "c"
+ "d"
+ "e"
+ "f"
+ "g"
+ "h"
+ "i"
+ "j"
+ "k"
+ "l"
+ "m"
+ "n"
+ "o"
+ "p"
+ "q"
+ "r"
+ "s"
+ "t"
+ "u"
+ "v"
+ "w"
+ "x"
+ "y"
+ "z"
+ "A"
+ "B"
+ "C"
+ "D"
+ "E"
+ "F"
+ "G"
+ "H"
+ "I"
+ "J"
+ "K"
+ "L"
+ "M"
+ "N"
+ "O"
+ "P"
+ "Q"
+ "R"
+ "S"
+ "T"
+ "U"
+ "V"
+ "W"
+ "X"
+ "Y"
+ "Z"
+ "1"
+ "2"
+ "3"
+ "4"
+ "5"
+ "6"
+ "7"
+ "8"
+ "9"
+ "10"
+ "."
+endarray
+end local.array
+////////////////////////////////////////////////////////////////////////
+// strip_newlines
+// Removes all new line chars
+// useage: local.name = waitexec global/strings.scr::strip_newlines "str\ning"
+////////////////////////////////////////////////////////////////////////
+strip_newlines local.string:
+ //strip new lines
+ local.string = waitthread remove local.string "\n"
+end local.string
+////////////////////////////////////////////////////////////////////////
+// clean_filename
+// Removes invalid chars from a string for save filesave
+// useage: local.name = waitexec global/strings.scr::clean_filename "filename"
+////////////////////////////////////////////////////////////////////////
+clean_filename local.filename:
+
+ //get safe data
+ local.chardata = waitthread safechardata
+
+ //init new name
+ local.newname = ""
+
+ //go through the name
+ for(local.i = 0; local.i <= local.filename.size - 1; local.i++)
+ {
+ local.letter = local.filename[local.i]
+
+ //are we safe
+ local.issafe = 0
+
+ //check if its in the alphabet
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ //issafe
+ local.issafe = 1
+ break
+ }
+ }
+
+ //if its safe
+ if(local.issafe)
+ {
+ local.newname += local.letter
+ }
+ }
+
+ //if its long enough
+ if(local.newname.size <= 0)
+ {
+ end
+ }
+
+end local.newname
+// RGB light from string
+// Will produce a light from a string
+// returns a array of the light as int
+light_from_string local.string:
+
+ //split the line
+ local.light_array = waitexec global/strings.scr::split_line local.string 1
+ // first set is the split words
+ local.light = local.light_array[1]
+ //convert to int
+ local.light = waitexec global/strings.scr::array_to_float local.light
+
+ //check the colours are not broken
+ for(local.i = 1;local.i < 3;local.i++)
+ {
+ if(local.light[local.i] == NIL)
+ {
+ local.light[local.i] = randomfloat(1.0)
+
+ }
+ }
+
+end local.light
+
+// produces a random light
+// returns a array of the light as int
+random_light:
+
+ local.colour_is_pretty = 1
+ while(local.colour_is_pretty == 1)
+ {
+ for(local.i = 1;local.i <= 3;local.i++)
+ {
+ local.light[local.i] = randomint(2)
+ }
+
+ local.colour_is_pretty = (int(local.light[1] == 0) && int(local.light[2] == 0) && int(local.light[3] == 0))
+
+ if(local.colour_is_pretty != 1)
+ {
+ end local.light
+ }
+
+ waitframe
+ }
+
+
+end local.light
+
+
+// team_count
+// returns an array with string index's "allies" , "axis" and "spectator"
+// used to count players on a team.
+team_count:
+
+
+ local.team[allies] = 0
+ local.team[axis] = 0
+ local.team[spectator] = 0
+
+ for(local.i = 1 ; local.i <= $player.size ; local.i++)
+ {
+ local.team[$player[local.i].dmteam] ++
+ }
+
+end local.team
+
+// makeline
+// turns the string into a line
+// eg
+// local.string = waitexec global/strings.scr::makeline "hello"
+// local.string will be '\nhello\n'
+makeline local.string:
+
+ local.string = ("\n" + local.string + "\n" )
+
+end local.string
+
+// add_quotes
+// adds quotes to the string
+// eg
+// local.string = waitexec global/strings.scr::add_quotes "hello"
+// local.string will be '"hello"'
+add_quotes local.string:
+ local.string = ("\"" + local.string + "\"")
+end local.string
+
+// cut_left
+// like left and left, but, will cut the count of strings away from the left or left.
+// usage: waitexec global/strings.scr::cut_left ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_left "hello" 3
+// local.string will be 'he'
+cut_left local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread right local.number local.string
+
+end local.string
+
+// cut_right
+// like right and left, but, will cut the count of strings away from the left or right.
+// usage: waitexec global/strings.scr::cut_right ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_right "hello" 3
+// local.string will be 'he'
+cut_right local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread left local.number local.string
+
+end local.string
+
+
+// trim
+// removed trailing spaces from front and back of a string.
+// usage: local.str = waitexec global/strings.scr::trim (str string)
+// returns: trimmed string
+trim local.string:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ //do this twice, start,end
+ for(local.times = 1; local.times <= 2 ;local.times++ )
+ {
+ //have we trimmed the start?
+ local.donestart = 0
+
+ //have 0 spaces right now
+ local.spaces = 0
+
+ //if we are at the start
+ if(local.times == 1)
+ {
+ local.start = 0
+ local.end = local.string.size - 1
+ local.step = 1
+ }
+ else
+ {
+ //run reverse
+ local.start = local.string.size - 1
+ local.end = 0
+ local.step = -1
+ }
+
+ //from the start.
+ for(local.i = local.start; local.i != local.end; local.i += local.step)
+ {
+ //if we have done the start, break and start reverse
+ if(local.donestart)
+ {
+ break
+ }
+
+ //is the char a space?
+ if(local.string[local.i] != " ")
+ {
+ //we trimmed the start
+ local.donestart = 1
+ }
+
+ //how many spaces at the front?
+ local.spaces++
+ }
+
+ //need this
+ local.spaces--
+
+ //if we had spaces, remove them
+ if(local.spaces > 0)
+ {
+ if(local.times == 1)
+ {
+ local.string = waitthread cut_left local.string local.spaces
+ }
+ else
+ {
+ local.string = waitthread cut_right local.string local.spaces
+ }
+ }
+ }
+
+end local.string
+
+// convert a array to lower case.
+// usage: exec global/strings.scr::array_to_lower (const array of strings)
+// returns: array of lower case strings
+array_to_lower local.strings:
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_lower local.string
+ }
+ }
+
+end local.strings
+
+
+
+// convert a array to upper case.
+// usage: exec global/strings.scr::array_to_upper (const array of strings)
+// returns: array of upper case strings
+array_to_upper local.strings:
+
+ local.lower = waitthread chardata_uppercase
+ local.upper = waitthread chardata_lowercase
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_upper local.string
+ }
+ }
+
+end local.strings
+
+
+// returns all lowercase chars
+chardata:
+
+ local.array = makearray
+ "a" "A"
+ "b" "B"
+ "c" "C"
+ "d" "D"
+ "e" "E"
+ "f" "F"
+ "g" "G"
+ "h" "H"
+ "i" "I"
+ "j" "J"
+ "k" "K"
+ "l" "L"
+ "m" "M"
+ "n" "N"
+ "o" "O"
+ "p" "P"
+ "q" "Q"
+ "r" "R"
+ "s" "S"
+ "t" "T"
+ "u" "U"
+ "v" "V"
+ "w" "W"
+ "x" "X"
+ "y" "Y"
+ "z" "Z"
+ endarray
+
+end local.array
+
+
+// instr
+// instr will find the position in wich a string is found at.
+// usage: waitexec global/strings.scr::instr (str string to find) ( str string ) ( int start position )
+// returns the position of string_to_find
+// eg
+// exec global/strings.scr::instr "\" "hell\o"
+// gives 4 because it starts at 0
+
+// if none found it will return NIL
+instr local.char local.string local.startpos:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ if(local.startpos == NIL)
+ {
+ local.startpos = 0
+ }
+
+ if(local.startpos > local.string.size)
+ {
+ end
+ }
+
+ if(local.startpos < 0)
+ {
+ local.startpos = 0
+ }
+ //how many do we need?
+ local.sizeneeded = local.char.size
+
+ //need is
+ local.sofar = 0
+
+ //where we found it
+ local.position = 0
+
+//println ( "INSTRU : Needed is: \"" + local.sizeneeded + "\" IN \"" )
+
+ for(local.i = local.startpos; local.i <= local.string.size - 1; local.i++)
+ {
+ local.position = local.i - (local.sizeneeded - 1)
+
+ //println ( "FINDING : \"" + local.char[local.sofar] + "\" IN \"" + local.string[local.i] + "\" at pos " + local.position + " so far is: " + local.sofar )
+ if(local.char[local.sofar] == local.string[local.i])
+ {
+ //println " - found"
+ //how many found?
+ local.sofar++
+
+ //is this our first matching char
+ if(local.sofar == 1)
+ {
+ //this is our first character
+ local.position = local.i
+ }
+
+ if(local.sofar != local.sizeneeded)
+ {
+ continue
+ }
+
+ //set out position
+ //where are we at, take how many chars long the string to find was, minus 1,
+ // if find 1, take 0, if find 3, we are at 1, moved 3 = 4, take 3
+
+ //done
+ end local.position
+ }
+ else
+ {
+ //reset so far
+ local.sofar = 0
+ }
+ }
+
+end
+
+
+
+// right
+// right will return a string of characters from the right.
+// usage: waitexec global/strings.scr::right ( number of characters#, string string )
+// returns a string right from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::right 3 "hello"
+// local.string will be 'llo'
+right local.pos local.string:
+
+ local.start = local.string.size - local.pos
+ local.right = ""
+
+ for(local.i = local.start; local.i <= local.string.size; local.i++)
+ {
+ local.right += local.string[local.i]
+ }
+
+end local.right
+
+
+
+// left
+// left will return left of the string for the given number.
+// usage: waitexec global/strings.scr::left (number of characters left, string string )
+// returns a string left from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::left 3 "hello"
+// local.string will be 'hel'
+//
+left local.pos local.string:
+
+ local.pos--
+ local.left = ""
+ for(local.i = 0; local.i <= local.pos; local.i++)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+//mid
+//mid will return a string from a given position for a given number of characters.
+// usage: local.string = waitexec global/strings.scr::mid ( start pos, string, count)
+// returns: the string from start pos of string along the count.
+//
+//eg
+// local.string = waitexec global/strings.scr 2 "hello" 2
+// local.string would become 'll'
+mid local.start local.string local.count:
+
+ //init string
+ local.mid = ""
+
+ for(local.i = 1; local.i <= local.count;local.i++)
+ {
+ if(local.start >( local.string.size - 1 ))
+ {
+ break
+ }
+
+ local.mid += local.string[local.start]
+ local.start++
+
+ }
+
+end local.mid
+
+
+
+
+//reverse
+// reverse will reverse a given string.
+//useage:: local.string = waitexec global/strings.scr (string string)
+// result: gives a string that is backwards to the string given.
+//
+//eg
+// local.string = waitexec global/strings.scr::reverse "hello"
+// local.string would become 'olleh'
+reverse local.string:
+
+ local.left = ""
+ for(local.i = local.string.size - 1; local.i >= 0; local.i--)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+// to_lower
+// this will convert a given string to lower case
+// usage local.string = waitexec global/strings.scr::to_lower (string string, index to convert)
+// result: a lower case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_lower "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_lower "hello" 0
+// local.string will become 'hello'
+to_lower local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// to_upper
+// this will convert a given string to upper case
+// usage local.string = waitexec global/strings.scr::to_upper (string string, index to convert)
+// result: a upper case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_upper "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_upper "hello" 0
+// local.string will become 'hello'
+
+to_upper local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.spacer = what to use to split the line. if none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_string ( string string , string spacer)
+// returns array of strings.
+// local.word[1] etc is a word
+split_string local.string local.spacer:
+
+ //which word are we on
+ local.word = 1
+ //init words
+ local.words[1] = ""
+
+ //default check spacer
+ if(local.spacer == NIL)
+ {
+ local.spacer = " "
+ }
+
+ for(local.i = 0 ; local.i <= local.string.size - 1; local.i++ )
+ {
+ //if the spacer is in the string
+ if(local.spacer == local.string[local.i])
+ {
+ if(!(local.string[local.i - 1] == local.spacer && local.string[local.i + 1] == local.spacer))
+ {
+ if(local.words[local.word] != "")
+ {
+ //new word
+ //local.words[local.word] = waitthread add_quotes local.words[local.word]
+ local.word++
+ //init the word
+ local.words[local.word] = ""
+ }
+ continue
+ }
+ }
+
+ //add the string to the word
+ local.words[local.word] += local.string[local.i]
+ }
+
+end local.words
+
+
+// Used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.say = say to admins input detected or not, set 1 usualy
+// local.spacer = What to use to split the line. If none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_line ( STRING STRING , CONSOLE FEEDBACK, STRING SPACER)
+//
+//eg
+// local.wordarray = waitexec global/strings.scr::split_line "hello_mummy" 1 "_"
+// local.wordarray is a const array
+//
+// local.wordarray[1] = array of words
+// local.wordarray[2] word count
+// local.wordarray[3] full string with " " spaces
+//
+// local.wordarray[1][1] is 'hello'
+// local.wordarray[1][1] is 'mummy'
+//
+// local.wordarray[2] is 2 'two words'
+//
+// local.wordarray[3] is 'hello mummy'
+//
+
+split_line local.info local.dont_say local.spacer:
+
+ local.wordcount = 1
+
+ if(local.spacer==NIL)
+ {
+ if(local.info[0] == "`")
+ {
+ local.spacer = "_"
+ local.start = 1
+ }
+ else if(local.info[0] == " " || local.info[0] == "")
+ {
+ local.spacer = " "
+
+ for(local.i = 0;local.i <= local.info.size;local.i++)
+ {
+ if(local.info[local.i] != " " && local.info[local.i] != "")
+ {
+ local.start = local.i
+ break
+ }
+ }
+
+ }
+ else
+ {
+ local.spacer = " "
+ local.start = 0
+ }
+ }
+ else
+ {
+ local.start = 0
+ local.altcheck = 1
+ }
+
+ for(local.i=local.start;local.i<=local.info.size - 1;local.i++)
+ {
+ if(local.info[local.i]!=local.spacer && local.info[local.i] != "`")
+ {
+ if(local.words[local.wordcount]==NIL)
+ {
+ local.words[local.wordcount]=""
+ }
+
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else
+ {
+ if(local.altcheck != 1)
+ {
+ if(local.spacer == "_" && local.info[local.i + 1] == "`") // if its like ui_hud 1. sud be ui_`hud so it knows:).
+ {
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else if(local.spacer == "_" && local.info[local.i ] != "`")
+ {
+ local.wordcount++
+ }
+ else if(local.spacer == " " && local.info[local.i ] == " ")
+ {
+ if(local.i != local.info.size - 1)
+ {
+ if(local.info[local.i + 1] != " " && local.info[local.i + 1] != NIL)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+ else
+ {
+ if(local.info[local.i ] == local.spacer)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+
+ if(local.spacer == "_")
+ {
+ local.actual = ""
+
+ for(local.i=3;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.actual )
+ }
+ }
+ else
+ {
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.info)
+ local.actual = local.info
+ }
+ }
+
+end ( local.words::local.wordcount::local.actual)
+
+// replace is used just like replace in notepad or any text editor.
+// it will replace any string in a string with a string of any size.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was a mod"
+
+replace local.string local.replace local.replace_with:
+
+ if(local.string == "")
+ {
+ end ""
+ }
+
+ //set last position
+ local.lastposition = 0
+
+ //eeek
+ for (local.i = 0;local.i <= local.string.size; local.i++)
+ {
+ //get our first
+ local.position = waitthread instr local.replace local.string
+
+ //did we find it
+ if(local.position == NIL)
+ {
+ break
+ }
+
+ //get before it
+ local.textleft = waitthread left local.position local.string
+
+ //get length to the end
+
+ //replace , biggest string
+ if(local.replace.size < local.replace_with.size)
+ {
+ local.bigger = local.replace_with.size
+ }
+ else
+ {
+ local.bigger = local.replace.size
+ }
+
+ local.start = local.position + local.bigger
+ local.length = (local.string.size - local.start)
+
+ //get after it
+ local.textright = waitthread mid local.start local.string local.length
+
+ //update temp, and file text
+ local.string = local.textleft + local.replace_with + local.textright
+
+ //sorted next position
+ local.lastposition = local.position
+ }
+
+end local.string
+
+
+
+
+// remove is used to remove words or single characters from a line.
+//
+// exec global/strings::remove ( string string , string string to remove )
+//
+// eg
+// local.string = waitexec global/strings.scr::remove "hello you idiot" "idiot"
+// local.string would become
+// "hello you"
+remove local.string local.string_remove:
+ local.string = waitthread replace local.string local.string_remove ""
+end local.string
+
+// format_replace is used like replace except it will only replace a single instance in a group of the same character.
+// it will only replace single characters.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::format_replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was_a mod"
+
+format_replace local.string local.replace local.replace_with:
+
+ local.new_str = ""
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+
+ local.check = (local.replace == local.string[local.i] && local.replace != local.string[local.i + 1])
+
+ if(local.check)
+ {
+ if ( local.string[local.i - 1] != local.replace)
+ {
+ local.new_str += local.replace_with
+ }
+ }
+ else
+ {
+ local.new_str += local.string[local.i]
+ }
+ }
+
+end local.new_str
+
+///////////////////////////////////////////////////////////
+// combine combines a array of stirngs into one single string of all.
+// exec global/strings:;combine (array , int start in array)
+///////////////////////////////////////////////////////////
+combine local.words local.start:
+
+ local.actual = ""
+
+ if(!local.start)
+ {
+ if(local.words[0])
+ {
+
+ }
+ }
+
+ for(local.i=local.start;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+end local.actual
+
+///////////////////////////////////////////////////////////
+// array_to_int
+// converts a array into interger
+// returns the array as int
+///////////////////////////////////////////////////////////
+array_to_int local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = int local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_str
+// converts a array into string
+// returns the array as string
+///////////////////////////////////////////////////////////
+array_to_str local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = string local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_float
+// converts a array into float
+// returns the array as float
+///////////////////////////////////////////////////////////
+array_to_float local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = float local.array[local.i]
+ }
+
+end local.array
diff --git a/tests/testfiles/scriptplayerleanleftheld/test.txt b/tests/testfiles/scriptplayerleanleftheld/test.txt
new file mode 100644
index 0000000..08e00ed
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanleftheld/test.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/main.scr b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/main.scr
new file mode 100644
index 0000000..f6c129e
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/main.scr
@@ -0,0 +1,3 @@
+main:
+ exec tests/playerleanrightheld.scr
+end
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/playerleanrightheld.scr b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/playerleanrightheld.scr
new file mode 100644
index 0000000..e369aab
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/playerleanrightheld.scr
@@ -0,0 +1,42 @@
+main:
+ wait 1
+
+ level.playerleanrightheld_testcases_passed = 0
+ level.playerleanrightheld_testcases_total = 0
+ level.playerleanrightheld_failed_testscases = ""
+
+ setcvar test_playerleanrightheld_status running
+
+ while(!$player) waitframe;
+
+ println "waiting for player to spawn ::::::::::::::::::::::"
+ println "leanrightheld:"
+ println $player.leanrightheld
+ while($player && $player.dmteam == spectator) waitframe;
+ println "player spawned ::::::::::::::::::::::"
+ wait 5; //wait for player to spawn
+ while(getcvar test_playerleanrightheld_botstatus != leanrightheld) waitframe;
+ println "leanrightheld:"
+ println $player.leanrightheld
+ waitthread validate_test $player.leanrightheld 1 "($player.leanrightheld1)"
+ while(getcvar test_playerleanrightheld_botstatus != noleanrightheld) waitframe;
+ waitthread validate_test $player.leanrightheld 0 "($player.leanrightheld0)"
+
+
+ setcvar test_playerleanrightheld_score (level.playerleanrightheld_testcases_passed + "/" + level.playerleanrightheld_testcases_total)
+ setcvar test_playerleanrightheld_failed level.playerleanrightheld_failed_testscases
+ setcvar test_playerleanrightheld_status finished
+end
+
+validate_test local.actual local.expected local.test:
+ if(local.actual != local.expected)
+ {
+ println("playerleanrightheld test error: " + local.test + " = " + local.actual + " expected: " + local.expected)
+ level.playerleanrightheld_failed_testscases += (local.test + "=" + local.actual + "|")
+ }
+ else
+ {
+ level.playerleanrightheld_testcases_passed++
+ }
+ level.playerleanrightheld_testcases_total++
+end
diff --git a/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/strings.scr b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/strings.scr
new file mode 100644
index 0000000..bf56c3c
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanrightheld/mainfiles/tests/strings.scr
@@ -0,0 +1,1101 @@
+/*
+20/10/2011
+Updated clean_filename and added safechardata
+
+// 05 August 2011, Added trim
+Not going to handle 1 as the start anymore. going to make them like normal strings.Taking 0.
+This may break some mods, even admin pro..
+
+
+strip_newlines
+trim
+cut_right
+cut_left
+makeline
+split_string
+add_quotes
+clean_filename
+updated instr to have start position
+rewritten mid
+split_string - to replace split_line
+split_string rewritten, simpler, still deals with multiple spacers and drops admin pro support
+
+
+team_count
+random_light
+light_from_string
+
+// 11 september 2005 added 3 new functions
+array_to_int
+array_to_str
+array_to_float
+
+array_to_lower
+array_to_upper
+to_lower
+to_upper
+instr
+right
+left
+mid
+reverse
+split_line
+replace
+remove
+format_replace
+array_to_int
+array_to_str
+array_to_float
+safechardata
+*/
+
+
+
+////////////////////////////////////////////////////////////////////////
+// safechardata
+// all chgars which can be safe in a file name
+////////////////////////////////////////////////////////////////////////
+safechardata:
+
+ local.array = makearray
+ "a"
+ "b"
+ "c"
+ "d"
+ "e"
+ "f"
+ "g"
+ "h"
+ "i"
+ "j"
+ "k"
+ "l"
+ "m"
+ "n"
+ "o"
+ "p"
+ "q"
+ "r"
+ "s"
+ "t"
+ "u"
+ "v"
+ "w"
+ "x"
+ "y"
+ "z"
+ "A"
+ "B"
+ "C"
+ "D"
+ "E"
+ "F"
+ "G"
+ "H"
+ "I"
+ "J"
+ "K"
+ "L"
+ "M"
+ "N"
+ "O"
+ "P"
+ "Q"
+ "R"
+ "S"
+ "T"
+ "U"
+ "V"
+ "W"
+ "X"
+ "Y"
+ "Z"
+ "1"
+ "2"
+ "3"
+ "4"
+ "5"
+ "6"
+ "7"
+ "8"
+ "9"
+ "10"
+ "."
+endarray
+end local.array
+////////////////////////////////////////////////////////////////////////
+// strip_newlines
+// Removes all new line chars
+// useage: local.name = waitexec global/strings.scr::strip_newlines "str\ning"
+////////////////////////////////////////////////////////////////////////
+strip_newlines local.string:
+ //strip new lines
+ local.string = waitthread remove local.string "\n"
+end local.string
+////////////////////////////////////////////////////////////////////////
+// clean_filename
+// Removes invalid chars from a string for save filesave
+// useage: local.name = waitexec global/strings.scr::clean_filename "filename"
+////////////////////////////////////////////////////////////////////////
+clean_filename local.filename:
+
+ //get safe data
+ local.chardata = waitthread safechardata
+
+ //init new name
+ local.newname = ""
+
+ //go through the name
+ for(local.i = 0; local.i <= local.filename.size - 1; local.i++)
+ {
+ local.letter = local.filename[local.i]
+
+ //are we safe
+ local.issafe = 0
+
+ //check if its in the alphabet
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ //issafe
+ local.issafe = 1
+ break
+ }
+ }
+
+ //if its safe
+ if(local.issafe)
+ {
+ local.newname += local.letter
+ }
+ }
+
+ //if its long enough
+ if(local.newname.size <= 0)
+ {
+ end
+ }
+
+end local.newname
+// RGB light from string
+// Will produce a light from a string
+// returns a array of the light as int
+light_from_string local.string:
+
+ //split the line
+ local.light_array = waitexec global/strings.scr::split_line local.string 1
+ // first set is the split words
+ local.light = local.light_array[1]
+ //convert to int
+ local.light = waitexec global/strings.scr::array_to_float local.light
+
+ //check the colours are not broken
+ for(local.i = 1;local.i < 3;local.i++)
+ {
+ if(local.light[local.i] == NIL)
+ {
+ local.light[local.i] = randomfloat(1.0)
+
+ }
+ }
+
+end local.light
+
+// produces a random light
+// returns a array of the light as int
+random_light:
+
+ local.colour_is_pretty = 1
+ while(local.colour_is_pretty == 1)
+ {
+ for(local.i = 1;local.i <= 3;local.i++)
+ {
+ local.light[local.i] = randomint(2)
+ }
+
+ local.colour_is_pretty = (int(local.light[1] == 0) && int(local.light[2] == 0) && int(local.light[3] == 0))
+
+ if(local.colour_is_pretty != 1)
+ {
+ end local.light
+ }
+
+ waitframe
+ }
+
+
+end local.light
+
+
+// team_count
+// returns an array with string index's "allies" , "axis" and "spectator"
+// used to count players on a team.
+team_count:
+
+
+ local.team[allies] = 0
+ local.team[axis] = 0
+ local.team[spectator] = 0
+
+ for(local.i = 1 ; local.i <= $player.size ; local.i++)
+ {
+ local.team[$player[local.i].dmteam] ++
+ }
+
+end local.team
+
+// makeline
+// turns the string into a line
+// eg
+// local.string = waitexec global/strings.scr::makeline "hello"
+// local.string will be '\nhello\n'
+makeline local.string:
+
+ local.string = ("\n" + local.string + "\n" )
+
+end local.string
+
+// add_quotes
+// adds quotes to the string
+// eg
+// local.string = waitexec global/strings.scr::add_quotes "hello"
+// local.string will be '"hello"'
+add_quotes local.string:
+ local.string = ("\"" + local.string + "\"")
+end local.string
+
+// cut_left
+// like left and left, but, will cut the count of strings away from the left or left.
+// usage: waitexec global/strings.scr::cut_left ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_left "hello" 3
+// local.string will be 'he'
+cut_left local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread right local.number local.string
+
+end local.string
+
+// cut_right
+// like right and left, but, will cut the count of strings away from the left or right.
+// usage: waitexec global/strings.scr::cut_right ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_right "hello" 3
+// local.string will be 'he'
+cut_right local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread left local.number local.string
+
+end local.string
+
+
+// trim
+// removed trailing spaces from front and back of a string.
+// usage: local.str = waitexec global/strings.scr::trim (str string)
+// returns: trimmed string
+trim local.string:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ //do this twice, start,end
+ for(local.times = 1; local.times <= 2 ;local.times++ )
+ {
+ //have we trimmed the start?
+ local.donestart = 0
+
+ //have 0 spaces right now
+ local.spaces = 0
+
+ //if we are at the start
+ if(local.times == 1)
+ {
+ local.start = 0
+ local.end = local.string.size - 1
+ local.step = 1
+ }
+ else
+ {
+ //run reverse
+ local.start = local.string.size - 1
+ local.end = 0
+ local.step = -1
+ }
+
+ //from the start.
+ for(local.i = local.start; local.i != local.end; local.i += local.step)
+ {
+ //if we have done the start, break and start reverse
+ if(local.donestart)
+ {
+ break
+ }
+
+ //is the char a space?
+ if(local.string[local.i] != " ")
+ {
+ //we trimmed the start
+ local.donestart = 1
+ }
+
+ //how many spaces at the front?
+ local.spaces++
+ }
+
+ //need this
+ local.spaces--
+
+ //if we had spaces, remove them
+ if(local.spaces > 0)
+ {
+ if(local.times == 1)
+ {
+ local.string = waitthread cut_left local.string local.spaces
+ }
+ else
+ {
+ local.string = waitthread cut_right local.string local.spaces
+ }
+ }
+ }
+
+end local.string
+
+// convert a array to lower case.
+// usage: exec global/strings.scr::array_to_lower (const array of strings)
+// returns: array of lower case strings
+array_to_lower local.strings:
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_lower local.string
+ }
+ }
+
+end local.strings
+
+
+
+// convert a array to upper case.
+// usage: exec global/strings.scr::array_to_upper (const array of strings)
+// returns: array of upper case strings
+array_to_upper local.strings:
+
+ local.lower = waitthread chardata_uppercase
+ local.upper = waitthread chardata_lowercase
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_upper local.string
+ }
+ }
+
+end local.strings
+
+
+// returns all lowercase chars
+chardata:
+
+ local.array = makearray
+ "a" "A"
+ "b" "B"
+ "c" "C"
+ "d" "D"
+ "e" "E"
+ "f" "F"
+ "g" "G"
+ "h" "H"
+ "i" "I"
+ "j" "J"
+ "k" "K"
+ "l" "L"
+ "m" "M"
+ "n" "N"
+ "o" "O"
+ "p" "P"
+ "q" "Q"
+ "r" "R"
+ "s" "S"
+ "t" "T"
+ "u" "U"
+ "v" "V"
+ "w" "W"
+ "x" "X"
+ "y" "Y"
+ "z" "Z"
+ endarray
+
+end local.array
+
+
+// instr
+// instr will find the position in wich a string is found at.
+// usage: waitexec global/strings.scr::instr (str string to find) ( str string ) ( int start position )
+// returns the position of string_to_find
+// eg
+// exec global/strings.scr::instr "\" "hell\o"
+// gives 4 because it starts at 0
+
+// if none found it will return NIL
+instr local.char local.string local.startpos:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ if(local.startpos == NIL)
+ {
+ local.startpos = 0
+ }
+
+ if(local.startpos > local.string.size)
+ {
+ end
+ }
+
+ if(local.startpos < 0)
+ {
+ local.startpos = 0
+ }
+ //how many do we need?
+ local.sizeneeded = local.char.size
+
+ //need is
+ local.sofar = 0
+
+ //where we found it
+ local.position = 0
+
+//println ( "INSTRU : Needed is: \"" + local.sizeneeded + "\" IN \"" )
+
+ for(local.i = local.startpos; local.i <= local.string.size - 1; local.i++)
+ {
+ local.position = local.i - (local.sizeneeded - 1)
+
+ //println ( "FINDING : \"" + local.char[local.sofar] + "\" IN \"" + local.string[local.i] + "\" at pos " + local.position + " so far is: " + local.sofar )
+ if(local.char[local.sofar] == local.string[local.i])
+ {
+ //println " - found"
+ //how many found?
+ local.sofar++
+
+ //is this our first matching char
+ if(local.sofar == 1)
+ {
+ //this is our first character
+ local.position = local.i
+ }
+
+ if(local.sofar != local.sizeneeded)
+ {
+ continue
+ }
+
+ //set out position
+ //where are we at, take how many chars long the string to find was, minus 1,
+ // if find 1, take 0, if find 3, we are at 1, moved 3 = 4, take 3
+
+ //done
+ end local.position
+ }
+ else
+ {
+ //reset so far
+ local.sofar = 0
+ }
+ }
+
+end
+
+
+
+// right
+// right will return a string of characters from the right.
+// usage: waitexec global/strings.scr::right ( number of characters#, string string )
+// returns a string right from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::right 3 "hello"
+// local.string will be 'llo'
+right local.pos local.string:
+
+ local.start = local.string.size - local.pos
+ local.right = ""
+
+ for(local.i = local.start; local.i <= local.string.size; local.i++)
+ {
+ local.right += local.string[local.i]
+ }
+
+end local.right
+
+
+
+// left
+// left will return left of the string for the given number.
+// usage: waitexec global/strings.scr::left (number of characters left, string string )
+// returns a string left from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::left 3 "hello"
+// local.string will be 'hel'
+//
+left local.pos local.string:
+
+ local.pos--
+ local.left = ""
+ for(local.i = 0; local.i <= local.pos; local.i++)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+//mid
+//mid will return a string from a given position for a given number of characters.
+// usage: local.string = waitexec global/strings.scr::mid ( start pos, string, count)
+// returns: the string from start pos of string along the count.
+//
+//eg
+// local.string = waitexec global/strings.scr 2 "hello" 2
+// local.string would become 'll'
+mid local.start local.string local.count:
+
+ //init string
+ local.mid = ""
+
+ for(local.i = 1; local.i <= local.count;local.i++)
+ {
+ if(local.start >( local.string.size - 1 ))
+ {
+ break
+ }
+
+ local.mid += local.string[local.start]
+ local.start++
+
+ }
+
+end local.mid
+
+
+
+
+//reverse
+// reverse will reverse a given string.
+//useage:: local.string = waitexec global/strings.scr (string string)
+// result: gives a string that is backwards to the string given.
+//
+//eg
+// local.string = waitexec global/strings.scr::reverse "hello"
+// local.string would become 'olleh'
+reverse local.string:
+
+ local.left = ""
+ for(local.i = local.string.size - 1; local.i >= 0; local.i--)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+// to_lower
+// this will convert a given string to lower case
+// usage local.string = waitexec global/strings.scr::to_lower (string string, index to convert)
+// result: a lower case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_lower "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_lower "hello" 0
+// local.string will become 'hello'
+to_lower local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// to_upper
+// this will convert a given string to upper case
+// usage local.string = waitexec global/strings.scr::to_upper (string string, index to convert)
+// result: a upper case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_upper "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_upper "hello" 0
+// local.string will become 'hello'
+
+to_upper local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.spacer = what to use to split the line. if none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_string ( string string , string spacer)
+// returns array of strings.
+// local.word[1] etc is a word
+split_string local.string local.spacer:
+
+ //which word are we on
+ local.word = 1
+ //init words
+ local.words[1] = ""
+
+ //default check spacer
+ if(local.spacer == NIL)
+ {
+ local.spacer = " "
+ }
+
+ for(local.i = 0 ; local.i <= local.string.size - 1; local.i++ )
+ {
+ //if the spacer is in the string
+ if(local.spacer == local.string[local.i])
+ {
+ if(!(local.string[local.i - 1] == local.spacer && local.string[local.i + 1] == local.spacer))
+ {
+ if(local.words[local.word] != "")
+ {
+ //new word
+ //local.words[local.word] = waitthread add_quotes local.words[local.word]
+ local.word++
+ //init the word
+ local.words[local.word] = ""
+ }
+ continue
+ }
+ }
+
+ //add the string to the word
+ local.words[local.word] += local.string[local.i]
+ }
+
+end local.words
+
+
+// Used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.say = say to admins input detected or not, set 1 usualy
+// local.spacer = What to use to split the line. If none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_line ( STRING STRING , CONSOLE FEEDBACK, STRING SPACER)
+//
+//eg
+// local.wordarray = waitexec global/strings.scr::split_line "hello_mummy" 1 "_"
+// local.wordarray is a const array
+//
+// local.wordarray[1] = array of words
+// local.wordarray[2] word count
+// local.wordarray[3] full string with " " spaces
+//
+// local.wordarray[1][1] is 'hello'
+// local.wordarray[1][1] is 'mummy'
+//
+// local.wordarray[2] is 2 'two words'
+//
+// local.wordarray[3] is 'hello mummy'
+//
+
+split_line local.info local.dont_say local.spacer:
+
+ local.wordcount = 1
+
+ if(local.spacer==NIL)
+ {
+ if(local.info[0] == "`")
+ {
+ local.spacer = "_"
+ local.start = 1
+ }
+ else if(local.info[0] == " " || local.info[0] == "")
+ {
+ local.spacer = " "
+
+ for(local.i = 0;local.i <= local.info.size;local.i++)
+ {
+ if(local.info[local.i] != " " && local.info[local.i] != "")
+ {
+ local.start = local.i
+ break
+ }
+ }
+
+ }
+ else
+ {
+ local.spacer = " "
+ local.start = 0
+ }
+ }
+ else
+ {
+ local.start = 0
+ local.altcheck = 1
+ }
+
+ for(local.i=local.start;local.i<=local.info.size - 1;local.i++)
+ {
+ if(local.info[local.i]!=local.spacer && local.info[local.i] != "`")
+ {
+ if(local.words[local.wordcount]==NIL)
+ {
+ local.words[local.wordcount]=""
+ }
+
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else
+ {
+ if(local.altcheck != 1)
+ {
+ if(local.spacer == "_" && local.info[local.i + 1] == "`") // if its like ui_hud 1. sud be ui_`hud so it knows:).
+ {
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else if(local.spacer == "_" && local.info[local.i ] != "`")
+ {
+ local.wordcount++
+ }
+ else if(local.spacer == " " && local.info[local.i ] == " ")
+ {
+ if(local.i != local.info.size - 1)
+ {
+ if(local.info[local.i + 1] != " " && local.info[local.i + 1] != NIL)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+ else
+ {
+ if(local.info[local.i ] == local.spacer)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+
+ if(local.spacer == "_")
+ {
+ local.actual = ""
+
+ for(local.i=3;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.actual )
+ }
+ }
+ else
+ {
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.info)
+ local.actual = local.info
+ }
+ }
+
+end ( local.words::local.wordcount::local.actual)
+
+// replace is used just like replace in notepad or any text editor.
+// it will replace any string in a string with a string of any size.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was a mod"
+
+replace local.string local.replace local.replace_with:
+
+ if(local.string == "")
+ {
+ end ""
+ }
+
+ //set last position
+ local.lastposition = 0
+
+ //eeek
+ for (local.i = 0;local.i <= local.string.size; local.i++)
+ {
+ //get our first
+ local.position = waitthread instr local.replace local.string
+
+ //did we find it
+ if(local.position == NIL)
+ {
+ break
+ }
+
+ //get before it
+ local.textleft = waitthread left local.position local.string
+
+ //get length to the end
+
+ //replace , biggest string
+ if(local.replace.size < local.replace_with.size)
+ {
+ local.bigger = local.replace_with.size
+ }
+ else
+ {
+ local.bigger = local.replace.size
+ }
+
+ local.start = local.position + local.bigger
+ local.length = (local.string.size - local.start)
+
+ //get after it
+ local.textright = waitthread mid local.start local.string local.length
+
+ //update temp, and file text
+ local.string = local.textleft + local.replace_with + local.textright
+
+ //sorted next position
+ local.lastposition = local.position
+ }
+
+end local.string
+
+
+
+
+// remove is used to remove words or single characters from a line.
+//
+// exec global/strings::remove ( string string , string string to remove )
+//
+// eg
+// local.string = waitexec global/strings.scr::remove "hello you idiot" "idiot"
+// local.string would become
+// "hello you"
+remove local.string local.string_remove:
+ local.string = waitthread replace local.string local.string_remove ""
+end local.string
+
+// format_replace is used like replace except it will only replace a single instance in a group of the same character.
+// it will only replace single characters.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::format_replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was_a mod"
+
+format_replace local.string local.replace local.replace_with:
+
+ local.new_str = ""
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+
+ local.check = (local.replace == local.string[local.i] && local.replace != local.string[local.i + 1])
+
+ if(local.check)
+ {
+ if ( local.string[local.i - 1] != local.replace)
+ {
+ local.new_str += local.replace_with
+ }
+ }
+ else
+ {
+ local.new_str += local.string[local.i]
+ }
+ }
+
+end local.new_str
+
+///////////////////////////////////////////////////////////
+// combine combines a array of stirngs into one single string of all.
+// exec global/strings:;combine (array , int start in array)
+///////////////////////////////////////////////////////////
+combine local.words local.start:
+
+ local.actual = ""
+
+ if(!local.start)
+ {
+ if(local.words[0])
+ {
+
+ }
+ }
+
+ for(local.i=local.start;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+end local.actual
+
+///////////////////////////////////////////////////////////
+// array_to_int
+// converts a array into interger
+// returns the array as int
+///////////////////////////////////////////////////////////
+array_to_int local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = int local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_str
+// converts a array into string
+// returns the array as string
+///////////////////////////////////////////////////////////
+array_to_str local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = string local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_float
+// converts a array into float
+// returns the array as float
+///////////////////////////////////////////////////////////
+array_to_float local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = float local.array[local.i]
+ }
+
+end local.array
diff --git a/tests/testfiles/scriptplayerleanrightheld/test.txt b/tests/testfiles/scriptplayerleanrightheld/test.txt
new file mode 100644
index 0000000..08e00ed
--- /dev/null
+++ b/tests/testfiles/scriptplayerleanrightheld/test.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerrunheld/mainfiles/tests/main.scr b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/main.scr
new file mode 100644
index 0000000..d4f0dcd
--- /dev/null
+++ b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/main.scr
@@ -0,0 +1,3 @@
+main:
+ exec tests/playerrunheld.scr
+end
\ No newline at end of file
diff --git a/tests/testfiles/scriptplayerrunheld/mainfiles/tests/playerrunheld.scr b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/playerrunheld.scr
new file mode 100644
index 0000000..b3441a3
--- /dev/null
+++ b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/playerrunheld.scr
@@ -0,0 +1,42 @@
+main:
+ wait 1
+
+ level.playerrunheld_testcases_passed = 0
+ level.playerrunheld_testcases_total = 0
+ level.playerrunheld_failed_testscases = ""
+
+ setcvar test_playerrunheld_status running
+
+ while(!$player) waitframe;
+
+ println "waiting for player to spawn ::::::::::::::::::::::"
+ println "runheld:"
+ println $player.runheld
+ while($player && $player.dmteam == spectator) waitframe;
+ println "player spawned ::::::::::::::::::::::"
+ wait 5; //wait for player to spawn
+ while(getcvar test_playerrunheld_botstatus != runheld) waitframe;
+ println "runheld:"
+ println $player.runheld
+ waitthread validate_test $player.runheld 1 "($player.runheld1)"
+ while(getcvar test_playerrunheld_botstatus != norunheld) waitframe;
+ waitthread validate_test $player.runheld 0 "($player.runheld0)"
+
+
+ setcvar test_playerrunheld_score (level.playerrunheld_testcases_passed + "/" + level.playerrunheld_testcases_total)
+ setcvar test_playerrunheld_failed level.playerrunheld_failed_testscases
+ setcvar test_playerrunheld_status finished
+end
+
+validate_test local.actual local.expected local.test:
+ if(local.actual != local.expected)
+ {
+ println("playerrunheld test error: " + local.test + " = " + local.actual + " expected: " + local.expected)
+ level.playerrunheld_failed_testscases += (local.test + "=" + local.actual + "|")
+ }
+ else
+ {
+ level.playerrunheld_testcases_passed++
+ }
+ level.playerrunheld_testcases_total++
+end
diff --git a/tests/testfiles/scriptplayerrunheld/mainfiles/tests/strings.scr b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/strings.scr
new file mode 100644
index 0000000..bf56c3c
--- /dev/null
+++ b/tests/testfiles/scriptplayerrunheld/mainfiles/tests/strings.scr
@@ -0,0 +1,1101 @@
+/*
+20/10/2011
+Updated clean_filename and added safechardata
+
+// 05 August 2011, Added trim
+Not going to handle 1 as the start anymore. going to make them like normal strings.Taking 0.
+This may break some mods, even admin pro..
+
+
+strip_newlines
+trim
+cut_right
+cut_left
+makeline
+split_string
+add_quotes
+clean_filename
+updated instr to have start position
+rewritten mid
+split_string - to replace split_line
+split_string rewritten, simpler, still deals with multiple spacers and drops admin pro support
+
+
+team_count
+random_light
+light_from_string
+
+// 11 september 2005 added 3 new functions
+array_to_int
+array_to_str
+array_to_float
+
+array_to_lower
+array_to_upper
+to_lower
+to_upper
+instr
+right
+left
+mid
+reverse
+split_line
+replace
+remove
+format_replace
+array_to_int
+array_to_str
+array_to_float
+safechardata
+*/
+
+
+
+////////////////////////////////////////////////////////////////////////
+// safechardata
+// all chgars which can be safe in a file name
+////////////////////////////////////////////////////////////////////////
+safechardata:
+
+ local.array = makearray
+ "a"
+ "b"
+ "c"
+ "d"
+ "e"
+ "f"
+ "g"
+ "h"
+ "i"
+ "j"
+ "k"
+ "l"
+ "m"
+ "n"
+ "o"
+ "p"
+ "q"
+ "r"
+ "s"
+ "t"
+ "u"
+ "v"
+ "w"
+ "x"
+ "y"
+ "z"
+ "A"
+ "B"
+ "C"
+ "D"
+ "E"
+ "F"
+ "G"
+ "H"
+ "I"
+ "J"
+ "K"
+ "L"
+ "M"
+ "N"
+ "O"
+ "P"
+ "Q"
+ "R"
+ "S"
+ "T"
+ "U"
+ "V"
+ "W"
+ "X"
+ "Y"
+ "Z"
+ "1"
+ "2"
+ "3"
+ "4"
+ "5"
+ "6"
+ "7"
+ "8"
+ "9"
+ "10"
+ "."
+endarray
+end local.array
+////////////////////////////////////////////////////////////////////////
+// strip_newlines
+// Removes all new line chars
+// useage: local.name = waitexec global/strings.scr::strip_newlines "str\ning"
+////////////////////////////////////////////////////////////////////////
+strip_newlines local.string:
+ //strip new lines
+ local.string = waitthread remove local.string "\n"
+end local.string
+////////////////////////////////////////////////////////////////////////
+// clean_filename
+// Removes invalid chars from a string for save filesave
+// useage: local.name = waitexec global/strings.scr::clean_filename "filename"
+////////////////////////////////////////////////////////////////////////
+clean_filename local.filename:
+
+ //get safe data
+ local.chardata = waitthread safechardata
+
+ //init new name
+ local.newname = ""
+
+ //go through the name
+ for(local.i = 0; local.i <= local.filename.size - 1; local.i++)
+ {
+ local.letter = local.filename[local.i]
+
+ //are we safe
+ local.issafe = 0
+
+ //check if its in the alphabet
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ //issafe
+ local.issafe = 1
+ break
+ }
+ }
+
+ //if its safe
+ if(local.issafe)
+ {
+ local.newname += local.letter
+ }
+ }
+
+ //if its long enough
+ if(local.newname.size <= 0)
+ {
+ end
+ }
+
+end local.newname
+// RGB light from string
+// Will produce a light from a string
+// returns a array of the light as int
+light_from_string local.string:
+
+ //split the line
+ local.light_array = waitexec global/strings.scr::split_line local.string 1
+ // first set is the split words
+ local.light = local.light_array[1]
+ //convert to int
+ local.light = waitexec global/strings.scr::array_to_float local.light
+
+ //check the colours are not broken
+ for(local.i = 1;local.i < 3;local.i++)
+ {
+ if(local.light[local.i] == NIL)
+ {
+ local.light[local.i] = randomfloat(1.0)
+
+ }
+ }
+
+end local.light
+
+// produces a random light
+// returns a array of the light as int
+random_light:
+
+ local.colour_is_pretty = 1
+ while(local.colour_is_pretty == 1)
+ {
+ for(local.i = 1;local.i <= 3;local.i++)
+ {
+ local.light[local.i] = randomint(2)
+ }
+
+ local.colour_is_pretty = (int(local.light[1] == 0) && int(local.light[2] == 0) && int(local.light[3] == 0))
+
+ if(local.colour_is_pretty != 1)
+ {
+ end local.light
+ }
+
+ waitframe
+ }
+
+
+end local.light
+
+
+// team_count
+// returns an array with string index's "allies" , "axis" and "spectator"
+// used to count players on a team.
+team_count:
+
+
+ local.team[allies] = 0
+ local.team[axis] = 0
+ local.team[spectator] = 0
+
+ for(local.i = 1 ; local.i <= $player.size ; local.i++)
+ {
+ local.team[$player[local.i].dmteam] ++
+ }
+
+end local.team
+
+// makeline
+// turns the string into a line
+// eg
+// local.string = waitexec global/strings.scr::makeline "hello"
+// local.string will be '\nhello\n'
+makeline local.string:
+
+ local.string = ("\n" + local.string + "\n" )
+
+end local.string
+
+// add_quotes
+// adds quotes to the string
+// eg
+// local.string = waitexec global/strings.scr::add_quotes "hello"
+// local.string will be '"hello"'
+add_quotes local.string:
+ local.string = ("\"" + local.string + "\"")
+end local.string
+
+// cut_left
+// like left and left, but, will cut the count of strings away from the left or left.
+// usage: waitexec global/strings.scr::cut_left ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_left "hello" 3
+// local.string will be 'he'
+cut_left local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread right local.number local.string
+
+end local.string
+
+// cut_right
+// like right and left, but, will cut the count of strings away from the left or right.
+// usage: waitexec global/strings.scr::cut_right ( string string , int cut count)
+// eg
+// local.string = waitexec global/strings.scr::cut_right "hello" 3
+// local.string will be 'he'
+cut_right local.string local.cutcount:
+
+ //get the size of the string.
+ local.number = local.string.size - local.cutcount
+
+ //
+ local.number++
+
+ //cut the string reversed left
+ local.string = waitthread left local.number local.string
+
+end local.string
+
+
+// trim
+// removed trailing spaces from front and back of a string.
+// usage: local.str = waitexec global/strings.scr::trim (str string)
+// returns: trimmed string
+trim local.string:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ //do this twice, start,end
+ for(local.times = 1; local.times <= 2 ;local.times++ )
+ {
+ //have we trimmed the start?
+ local.donestart = 0
+
+ //have 0 spaces right now
+ local.spaces = 0
+
+ //if we are at the start
+ if(local.times == 1)
+ {
+ local.start = 0
+ local.end = local.string.size - 1
+ local.step = 1
+ }
+ else
+ {
+ //run reverse
+ local.start = local.string.size - 1
+ local.end = 0
+ local.step = -1
+ }
+
+ //from the start.
+ for(local.i = local.start; local.i != local.end; local.i += local.step)
+ {
+ //if we have done the start, break and start reverse
+ if(local.donestart)
+ {
+ break
+ }
+
+ //is the char a space?
+ if(local.string[local.i] != " ")
+ {
+ //we trimmed the start
+ local.donestart = 1
+ }
+
+ //how many spaces at the front?
+ local.spaces++
+ }
+
+ //need this
+ local.spaces--
+
+ //if we had spaces, remove them
+ if(local.spaces > 0)
+ {
+ if(local.times == 1)
+ {
+ local.string = waitthread cut_left local.string local.spaces
+ }
+ else
+ {
+ local.string = waitthread cut_right local.string local.spaces
+ }
+ }
+ }
+
+end local.string
+
+// convert a array to lower case.
+// usage: exec global/strings.scr::array_to_lower (const array of strings)
+// returns: array of lower case strings
+array_to_lower local.strings:
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_lower local.string
+ }
+ }
+
+end local.strings
+
+
+
+// convert a array to upper case.
+// usage: exec global/strings.scr::array_to_upper (const array of strings)
+// returns: array of upper case strings
+array_to_upper local.strings:
+
+ local.lower = waitthread chardata_uppercase
+ local.upper = waitthread chardata_lowercase
+
+ for(local.linni = 1; local.linni <= local.strings.size; local.linni++)
+ {
+ for(local.anniken = 1; local.anniken <= local.strings[local.linni].size; local.anniken++)
+ {
+ local.string = local.strings[local.linni] [local.anniken]
+ local.strings[local.linni][local.anniken] = waitthread to_upper local.string
+ }
+ }
+
+end local.strings
+
+
+// returns all lowercase chars
+chardata:
+
+ local.array = makearray
+ "a" "A"
+ "b" "B"
+ "c" "C"
+ "d" "D"
+ "e" "E"
+ "f" "F"
+ "g" "G"
+ "h" "H"
+ "i" "I"
+ "j" "J"
+ "k" "K"
+ "l" "L"
+ "m" "M"
+ "n" "N"
+ "o" "O"
+ "p" "P"
+ "q" "Q"
+ "r" "R"
+ "s" "S"
+ "t" "T"
+ "u" "U"
+ "v" "V"
+ "w" "W"
+ "x" "X"
+ "y" "Y"
+ "z" "Z"
+ endarray
+
+end local.array
+
+
+// instr
+// instr will find the position in wich a string is found at.
+// usage: waitexec global/strings.scr::instr (str string to find) ( str string ) ( int start position )
+// returns the position of string_to_find
+// eg
+// exec global/strings.scr::instr "\" "hell\o"
+// gives 4 because it starts at 0
+
+// if none found it will return NIL
+instr local.char local.string local.startpos:
+
+ if(local.string == "")
+ {
+ end
+ }
+
+ if(local.startpos == NIL)
+ {
+ local.startpos = 0
+ }
+
+ if(local.startpos > local.string.size)
+ {
+ end
+ }
+
+ if(local.startpos < 0)
+ {
+ local.startpos = 0
+ }
+ //how many do we need?
+ local.sizeneeded = local.char.size
+
+ //need is
+ local.sofar = 0
+
+ //where we found it
+ local.position = 0
+
+//println ( "INSTRU : Needed is: \"" + local.sizeneeded + "\" IN \"" )
+
+ for(local.i = local.startpos; local.i <= local.string.size - 1; local.i++)
+ {
+ local.position = local.i - (local.sizeneeded - 1)
+
+ //println ( "FINDING : \"" + local.char[local.sofar] + "\" IN \"" + local.string[local.i] + "\" at pos " + local.position + " so far is: " + local.sofar )
+ if(local.char[local.sofar] == local.string[local.i])
+ {
+ //println " - found"
+ //how many found?
+ local.sofar++
+
+ //is this our first matching char
+ if(local.sofar == 1)
+ {
+ //this is our first character
+ local.position = local.i
+ }
+
+ if(local.sofar != local.sizeneeded)
+ {
+ continue
+ }
+
+ //set out position
+ //where are we at, take how many chars long the string to find was, minus 1,
+ // if find 1, take 0, if find 3, we are at 1, moved 3 = 4, take 3
+
+ //done
+ end local.position
+ }
+ else
+ {
+ //reset so far
+ local.sofar = 0
+ }
+ }
+
+end
+
+
+
+// right
+// right will return a string of characters from the right.
+// usage: waitexec global/strings.scr::right ( number of characters#, string string )
+// returns a string right from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::right 3 "hello"
+// local.string will be 'llo'
+right local.pos local.string:
+
+ local.start = local.string.size - local.pos
+ local.right = ""
+
+ for(local.i = local.start; local.i <= local.string.size; local.i++)
+ {
+ local.right += local.string[local.i]
+ }
+
+end local.right
+
+
+
+// left
+// left will return left of the string for the given number.
+// usage: waitexec global/strings.scr::left (number of characters left, string string )
+// returns a string left from the number given as position
+// eg
+// local.string = waitexec global/strings.scr::left 3 "hello"
+// local.string will be 'hel'
+//
+left local.pos local.string:
+
+ local.pos--
+ local.left = ""
+ for(local.i = 0; local.i <= local.pos; local.i++)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+//mid
+//mid will return a string from a given position for a given number of characters.
+// usage: local.string = waitexec global/strings.scr::mid ( start pos, string, count)
+// returns: the string from start pos of string along the count.
+//
+//eg
+// local.string = waitexec global/strings.scr 2 "hello" 2
+// local.string would become 'll'
+mid local.start local.string local.count:
+
+ //init string
+ local.mid = ""
+
+ for(local.i = 1; local.i <= local.count;local.i++)
+ {
+ if(local.start >( local.string.size - 1 ))
+ {
+ break
+ }
+
+ local.mid += local.string[local.start]
+ local.start++
+
+ }
+
+end local.mid
+
+
+
+
+//reverse
+// reverse will reverse a given string.
+//useage:: local.string = waitexec global/strings.scr (string string)
+// result: gives a string that is backwards to the string given.
+//
+//eg
+// local.string = waitexec global/strings.scr::reverse "hello"
+// local.string would become 'olleh'
+reverse local.string:
+
+ local.left = ""
+ for(local.i = local.string.size - 1; local.i >= 0; local.i--)
+ {
+ local.left += local.string[local.i]
+ }
+
+end local.left
+
+
+
+// to_lower
+// this will convert a given string to lower case
+// usage local.string = waitexec global/strings.scr::to_lower (string string, index to convert)
+// result: a lower case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_lower "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_lower "hello" 0
+// local.string will become 'hello'
+to_lower local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// to_upper
+// this will convert a given string to upper case
+// usage local.string = waitexec global/strings.scr::to_upper (string string, index to convert)
+// result: a upper case string
+//
+//eg
+// local.string = waitexec global/strings.scr::to_upper "hello"
+// local.string will become 'hello'
+//eg2
+// local.string = waitexec global/strings.scr::to_upper "hello" 0
+// local.string will become 'hello'
+
+to_upper local.string local.index:
+
+ local.chardata = waitthread chardata
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+ local.letter = local.string[local.i]
+
+ if(local.i == local.index || local.index == NIL)
+ {
+ for(local.t = 1; local.t <= local.chardata.size; local.t++)
+ {
+ if(local.letter == local.chardata[local.t][1])
+ {
+ local.letter = local.chardata[local.t][2]
+ local.string[local.i] = local.letter
+ }
+ }
+ }
+ else
+ {
+ local.string[local.i] = local.letter
+ }
+
+ }
+
+end local.string
+
+
+
+// used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.spacer = what to use to split the line. if none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_string ( string string , string spacer)
+// returns array of strings.
+// local.word[1] etc is a word
+split_string local.string local.spacer:
+
+ //which word are we on
+ local.word = 1
+ //init words
+ local.words[1] = ""
+
+ //default check spacer
+ if(local.spacer == NIL)
+ {
+ local.spacer = " "
+ }
+
+ for(local.i = 0 ; local.i <= local.string.size - 1; local.i++ )
+ {
+ //if the spacer is in the string
+ if(local.spacer == local.string[local.i])
+ {
+ if(!(local.string[local.i - 1] == local.spacer && local.string[local.i + 1] == local.spacer))
+ {
+ if(local.words[local.word] != "")
+ {
+ //new word
+ //local.words[local.word] = waitthread add_quotes local.words[local.word]
+ local.word++
+ //init the word
+ local.words[local.word] = ""
+ }
+ continue
+ }
+ }
+
+ //add the string to the word
+ local.words[local.word] += local.string[local.i]
+ }
+
+end local.words
+
+
+// Used to split a line of words into a array of words. return with word count
+// localinfo == line to split
+// local.say = say to admins input detected or not, set 1 usualy
+// local.spacer = What to use to split the line. If none is set then " " will be used.
+// usage local.wordarray = waitexec global/strings.scr::split_line ( STRING STRING , CONSOLE FEEDBACK, STRING SPACER)
+//
+//eg
+// local.wordarray = waitexec global/strings.scr::split_line "hello_mummy" 1 "_"
+// local.wordarray is a const array
+//
+// local.wordarray[1] = array of words
+// local.wordarray[2] word count
+// local.wordarray[3] full string with " " spaces
+//
+// local.wordarray[1][1] is 'hello'
+// local.wordarray[1][1] is 'mummy'
+//
+// local.wordarray[2] is 2 'two words'
+//
+// local.wordarray[3] is 'hello mummy'
+//
+
+split_line local.info local.dont_say local.spacer:
+
+ local.wordcount = 1
+
+ if(local.spacer==NIL)
+ {
+ if(local.info[0] == "`")
+ {
+ local.spacer = "_"
+ local.start = 1
+ }
+ else if(local.info[0] == " " || local.info[0] == "")
+ {
+ local.spacer = " "
+
+ for(local.i = 0;local.i <= local.info.size;local.i++)
+ {
+ if(local.info[local.i] != " " && local.info[local.i] != "")
+ {
+ local.start = local.i
+ break
+ }
+ }
+
+ }
+ else
+ {
+ local.spacer = " "
+ local.start = 0
+ }
+ }
+ else
+ {
+ local.start = 0
+ local.altcheck = 1
+ }
+
+ for(local.i=local.start;local.i<=local.info.size - 1;local.i++)
+ {
+ if(local.info[local.i]!=local.spacer && local.info[local.i] != "`")
+ {
+ if(local.words[local.wordcount]==NIL)
+ {
+ local.words[local.wordcount]=""
+ }
+
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else
+ {
+ if(local.altcheck != 1)
+ {
+ if(local.spacer == "_" && local.info[local.i + 1] == "`") // if its like ui_hud 1. sud be ui_`hud so it knows:).
+ {
+ local.words[local.wordcount] += local.info[local.i]
+ }
+ else if(local.spacer == "_" && local.info[local.i ] != "`")
+ {
+ local.wordcount++
+ }
+ else if(local.spacer == " " && local.info[local.i ] == " ")
+ {
+ if(local.i != local.info.size - 1)
+ {
+ if(local.info[local.i + 1] != " " && local.info[local.i + 1] != NIL)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+ else
+ {
+ if(local.info[local.i ] == local.spacer)
+ {
+ local.wordcount++
+ }
+ }
+ }
+ }
+
+ if(local.spacer == "_")
+ {
+ local.actual = ""
+
+ for(local.i=3;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.actual )
+ }
+ }
+ else
+ {
+ if(local.dont_say != 1)
+ {
+ exec global/ac/console_feedback.scr ( "> Input detected: " + local.info)
+ local.actual = local.info
+ }
+ }
+
+end ( local.words::local.wordcount::local.actual)
+
+// replace is used just like replace in notepad or any text editor.
+// it will replace any string in a string with a string of any size.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was a mod"
+
+replace local.string local.replace local.replace_with:
+
+ if(local.string == "")
+ {
+ end ""
+ }
+
+ //set last position
+ local.lastposition = 0
+
+ //eeek
+ for (local.i = 0;local.i <= local.string.size; local.i++)
+ {
+ //get our first
+ local.position = waitthread instr local.replace local.string
+
+ //did we find it
+ if(local.position == NIL)
+ {
+ break
+ }
+
+ //get before it
+ local.textleft = waitthread left local.position local.string
+
+ //get length to the end
+
+ //replace , biggest string
+ if(local.replace.size < local.replace_with.size)
+ {
+ local.bigger = local.replace_with.size
+ }
+ else
+ {
+ local.bigger = local.replace.size
+ }
+
+ local.start = local.position + local.bigger
+ local.length = (local.string.size - local.start)
+
+ //get after it
+ local.textright = waitthread mid local.start local.string local.length
+
+ //update temp, and file text
+ local.string = local.textleft + local.replace_with + local.textright
+
+ //sorted next position
+ local.lastposition = local.position
+ }
+
+end local.string
+
+
+
+
+// remove is used to remove words or single characters from a line.
+//
+// exec global/strings::remove ( string string , string string to remove )
+//
+// eg
+// local.string = waitexec global/strings.scr::remove "hello you idiot" "idiot"
+// local.string would become
+// "hello you"
+remove local.string local.string_remove:
+ local.string = waitthread replace local.string local.string_remove ""
+end local.string
+
+// format_replace is used like replace except it will only replace a single instance in a group of the same character.
+// it will only replace single characters.
+//
+// exec global/strings::replace ( string string , string string to replace, string string to replace with )
+//
+// eg
+// local.string = waitexec global/strings.scr::format_replace "once_upon_a_time_there_was__a_mod" "_" " "
+// local.string would become
+// "once upon a time there was_a mod"
+
+format_replace local.string local.replace local.replace_with:
+
+ local.new_str = ""
+
+ for(local.i = 0; local.i <= local.string.size - 1; local.i++)
+ {
+
+ local.check = (local.replace == local.string[local.i] && local.replace != local.string[local.i + 1])
+
+ if(local.check)
+ {
+ if ( local.string[local.i - 1] != local.replace)
+ {
+ local.new_str += local.replace_with
+ }
+ }
+ else
+ {
+ local.new_str += local.string[local.i]
+ }
+ }
+
+end local.new_str
+
+///////////////////////////////////////////////////////////
+// combine combines a array of stirngs into one single string of all.
+// exec global/strings:;combine (array , int start in array)
+///////////////////////////////////////////////////////////
+combine local.words local.start:
+
+ local.actual = ""
+
+ if(!local.start)
+ {
+ if(local.words[0])
+ {
+
+ }
+ }
+
+ for(local.i=local.start;local.i<=local.words.size;local.i++)
+ {
+ if(local.i < local.words.size)
+ {
+ local.space = " "
+ }
+ else
+ {
+ local.space = ""
+ }
+
+ local.actual += ( local.words[local.i] + local.space )
+ }
+
+end local.actual
+
+///////////////////////////////////////////////////////////
+// array_to_int
+// converts a array into interger
+// returns the array as int
+///////////////////////////////////////////////////////////
+array_to_int local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = int local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_str
+// converts a array into string
+// returns the array as string
+///////////////////////////////////////////////////////////
+array_to_str local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = string local.array[local.i]
+ }
+
+end local.array
+
+///////////////////////////////////////////////////////////
+// array_to_float
+// converts a array into float
+// returns the array as float
+///////////////////////////////////////////////////////////
+array_to_float local.array:
+
+ if(local.array[0] != NIL)
+ {
+ local.start = 0
+ }
+ else
+ {
+ local.start = 1
+ }
+
+ for(local.i = local.start;local.i <= local.array.size; local.i++)
+ {
+ local.array[local.i] = float local.array[local.i]
+ }
+
+end local.array
diff --git a/tests/testfiles/scriptplayerrunheld/test.txt b/tests/testfiles/scriptplayerrunheld/test.txt
new file mode 100644
index 0000000..08e00ed
--- /dev/null
+++ b/tests/testfiles/scriptplayerrunheld/test.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
diff --git a/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/main.scr b/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/main.scr
new file mode 100644
index 0000000..b5548b5
--- /dev/null
+++ b/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/main.scr
@@ -0,0 +1,3 @@
+main:
+ exec tests/teamswitchdelay.scr
+end
\ No newline at end of file
diff --git a/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/teamswitchdelay.scr b/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/teamswitchdelay.scr
new file mode 100644
index 0000000..1b490c0
--- /dev/null
+++ b/tests/testfiles/scriptteamswitchdelay/mainfiles/tests/teamswitchdelay.scr
@@ -0,0 +1,19 @@
+main:
+ teamswitchdelay 16
+ wait 1
+ setcvar test_scriptteamswitchdelay_stage 1
+
+ while(!$player) waitframe;
+ while($player.dmteam != "axis") waitframe;
+ local.axistime = level.time
+ while($player.dmteam == "axis") waitframe;
+ local.axistime = level.time - local.axistime
+ println "axistime " local.axistime
+ teamswitchdelay 60
+
+ local.alliestime = level.time
+ while($player.dmteam == "allies") waitframe;
+ local.alliestime = level.time - local.alliestime
+ println "alliestime " local.axistime
+
+end
\ No newline at end of file
diff --git a/tests/tests/test_scriptfilesystem.py b/tests/tests/test_scriptfilesystem.py
index 3e7cdbc..39a9e7c 100644
--- a/tests/tests/test_scriptfilesystem.py
+++ b/tests/tests/test_scriptfilesystem.py
@@ -24,4 +24,5 @@ def test_scriptfilesystem(self, game_manager, file_manager, rcon_manager):
failed = rcon_manager.get_cvar_value(b'test_filesystem_failed')
failed = failed.replace('\n','\\n')
assert passed == total, f'Score is {score}, failed tests are: {failed}'
+ file_manager.delete('test_fnewdir')
diff --git a/tests/tests/test_scriptmisc.py b/tests/tests/test_scriptmisc.py
index 2d7f42d..c5d3841 100644
--- a/tests/tests/test_scriptmisc.py
+++ b/tests/tests/test_scriptmisc.py
@@ -64,6 +64,23 @@ def test_scriptgettimezone(self, game_manager, file_manager, rcon_manager):
tz = -time.timezone//3600
assert int(res) == tz
+ #@pytest.mark.supported_game_vers('1.11')
+ @pytest.mark.timeout(240)
+ def test_scriptteamswitchdelay(self, game_manager, file_manager, rcon_manager, bot_manager):
+ time.sleep(30) #team switch delays are ignored for the first 30 seconds
+ bot_manager.spawn(b'axis')
+ time.sleep(1)
+ bot_manager.send_cmd_await_output(b'join_team allies', b'Can not change teams again for another')
+ time.sleep(16)
+ bot_manager.join_team(b'allies')
+
+ time.sleep(1)
+
+ #bot_manager.join_team(b'allies')
+ time.sleep(30)
+ bot_manager.send_cmd_await_output(b'join_team axis', b'Can not change teams again for another')
+ time.sleep(30)
+ bot_manager.join_team(b'axis')
def test_scriptmiscall(self, game_manager, file_manager, rcon_manager, bot_manager):
diff --git a/tests/tests/test_scriptplayermisc.py b/tests/tests/test_scriptplayermisc.py
index 1d7cd92..ecb5f5d 100644
--- a/tests/tests/test_scriptplayermisc.py
+++ b/tests/tests/test_scriptplayermisc.py
@@ -48,7 +48,73 @@ def test_scriptplayersecfireheld(self, game_manager, file_manager, rcon_manager,
failed = rcon_manager.get_cvar_value(b'test_playersecfireheld_failed')
assert passed == total, f'Score is {score}, failed tests are: {failed}'
-
+ """
+ def test_scriptplayerrunheld(self, game_manager, file_manager, rcon_manager, bot_manager):
+ bot_manager.spawn()
+ time.sleep(5)
+ bot_manager.send_command(b'-speed')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playerrunheld_botstatus runheld')
+ time.sleep(10)
+ bot_manager.send_command(b'+speed')
+ time.sleep(5)
+ rcon_manager.send_command(b'set test_playerrunheld_botstatus norunheld')
+ res = ''
+ while True:
+ res = rcon_manager.get_cvar_value(b'test_playerrunheld_status')
+ if not res or res == 'running':
+ time.sleep(5)
+ else:
+ break
+ score = rcon_manager.get_cvar_value(b'test_playerrunheld_score')
+ passed, total = score.split('/')
+ failed = rcon_manager.get_cvar_value(b'test_playerrunheld_failed')
+ assert passed == total, f'Score is {score}, failed tests are: {failed}'
+
+ def test_scriptplayerleanleftheld(self, game_manager, file_manager, rcon_manager, bot_manager):
+ bot_manager.spawn()
+ time.sleep(5)
+ bot_manager.send_command(b'leanleft')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playerleanleftheld_botstatus leanleftheld')
+ time.sleep(10)
+ bot_manager.send_command(b'stop')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playerleanleftheld_botstatus noleanleftheld')
+ res = ''
+ while True:
+ res = rcon_manager.get_cvar_value(b'test_playerleanleftheld_status')
+ if not res or res == 'running':
+ time.sleep(5)
+ else:
+ break
+ score = rcon_manager.get_cvar_value(b'test_playerleanleftheld_score')
+ passed, total = score.split('/')
+ failed = rcon_manager.get_cvar_value(b'test_playerleanleftheld_failed')
+ assert passed == total, f'Score is {score}, failed tests are: {failed}'
+
+ def test_scriptplayerleanrightheld(self, game_manager, file_manager, rcon_manager, bot_manager):
+ bot_manager.spawn()
+ time.sleep(5)
+ bot_manager.send_command(b'leanright')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playerleanrightheld_botstatus leanrightheld')
+ time.sleep(10)
+ bot_manager.send_command(b'stop')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playerleanrightheld_botstatus noleanrightheld')
+ res = ''
+ while True:
+ res = rcon_manager.get_cvar_value(b'test_playerleanrightheld_status')
+ if not res or res == 'running':
+ time.sleep(5)
+ else:
+ break
+ score = rcon_manager.get_cvar_value(b'test_playerleanrightheld_score')
+ passed, total = score.split('/')
+ failed = rcon_manager.get_cvar_value(b'test_playerleanrightheld_failed')
+ assert passed == total, f'Score is {score}, failed tests are: {failed}'
+ """
def test_scriptplayerisadmin(self, game_manager, file_manager, rcon_manager, bot_manager):
#bot_manager.spawn()
time.sleep(5)
@@ -69,4 +135,36 @@ def test_scriptplayerisadmin(self, game_manager, file_manager, rcon_manager, bot
score = rcon_manager.get_cvar_value(b'test_playerisadmin_score')
passed, total = score.split('/')
failed = rcon_manager.get_cvar_value(b'test_playerisadmin_failed')
+ assert passed == total, f'Score is {score}, failed tests are: {failed}'
+
+
+ def test_scriptplayeradminrights(self, game_manager, file_manager, rcon_manager, bot_manager):
+ #bot_manager.spawn()
+ time.sleep(5)
+ assert bot_manager.send_cmd_await_output(b'ad_login test test', b'Admin System> You have been authed as admin')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playeradminrights_botstatus admin1')
+ time.sleep(10)
+ assert bot_manager.send_cmd_await_output(b'ad_logout', b'Admin System> You have logged out as admin')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playeradminrights_botstatus noadmin1')
+ time.sleep(5)
+ assert bot_manager.send_cmd_await_output(b'ad_login test2 test2', b'Admin System> You have been authed as admin')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playeradminrights_botstatus admin2')
+ time.sleep(10)
+ assert bot_manager.send_cmd_await_output(b'ad_logout', b'Admin System> You have logged out as admin')
+ time.sleep(1)
+ rcon_manager.send_command(b'set test_playeradminrights_botstatus noadmin2')
+
+ res = ''
+ while True:
+ res = rcon_manager.get_cvar_value(b'test_playeradminrights_status')
+ if not res or res == 'running':
+ time.sleep(5)
+ else:
+ break
+ score = rcon_manager.get_cvar_value(b'test_playeradminrights_score')
+ passed, total = score.split('/')
+ failed = rcon_manager.get_cvar_value(b'test_playeradminrights_failed')
assert passed == total, f'Score is {score}, failed tests are: {failed}'
\ No newline at end of file