/* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** @file script_controller.cpp Implementation of ScriptControler. */ #include "../../stdafx.h" #include "../../string_func.h" #include "../../script/squirrel.hpp" #include "../../rev.h" #include "script_controller.hpp" #include "script_error.hpp" #include "../script_fatalerror.hpp" #include "../script_info.hpp" #include "../script_instance.hpp" #include "script_log.hpp" #include "../script_gui.h" #include "../../settings_type.h" #include "../../network/network.h" #include "../../misc_cmd.h" #include "../../safeguards.h" /* static */ void ScriptController::SetCommandDelay(int ticks) { if (ticks <= 0) return; ScriptObject::SetDoCommandDelay(ticks); } /* static */ void ScriptController::Sleep(int ticks) { if (!ScriptObject::CanSuspend()) { throw Script_FatalError("You are not allowed to call Sleep in your constructor, Save(), Load(), and any valuator."); } if (ticks <= 0) { ScriptLog::Warning("Sleep() value should be > 0. Assuming value 1."); ticks = 1; } throw Script_Suspend(ticks, nullptr); } /* static */ void ScriptController::Break(const std::string &message) { if (_network_dedicated || !_settings_client.gui.ai_developer_tools) return; ScriptObject::GetActiveInstance()->Pause(); ScriptLog::Log(ScriptLogTypes::LOG_SQ_ERROR, fmt::format("Break: {}", message)); /* Inform script developer that their script has been paused and * needs manual action to continue. */ ShowScriptDebugWindow(ScriptObject::GetRootCompany()); if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) { ScriptObject::Command::Do(PM_PAUSED_NORMAL, true); } } /* static */ void ScriptController::Print(bool error_msg, const std::string &message) { ScriptLog::Log(error_msg ? ScriptLogTypes::LOG_SQ_ERROR : ScriptLogTypes::LOG_SQ_INFO, message); } ScriptController::ScriptController(CompanyID company) : ticks(0), loaded_library_count(0) { ScriptObject::SetCompany(company); } /* static */ uint ScriptController::GetTick() { return ScriptObject::GetActiveInstance()->GetController()->ticks; } /* static */ int ScriptController::GetOpsTillSuspend() { return ScriptObject::GetActiveInstance()->GetOpsTillSuspend(); } /* static */ int ScriptController::GetSetting(const std::string &name) { return ScriptObject::GetActiveInstance()->GetSetting(name); } /* static */ uint ScriptController::GetVersion() { return _openttd_newgrf_version; } /* static */ HSQOBJECT ScriptController::Import(const std::string &library, const std::string &class_name, int version) { ScriptController *controller = ScriptObject::GetActiveInstance()->GetController(); Squirrel *engine = ScriptObject::GetActiveInstance()->engine; HSQUIRRELVM vm = engine->GetVM(); ScriptInfo *lib = ScriptObject::GetActiveInstance()->FindLibrary(library, version); if (lib == nullptr) { throw sq_throwerror(vm, fmt::format("couldn't find library '{}' with version {}", library, version)); } /* Internally we store libraries as 'library.version' */ std::string library_name = fmt::format("{}.{}", library, version); /* Get the current table/class we belong to */ HSQOBJECT parent; sq_getstackobj(vm, 1, &parent); std::string fake_class; LoadedLibraryList::iterator it = controller->loaded_library.find(library_name); if (it != controller->loaded_library.end()) { fake_class = (*it).second; } else { int next_number = ++controller->loaded_library_count; /* Create a new fake internal name */ fake_class = fmt::format("_internalNA{}", next_number); /* Load the library in a 'fake' namespace, so we can link it to the name the user requested */ sq_pushroottable(vm); sq_pushstring(vm, fake_class, -1); sq_newclass(vm, SQFalse); /* Load the library */ if (!engine->LoadScript(vm, lib->GetMainScript(), false)) { throw sq_throwerror(vm, fmt::format("there was a compile error when importing '{}' version {}", library, version)); } /* Create the fake class */ sq_newslot(vm, -3, SQFalse); sq_pop(vm, 1); controller->loaded_library[library_name] = fake_class; } /* Find the real class inside the fake class (like 'sets.Vector') */ sq_pushroottable(vm); sq_pushstring(vm, fake_class, -1); if (SQ_FAILED(sq_get(vm, -2))) { throw sq_throwerror(vm, "internal error assigning library class"); } sq_pushstring(vm, lib->GetInstanceName(), -1); if (SQ_FAILED(sq_get(vm, -2))) { throw sq_throwerror(vm, fmt::format("unable to find class '{}' in the library '{}' version {}", lib->GetInstanceName(), library, version)); } HSQOBJECT obj; sq_getstackobj(vm, -1, &obj); sq_pop(vm, 3); if (class_name.empty()) return obj; /* Now link the name the user wanted to our 'fake' class */ sq_pushobject(vm, parent); sq_pushstring(vm, class_name, -1); sq_pushobject(vm, obj); sq_newclass(vm, SQTrue); sq_newslot(vm, -3, SQFalse); sq_pop(vm, 1); return obj; }