diff --git a/apps/openmw-mp/Script/API/TimerAPI.cpp b/apps/openmw-mp/Script/API/TimerAPI.cpp
index 7489497d9..6aac348b5 100644
--- a/apps/openmw-mp/Script/API/TimerAPI.cpp
+++ b/apps/openmw-mp/Script/API/TimerAPI.cpp
@@ -26,6 +26,15 @@ Timer::Timer(lua_State *lua, ScriptFuncLua callback, long msec, const std::strin
 }
 #endif
 
+#ifdef ENABLE_MONO
+Timer::Timer(MonoObject *callback, long msec, const std::string &def, std::vector<boost::any> args) : ScriptFunction(callback, 'v', def)
+{
+    targetMsec = msec;
+    this->args = args;
+    end = true;
+}
+#endif
+
 void Timer::Tick()
 {
     if (end)
@@ -93,6 +102,28 @@ int TimerAPI::CreateTimerLua(lua_State *lua, ScriptFuncLua callback, long msec,
 }
 #endif
 
+#if defined(ENABLE_MONO)
+int TimerAPI::CreateTimerMono(MonoObject *callback, long msec, const std::string& def, std::vector<boost::any> args)
+{
+    int id = -1;
+
+    for (auto timer : timers)
+    {
+        if (timer.second != nullptr)
+            continue;
+        timer.second = new Timer(callback, msec, def, args);
+        id = timer.first;
+    }
+
+    if (id == -1)
+    {
+        timers[pointer] = new Timer(callback, msec, def, args);
+        id = pointer;
+        pointer++;
+    }
+    return id;
+}
+#endif
 
 int TimerAPI::CreateTimer(ScriptFunc callback, long msec, const std::string &def, std::vector<boost::any> args)
 {
diff --git a/apps/openmw-mp/Script/API/TimerAPI.hpp b/apps/openmw-mp/Script/API/TimerAPI.hpp
index b20be12d6..ae324329d 100644
--- a/apps/openmw-mp/Script/API/TimerAPI.hpp
+++ b/apps/openmw-mp/Script/API/TimerAPI.hpp
@@ -10,6 +10,10 @@
 #include <Script/Script.hpp>
 #include <Script/ScriptFunction.hpp>
 
+#ifdef ENABLE_MONO
+#include <mono/metadata/object.h>
+#endif
+
 namespace mwmp
 {
 
@@ -24,6 +28,9 @@ namespace mwmp
         Timer(ScriptFunc callback, long msec, const std::string& def, std::vector<boost::any> args);
 #if defined(ENABLE_LUA)
         Timer(lua_State *lua, ScriptFuncLua callback, long msec, const std::string& def, std::vector<boost::any> args);
+#endif
+#ifdef ENABLE_MONO
+        Timer(MonoObject *callback, long msec, const std::string& def, std::vector<boost::any> args);
 #endif
         void Tick();
 
@@ -44,6 +51,9 @@ namespace mwmp
     public:
 #if defined(ENABLE_LUA)
         static int CreateTimerLua(lua_State *lua, ScriptFuncLua callback, long msec, const std::string& def, std::vector<boost::any> args);
+#endif
+#if defined(ENABLE_MONO)
+        static int CreateTimerMono(MonoObject *callback, long msec, const std::string& def, std::vector<boost::any> args);
 #endif
         static int CreateTimer(ScriptFunc callback, long msec, const std::string& def, std::vector<boost::any> args);
         static void FreeTimer(int timerid);
diff --git a/apps/openmw-mp/Script/LangMono/LangMono.cpp b/apps/openmw-mp/Script/LangMono/LangMono.cpp
index 1a4a107f7..65c37f514 100644
--- a/apps/openmw-mp/Script/LangMono/LangMono.cpp
+++ b/apps/openmw-mp/Script/LangMono/LangMono.cpp
@@ -4,13 +4,107 @@
 
 #include <cstdarg>
 #include <mono/metadata/appdomain.h>
+#include <mono/metadata/object-forward.h>
 #include <mono/jit/jit.h>
 #include <mono/metadata/assembly.h>
+#include <mono/metadata/mono-config.h>
 #include <apps/openmw-mp/Script/ScriptFunctions.hpp>
+#include <apps/openmw-mp/Script/API/TimerAPI.hpp>
 #include "LangMono.hpp"
 
 static MonoDomain *domain = nullptr; // shared domain
 
+std::string monoStringToStdString(MonoString *monoString)
+{
+    char *utf8 = mono_string_to_utf8(monoString);
+    std::string str = utf8;
+    mono_free(utf8);
+    return str;
+}
+
+template<typename T>
+T unbox(MonoObject *obj)
+{
+    return *(T *) mono_object_unbox(obj);
+}
+
+boost::any monoObjectToAny(MonoObject *obj)
+{
+
+    MonoClass *klass = mono_object_get_class(obj);
+    MonoType *rawType = mono_class_get_type(klass);
+
+    switch ((MonoTypeEnum) mono_type_get_type(rawType))
+    {
+        case MONO_TYPE_END:
+        case MONO_TYPE_VOID:
+            break;
+        case MONO_TYPE_BOOLEAN:
+            return (bool) unbox<MonoBoolean>(obj);
+        case MONO_TYPE_CHAR:
+            return unbox<uint16_t>(obj);
+        case MONO_TYPE_I1:
+            return unbox<int8_t>(obj);
+        case MONO_TYPE_U1:
+            return unbox<uint8_t>(obj);
+        case MONO_TYPE_I2:
+            return unbox<int16_t>(obj);
+        case MONO_TYPE_U2:
+            return unbox<uint16_t>(obj);
+        case MONO_TYPE_I4:
+            return unbox<int32_t>(obj);
+        case MONO_TYPE_U4:
+            return unbox<uint32_t>(obj);
+        case MONO_TYPE_I8:
+            return unbox<int64_t>(obj);
+        case MONO_TYPE_U8:
+            return unbox<uint64_t>(obj);
+        case MONO_TYPE_R4:
+            return unbox<float>(obj);
+        case MONO_TYPE_R8:
+            return unbox<double>(obj);
+        case MONO_TYPE_STRING:
+            return monoStringToStdString((MonoString *) obj);
+        case MONO_TYPE_ARRAY:
+        case MONO_TYPE_SZARRAY:
+        {
+            MonoArrayType *arrayType = mono_type_get_array_type(rawType);
+        }
+        default:
+            return boost::any();
+    }
+    return boost::any();
+}
+
+int LangMono::CreateTimerEx(MonoObject *delegate, long msec, MonoString *monoStr, MonoArray *monoArgs)
+{
+    size_t argsLength = mono_array_length(monoArgs);
+    std::vector<boost::any> params (argsLength);
+    try
+    {
+        for(size_t i = 0; i < argsLength; ++i)
+            params[i] = monoObjectToAny(mono_array_get(monoArgs, MonoObject*, i));
+
+        char *types = mono_string_to_utf8(monoStr);
+        int id = mwmp::TimerAPI::CreateTimerMono(delegate, msec, types, params);
+        mono_free(types);
+        return id;
+    }
+    catch (...)
+    {
+        return -1;
+    }
+}
+
+void LangMono::MakePublic(MonoObject *delegate, const char *name) noexcept
+{
+
+}
+
+MonoObject *LangMono::CallPublic(const char *name, MonoArray *args)
+{
+    return nullptr;
+}
 
 lib_t LangMono::GetInterface()
 {
@@ -40,6 +134,9 @@ void LangMono::Init()
         return;
 
     domain = mono_jit_init("TES3MP Mono VM"); // will leak :P
+    mono_add_internal_call("TES3MPSharp.TES3MP::CreateTimerEx", (void*) &LangMono::CreateTimerEx);
+    mono_add_internal_call("TES3MPSharp.TES3MP::MakePublic", (void*) &LangMono::MakePublic);
+    mono_add_internal_call("TES3MPSharp.TES3MP::CallPublic", (void*) &LangMono::CallPublic);
 }
 
 std::vector<MonoClass *> getInstanceClassList(MonoImage *image, const std::string &parentName)
@@ -109,8 +206,7 @@ boost::any LangMono::Call(const char *name, const char *argl, int buf, ...)
 
     int n_args = (int) (strlen(argl));
 
-    std::vector<void *> vec;
-    vec.reserve(n_args);
+    std::vector<void *> vec(n_args);
 
     for (int index = 0; index < n_args; index++)
     {
@@ -119,48 +215,48 @@ boost::any LangMono::Call(const char *name, const char *argl, int buf, ...)
             case 'i':
             {
                 auto val = va_arg(vargs, unsigned int);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 'q':
             {
                 auto val = va_arg(vargs, signed int);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 'l':
             {
                 auto val = va_arg(vargs, unsigned long long);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 'w':
             {
                 auto val = va_arg(vargs, signed long long);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 'f':
             {
                 auto val = va_arg(vargs, double);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 'p':
             {
                 auto val = va_arg(vargs, void*);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
             case 's':
             {
-                vec.push_back(mono_string_new(mono_domain_get(), va_arg(vargs, const char*)));
+                vec[index] = (mono_string_new(mono_domain_get(), va_arg(vargs, const char*)));
                 break;
             }
             case 'b':
             {
                 auto val = va_arg(vargs, int);
-                vec.push_back((void *) &val);
+                vec[index] = (void *) &val;
                 break;
             }
 
@@ -182,6 +278,9 @@ boost::any LangMono::Call(const char *name, const char *argl, int buf, ...)
         methodsCache[{name, n_args}] = method;
     }
 
+    if (method == nullptr)
+        return boost::any();
+
     MonoObject *ret = mono_runtime_invoke(method, instance->object, vec.data(), nullptr);
     if (ret)
         return boost::any(mono_object_unbox(ret));
diff --git a/apps/openmw-mp/Script/LangMono/LangMono.hpp b/apps/openmw-mp/Script/LangMono/LangMono.hpp
index df446af9c..00269d228 100644
--- a/apps/openmw-mp/Script/LangMono/LangMono.hpp
+++ b/apps/openmw-mp/Script/LangMono/LangMono.hpp
@@ -63,6 +63,10 @@ public:
     virtual boost::any Call(const char *name, const char *argl, int buf, ...) override;
     virtual boost::any Call(const char *name, const char *argl, const std::vector<boost::any> &args) override;
 
+    static int CreateTimerEx(MonoObject *delegate, long msec, MonoString *monoStr, MonoArray *args);
+    static void MakePublic(MonoObject *delegate, const char *name) noexcept;
+    static MonoObject *CallPublic(const char *name, MonoArray *args);
+
 private:
     void Init();
 };
diff --git a/apps/openmw-mp/Script/ScriptFunction.cpp b/apps/openmw-mp/Script/ScriptFunction.cpp
index b176776f9..b2483dfd3 100644
--- a/apps/openmw-mp/Script/ScriptFunction.cpp
+++ b/apps/openmw-mp/Script/ScriptFunction.cpp
@@ -24,7 +24,12 @@ ScriptFunction::ScriptFunction(const ScriptFuncLua &fLua, lua_State *lua, char r
 
 }
 #endif
-
+#if defined (ENABLE_MONO)
+ScriptFunction::ScriptFunction(MonoObject *delegate, char ret_type, const std::string &def) :
+        fMono({delegate}), ret_type(ret_type), def(def), script_type(SCRIPT_MONO)
+{
+}
+#endif
 
 ScriptFunction::~ScriptFunction()
 {
@@ -36,7 +41,7 @@ ScriptFunction::~ScriptFunction()
 
 boost::any ScriptFunction::Call(const vector<boost::any> &args)
 {
-    boost::any result;
+    boost::any result = boost::any();
 
     if (def.length() != args.size())
         throw runtime_error("Script call: Number of arguments does not match definition");
@@ -61,13 +66,84 @@ boost::any ScriptFunction::Call(const vector<boost::any> &args)
                 result = boost::any_cast<luabridge::LuaRef>(any).cast<const char*>();
                 break;
             case 'v':
-                result = boost::any();
                 break;
             default:
                 throw runtime_error("Lua call: Unknown return type" + ret_type);
         }
     }
 #endif
+#if defined (ENABLE_MONO)
+    else if (script_type == SCRIPT_MONO)
+    {
+        std::vector<void*> argList;
+        argList.resize(args.size());
+
+        for (int index = 0; index < args.size(); index++)
+        {
+            switch (def[index])
+            {
+                case 'i':
+                {
+                    auto val = boost::any_cast<unsigned int>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 'q':
+                {
+                    auto val = boost::any_cast<signed int>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 'l':
+                {
+                    auto val = boost::any_cast<unsigned long long>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 'w':
+                {
+                    auto val = boost::any_cast<signed long long>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 'f':
+                {
+                    auto val = boost::any_cast<double>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 'p':
+                {
+                    auto val = boost::any_cast<void *>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+                case 's':
+                {
+                    if (args.at(index).type() == typeid(std::string)) // mono to mono call
+                        argList[index] = mono_string_new(mono_domain_get(), boost::any_cast<std::string>(args.at(index)).c_str());
+                    else // lua to mono
+                        argList[index] = mono_string_new(mono_domain_get(), boost::any_cast<const char *>(args.at(index)));
+                    break;
+                }
+                case 'b':
+                {
+                    auto val = boost::any_cast<int>(args.at(index));
+                    argList[index] = ((void *) &val);
+                    break;
+                }
+
+                default:
+                    throw std::runtime_error("Call: Unknown argument identifier " + def[index]);
+            }
+        }
+
+        MonoObject *monoRet = mono_runtime_delegate_invoke(fMono.delegate, argList.data(), NULL);
+        if (monoRet != nullptr)
+            result = mono_object_unbox(monoRet); // todo cast
+
+    }
+#endif
 
     return result;
 }
diff --git a/apps/openmw-mp/Script/ScriptFunction.hpp b/apps/openmw-mp/Script/ScriptFunction.hpp
index 6659d8098..e58e2cbcd 100644
--- a/apps/openmw-mp/Script/ScriptFunction.hpp
+++ b/apps/openmw-mp/Script/ScriptFunction.hpp
@@ -12,6 +12,11 @@
 #include "LangLua/LangLua.hpp"
 #endif
 
+#if defined (ENABLE_MONO)
+#include <mono/metadata/object.h>
+#include <mono/metadata/appdomain.h>
+#endif
+
 typedef unsigned long long(*ScriptFunc)();
 #if defined (ENABLE_LUA)
 typedef std::string ScriptFuncLua;
@@ -29,6 +34,12 @@ protected:
             lua_State *lua;
             ScriptFuncLua name;
         } fLua;
+#endif
+#ifdef ENABLE_MONO
+        struct
+        {
+            MonoObject *delegate;
+        } fMono;
 #endif
     };
 
@@ -39,12 +50,16 @@ protected:
     enum
     {
         SCRIPT_CPP,
-        SCRIPT_LUA
+        SCRIPT_LUA,
+        SCRIPT_MONO
     };
 
     ScriptFunction(ScriptFunc fCpp, char ret_type, const std::string &def);
 #if defined (ENABLE_LUA)
     ScriptFunction(const ScriptFuncLua &fPawn, lua_State *lua, char ret_type, const std::string &def);
+#endif
+#if defined (ENABLE_MONO)
+    ScriptFunction(MonoObject *delegate, char ret_type, const std::string &def);
 #endif
     virtual ~ScriptFunction();