From 5644206e42f66ed31f5351284aed832101e66489 Mon Sep 17 00:00:00 2001 From: milek7 Date: Wed, 30 Aug 2017 13:55:07 +0200 Subject: [PATCH] basic lua support --- CMakeLists.txt | 10 +- CMake_modules/FindLuaJIT.cmake | 81 ++++++++++++++++ EU07.cpp | 7 +- Event.h | 3 +- Ground.cpp | 139 +++++++++++++++------------ Ground.h | 4 + World.h | 3 +- builds/cmake_win32.bat | 4 +- builds/cmake_win64.bat | 4 +- builds/download_windeps.bat | 4 +- lua.cpp | 170 +++++++++++++++++++++++++++++++++ lua.h | 21 ++++ lua_ffi.h | 78 +++++++++++++++ 13 files changed, 457 insertions(+), 71 deletions(-) create mode 100644 CMake_modules/FindLuaJIT.cmake create mode 100644 lua.cpp create mode 100644 lua.h create mode 100644 lua_ffi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed73daa1..5ecb7fbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ set(SOURCES "mouseinput.cpp" "translation.cpp" "material.cpp" +"lua.cpp" ) if (WIN32) @@ -125,5 +126,10 @@ find_package(libsndfile REQUIRED) include_directories(${LIBSNDFILE_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} ${LIBSNDFILE_LIBRARY}) -#set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -g") +find_package(LuaJIT REQUIRED) +include_directories(${LUAJIT_INCLUDE_DIR}) +target_link_libraries(${PROJECT_NAME} ${LUAJIT_LIBRARIES}) + +if (UNIX) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") +endif() diff --git a/CMake_modules/FindLuaJIT.cmake b/CMake_modules/FindLuaJIT.cmake new file mode 100644 index 00000000..b9bdcdbf --- /dev/null +++ b/CMake_modules/FindLuaJIT.cmake @@ -0,0 +1,81 @@ +# Locate Lua library +# This module defines +# LUAJIT_FOUND, if false, do not try to link to Lua +# LUAJIT_LIBRARIES +# LUAJIT_INCLUDE_DIR, where to find lua.h +# LUAJIT_VERSION_STRING, the version of Lua found (since CMake 2.8.8) +# +# Note that the expected include convention is +# #include "lua.h" +# and not +# #include +# This is because, the lua location is not standardized and may exist +# in locations other than lua/ + +#============================================================================= +# Copyright 2007-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +find_path(LUAJIT_INCLUDE_DIR luajit.h + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES include/luajit-2.0 include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +find_library(LUAJIT_LIBRARY + NAMES luajit luajit-5.1 + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES lib64 lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw + /opt/local + /opt/csw + /opt +) + +if (LUAJIT_LIBRARY) + # include the math library for Unix + if (UNIX AND NOT APPLE) + find_library(LUAJIT_MATH_LIBRARY m) + set(LUAJIT_LIBRARIES "${LUAJIT_LIBRARY};${LUAJIT_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + else () + set(LUAJIT_LIBRARIES "${LUAJIT_LIBRARY}" CACHE STRING "Lua Libraries") + endif () +endif () + +if(LUAJIT_INCLUDE_DIR AND EXISTS "${LUAJIT_INCLUDE_DIR}/lua.h") + file(STRINGS "${LUAJIT_INCLUDE_DIR}/lua.h" luajit_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"") + + string(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUAJIT_VERSION_STRING "${luajit_version_str}") + unset(luajit_version_str) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE if +# all listed variables are TRUE +find_package_handle_standard_args(LuaJIT + REQUIRED_VARS LUAJIT_LIBRARIES LUAJIT_INCLUDE_DIR + VERSION_VAR LUAJIT_VERSION_STRING) + +mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIBRARIES LUAJIT_LIBRARY LUAJIT_MATH_LIBRARY) + diff --git a/EU07.cpp b/EU07.cpp index 68118ac4..dffda597 100644 --- a/EU07.cpp +++ b/EU07.cpp @@ -365,7 +365,7 @@ int main(int argc, char *argv[]) } catch (std::runtime_error e) { - WriteLog(e.what()); + ErrorLog(e.what()); return -1; } @@ -406,6 +406,11 @@ int main(int argc, char *argv[]) ErrorLog( "Critical error, memory allocation failure: " + std::string( Error.what() ) ); return -1; } + catch (std::runtime_error e) + { + ErrorLog(e.what()); + return -1; + } #ifdef _WIN32 Console::Off(); // wyłączenie konsoli (komunikacji zwrotnej) diff --git a/Event.h b/Event.h index f292fa87..d729729f 100644 --- a/Event.h +++ b/Event.h @@ -39,7 +39,8 @@ enum TEventType { tp_Visible, tp_Voltage, tp_Message, - tp_Friction + tp_Friction, + tp_Lua }; const int update_memstring = 0x0000001; // zmodyfikować tekst (UpdateValues) diff --git a/Ground.cpp b/Ground.cpp index 3beb69fc..34f41734 100644 --- a/Ground.cpp +++ b/Ground.cpp @@ -1761,6 +1761,72 @@ void TGround::FirstInit() WriteLog("FirstInit is done"); }; +void TGround::add_event(TEvent *tmp) +{ + if (tmp->Type == tp_Unknown) + delete tmp; + else + { // najpierw sprawdzamy, czy nie ma, a potem dopisujemy + TEvent *found = FindEvent(tmp->asName); + if (found) + { // jeśli znaleziony duplikat + auto const size = tmp->asName.size(); + if( tmp->asName[0] == '#' ) // zawsze jeden znak co najmniej jest + { + delete tmp; + tmp = nullptr; + } // utylizacja duplikatu z krzyżykiem + else if( ( size > 8 ) + && ( tmp->asName.substr( 0, 9 ) == "lineinfo:" )) + // tymczasowo wyjątki + { + delete tmp; + tmp = nullptr; + } // tymczasowa utylizacja duplikatów W5 + else if( ( size > 8 ) + && ( tmp->asName.substr( size - 8 ) == "_warning")) + // tymczasowo wyjątki + { + delete tmp; + tmp = nullptr; + } // tymczasowa utylizacja duplikatu z trąbieniem + else if( ( size > 4 ) + && ( tmp->asName.substr( size - 4 ) == "_shp" )) + // nie podlegają logowaniu + { + delete tmp; + tmp = NULL; + } // tymczasowa utylizacja duplikatu SHP + if (tmp) // jeśli nie został zutylizowany + if (Global::bJoinEvents) + found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) + else + { + ErrorLog("Duplicated event: " + tmp->asName); + found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) + found->Type = tp_Ignored; // dezaktywacja pierwotnego - taka proteza na + // wsteczną zgodność + // SafeDelete(tmp); //bezlitośnie usuwamy wszelkie duplikaty, żeby nie + // zaśmiecać drzewka + } + } + if ( nullptr != tmp ) + { // jeśli nie duplikat + tmp->evNext2 = RootEvent; // lista wszystkich eventów (m.in. do InitEvents) + RootEvent = tmp; + if (!found) + { // jeśli nazwa wystąpiła, to do kolejki i wyszukiwarki dodawany jest tylko pierwszy + if( ( RootEvent->Type != tp_Ignored ) + && ( RootEvent->asName.find( "onstart" ) != std::string::npos ) ) { + // event uruchamiany automatycznie po starcie + AddToQuery( RootEvent, NULL ); // dodanie do kolejki + } + m_eventmap.emplace( tmp->asName, tmp ); // dodanie do wyszukiwarki + } + } + } +} + bool TGround::Init(std::string File) { // główne wczytywanie scenerii if (ToLower(File).substr(0, 7) == "scenery") @@ -1920,68 +1986,14 @@ bool TGround::Init(std::string File) { TEvent *tmp = new TEvent(); tmp->Load(&parser, &pOrigin); - if (tmp->Type == tp_Unknown) - delete tmp; - else - { // najpierw sprawdzamy, czy nie ma, a potem dopisujemy - TEvent *found = FindEvent(tmp->asName); - if (found) - { // jeśli znaleziony duplikat - auto const size = tmp->asName.size(); - if( tmp->asName[0] == '#' ) // zawsze jeden znak co najmniej jest - { - delete tmp; - tmp = nullptr; - } // utylizacja duplikatu z krzyżykiem - else if( ( size > 8 ) - && ( tmp->asName.substr( 0, 9 ) == "lineinfo:" )) - // tymczasowo wyjątki - { - delete tmp; - tmp = nullptr; - } // tymczasowa utylizacja duplikatów W5 - else if( ( size > 8 ) - && ( tmp->asName.substr( size - 8 ) == "_warning")) - // tymczasowo wyjątki - { - delete tmp; - tmp = nullptr; - } // tymczasowa utylizacja duplikatu z trąbieniem - else if( ( size > 4 ) - && ( tmp->asName.substr( size - 4 ) == "_shp" )) - // nie podlegają logowaniu - { - delete tmp; - tmp = NULL; - } // tymczasowa utylizacja duplikatu SHP - if (tmp) // jeśli nie został zutylizowany - if (Global::bJoinEvents) - found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) - else - { - ErrorLog("Duplicated event: " + tmp->asName); - found->Append(tmp); // doczepka (taki wirtualny multiple bez warunków) - found->Type = tp_Ignored; // dezaktywacja pierwotnego - taka proteza na - // wsteczną zgodność - // SafeDelete(tmp); //bezlitośnie usuwamy wszelkie duplikaty, żeby nie - // zaśmiecać drzewka - } - } - if ( nullptr != tmp ) - { // jeśli nie duplikat - tmp->evNext2 = RootEvent; // lista wszystkich eventów (m.in. do InitEvents) - RootEvent = tmp; - if (!found) - { // jeśli nazwa wystąpiła, to do kolejki i wyszukiwarki dodawany jest tylko pierwszy - if( ( RootEvent->Type != tp_Ignored ) - && ( RootEvent->asName.find( "onstart" ) != std::string::npos ) ) { - // event uruchamiany automatycznie po starcie - AddToQuery( RootEvent, NULL ); // dodanie do kolejki - } - m_eventmap.emplace( tmp->asName, tmp ); // dodanie do wyszukiwarki - } - } - } + add_event(tmp); + } + else if (str == "lua") + { + parser.getTokens(); + std::string file; + parser >> file; + m_lua.interpret(subpath + file); } else if (str == "rotate") { @@ -3445,6 +3457,9 @@ bool TGround::CheckQuery() break; case tp_Message: // wyświetlenie komunikatu break; + case tp_Lua: + ((lua::eventhandler_t)tmpEvent->Params[0].asPointer)(tmpEvent, tmpEvent->Activator); + break; } // switch (tmpEvent->Type) } // if (tmpEvent->bEnabled) } // while diff --git a/Ground.h b/Ground.h index 938adf2a..56c316d9 100644 --- a/Ground.h +++ b/Ground.h @@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/. #include "Float3d.h" #include "Names.h" #include "lightarray.h" +#include "lua.h" typedef int TGroundNodeType; // Ra: zmniejszone liczby, aby zrobić tabelkę i zoptymalizować wyszukiwanie @@ -260,6 +261,7 @@ class TGround event_map m_eventmap; TNames m_trackmap; light_array m_lights; // collection of dynamic light sources present in the scene + lua m_lua; vector3 pOrigin; vector3 aRotate; @@ -342,6 +344,8 @@ class TGround void IsolatedBusyList(); void IsolatedBusy(const std::string t); void Silence(vector3 gdzie); + + void add_event(TEvent *event); }; //--------------------------------------------------------------------------- diff --git a/World.h b/World.h index 2b689f5e..fbda7cd4 100644 --- a/World.h +++ b/World.h @@ -121,7 +121,6 @@ private: TCamera Camera; TCamera DebugCamera; - TGround Ground; world_environment Environment; TTrain *Train; TDynamicObject *pDynamicNearest; @@ -147,6 +146,8 @@ private: void CabChange(TDynamicObject *old, TDynamicObject *now); // handles vehicle change flag void ChangeDynamic(); + + TGround Ground; //m7todo: tmp }; //--------------------------------------------------------------------------- diff --git a/builds/cmake_win32.bat b/builds/cmake_win32.bat index 51ab4f24..05d0c7b62 100644 --- a/builds/cmake_win32.bat +++ b/builds/cmake_win32.bat @@ -15,5 +15,7 @@ cmake ../.. -T v140_xp ^ -DOPENAL_INCLUDE_DIR=%DEPS_DIR%/openal/include ^ -DOPENAL_LIBRARY=%DEPS_DIR%/openal/lib/win32/OpenAL32.lib ^ -DLIBSNDFILE_INCLUDE_DIR=%DEPS_DIR%/libsndfile/include ^ --DLIBSNDFILE_LIBRARY=%DEPS_DIR%/libsndfile/lib/win32/libsndfile-1.lib +-DLIBSNDFILE_LIBRARY=%DEPS_DIR%/libsndfile/lib/win32/libsndfile-1.lib ^ +-DLUAJIT_INCLUDE_DIR=%DEPS_DIR%/luajit/include ^ +-DLUAJIT_LIBRARIES=%DEPS_DIR%/luajit/lib/win32/lua51.lib popd \ No newline at end of file diff --git a/builds/cmake_win64.bat b/builds/cmake_win64.bat index 9b35e9fe..4294b1a2 100644 --- a/builds/cmake_win64.bat +++ b/builds/cmake_win64.bat @@ -15,5 +15,7 @@ cmake ../.. -A x64 ^ -DOPENAL_INCLUDE_DIR=%DEPS_DIR%/openal/include ^ -DOPENAL_LIBRARY=%DEPS_DIR%/openal/lib/win64/OpenAL32.lib ^ -DLIBSNDFILE_INCLUDE_DIR=%DEPS_DIR%/libsndfile/include ^ --DLIBSNDFILE_LIBRARY=%DEPS_DIR%/libsndfile/lib/win64/libsndfile-1.lib +-DLIBSNDFILE_LIBRARY=%DEPS_DIR%/libsndfile/lib/win64/libsndfile-1.lib ^ +-DLUAJIT_INCLUDE_DIR=%DEPS_DIR%/luajit/include ^ +-DLUAJIT_LIBRARIES=%DEPS_DIR%/luajit/lib/win64/lua51.lib popd \ No newline at end of file diff --git a/builds/download_windeps.bat b/builds/download_windeps.bat index 76f660fc..ee15b44c 100644 --- a/builds/download_windeps.bat +++ b/builds/download_windeps.bat @@ -1,2 +1,2 @@ -powershell "$wc = New-Object System.Net.WebClient; $wc.DownloadFile(\"https://milek7.pl/.stuff/eu07exe/builddep2.zip\", \"%cd%\deps_win.zip\")" -powershell "$s = New-Object -ComObject shell.application; $z = $s.Namespace(\"%cd%\deps_win.zip\"); foreach ($i in $z.items()) { $s.Namespace(\"%cd%\").CopyHere($i) }" \ No newline at end of file +powershell "$wc = New-Object System.Net.WebClient; $wc.DownloadFile(\"https://milek7.pl/.stuff/eu07exe/builddep3.zip\", \"%cd%\deps_win.zip\")" +powershell "$s = New-Object -ComObject shell.application; $z = $s.Namespace(\"%cd%\deps_win.zip\"); foreach ($i in $z.items()) { $s.Namespace(\"%cd%\").CopyHere($i) }" diff --git a/lua.cpp b/lua.cpp new file mode 100644 index 00000000..3d61a2d8 --- /dev/null +++ b/lua.cpp @@ -0,0 +1,170 @@ +#include "stdafx.h" +#include "lua.h" +#include "Event.h" +#include "Logs.h" +#include "MemCell.h" +#include "World.h" +#include "Driver.h" +#include "lua_ffi.h" + +extern TWorld World; + +lua::lua() +{ + state = luaL_newstate(); + if (!state) + throw std::runtime_error("cannot create lua state"); + lua_atpanic(state, atpanic); + luaL_openlibs(state); + + lua_getglobal(state, "package"); + lua_pushstring(state, "preload"); + lua_gettable(state, -2); + lua_pushcclosure(state, openffi, 0); + lua_setfield(state, -2, "eu07.events"); + lua_settop(state, 0); +} + +lua::~lua() +{ + lua_close(state); + state = nullptr; +} + +void lua::interpret(std::string file) +{ + if (luaL_dofile(state, file.c_str())) + throw std::runtime_error(lua_tostring(state, -1)); +} + +// NOTE: we cannot throw exceptions in callbacks +// because it is not supported by LuaJIT on x86 windows + +int lua::atpanic(lua_State *s) +{ + ErrorLog(std::string(lua_tostring(s, -1))); + return 0; +} + +int lua::openffi(lua_State *s) +{ + if (luaL_dostring(s, lua_ffi)) + { + ErrorLog(std::string(lua_tostring(s, -1))); + return 0; + } + return 1; +} + +extern "C" +{ + TEvent* scriptapi_event_create(const char* name, lua::eventhandler_t handler, double delay) + { + TEvent *event = new TEvent(); + event->bEnabled = true; + event->Type = tp_Lua; + event->asName = std::string(name); + event->fDelay = delay; + event->Params[0].asPointer = (void*)handler; + World.Ground.add_event(event); + return event; + } + + TEvent* scriptapi_event_find(const char* name) + { + std::string str(name); + TEvent *e = World.Ground.FindEvent(str); + if (e) + return e; + else + WriteLog("missing event: " + str); + return nullptr; + } + + TTrack* scriptapi_track_find(const char* name) + { + std::string str(name); + TGroundNode *n = World.Ground.FindGroundNode(str, TP_TRACK); + if (n) + return n->pTrack; + else + WriteLog("missing track: " + str); + return nullptr; + } + + bool scriptapi_track_isoccupied(TTrack* track) + { + if (track) + return !track->IsEmpty(); + return false; + } + + const char* scriptapi_event_getname(TEvent *e) + { + if (e) + return e->asName.c_str(); + return nullptr; + } + + const char* scriptapi_train_getname(TDynamicObject *dyn) + { + if (dyn && dyn->Mechanik) + return dyn->Mechanik->TrainName().c_str(); + return nullptr; + } + + void scriptapi_event_dispatch(TEvent *e, TDynamicObject *activator) + { + if (e) + World.Ground.AddToQuery(e, activator); + } + + double scriptapi_random(double a, double b) + { + return Random(a, b); + } + + void scriptapi_writelog(const char* txt) + { + WriteLog("lua log: " + std::string(txt)); + } + + struct memcell_values { const char *str; double num1; double num2; }; + + TMemCell* scriptapi_memcell_find(const char *name) + { + std::string str(name); + TGroundNode *n = World.Ground.FindGroundNode(str, TP_MEMCELL); + if (n) + return n->MemCell; + else + WriteLog("missing memcell: " + str); + return nullptr; + } + + memcell_values scriptapi_memcell_read(TMemCell *mc) + { + if (!mc) + return { nullptr, 0.0, 0.0 }; + return { mc->Text().c_str(), mc->Value1(), mc->Value2() }; + } + + void scriptapi_memcell_update(TMemCell *mc, const char *str, double num1, double num2) + { + if (!mc) + return; + mc->UpdateValues(std::string(str), num1, num2, + update_memstring | update_memval1 | update_memval2); + } + + void scriptapi_dynobj_putvalues(TDynamicObject *dyn, const char *str, double num1, double num2) + { + if (!dyn) + return; + TLocation loc; + if (dyn->Mechanik) + dyn->Mechanik->PutCommand(std::string(str), num1, num2, nullptr); + else + dyn->MoverParameters->PutCommand(std::string(str), num1, num2, loc); + } +} diff --git a/lua.h b/lua.h new file mode 100644 index 00000000..6731bcb6 --- /dev/null +++ b/lua.h @@ -0,0 +1,21 @@ +#pragma once +#include + +class TEvent; +class TDynamicObject; + +class lua +{ + lua_State *state; + + static int atpanic(lua_State *s); + static int openffi(lua_State *s); + +public: + lua(); + ~lua(); + + void interpret(std::string file); + + typedef void (*eventhandler_t)(TEvent*, TDynamicObject*); +}; diff --git a/lua_ffi.h b/lua_ffi.h new file mode 100644 index 00000000..f07c69fe --- /dev/null +++ b/lua_ffi.h @@ -0,0 +1,78 @@ +const char lua_ffi[] = "local ffi = require(\"ffi\")\n" +"ffi.cdef[[\n" +"struct memcell_values { const char *str; double num1; double num2; };\n" +"\n" +"typedef struct TEvent TEvent;\n" +"typedef struct TTrack TTrack;\n" +"typedef struct TDynamicObject TDynamicObject;\n" +"typedef struct TMemCell TMemCell;\n" +"typedef struct memcell_values memcell_values;\n" +"\n" +"TEvent* scriptapi_event_create(const char* name, void (*handler)(TEvent*, TDynamicObject*), double delay);\n" +"TEvent* scriptapi_event_find(const char* name);\n" +"const char* scriptapi_event_getname(TEvent *e);\n" +"void scriptapi_event_dispatch(TEvent *e, TDynamicObject *activator);\n" +"\n" +"TTrack* scriptapi_track_find(const char* name);\n" +"bool scriptapi_track_isoccupied(TTrack *track);\n" +"\n" +"const char* scriptapi_train_getname(TDynamicObject *dyn);\n" +"void scriptapi_dynobj_putvalues(TDynamicObject *dyn, const char *str, double num1, double num2);\n" +"\n" +"TMemCell* scriptapi_memcell_find(const char *name);\n" +"memcell_values scriptapi_memcell_read(TMemCell *mc);\n" +"void scriptapi_memcell_update(TMemCell *mc, const char *str, double num1, double num2);\n" +"\n" +"double scriptapi_random(double a, double b);\n" +"void scriptapi_writelog(const char* txt);\n" +"]]\n" +"\n" +"local ns = ffi.C\n" +"\n" +"local module = {}\n" +"\n" +"module.event_create = ns.scriptapi_event_create\n" +"module.event_find = ns.scriptapi_event_find\n" +"function module.event_getname(a)\n" +" return ffi.string(ns.scriptapi_event_getname(a))\n" +"end\n" +"module.event_dispatch = ns.scriptapi_event_dispatch\n" +"function module.event_dispatch_n(a, b)\n" +" ns.scriptapi_event_dispatch(ns.scriptapi_event_find(a), b)\n" +"end\n" +"\n" +"module.track_find = ns.scriptapi_track_find\n" +"module.track_isoccupied = ns.scriptapi_track_isoccupied\n" +"function module.track_isoccupied_n(a)\n" +" return ns.scriptapi_track_isoccupied(ns.scriptapi_track_find(a))\n" +"end\n" +"\n" +"function module.train_getname(a)\n" +" return ffi.string(ns.scriptapi_train_getname(a))\n" +"end\n" +"function module.dynobj_putvalues(a, b)\n" +" ns.scriptapi_dynobj_putvalues(a, b.str, b.num1, b.num2)\n" +"end\n" +"\n" +"module.memcell_find = ns.scriptapi_memcell_find\n" +"function module.memcell_read(a)\n" +" native = ns.scriptapi_memcell_read(a)\n" +" mc = { }\n" +" mc.str = ffi.string(native.str)\n" +" mc.num1 = native.num1\n" +" mc.num2 = native.num2\n" +" return mc\n" +"end\n" +"function module.memcell_read_n(a)\n" +" return module.memcell_read(ns.scriptapi_memcell_find(a))\n" +"end\n" +"function module.memcell_update(a, b)\n" +" ns.scriptapi_memcell_update(a, b.str, b.num1, b.num2)\n" +"end\n" +"function module.memcell_update_n(a, b)\n" +" ns.scriptapi_memcell_update(ns.scriptapi_memcell_find(a), b.str, b.num1, b.num2)\n" +"end\n" +"\n" +"module.random = ns.scriptapi_random\n" +"module.writelog = ns.scriptapi_writelog\n" +"\nreturn module\n";