diff --git a/Makefile b/Makefile index 37322adfc..8f3504204 100644 --- a/Makefile +++ b/Makefile @@ -232,6 +232,7 @@ SOURCES += bauer/fabrikbauer.cc SOURCES += bauer/goods_manager.cc SOURCES += bauer/hausbauer.cc SOURCES += bauer/tunnelbauer.cc +SOURCES += bauer/script_tool_manager.cc SOURCES += bauer/vehikelbauer.cc SOURCES += bauer/wegbauer.cc SOURCES += boden/boden.cc @@ -405,6 +406,7 @@ SOURCES += gui/scenario_frame.cc SOURCES += gui/scenario_info.cc SOURCES += gui/schedule_gui.cc SOURCES += gui/schedule_list.cc +SOURCES += gui/script_tool_frame.cc SOURCES += gui/server_frame.cc SOURCES += gui/settings_frame.cc SOURCES += gui/settings_stats.cc diff --git a/bauer/script_tool_manager.cc b/bauer/script_tool_manager.cc new file mode 100644 index 000000000..dd4250432 --- /dev/null +++ b/bauer/script_tool_manager.cc @@ -0,0 +1,66 @@ +#include "script_tool_manager.h" +#include "../simtool.h" +#include "../sys/simsys.h" +#include "../simdebug.h" +#include "../gui/tool_selector.h" +#include "../utils/searchfolder.h" +#include "../dataobj/tabfile.h" +#include "../simskin.h" +#include "../descriptor/reader/obj_reader.h" + +vector_tpl script_tool_manager_t::script_tools; + +bool script_tool_manager_t::check_file( const char *filename ) +{ + char buf[PATH_MAX]; + sprintf( buf, "%s/tool.nut", filename ); + if (FILE* const f = dr_fopen(buf, "r")) { + fclose(f); + return true; + } + return false; +} + +void script_tool_manager_t::load_scripts(char const* path) { + dbg->message("script_tool_manager_t::load_scripts", "Loading scripts from %s", path); + searchfolder_t find; + find.search(path, "", true, false); + FOR(searchfolder_t, const &name, find) { + char* fullname = new char [strlen(path)+strlen(name)+1]; + sprintf(fullname,"%s%s",path,name); + if( !check_file(fullname) ) { + continue; + } + // register script + tool_exec_script_t* tool = new tool_exec_script_t(); + tool->set_default_param(fullname); + tool->set_title(name); + script_tools.append(tool); + + // open description.tab and get more info + char buf[PATH_MAX]; + sprintf( buf, "%s/description.tab", fullname ); + tabfile_t file; + if ( !file.open(buf) ) { + continue; + } + tabfileobj_t contents; + file.read( contents ); + tool->set_title(contents.get_string("title", tool->get_default_param())); + tool->set_menu_arg(contents.get_string("menu", tool->get_default_param())); + const char* cursor_name = contents.get_string("icon", "-"); + const skin_desc_t * desc = skinverwaltung_t::get_extra(cursor_name, strlen(cursor_name)); + if( desc ) { + tool->set_icon(desc->get_image_id(0)); + } + } +} + +void script_tool_manager_t::fill_menu(tool_selector_t* tool_selector, char const* arg, sint16 /*sound_ok*/) { + for(uint32 i=0; iget_menu_arg(), arg)==0 ) { + tool_selector->add_tool_selector(tool); + } + } +} diff --git a/bauer/script_tool_manager.h b/bauer/script_tool_manager.h new file mode 100644 index 000000000..b38fd3ce1 --- /dev/null +++ b/bauer/script_tool_manager.h @@ -0,0 +1,33 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef BAUER_SCRIPT_TOOL_MANAGER_H +#define BAUER_SCRIPT_TOOL_MANAGER_H + + +#include "../dataobj/koord3d.h" +#include "../simtypes.h" +#include "../tpl/vector_tpl.h" + +class tool_selector_t; +class tool_exec_script_t; + +/** + * There's no need to construct an instance since everything is static here. + */ +class script_tool_manager_t +{ +private: + static vector_tpl script_tools; ///< All script tools + +public: + static void load_scripts(char const* path); + + static bool check_file(char const* path); + + static void fill_menu(tool_selector_t* tool_selector, char const* arg, sint16 sound_ok); +}; + +#endif diff --git a/gui/script_tool_frame.cc b/gui/script_tool_frame.cc new file mode 100644 index 000000000..579a59157 --- /dev/null +++ b/gui/script_tool_frame.cc @@ -0,0 +1,78 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "../simdebug.h" +#include "../sys/simsys.h" + +#include "script_tool_frame.h" +#include "messagebox.h" + +#include "simwin.h" +#include "../simworld.h" +#include "../simmenu.h" +#include "../simtool.h" + +#include "../dataobj/environment.h" +#include "../dataobj/translator.h" + +#include "../network/network.h" +#include "../network/network_cmd.h" + +#include "../utils/cbuffer_t.h" + +script_tool_frame_t::script_tool_frame_t() : savegame_frame_t(NULL, true, NULL, false) +{ + static cbuffer_t pakset_script_tool; + static cbuffer_t addons_script_tool; + + pakset_script_tool.clear(); + pakset_script_tool.printf("%stool/", env_t::program_dir, env_t::objfilename.c_str()); + + addons_script_tool.clear(); + addons_script_tool.printf("%stool/", env_t::objfilename.c_str()); + + if (env_t::default_settings.get_with_private_paks()) { + this->add_path(addons_script_tool); + } + this->add_path(pakset_script_tool); + + set_name(translator::translate("Load script_tool")); + set_focus(NULL); +} + + +/** + * Action, started after button pressing. + */ +bool script_tool_frame_t::item_action(const char *fullpath) +{ + tool_exec_script_t* tool = static_cast(tool_t::general_tool[TOOL_EXEC_SCRIPT]); + tool->set_default_param(fullpath); + tool->enable_restart(); + welt->set_tool( tool, welt->get_active_player() ); + return true; +} + + +const char *script_tool_frame_t::get_info(const char *filename) +{ + static char info[PATH_MAX]; + + sprintf(info,"%s",this->get_filename(filename, false).c_str()); + + return info; +} + + +bool script_tool_frame_t::check_file( const char *filename, const char * ) +{ + char buf[PATH_MAX]; + sprintf( buf, "%s/tool.nut", filename ); + if (FILE* const f = dr_fopen(buf, "r")) { + fclose(f); + return true; + } + return false; +} diff --git a/gui/script_tool_frame.h b/gui/script_tool_frame.h new file mode 100644 index 000000000..5ea14efc4 --- /dev/null +++ b/gui/script_tool_frame.h @@ -0,0 +1,47 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef GUI_SCRIPT_TOOL_INFO_H +#define GUI_SCRIPT_TOOL_INFO_H + + +#include "savegame_frame.h" +#include "../utils/cbuffer_t.h" + + +class dynamic_string; +class script_tool_frame_t : public savegame_frame_t +{ +private: + cbuffer_t path; + +protected: + /** + * Action that's started by the press of a button. + */ + bool item_action(const char *fullpath) OVERRIDE; + + /** + * Action, started after X-Button pressing + */ + bool del_action(const char *f) OVERRIDE { return item_action(f); } + + // returns extra file info + const char *get_info(const char *fname) OVERRIDE; + + // true, if valid + bool check_file( const char *filename, const char *suffix ) OVERRIDE; + +public: + /** + * Set the window associated helptext + * @return the filename for the helptext, or NULL + */ + const char * get_help_filename() const OVERRIDE { return "script_tool.txt"; } + + script_tool_frame_t(); +}; + +#endif diff --git a/simmain.cc b/simmain.cc index bafb2bf56..d8c7a753e 100644 --- a/simmain.cc +++ b/simmain.cc @@ -81,6 +81,7 @@ #include "utils/simrandom.h" #include "bauer/vehikelbauer.h" +#include "bauer/script_tool_manager.h" #include "vehicle/simvehicle.h" #include "vehicle/simroadtraffic.h" @@ -1113,6 +1114,13 @@ int simu_main(int argc, char** argv) modal_dialogue( win, magic_pakset_info_t, NULL, wait_for_key ); destroy_all_win(true); } + + // load tool scripts + dbg->message("simmain()","Reading tool scripts ..."); + script_tool_manager_t::load_scripts((env_t::objfilename + "tool/").c_str()); + if( env_t::default_settings.get_with_private_paks() ) { + script_tool_manager_t::load_scripts(("addons/" + env_t::objfilename + "tool/").c_str()); + } dbg->message("simmain()","Reading menu configuration ..."); tool_t::read_menu(env_t::objfilename); diff --git a/simmenu.cc b/simmenu.cc index 9e43aee99..02a3ed2d5 100644 --- a/simmenu.cc +++ b/simmenu.cc @@ -22,6 +22,7 @@ #include "bauer/wegbauer.h" #include "bauer/brueckenbauer.h" #include "bauer/tunnelbauer.h" +#include "bauer/script_tool_manager.h" #include "descriptor/building_desc.h" #include "descriptor/bridge_desc.h" @@ -118,6 +119,7 @@ tool_t *create_general_tool(int toolnr) case TOOL_SET_CLIMATE: tool = new tool_set_climate_t(); break; case TOOL_ROTATE_BUILDING: tool = new tool_rotate_building_t(); break; case TOOL_MERGE_STOP: tool = new tool_merge_stop_t(); break; + case TOOL_EXEC_SCRIPT: tool = new tool_exec_script_t(); break; default: dbg->error("create_general_tool()","cannot satisfy request for general_tool[%i]!",toolnr); return NULL; } @@ -213,6 +215,7 @@ tool_t *create_dialog_tool(int toolnr) case DIALOG_SCENARIO_INFO: tool = new dialog_scenario_info_t(); break; case DIALOG_LIST_DEPOT: tool = new dialog_list_depot_t(); break; case DIALOG_LIST_VEHICLE: tool = new dialog_list_vehicle_t(); break; + case DIALOG_SCRIPT_TOOL: tool = new dialog_script_tool_t(); break; default: dbg->error("create_dialog_tool()","cannot satisfy request for dialog_tool[%i]!",toolnr); return NULL; } @@ -887,6 +890,12 @@ void toolbar_t::update(player_t *player) waytype_t way = (waytype_t)(*c!=0 ? atoi(++c) : 0); hausbauer_t::fill_menu( tool_selector, utype, way, get_sound(c)); } + else if (char const* const c = strstart(param, "scripts(")) { + char buf[1000]; + strcpy(buf, c); + buf[strlen(c)-1] = '\0'; // omit the last ')' charactor + script_tool_manager_t::fill_menu(tool_selector, buf, get_sound(c)); + } else if (param[0] == '-') { // add dummy tool_t as seperator tool_selector->add_tool_selector( dummy ); diff --git a/simmenu.h b/simmenu.h index 88964c8b5..a7f343774 100644 --- a/simmenu.h +++ b/simmenu.h @@ -74,6 +74,7 @@ enum { TOOL_SET_CLIMATE, TOOL_ROTATE_BUILDING, TOOL_MERGE_STOP, + TOOL_EXEC_SCRIPT, GENERAL_TOOL_COUNT, GENERAL_TOOL = 0x1000 }; @@ -156,6 +157,7 @@ enum { DIALOG_SCENARIO_INFO, DIALOG_LIST_DEPOT, DIALOG_LIST_VEHICLE, + DIALOG_SCRIPT_TOOL, DIALOGE_TOOL_COUNT, DIALOGE_TOOL = 0x4000 }; diff --git a/simtool-dialogs.h b/simtool-dialogs.h index a282bc6cc..0c2a4151f 100644 --- a/simtool-dialogs.h +++ b/simtool-dialogs.h @@ -48,6 +48,7 @@ #include "gui/scenario_info.h" #include "gui/depotlist_frame.h" #include "gui/vehiclelist_frame.h" +#include "gui/script_tool_frame.h" class player_t; @@ -273,6 +274,21 @@ public: bool is_init_network_save() const OVERRIDE{ return true; } }; +// open scenario dialog +class dialog_script_tool_t : public tool_t { +public: + dialog_script_tool_t() : tool_t(DIALOG_SCRIPT_TOOL | DIALOGE_TOOL) {} + char const* get_tooltip(player_t const*) const OVERRIDE{ return translator::translate("Load tool script"); } + bool is_selected() const OVERRIDE{ return win_get_magic(magic_load_t); } + bool init(player_t*) OVERRIDE{ + destroy_win(magic_save_t); + create_win( new script_tool_frame_t(), w_info, magic_load_t ); + return false; + } + bool exit(player_t*) OVERRIDE{ destroy_win(magic_load_t); return false; } + bool is_init_network_save() const OVERRIDE{ return true; } +}; + // open scenario dialog class dialog_scenario_t : public tool_t { public: diff --git a/simtool.cc b/simtool.cc index 6f84a189d..d993a46bb 100644 --- a/simtool.cc +++ b/simtool.cc @@ -92,6 +92,11 @@ #include "simtool.h" #include "player/finance.h" +// scripting +#include "script/script.h" +#include "script/export_objs.h" +#include "script/api/api.h" + #define is_scenario() welt->get_scenario()->is_scripted() @@ -6466,6 +6471,115 @@ const char *tool_merge_stop_t::do_work( player_t *player, const koord3d &last_po return NULL; } + +bool tool_exec_script_t::init( player_t * p ) { + player = p; + if( !script ) { + // default_param holds script path + load_script(get_default_param()); + } + // exec init() here + if( script ) { + return call_function("init", player); + } else { + dbg->error("tool_exec_script_t::init()", "failed to launch script vm!"); + return false; + } +} + +bool tool_exec_script_t::exit( player_t* p ) { + if( !script ) { + dbg->warning("tool_exec_script_t::exit()", "script vm is not available"); + return true; + } + bool ret_val = call_function("exit", p); + if( restart ) { + delete script; + script = NULL; + } + return ret_val; +} + +bool load_base_script(script_vm_t *script, const char* base); // scenario.cc + +void tool_exec_script_t::load_script( const char* path ) { + cbuffer_t buf; + buf.printf("script-exec-%d.log", player->get_player_nr()); + if( script ) { + delete script; + } + script = new script_vm_t(path, buf); + // load ai definition + char filename[PATH_MAX]; + sprintf( filename, "%s/tool.nut", path ); + // load global stuff + // constants must be known compile time + export_global_constants(script->get_vm()); + + // load scripting base definitions and tool base definitions + if ( !load_base_script(script, "script_base.nut") || + !load_base_script(script, "tool_base.nut") ) { + return; + } + + // register api functions + register_export_function(script->get_vm(), false); + if (script->get_error()) { + dbg->error("tool_exec_script_t::load_script", "error [%s] calling register_export_function", script->get_error()); + return; + } + // set my player number + script->set_my_player(player->get_player_nr()); + if (const char* err = script->call_script(filename)) { + if (strcmp(err, "suspended")) { + dbg->error("tool_exec_script_t::load_script", "error [%s] calling %s", err, filename); + } + return; + } +} + +bool tool_exec_script_t::call_function(const char* func_name, player_t* player) { + // exec script function that takes player and return bool + if( !script ) { + dbg->error("tool_exec_script_t::call_function", "script vm is not available."); + return "script vm internal error!"; + } + + bool ret_val; + const char* err; + err = script->call_function(script_vm_t::QUEUE, func_name, ret_val, player); + + if( err ) { + // script execution error + dbg->error("tool_exec_script_t::call_function", "%s", err); + return false; + } + return ret_val; +} + +const char* tool_exec_script_t::call_function(const char* func_name, player_t* player, koord3d pos) { + // exec script function that takes player and pos + plainstring* msg = new plainstring(); + if( !script ) { + dbg->error("tool_exec_script_t::call_function", "script vm is not available."); + return "script vm internal error!"; + } + const char* err = script->call_function(script_vm_t::QUEUE, func_name, *msg, player, pos); + if( err ) { + // script execution error + dbg->error("tool_exec_script_t::call_function", "%s", err); + return err; + } + // propagate error + if( msg->c_str()==NULL ) { + delete msg; + return NULL; + } else { + return msg->c_str(); + } +} + + bool tool_show_trees_t::init( player_t * ) { env_t::hide_trees = !env_t::hide_trees; diff --git a/simtool.h b/simtool.h index 555215c08..0a3aa7c1b 100644 --- a/simtool.h +++ b/simtool.h @@ -33,6 +33,7 @@ class roadsign_desc_t; class way_desc_t; class route_t; class way_obj_desc_t; +class script_vm_t; /****************************** helper functions: *****************************/ @@ -645,6 +646,31 @@ public: char const* work(player_t*, koord3d) OVERRIDE { return default_param ? default_param : ""; } }; + +class tool_exec_script_t : public tool_t { +private: + script_vm_t *script; + player_t* player; + char title[PATH_MAX]; + char menu_arg[PATH_MAX]; + bool restart = false; // true -> the script vm is always scrapped when exit() is called. +public: + tool_exec_script_t() : tool_t(TOOL_EXEC_SCRIPT | GENERAL_TOOL) {} + bool is_init_network_save() const OVERRIDE { return true; } + bool init(player_t*) OVERRIDE; + bool exit( player_t * ) OVERRIDE; + void load_script(const char* path); + bool call_function(const char*, player_t*); + const char *call_function(const char*, player_t*, koord3d); + const char *work(player_t* pl, koord3d pos) OVERRIDE { return call_function("work", pl, pos); } + const char *check_pos(player_t* pl, koord3d pos) OVERRIDE { return call_function("check_pos", pl, pos); } + void set_title(const char* str) { strcpy(title, str); } + const char *get_tooltip(const player_t *) const OVERRIDE { return title; } + const char* get_menu_arg() const { return menu_arg; } + void set_menu_arg(const char* arg) { strcpy(menu_arg, arg); } + void enable_restart() { restart = true; } +}; + /********************* one click tools ****************************/ class tool_pause_t : public tool_t { diff --git a/simutrans/script/tool_base.nut b/simutrans/script/tool_base.nut new file mode 100644 index 000000000..766766105 --- /dev/null +++ b/simutrans/script/tool_base.nut @@ -0,0 +1,46 @@ +/** + * Base file for tools + */ + +/** + * initialization, called when tool is called + * Returning true will select tool and will make it possible to call work. + * @param pl player_x object by whom the tool is called + */ +function init(pl) +{ + return true +} + +/** + * function called when the tool is clicked on a ground + * the return string can have different meanings: + * NULL: ok + * "": unspecified error + * "blabla": errors message, will be handled and translated as appropriate + * @param pl player_x object by whom the tool is called + * @param pos coord3d object that represents the position + */ +function work(pl, pos) +{ + return null +} + +/** + * function for position check. called before work() + * @param pl player_x object by whom the tool is called + * @param pos coord3d object that represents the position + */ +function check_pos(pl, pos) +{ + return null +} + +/** + * termination, called when the player exits from this tool + * @param pl player_x object by whom the tool is called + */ +function exit(pl) +{ + return true +} \ No newline at end of file