Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
ScriptEngine.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2013-2020 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
25
26#include "ScriptEngine.h"
28
29// AS addons start
30#include "scriptstdstring/scriptstdstring.h"
31#include "scriptmath/scriptmath.h"
32#include "scriptany/scriptany.h"
33#include "scriptarray/scriptarray.h"
34#include "scripthelper/scripthelper.h"
35// AS addons end
36
37#ifdef USE_CURL
38#include <stdio.h>
39#include <curl/curl.h>
40//#include <curl/types.h>
41#include <curl/easy.h>
42#endif //USE_CURL
43
44#include <cfloat>
45
46#include "Application.h"
47#include "Actor.h"
48#include "ActorManager.h"
49#include "Collisions.h"
50#include "Console.h"
51#include "GameContext.h"
52#include "GameScript.h"
53#include "LocalStorage.h"
54#include "OgreScriptBuilder.h"
55#include "PlatformUtils.h"
56#include "ScriptEvents.h"
57#include "ScriptUtils.h"
58#include "ServerScriptEngine.h"
59#include "Utils.h"
60#include "VehicleAI.h"
61
62#include "InputEngine.h"
63
64using namespace Ogre;
65using namespace RoR;
66using namespace AngelScript;
67
69{
70 switch (c)
71 {
72 case ScriptCategory::INVALID: return "INVALID";
73 case ScriptCategory::ACTOR: return "ACTOR";
74 case ScriptCategory::TERRAIN: return "TERRAIN";
75 case ScriptCategory::CUSTOM: return "CUSTOM";
76 default: return "";
77 }
78}
79
81{
82 // Constructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
83}
84
86{
87 // Destructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
88}
89
90// some hacky functions
91
92void logString(const std::string &str)
93{
95}
96
97// the class implementation
98
100 context(0)
101 , engine(0)
102 , scriptLog(0)
103{
104 scriptLog = LogManager::getSingleton().createLog(PathCombine(App::sys_logs_dir->getStr(), "Angelscript.log"), false);
105 this->init();
106}
107
109{
110 // Clean up
111 if (engine) engine->Release();
112 if (context) context->Release();
113}
114
115void ScriptEngine::messageLogged( const String& message, LogMessageLevel lml, bool maskDebug, const String &logName, bool& skipThisMessage)
116{
118}
119
120// continue with initializing everything
122{
123 SLOG("ScriptEngine initializing ...");
124 int result;
125
126 // Create the script engine
127 engine = AngelScript::asCreateScriptEngine(ANGELSCRIPT_VERSION);
128
129 engine->SetEngineProperty(AngelScript::asEP_ALLOW_UNSAFE_REFERENCES, true); // Needed for ImGui
130
131 // Set the message callback to receive information on errors in human readable form.
132 // It's recommended to do this right after the creation of the engine, because if
133 // some registration fails the engine may send valuable information to the message
134 // stream.
135 result = engine->SetMessageCallback(AngelScript::asMETHOD(ScriptEngine,msgCallback), this, AngelScript::asCALL_THISCALL);
136 if (result < 0)
137 {
138 if (result == AngelScript::asINVALID_ARG)
139 {
140 SLOG("One of the arguments is incorrect, e.g. obj is null for a class method.");
141 return;
142 }
143 else if (result == AngelScript::asNOT_SUPPORTED)
144 {
145 SLOG(" The arguments are not supported, e.g. asCALL_GENERIC.");
146 return;
147 }
148 SLOG("Unkown error while setting up message callback");
149 return;
150 }
151
152 // AngelScript doesn't have a built-in string type, as there is no definite standard
153 // string type for C++ applications. Every developer is free to register it's own string type.
154 // The SDK do however provide a standard add-on for registering a string type, so it's not
155 // necessary to register your own string type if you don't want to.
156 AngelScript::RegisterScriptArray(engine, true);
157 AngelScript::RegisterStdString(engine);
158 AngelScript::RegisterStdStringUtils(engine);
159 AngelScript::RegisterScriptMath(engine);
160 static float SCRIPT_FLT_MAX = FLT_MAX;
161 static int SCRIPT_INT_MAX = INT_MAX;
162 result = engine->RegisterGlobalProperty("const float FLT_MAX", &SCRIPT_FLT_MAX); ROR_ASSERT( result >= 0 );
163 result = engine->RegisterGlobalProperty("const int INT_MAX", &SCRIPT_INT_MAX); ROR_ASSERT(result >= 0);
164 AngelScript::RegisterScriptAny(engine);
165 AngelScript::RegisterScriptDictionary(engine);
166
167 // some useful global functions
168 if (strstr(AngelScript::asGetLibraryOptions(), "AS_MAX_PORTABILITY") || App::diag_angelscript_generic_bind_test->getBool())
169 {
170 result = engine->RegisterGlobalFunction("void log(const string &in)", WRAP_FN(logString), AngelScript::asCALL_GENERIC); ROR_ASSERT(result >= 0);
171 result = engine->RegisterGlobalFunction("void print(const string &in)", WRAP_FN(logString), AngelScript::asCALL_GENERIC); ROR_ASSERT(result >= 0);
172 }
173 else
174 {
175 result = engine->RegisterGlobalFunction("void log(const string &in)", AngelScript::asFUNCTION(logString), AngelScript::asCALL_CDECL); ROR_ASSERT(result >= 0);
176 result = engine->RegisterGlobalFunction("void print(const string &in)", AngelScript::asFUNCTION(logString), AngelScript::asCALL_CDECL); ROR_ASSERT(result >= 0);
177 }
178
179 RegisterOgreObjects(engine); // vector2/3, degree, radian, quaternion, color
180 RegisterCacheSystem(engine); // LoaderType, CacheEntryClass, CacheSystemClass
181 RegisterLocalStorage(engine); // LocalStorage
182 RegisterInputEngine(engine); // InputEngineClass, inputEvents
183 RegisterImGuiBindings(engine); // ImGui::
184 RegisterVehicleAi(engine); // VehicleAIClass, aiEvents, AiValues
185 RegisterConsole(engine); // ConsoleClass, CVarClass, CVarFlags
186 RegisterEngine(engine); // EngineClass, enum autoswitch, enum
187 RegisterDashBoardManager(engine); // DashBoardManagerClass, DashboardDataTypes
188 RegisterTurbojet(engine); // TurbojetClass
189 RegisterTurboprop(engine); // TurbopropClass
190 RegisterAircraftEngine(engine); // AircraftEngineClass, AircraftEngineTypes
191 RegisterAutopilot(engine); // AutopilotClass, APHeadingMode, APAltitudeMode
192 RegisterScrewprop(engine); // ScrewpropClass
193 RegisterActor(engine); // BeamClass
194 RegisterProceduralRoad(engine); // procedural_point, ProceduralRoadClass, ProceduralObjectClass, ProceduralManagerClass
195 RegisterTerrain(engine); // TerrainClass
196 RegisterMessageQueue(engine); // enum MsgType
197 RegisterSoundScript(engine); // SoundTriggers, ModulationSource, SoundScriptTemplate...
198 RegisterGameScript(engine); // GameScriptClass
199 RegisterScriptEvents(engine); // scriptEvents
200 RegisterGenericFileFormat(engine); // TokenType, GenericDocumentClass, GenericDocReaderClass
201
202 // now the global instances
203 result = engine->RegisterGlobalProperty("GameScriptClass game", &m_game_script); ROR_ASSERT(result>=0);
204 result = engine->RegisterGlobalProperty("ConsoleClass console", App::GetConsole()); ROR_ASSERT(result>=0);
205 result = engine->RegisterGlobalProperty("InputEngineClass inputs", App::GetInputEngine()); ROR_ASSERT(result>=0);
206 result = engine->RegisterGlobalProperty("CacheSystemClass modcache", App::GetCacheSystem()); ROR_ASSERT(result>=0);
207
208 SLOG("Type registrations done. If you see no error above everything should be working");
209
210 context = engine->CreateContext();
211}
212
213void ScriptEngine::msgCallback(const AngelScript::asSMessageInfo *msg)
214{
215 const char *type = "Error";
216 if ( msg->type == AngelScript::asMSGTYPE_INFORMATION )
217 type = "Info";
218 else if ( msg->type == AngelScript::asMSGTYPE_WARNING )
219 type = "Warning";
220
221 char tmp[1024]="";
222 sprintf(tmp, "%s (%d, %d): %s = %s", msg->section, msg->row, msg->col, type, msg->message);
223 SLOG(tmp);
224
226 m_currently_executing_script_unit, msg->type, msg->row, msg->col, // ints
227 msg->section, msg->message); // strings
228}
229
230std::string ptr2str(const char* ptr) { if (ptr) return ptr; else return ""; }
231
232void ScriptEngine::lineCallback(AngelScript::asIScriptContext* ctx)
233{
234 std::string funcName, funcObjTypeName, objName;
235 if (ctx->GetFunction())
236 {
237 funcName = ptr2str(ctx->GetFunction()->GetName());
238 objName = ptr2str(ctx->GetFunction()->GetObjectName());
239 if (ctx->GetFunction()->GetObjectType())
240 funcObjTypeName = ptr2str(ctx->GetFunction()->GetObjectType()->GetName());
241 }
243 // ints
244 m_currently_executing_script_unit, ctx->GetLineNumber(), ctx->GetCallstackSize(), 0,
245 // strings
246 funcName, funcObjTypeName, objName
247 );
248}
249
250void ScriptEngine::exceptionCallback(AngelScript::asIScriptContext* ctx)
251{
252 //Disabled, too noisy//this->logExceptionDetails();
253 std::string funcName;
254 if (ctx->GetExceptionFunction())
255 {
256 funcName = ptr2str(ctx->GetExceptionFunction()->GetName());
257 }
259 m_currently_executing_script_unit, 0, ctx->GetExceptionLineNumber(), 0, // ints
260 funcName, ctx->GetExceptionString()); // strings
261}
262
264{
265 // Forwards useful info from C++ exceptions to script in the form of game event.
266 // AngelScript doesn't have exceptions in the `try{}catch{}` sense
267 // (in AS jargon, 'Exception' means basically 'panic' as in Lua/Rust...)
268 // and most exceptions this game encounters (`Ogre::Exception`) are trivially recoverable,
269 // so it doesn't make sense to panic AngelScript when they happen.
270 // =======================================================================================
271
272 try { throw; } // Rethrow
273
274 // OGRE
275 catch (Ogre::IOException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "IOException", e.getDescription()); }
276 catch (Ogre::InvalidStateException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "InvalidStateException", e.getDescription()); }
277 catch (Ogre::InvalidParametersException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "InvalidParametersException", e.getDescription()); }
278 catch (Ogre::RenderingAPIException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "RenderingAPIException", e.getDescription()); }
279 catch (Ogre::ItemIdentityException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "ItemIdentityException", e.getDescription()); }
280 catch (Ogre::FileNotFoundException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "FileNotFoundException", e.getDescription()); }
281 catch (Ogre::InternalErrorException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "InternalErrorException", e.getDescription()); }
282 catch (Ogre::RuntimeAssertionException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "RuntimeAssertionException", e.getDescription()); }
283 catch (Ogre::UnimplementedException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "UnimplementedException", e.getDescription()); }
284 catch (Ogre::InvalidCallException& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "InvalidCallException", e.getDescription()); }
285 catch (Ogre::Exception& e ) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "Exception", e.getDescription()); }
286
287 // STD
288 catch (std::exception& e) { TRIGGER_EVENT_ASYNC(SE_GENERIC_EXCEPTION_CAUGHT, m_currently_executing_script_unit,0,0,0, from, "std::exception", e.what()); }
289
290 // Unrecognized
292}
293
295{
296 // Helper for executing any script function/snippet;
297 // * sets LineCallback (on demand - when script registers for SE_ANGELSCRIPT_LINECALLBACK event)
298 // * sets ExceptionCallback (on demand - when script registers for SE_ANGELSCRIPT_EXCEPTIONCALLBACK event)
299 // * sets currently executed NID;
300 // IMPORTANT: The `asIScriptContext::Prepare()` must be already done (this enables programmer to set args).
301 // IMPORTANT: `m_currently_executing_event_trigger` must be set externally!
302 // =========================================================================================================
303
304 // Automatically attach the LineCallback if the script registered for the event.
305 // (except if we're about to run that callback - that would trap us in loop)
308 {
309 int result = context->SetLineCallback(asMETHOD(ScriptEngine, lineCallback), this, asCALL_THISCALL);
310 if (result < 0)
311 {
312 SLOG(fmt::format("Warning: Could not attach LineCallback to NID {}, error code {}; continuing without it...", result, nid));
313 }
314 }
315
316 // Attach the ExceptionCallback if the script registered for the event.
318 {
319 int result = context->SetExceptionCallback(asMETHOD(ScriptEngine, exceptionCallback), this, asCALL_THISCALL);
320 if (result < 0)
321 {
322 SLOG(fmt::format("Warning: Could not attach ExceptionCallback to NID {}, error code {}; continuing without it...", result, nid));
323 }
324 }
325
326 // Run the script
328 int result = context->Execute();
330
331 if ( result != AngelScript::asEXECUTION_FINISHED )
332 {
333 // The execution didn't complete as expected. Determine what happened.
334 if ( result == AngelScript::asEXECUTION_ABORTED )
335 {
336 SLOG("The script was aborted before it could finish. Probably it timed out.");
337 }
338 else if ( result == AngelScript::asEXECUTION_EXCEPTION )
339 {
340 // An exception occurred, let the script writer know what happened so it can be corrected.
341 // NOTE: this result is only reported if exception callback is not registered, see `SetExceptionCallback()`
342 SLOG("The script ended with exception; details below:");
343 // Write some information about the script exception
344 SLOG("\tcontext.ExceptionLineNumber: " + TOSTRING(context->GetExceptionLineNumber()));
345 SLOG("\tcontext.ExceptionString: " + ptr2str(context->GetExceptionString()));
346 AngelScript::asIScriptFunction* func = context->GetExceptionFunction();
347 if (func)
348 {
349 const char *sectionName;
350 int s_col, s_row;
351 func->GetDeclaredAt(&sectionName, &s_row, &s_col);
352 SLOG("\tcontext.ExceptionFunction.Declaration: " + ptr2str(func->GetDeclaration()));
353 SLOG("\tcontext.ExceptionFunction.ModuleName: " + ptr2str(func->GetModuleName()));
354 SLOG("\tcontext.ExceptionFunction.ScriptSectionName: " + ptr2str(sectionName));
355 SLOG("\tcontext.ExceptionFunction.ObjectName: " + ptr2str(func->GetObjectName()));
356 }
357 }
358 else if (result == AngelScript::asCONTEXT_NOT_PREPARED)
359 {
360 if (context->GetFunction())
361 {
362 SLOG(fmt::format("The script ended with error code asCONTEXT_NOT_PREPARED; Function to execute: {},currently triggered event: {}, NID: {}",
363 context->GetFunction()->GetName(), fmt::underlying(m_currently_executing_event_trigger), nid));
364 }
365 else
366 {
367 SLOG(fmt::format("The script ended with error code asCONTEXT_NOT_PREPARED; Function to execute NOT SET,currently triggered event: {}, NID: {}",
368 fmt::underlying(m_currently_executing_event_trigger), nid));
369 }
370 }
371 else
372 {
373 SLOG("The script ended for some unforeseen reason " + TOSTRING(result));
374 }
375 }
376
377 // Clear the callbacks so they don't intercept unrelated operations.
378 context->ClearLineCallback();
379 context->ClearExceptionCallback();
380
381 return result;
382}
383
385{
386 asIScriptFunction* scriptFunc = this->engine->GetFunctionById(asFunctionID);
387 if (!scriptFunc)
388 {
389 SLOG(fmt::format("Cannot execute script function with ID {} - not found", asFunctionID));
390 return false;
391 }
392
393 int result = this->context->Prepare(scriptFunc);
394 if (result < 0)
395 {
396 SLOG(fmt::format("Cannot execute script function '{}': `AngelScript::Context::Prepare()` reported error code {}",
397 scriptFunc->GetName(), result));
398 return false;
399 }
400
401 return true;
402}
403
405{
406 if (!engine)
408
409 if (!context)
411
412 if (!this->scriptUnitExists(nid))
414
415 out_mod = this->getScriptUnit(nid).scriptModule;
416 if (!out_mod)
418
420}
421
423{
424 // Check if we need to execute any strings
425 std::vector<String> tmpQueue;
426 stringExecutionQueue.pull(tmpQueue);
427 std::vector<String>::iterator it;
428 for (it=tmpQueue.begin(); it!=tmpQueue.end();it++)
429 {
430 executeString(*it);
431 }
432
433 // framestep stuff below
434 if (!engine || !context) return;
435
436 for (auto& pair: m_script_units)
437 {
438 ScriptUnitID_t nid = pair.first;
439 if (m_script_units[nid].frameStepFunctionPtr)
440 {
441 // Set the function pointer and arguments
442 context->Prepare(m_script_units[nid].frameStepFunctionPtr);
443 context->SetArgFloat(0, dt);
444
445 // Run the context via helper
447 }
448 }
449}
450
451int ScriptEngine::fireEvent(std::string instanceName, float intensity)
452{
453 if (!engine || !context)
454 return 0;
455
456 for (auto& pair: m_script_units)
457 {
458 ScriptUnitID_t id = pair.first;
459 AngelScript::asIScriptFunction* func = m_script_units[id].scriptModule->GetFunctionByDecl(
460 "void fireEvent(string, float)"); // TODO: this shouldn't be hard coded --neorej16
461
462 context->Prepare(func);
463
464 // Set the function arguments
465 context->SetArgObject(0, &instanceName);
466 context->SetArgFloat (1, intensity);
467
469 int r = context->Execute();
471 if ( r == AngelScript::asEXECUTION_FINISHED )
472 {
473 // The return value is only valid if the execution finished successfully
474 AngelScript::asDWORD ret = context->GetReturnDWord();
475 }
476 }
477
478 return 0;
479}
480
481void ScriptEngine::envokeCallback(int _functionId, eventsource_t *source, NodeNum_t nodenum, int type)
482{
483 // THIS IS OBSOLETE - Use `eventCallbackEx()` and `SE_EVENTBOX_ENTER` instead.
484 // ################
485 // Legacy eventbox handler, specified by name in .TOBJ file or in call to `game.spawnObject()`
486 // ===========================================================================================
487 if (!engine || !context)
488 return;
489
490 for (auto& pair: m_script_units)
491 {
492 ScriptUnitID_t id = pair.first;
493 int functionId = _functionId;
494 if (functionId <= 0 && (m_script_units[id].defaultEventCallbackFunctionPtr != nullptr))
495 {
496 // use the default event handler instead then
497 functionId = m_script_units[id].defaultEventCallbackFunctionPtr->GetId();
498 }
499
500 if (this->prepareContextAndHandleErrors(id, functionId))
501 {
502 // Set the function arguments
503 context->SetArgDWord (0, type);
504 context->SetArgObject(1, &source->es_instance_name);
505 context->SetArgObject(2, &source->es_box_name);
506 if (nodenum != NODENUM_INVALID)
507 context->SetArgDWord (3, static_cast<AngelScript::asDWORD>(nodenum));
508 else
509 context->SetArgDWord (3, static_cast<AngelScript::asDWORD>(-1));
510
512 }
513 }
514}
515
517{
518 stringExecutionQueue.push(command);
519}
520
522{
523 int result = 0;
524 AngelScript::asIScriptModule* mod = nullptr;
525 // Only works with terrain script module (classic behavior)
526 result = this->validateScriptModule(m_terrain_script_unit, /*[out]*/ mod);
527 if (result == 0)
528 {
529 result = ExecuteString(engine, command.c_str(), mod, context);
530 if (result < 0)
531 {
532 SLOG(fmt::format("Error {} while executing string `{}`", result, command));
533 }
534 }
535 return result;
536}
537
538ScriptRetCode_t ScriptEngine::addFunction(const String &arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
539{
540 int result = 0;
541 AngelScript::asIScriptModule* mod = nullptr;
542 result = this->validateScriptModule(nid, /*[out]*/ mod);
543 if (result == 0)
544 {
545 AngelScript::asIScriptFunction* func = nullptr;
546 result = mod->CompileFunction("addfunc", arg.c_str(), 0, AngelScript::asCOMP_ADD_TO_MODULE, &func);
547 if (result == 0)
548 {
549 // successfully added function; Check if we added a "special" function
550
551 if (func == mod->GetFunctionByDecl("void frameStep(float)"))
552 {
553 if (m_script_units[m_terrain_script_unit].frameStepFunctionPtr == nullptr)
554 m_script_units[m_terrain_script_unit].frameStepFunctionPtr = func;
555 }
556 else if (func == mod->GetFunctionByDecl("void eventCallback(int, int)"))
557 {
558 if (m_script_units[m_terrain_script_unit].eventCallbackFunctionPtr == nullptr)
559 m_script_units[m_terrain_script_unit].eventCallbackFunctionPtr = func;
560 }
561 else if (func == mod->GetFunctionByDecl("void eventCallbackEx(scriptEvents, int, int, int, int, string, string, string, string)"))
562 {
563 if (m_script_units[m_terrain_script_unit].eventCallbackExFunctionPtr == nullptr)
564 m_script_units[m_terrain_script_unit].eventCallbackExFunctionPtr = func;
565 }
566 // THIS IS OBSOLETE - Use `eventCallbackEx()` and `SE_EVENTBOX_ENTER` instead. See commentary in `envokeCallback()`
567 else if (func == this->getFunctionByDeclAndLogCandidates(
570 {
571 if (m_script_units[m_terrain_script_unit].defaultEventCallbackFunctionPtr == nullptr)
572 m_script_units[m_terrain_script_unit].defaultEventCallbackFunctionPtr = func;
573 }
574 }
575 else
576 {
577 SLOG(fmt::format("Error {} adding function `{}` to script module '{}'", result, arg, mod->GetName()));
578 }
579 // We must release the function object
580 if (func)
581 func->Release();
582 }
583
584 return result;
585}
586
587ScriptRetCode_t ScriptEngine::functionExists(const String& arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
588{
589 int result = 0;
590 AngelScript::asIScriptModule* mod = nullptr;
591 result = this->validateScriptModule(nid, /*[out]*/ mod);
592 if (result == 0)
593 {
594 if (mod->GetFunctionByDecl(arg.c_str()) != nullptr)
596 }
597 return result;
598}
599
600ScriptRetCode_t ScriptEngine::deleteFunction(const String &arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
601{
602 int result = 0;
603 AngelScript::asIScriptModule* mod = nullptr;
604 result = this->validateScriptModule(nid, /*[out]*/ mod);
605 if (result == 0)
606 {
607 AngelScript::asIScriptFunction* func = mod->GetFunctionByDecl(arg.c_str());
608 if (func != nullptr)
609 {
610 // Warning: The function is not destroyed immediately, only when no more references point to it.
611 result = mod->RemoveFunction(func);
612 if (result != 0)
613 {
614 SLOG(fmt::format("Error {} removing function `{}` from module '{}' - continuing anyway (compatibility).", result, arg, mod->GetName()));
615 }
616
617 // Since functions can be recursive, we'll call the garbage
618 // collector to make sure the object is really freed
619 engine->GarbageCollect();
620
621 // Check if we removed a "special" function
622
623 if (m_script_units[m_terrain_script_unit].frameStepFunctionPtr == func)
624 m_script_units[m_terrain_script_unit].frameStepFunctionPtr = nullptr;
625
626 if (m_script_units[m_terrain_script_unit].eventCallbackFunctionPtr == func)
627 m_script_units[m_terrain_script_unit].eventCallbackFunctionPtr = nullptr;
628
629 if (m_script_units[m_terrain_script_unit].eventCallbackExFunctionPtr == func)
630 m_script_units[m_terrain_script_unit].eventCallbackExFunctionPtr = nullptr;
631
632 if (m_script_units[m_terrain_script_unit].defaultEventCallbackFunctionPtr == func)
633 m_script_units[m_terrain_script_unit].defaultEventCallbackFunctionPtr = nullptr;
634 }
635 else
636 {
637 SLOG(fmt::format("Could not remove function `{}` from module '{}' - not found.", arg, mod->GetName()));
638 }
639 }
640 return result;
641}
642
643ScriptRetCode_t ScriptEngine::addVariable(const String &arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
644{
645 int result = 0;
646 AngelScript::asIScriptModule* mod = nullptr;
647 result = this->validateScriptModule(nid, /*[out]*/ mod);
648 if (result == 0)
649 {
650 result = mod->CompileGlobalVar("addvar", arg.c_str(), 0);
651 if (result < 0)
652 {
653 SLOG(fmt::format("Error {} while adding variable `{}` to module '{}'", result, arg, mod->GetName()));
654 }
655 }
656 return result;
657}
658
659ScriptRetCode_t ScriptEngine::variableExists(const String& arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
660{
661 int result = 0;
662 AngelScript::asIScriptModule* mod = nullptr;
663 result = this->validateScriptModule(nid, /*[out]*/ mod);
664 if (result == 0)
665 {
666 result = mod->GetGlobalVarIndexByName(arg.c_str());
667 if (result >= 0)
668 {
669 result = 0;
670 }
671 }
672 return result;
673}
674
675ScriptRetCode_t ScriptEngine::deleteVariable(const String &arg, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
676{
677 int result = 0;
678 AngelScript::asIScriptModule* mod = nullptr;
679 result = this->validateScriptModule(nid, /*[out]*/ mod);
680 if (result == 0)
681 {
682 result = mod->GetGlobalVarIndexByName(arg.c_str());
683 if (result >= 0)
684 {
685 result = mod->RemoveGlobalVar(result);
686 }
687 }
688 if (result < 0)
689 {
690 SLOG(fmt::format("Error {} while removing variable `{}` from module '{}'", result, arg, mod->GetName()));
691 }
692 return result;
693}
694
695ScriptRetCode_t ScriptEngine::getVariable(const Ogre::String& varName, void *ref, int refTypeId, const ScriptUnitID_t nid /*= SCRIPTUNITID_DEFAULT*/)
696{
697 AngelScript::asIScriptModule* mod = nullptr;
698 int modResult = this->validateScriptModule(nid, /*[out]*/ mod);
699 if (modResult < 0)
700 return modResult;
701
702 int index = mod->GetGlobalVarIndexByName(varName.c_str());
703 if (index < 0)
704 {
705 return index;
706 }
707
708 const char* asVarName = nullptr;
709 const char* asNamespace = nullptr;
710 int asTypeId = 0;
711 bool asConst = false;
712 int getResult = mod->GetGlobalVar(index, &asVarName, &asNamespace, &asTypeId, &asConst);
713 if (getResult < 0)
714 {
715 return getResult;
716 }
717
718 LOG(fmt::format("[RoR|Scripting] getScriptVariable() - '{}' global var info: name='{}', namespace='{}', typeid={}, const={}",
719 varName, asVarName, asNamespace, asTypeId, asConst));
720
721 // ~~ DEV NOTE: The following code is adopted from AngelScript's add-on 'scriptany.cpp', function `Retrieve()` ~~
722
723 if( refTypeId & asTYPEID_OBJHANDLE )
724 {
725 // Is the handle type compatible with the stored value?
726
727 // A handle can be retrieved if the stored type is a handle of same or compatible type
728 // or if the stored type is an object that implements the interface that the handle refer to.
729 if( (asTypeId & asTYPEID_MASK_OBJECT) )
730 {
731 // Don't allow the retrieval if the stored handle is to a const object but not the wanted handle
732 if( (asTypeId & asTYPEID_HANDLETOCONST) && !(refTypeId & asTYPEID_HANDLETOCONST) )
733 {
734 SLOG(fmt::format("Error in `getScriptVariable()` - '{}' is a handle to `const` object but the requested type is not.", varName));
736 }
737
738 // RefCastObject will increment the refCount of the returned pointer if successful
739 engine->RefCastObject(mod->GetAddressOfGlobalVar(index), engine->GetTypeInfoById(asTypeId), engine->GetTypeInfoById(refTypeId), reinterpret_cast<void**>(ref));
740 if( *(asPWORD*)ref == 0 )
741 {
742 SLOG(fmt::format("Error in `getScriptVariable()` - '{}': reference-cast from '{}' to '{}' yielded null",
743 varName, engine->GetTypeDeclaration(asTypeId), engine->GetTypeDeclaration(refTypeId)));
745 }
746 return 0;
747 }
748 }
749 else if( refTypeId & asTYPEID_MASK_OBJECT )
750 {
751 // Is the object type compatible with the stored value?
752
753 // Copy the object into the given reference
754 if( asTypeId == refTypeId )
755 {
756 engine->AssignScriptObject(ref, mod->GetAddressOfGlobalVar(index), engine->GetTypeInfoById(asTypeId));
757 return 0;
758 }
759 }
760 else
761 {
762 // Is the primitive type _IDENTICAL TO_ the stored value?
763 // NOTE: implicit conversions are not done automatically, we would have to write code for each case separately
764
765 if( asTypeId == refTypeId )
766 {
767 int size = engine->GetSizeOfPrimitiveType(refTypeId);
768 memcpy(ref, mod->GetAddressOfGlobalVar(index), size);
769 return 0;
770 }
771 }
772
773 SLOG(fmt::format("Error in `getScriptVariable()` - '{}' has incompatible type, expected '{}' (typeid {}), got '{}' (typeid {})",
774 varName, engine->GetTypeDeclaration(refTypeId), refTypeId, engine->GetTypeDeclaration(asTypeId), asTypeId));
776}
777
778asIScriptFunction* ScriptEngine::getFunctionByDeclAndLogCandidates(ScriptUnitID_t nid, GetFuncFlags_t flags, const std::string& funcName, const std::string& fmtFuncDecl)
779{
780 std::string decl = fmt::format(fmtFuncDecl, funcName);
781 asIScriptFunction* retval = m_script_units[nid].scriptModule->GetFunctionByDecl(decl.c_str());
782 if (!retval)
783 {
784 asIScriptFunction* candidate = m_script_units[nid].scriptModule->GetFunctionByName(funcName.c_str());
785 if (candidate && BITMASK_IS_0(flags, GETFUNCFLAG_SILENT))
786 {
787 SLOG(fmt::format("Warning: a callback function with signature '{}' was not found"
788 " but a function with given name exists: '{}' - did you make a typo in arguments?",
789 decl, candidate->GetDeclaration()));
790 }
791 else if (!candidate && BITMASK_IS_1(flags, GETFUNCFLAG_REQUIRED))
792 {
793 SLOG(fmt::format("Warning: a callback function with signature '{}' was not found",
794 decl));
795 }
796 }
797 return retval;
798}
799
800void ScriptEngine::triggerEvent(scriptEvents eventnum, int arg1, int arg2ex, int arg3ex, int arg4ex, std::string arg5ex, std::string arg6ex, std::string arg7ex, std::string arg8ex)
801{
802 if (!engine || !context || !m_events_enabled) return;
803
804 for (auto& pair: m_script_units)
805 {
806 ScriptUnitID_t id = pair.first;
807 asIScriptFunction* callback = m_script_units[id].eventCallbackExFunctionPtr;
808 if (!callback)
809 callback = m_script_units[id].eventCallbackFunctionPtr;
810 if (!callback)
811 continue;
812
813 if (m_script_units[id].eventMask & eventnum)
814 {
815 // script registered for that event, so sent it
816 if (this->prepareContextAndHandleErrors(id, callback->GetId()))
817 {
818
819 // Set the function arguments
820 context->SetArgDWord(0, eventnum);
821 context->SetArgDWord(1, arg1);
822 if (callback == m_script_units[id].eventCallbackExFunctionPtr)
823 {
824 // Extended arguments
825 context->SetArgDWord(2, arg2ex);
826 context->SetArgDWord(3, arg3ex);
827 context->SetArgDWord(4, arg4ex);
828 context->SetArgObject(5, &arg5ex);
829 context->SetArgObject(6, &arg6ex);
830 context->SetArgObject(7, &arg7ex);
831 context->SetArgObject(8, &arg8ex);
832 }
833 }
834
838 }
839 }
840}
841
842String ScriptEngine::composeModuleName(String const& scriptName, ScriptCategory origin, ScriptUnitID_t id)
843{
844 return fmt::format("{}(category:{},unique ID:{})", scriptName, ScriptCategoryToString(origin), id);
845}
846
848 String scriptOrGadgetFileName, ScriptCategory category/* = ScriptCategory::TERRAIN*/,
849 ActorPtr associatedActor /*= nullptr*/, int32_t associatedNetUid /* = -1*/, std::string buffer /* =""*/)
850{
851 // This function creates a new script unit, tries to set it up and removes it if setup fails.
852 // -----------------------------------------------------------------------------------------
853 // A script unit is how Rigs of Rods organizes scripts from various sources.
854 // Because the script is executed during loading, it's wrapping unit must
855 // be created early, and removed if setup fails.
856 static ScriptUnitID_t id_counter = 0;
857
858 std::string basename, ext, scriptName;
859 Ogre::StringUtil::splitBaseFilename(scriptOrGadgetFileName, basename, ext);
860 CacheEntryPtr originatingGadget;
861 if (ext == "gadget")
862 {
863 originatingGadget = App::GetCacheSystem()->FindEntryByFilename(LT_Gadget, /* partial: */false, scriptOrGadgetFileName);
864 if (!originatingGadget)
865 {
867 fmt::format("Could not load script '{}' - gadget not found.", scriptOrGadgetFileName));
869 }
870 App::GetCacheSystem()->LoadResource(originatingGadget);
871 scriptName = fmt::format("{}.as", basename);
872 // Ensure a .gadget file is always loaded as `GADGET`, even if requested as `CUSTOM`
873 category = ScriptCategory::GADGET;
874 }
875 else if (ext == "as")
876 {
877 scriptName = scriptOrGadgetFileName;
878 }
879 else
880 {
882 fmt::format("Could not load script '{}' - unknown file extension.", scriptOrGadgetFileName));
884 }
885
886 ScriptUnitID_t unit_id = id_counter++;
887 auto itor_pair = m_script_units.insert(std::make_pair(unit_id, ScriptUnit()));
888 m_script_units[unit_id].uniqueId = unit_id;
889 m_script_units[unit_id].scriptName = scriptName;
890 m_script_units[unit_id].scriptCategory = category;
891 m_script_units[unit_id].scriptBuffer = buffer;
892 m_script_units[unit_id].originatingGadget = originatingGadget;
893 if (category == ScriptCategory::TERRAIN)
894 {
895 m_terrain_script_unit = unit_id;
896 }
897 else if (category == ScriptCategory::ACTOR)
898 {
899 m_script_units[unit_id].associatedActor = associatedActor;
900 }
901 else if (category == ScriptCategory::AI_BOT)
902 {
903 m_script_units[unit_id].associatedNetUid = associatedNetUid;
904 }
905
906 // Perform the actual script loading, building and running main().
907 int result = this->setupScriptUnit(unit_id);
908
909 // Regardless of result, add to recent script list. Running scripts are filtered out when displaying.
910 if (category == ScriptCategory::CUSTOM && buffer == "")
911 {
913 }
914 else if (category == ScriptCategory::GADGET && originatingGadget)
915 {
917 }
918
919 // If setup failed, remove the unit.
920 if (result != 0)
921 {
922 m_script_units.erase(itor_pair.first);
923 if (category == ScriptCategory::TERRAIN)
924 {
926 }
927 // Notify running scripts that loading failed (the request might have come from one of them).
929 ASMANIP_SCRIPT_LOAD_FAILED, 0, (int)category, 0, scriptName);
931 }
932
933 // The script is running at this point.
934 // Notify any running scripts that we might change something (prevent cheating).
936 ASMANIP_SCRIPT_LOADED, unit_id, (int)category, 0, scriptName);
937
938 return unit_id;
939}
940
942{
943 int result=0;
944
945 String moduleName = this->composeModuleName(
946 m_script_units[unit_id].scriptName, m_script_units[unit_id].scriptCategory, m_script_units[unit_id].uniqueId);
947
948 // The builder is a helper class that will load the script file,
949 // search for #include directives, and load any included files as
950 // well.
951 OgreScriptBuilder builder;
952
953 // A script module is how AngelScript organizes scripts.
954 // It contains the script loaded by user plus all `#include`-d scripts.
955 result = builder.StartNewModule(engine, moduleName.c_str());
956 if ( result < 0 )
957 {
959 fmt::format("Could not load script '{}' - failed to create module.", moduleName));
960 return result;
961 }
962 m_script_units[unit_id].scriptModule = engine->GetModule(moduleName.c_str(), AngelScript::asGM_ONLY_IF_EXISTS);
963
964 // For actor scripts, add global var `thisActor` to the module
965 if (m_script_units[unit_id].scriptCategory == ScriptCategory::ACTOR)
966 {
967 result = m_script_units[unit_id].scriptModule->AddScriptSection(m_script_units[unit_id].scriptName.c_str(), "BeamClass@ thisActor;");
968 if (result < 0)
969 {
971 fmt::format("Could not load script '{}' - failed to create global variable `thisActor`.", moduleName));
972 return result;
973 }
974 }
975
976 // add global var `thisScript` to the module (initialized in place).
977 result = m_script_units[unit_id].scriptModule->AddScriptSection(m_script_units[unit_id].scriptName.c_str(),
978 fmt::format("const int thisScript = {};", unit_id).c_str());
979 if (result < 0)
980 {
982 fmt::format("Could not load script '{}' - failed to create global variable `thisScript`.", moduleName));
983 return result;
984 }
985
986 // If buffer is non-empty, load from memory; otherwise from filesystem as usual.
987 if (m_script_units[unit_id].scriptBuffer != "")
988 {
989 result = builder.AddSectionFromMemory(m_script_units[unit_id].scriptName.c_str(), m_script_units[unit_id].scriptBuffer.c_str());
990 if (result < 0)
991 {
993 fmt::format("Could not load script '{}' from buffer", moduleName));
994 return result;
995 }
996 }
997 else
998 {
999 // Load the script from the file system.
1000 result = builder.AddSectionFromFile(m_script_units[unit_id].scriptName.c_str());
1001 if ( result < 0 )
1002 {
1004 fmt::format("Could not load script '{}' - failed to process file.", moduleName));
1005 return result;
1006 }
1007 }
1008
1009 // Build the AngelScript module - this loads `#include`-d scripts
1010 // and runs any global statements, for example constructors of
1011 // global objects like raceManager in 'races.as'. For this reason,
1012 // the game must already be aware of the script, but only temporarily.
1013 m_currently_executing_script_unit = unit_id; // for `BuildModule()` below.
1014 result = builder.BuildModule();
1016 if ( result < 0 )
1017 {
1019 fmt::format("Could not load script '{}' - failed to build module. See 'Angelscript.log' for more info.", moduleName));
1020 return result;
1021 }
1022
1023 String scriptHash;
1024 if (m_script_units[unit_id].scriptCategory == ScriptCategory::TERRAIN) // Classic behavior
1025 scriptHash = builder.GetHash();
1026
1027 // get some other optional functions
1028 m_script_units[unit_id].frameStepFunctionPtr = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void frameStep(float)");
1029
1030 m_script_units[unit_id].eventCallbackFunctionPtr = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void eventCallback(int, int)");
1031 m_script_units[unit_id].eventCallbackExFunctionPtr = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void eventCallbackEx(scriptEvents, int, int, int, int, string, string, string, string)");
1032
1033 // THIS IS OBSOLETE - Use `eventCallbackEx()` and `SE_EVENTBOX_ENTER` instead. See commentary in `envokeCallback()`
1034 m_script_units[unit_id].defaultEventCallbackFunctionPtr = this->getFunctionByDeclAndLogCandidates(
1036
1037 // Find the function that is to be called.
1038 auto main_func = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void main()");
1039 if ( main_func == nullptr )
1040 {
1041 // The function couldn't be found. Continue without invoking it - other callbacks like `frameStep()` can still run.
1042 return 0;
1043 }
1044
1045 // Prepare the script context with the function we wish to execute. Prepare()
1046 // must be called on the context before each new script function that will be
1047 // executed. Note, that if you intend to execute the same function several
1048 // times, it might be a good idea to store the function id returned by
1049 // GetFunctionIDByDecl(), so that this relatively slow call can be skipped.
1050 result = context->Prepare(main_func);
1051 if (result < 0)
1052 {
1054 fmt::format("Could not load script '{}' - failed to build module.", moduleName));
1055 context->Release();
1056 return -1;
1057 }
1058
1059 // For actor scripts, initialize the global var `thisActor`
1060 if (m_script_units[unit_id].scriptCategory == ScriptCategory::ACTOR)
1061 {
1062 int var_index = m_script_units[unit_id].scriptModule->GetGlobalVarIndexByName("thisActor");
1063 if (var_index < 0)
1064 {
1065 SLOG("Could not find global var `thisActor`");
1066 return -1;
1067 }
1068
1069 // Example: https://www.gamedev.net/forums/topic/644188-angelscript-2263-global-property-issues-solved/5069638/
1070 Actor** thisActorAddr = (Actor**)m_script_units[unit_id].scriptModule->GetAddressOfGlobalVar(var_index);
1071 if (thisActorAddr == nullptr)
1072 {
1073 SLOG("Could not retrieve address of global var `thisActor`");
1074 return -1;
1075 }
1076 *thisActorAddr = m_script_units[unit_id].associatedActor.GetRef();
1077 (*thisActorAddr)->AddRef();
1078 }
1079
1080 // Execute the `main()` function in the script.
1081 // The function must have full access to the game API.
1082 SLOG(fmt::format("Executing main() in {}", moduleName));
1083 int mainfunc_result = this->executeContextAndHandleErrors(unit_id);
1084 if ( mainfunc_result != AngelScript::asEXECUTION_FINISHED )
1085 {
1087 fmt::format("Could not load script '{}' - error running function `main()`, check AngelScript.log", moduleName));
1088 }
1089 else
1090 {
1091 SLOG("The script finished successfully.");
1092 }
1093
1094 return mainfunc_result;
1095}
1096
1098{
1099 if (this->scriptUnitExists(nid))
1100 {
1101 // Notify any running scripts that we might change something (prevent cheating).
1103 ASMANIP_SCRIPT_UNLOADING, nid, (int)m_script_units[nid].scriptCategory, 0, m_script_units[nid].scriptName);
1104
1105 if (m_script_units[nid].scriptModule != nullptr)
1106 {
1107 engine->DiscardModule(m_script_units[nid].scriptModule->GetName());
1108 m_script_units[nid].scriptModule = nullptr;
1109 }
1110 m_script_units.erase(nid);
1111 }
1112
1113 if (m_terrain_script_unit == nid)
1114 {
1116 }
1117}
1118
1119void ScriptEngine::unloadBotByUid(int32_t bot_net_uid)
1120{
1121 for (const auto& pair: m_script_units)
1122 {
1123 const ScriptUnit& unit = pair.second;
1124 if (unit.scriptCategory == ScriptCategory::AI_BOT && unit.associatedNetUid == bot_net_uid)
1125 {
1128 fmt::format("Unloading AI bot script with net UID {} (script unit ID {})", bot_net_uid, pair.first));
1129 return;
1130 }
1131 }
1133 fmt::format("No AI bot script found with net UID {}", bot_net_uid));
1134}
1135
1137{
1138 // Always remove right away, to avoid attaching twice
1139 scriptLog->removeListener(this);
1140
1141 // Re-attach if requested
1142 if (doForward)
1143 {
1144 scriptLog->addListener(this);
1145 }
1146}
1147
1149{
1150 return nid != SCRIPTUNITID_INVALID
1151 && m_script_units.find(nid) != m_script_units.end();
1152}
1153
Central state/object manager and communications hub.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
#define TOSTRING(x)
Definition Application.h:58
void LOG(const char *msg)
Legacy alias - formerly a macro.
#define BITMASK_IS_1(VAR, FLAGS)
Definition BitFlags.h:14
#define BITMASK_IS_0(VAR, FLAGS)
Definition BitFlags.h:13
Game state manager and message-queue provider.
Handles controller inputs from player.
Platform-specific utilities. We use narrow UTF-8 encoded strings as paths. Inspired by http://utf8eve...
static int id_counter
std::string ptr2str(const char *ptr)
void logString(const std::string &str)
Simple waypoint AI.
#define WRAP_FN(name)
void pull(std::vector< T > &res)
Ogre::String GetHash()
Softbody object; can be anything from soda can to a space shuttle Constructed from a truck definition...
Definition Actor.h:55
Ogre::String fname
filename
Definition CacheSystem.h:67
CacheEntryPtr FindEntryByFilename(RoR::LoaderType type, bool partial, const std::string &_filename_maybe_bundlequalified)
Returns NULL if none found; "Bundle-qualified" format also specifies the ZIP/directory in modcache,...
void LoadResource(CacheEntryPtr &t)
Loads the associated resource bundle if not already done.
@ CONSOLE_MSGTYPE_SCRIPT
Messages sent from scripts.
Definition Console.h:62
@ CONSOLE_MSGTYPE_INFO
Generic message.
Definition Console.h:60
void putMessage(MessageArea area, MessageType type, std::string const &msg, std::string icon="")
Definition Console.cpp:103
@ CONSOLE_SYSTEM_ERROR
Definition Console.h:52
@ CONSOLE_SYSTEM_NOTICE
Definition Console.h:51
void forwardLogMessage(MessageArea area, std::string const &msg, Ogre::LogMessageLevel lml)
Definition Console.cpp:40
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
This class represents the angelscript scripting interface.
ScriptUnit & getScriptUnit(ScriptUnitID_t unique_id)
ScriptRetCode_t variableExists(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Adds a global variable to the script.
ScriptUnitID_t m_terrain_script_unit
void exceptionCallback(AngelScript::asIScriptContext *ctx)
Optional callback invoked when the script critically fails, allowing debugging.
bool m_events_enabled
Hack to enable fast shutdown without cleanup.
ScriptRetCode_t deleteVariable(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Deletes a global variable from the script.
void forwardExceptionAsScriptEvent(const std::string &from)
Forwards useful info from C++ try{}catch{} exceptions to script in the form of game event.
ScriptRetCode_t addVariable(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Adds a global variable to the script.
scriptEvents m_currently_executing_event_trigger
GameScript m_game_script
ScriptUnitMap m_script_units
int setupScriptUnit(int unit_id)
Helper for loadScript(), does the actual building without worry about unit management.
AngelScript::asIScriptEngine * engine
instance of the scripting engine
bool scriptUnitExists(ScriptUnitID_t unique_id)
Ogre::Log * scriptLog
int fireEvent(std::string instanceName, float intensity)
ScriptRetCode_t deleteFunction(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Deletes a global function from the script.
int executeContextAndHandleErrors(ScriptUnitID_t nid)
Helper for executing any script function/snippet; registers Line/Exception callbacks (on demand) and ...
InterThreadStoreVector< Ogre::String > stringExecutionQueue
The string execution queue.
void unloadBotByUid(int32_t bot_net_uid)
Helper for ServerScriptEngine - requests unloading the AI_BOT script.
void msgCallback(const AngelScript::asSMessageInfo *msg)
Optional (but very recommended!) callback providing diagnostic info when things fail to start (most n...
void SLOG(const char *msg)
Replacement of macro.
ScriptRetCode_t getVariable(const Ogre::String &varName, void *ref, int typeID, ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Retrieves a global variable from any running script.
ScriptUnitID_t loadScript(Ogre::String filename, ScriptCategory category=ScriptCategory::TERRAIN, ActorPtr associatedActor=nullptr, int32_t associatedNetUid=-1, std::string buffer="")
Loads a script.
ScriptRetCode_t addFunction(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Adds a global function to the script.
ScriptRetCode_t executeString(Ogre::String command)
executes a string (useful for the console)
ScriptRetCode_t validateScriptModule(const ScriptUnitID_t nid, AngelScript::asIScriptModule *&out_mod)
Helper for all manipulations with functions/variables; ensures the script unit exists and is fully se...
AngelScript::asIScriptContext * context
context in which all scripting happens
void queueStringForExecution(const Ogre::String command)
Queues a string for execution.
void envokeCallback(int functionId, eventsource_t *source, NodeNum_t nodenum=NODENUM_INVALID, int type=0)
void messageLogged(const Ogre::String &message, Ogre::LogMessageLevel lml, bool maskDebug, const Ogre::String &logName, bool &skipThisMessage)
ScriptUnitID_t m_currently_executing_script_unit
AngelScript::asIScriptFunction * getFunctionByDeclAndLogCandidates(ScriptUnitID_t nid, GetFuncFlags_t flags, const std::string &funcName, const std::string &fmtFuncDecl)
Finds a function by full declaration, and if not found, finds candidates by name and logs them to Ang...
void lineCallback(AngelScript::asIScriptContext *ctx)
Optional callback which receives diagnostic info for every executed statement.
Ogre::String composeModuleName(Ogre::String const &scriptName, ScriptCategory origin, ScriptUnitID_t id)
Packs name + important info to one string, for logging and reporting purposes.
ScriptRetCode_t functionExists(const Ogre::String &arg, const ScriptUnitID_t nid=SCRIPTUNITID_DEFAULT)
Checks if a global function exists.
bool prepareContextAndHandleErrors(ScriptUnitID_t nid, int asFunctionID)
Helper for executing any script function/snippet; does asIScriptContext::Prepare() and reports any er...
void setForwardScriptLogToConsole(bool doForward)
void init()
This function initialzies the engine and registeres all types.
void framestep(Ogre::Real dt)
Calls the script's framestep function to be able to use timed things inside the script.
void triggerEvent(scriptEvents eventnum, int arg1=0, int arg2ex=0, int arg3ex=0, int arg4ex=0, std::string arg5ex="", std::string arg6ex="", std::string arg7ex="", std::string arg8ex="")
triggers an event; Not to be used by the end-user.
void unloadScript(ScriptUnitID_t unique_id)
Unloads a script.
std::string PathCombine(std::string a, std::string b)
@ MSG_APP_UNLOAD_SCRIPT_REQUESTED
Payload = RoR::ScriptUnitId_t* (owner)
Definition Application.h:95
void RegisterGameScript(AngelScript::asIScriptEngine *engine)
ScriptCategory
Note: Either of these can be loaded from script using game.pushMessage(MSG_APP_LOAD_SCRIPT_REQUESTED....
void RegisterProceduralRoad(AngelScript::asIScriptEngine *engine)
void RegisterLocalStorage(AngelScript::asIScriptEngine *engine)
void RegisterAutopilot(AngelScript::asIScriptEngine *engine)
void RegisterGenericFileFormat(AngelScript::asIScriptEngine *engine)
void RegisterCacheSystem(AngelScript::asIScriptEngine *engine)
const std::string GETFUNC_DEFAULTEVENTCALLBACK_NAME
void RegisterOgreObjects(AngelScript::asIScriptEngine *engine)
BitMask_t GetFuncFlags_t
Flags for RoR::ScriptEngine::getFunctionByDeclAndLogCandidates()
void RegisterConsole(AngelScript::asIScriptEngine *engine)
void TRIGGER_EVENT_ASYNC(scriptEvents type, int arg1, int arg2ex=0, int arg3ex=0, int arg4ex=0, std::string arg5ex="", std::string arg6ex="", std::string arg7ex="", std::string arg8ex="")
Asynchronously (via MSG_SIM_SCRIPT_EVENT_TRIGGERED) invoke script function eventCallbackEx(),...
void RegisterSoundScript(AngelScript::asIScriptEngine *engine)
void RegisterDashBoardManager(AngelScript::asIScriptEngine *engine)
const GetFuncFlags_t GETFUNCFLAG_OPTIONAL
Only logs warning if candidate is found, to help modder find a typo.
void RegisterScriptEvents(AngelScript::asIScriptEngine *engine)
const std::string GETFUNC_DEFAULTEVENTCALLBACK_SIGFMT
void RegisterTurbojet(AngelScript::asIScriptEngine *engine)
void RegisterActor(AngelScript::asIScriptEngine *engine)
const GetFuncFlags_t GETFUNCFLAG_REQUIRED
Always logs warning that function was not found.
const GetFuncFlags_t GETFUNCFLAG_SILENT
Never logs.
void RegisterInputEngine(AngelScript::asIScriptEngine *engine)
void RegisterScrewprop(AngelScript::asIScriptEngine *engine)
void RegisterAircraftEngine(AngelScript::asIScriptEngine *engine)
const char * ScriptCategoryToString(ScriptCategory c)
void RegisterTurboprop(AngelScript::asIScriptEngine *engine)
void RegisterEngine(AngelScript::asIScriptEngine *engine)
void RegisterImGuiBindings(AngelScript::asIScriptEngine *engine)
void RegisterVehicleAi(AngelScript::asIScriptEngine *engine)
void RegisterTerrain(AngelScript::asIScriptEngine *engine)
void RegisterMessageQueue(AngelScript::asIScriptEngine *engine)
@ CUSTOM
Loaded by user via either: A) ingame console 'loadscript'; B) RoR.cfg 'app_custom_scripts'; C) comman...
@ AI_BOT
Only valid under MpState::LOCAL_SCRIPT; registers as RoRnet user in the server script (i....
@ GADGET
Associated with a .gadget mod file, launched via UI or any method given below for CUSTOM scripts (use...
@ TERRAIN
Defined in terrn2 file under '[Scripts]', receives terrain eventbox notifications.
@ ACTOR
Defined in truck file under 'scripts', contains global variable BeamClass@ thisActor.
@ SCRIPTRETCODE_SUCCESS
@ SCRIPTRETCODE_ENGINE_NOT_CREATED
@ SCRIPTRETCODE_SCRIPTUNIT_NOT_EXISTS
@ SCRIPTRETCODE_UNSPECIFIED_ERROR
@ SCRIPTRETCODE_CONTEXT_NOT_CREATED
@ SCRIPTRETCODE_SCRIPTUNIT_NO_MODULE
@ SCRIPTRETCODE_FUNCTION_NOT_EXISTS
InputEngine * GetInputEngine()
CVar * sys_logs_dir
CVar * app_recent_scripts
GameContext * GetGameContext()
Console * GetConsole()
ScriptEngine * GetScriptEngine()
CVar * diag_angelscript_generic_bind_test
CacheSystem * GetCacheSystem()
static const NodeNum_t NODENUM_INVALID
@ ASMANIP_SCRIPT_UNLOADING
Triggered before unloading the script to let it clean up. Args: #2 ScriptUnitId_t,...
@ ASMANIP_SCRIPT_LOADED
Triggered after the script's main() completed; Args: #2 ScriptUnitId_t, #3 RoR::ScriptCategory,...
@ ASMANIP_SCRIPT_LOAD_FAILED
Triggered if the script fails to start at all. Args: #2 unused, #3 RoR::ScriptCategory,...
@ LT_Gadget
void CvarAddFileToList(CVar *cvar, const std::string &filename)
Definition Utils.cpp:217
int ScriptRetCode_t
see enum RoR::ScriptRetCode - combines AngelScript codes and RoR internal codes.
int ScriptUnitID_t
Unique sequentially generated ID of a loaded and running scriptin session. Use ScriptEngine::getScrip...
static const ScriptUnitID_t SCRIPTUNITID_INVALID
scriptEvents
This enum describes what events are existing. The script can register to receive events.
@ SE_ANGELSCRIPT_MANIPULATIONS
triggered when the user tries to dynamically use the scripting capabilities (prevent cheating) args: ...
@ SE_ANGELSCRIPT_MSGCALLBACK
The diagnostic info directly from AngelScript engine (see asSMessageInfo), args: #1 ScriptUnitID,...
@ SE_ANGELSCRIPT_LINECALLBACK
The diagnostic info directly from AngelScript engine (see SetLineCallback()), args: #1 ScriptUnitID,...
@ SE_ANGELSCRIPT_EXCEPTIONCALLBACK
The diagnostic info directly from AngelScript engine (see SetExceptionCallback()),...
@ SE_NO_EVENTS
@ SE_GENERIC_EXCEPTION_CAUGHT
Triggered when C++ exception (usually Ogre::Exception) is thrown; #1 ScriptUnitID,...
uint16_t NodeNum_t
Node position within Actor::ar_nodes; use RoR::NODENUM_INVALID as empty value.
Unified game event system - all requests and state changes are reported using a message.
Definition GameContext.h:52
Represents a loaded script and all associated resources/handles.
int32_t associatedNetUid
For ScriptCategory::AI_BOT ~ used to identify the bot in the server script.
ScriptCategory scriptCategory
AngelScript::asIScriptModule * scriptModule
< Scripting
Definition Collisions.h:41
std::string es_box_name
Specified in ODEF file as "event".
Definition Collisions.h:43
std::string es_instance_name
Specified by user when calling "GameScript::spawnObject()".
Definition Collisions.h:42