diff --git a/simutrans/trunk/Makefile b/simutrans/trunk/Makefile index c4dd72b05..f29de5054 100644 --- a/simutrans/trunk/Makefile +++ b/simutrans/trunk/Makefile @@ -405,6 +405,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 @@ -483,6 +484,7 @@ SOURCES += script/dynamic_string.cc SOURCES += script/export_objs.cc SOURCES += script/script.cc SOURCES += script/script_loader.cc +SOURCES += script/script_tool_manager.cc SOURCES += simcity.cc SOURCES += simconvoi.cc SOURCES += simdebug.cc @@ -506,6 +508,7 @@ SOURCES += simskin.cc SOURCES += simsound.cc SOURCES += simticker.cc SOURCES += simtool.cc +SOURCES += simtool-scripted.cc SOURCES += simware.cc SOURCES += simworld.cc SOURCES += squirrel/sq_extensions.cc diff --git a/simutrans/trunk/gui/script_tool_frame.cc b/simutrans/trunk/gui/script_tool_frame.cc new file mode 100644 index 000000000..60f44be32 --- /dev/null +++ b/simutrans/trunk/gui/script_tool_frame.cc @@ -0,0 +1,74 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "script_tool_frame.h" + +#include "../script/script_tool_manager.h" +#include "../dataobj/environment.h" +#include "../dataobj/tabfile.h" +#include "../dataobj/translator.h" +#include "../simdebug.h" +#include "../simworld.h" +#include "../simtool-scripted.h" +#include "../sys/simsys.h" +#include "../utils/cbuffer_t.h" +#include "../utils/simstring.h" + + +script_tool_frame_t::~script_tool_frame_t() +{ + clear_ptr_vector(infos); +} + + +script_tool_frame_t::script_tool_frame_t() : savegame_frame_t(NULL, true, NULL, false) +{ + cbuffer_t pakset_script_tool; + cbuffer_t addons_script_tool; + + pakset_script_tool.printf("%s%stool/", env_t::program_dir, env_t::objfilename.c_str()); + addons_script_tool.printf("addons/%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) +{ + const scripted_tool_info_t* info = script_tool_manager_t::get_script_info(fullpath); + bool is_one_click = info->is_one_click; + delete info; + + tool_t* tool = script_tool_manager_t::load_tool(fullpath, tool_t::general_tool[is_one_click ? TOOL_EXEC_SCRIPT : TOOL_EXEC_TWO_CLICK_SCRIPT]); + assert(tool); + + welt->set_tool(tool, welt->get_active_player()); + return true; +} + + +// calls tool manager to read description.tab +const char *script_tool_frame_t::get_info(const char *path) +{ + const scripted_tool_info_t* info = script_tool_manager_t::get_script_info(path); + infos.append(info); + + return info->title.c_str(); +} + + +bool script_tool_frame_t::check_file( const char *filename, const char * ) +{ + return script_tool_manager_t::check_file(filename); +} diff --git a/simutrans/trunk/gui/script_tool_frame.h b/simutrans/trunk/gui/script_tool_frame.h new file mode 100644 index 000000000..204d70002 --- /dev/null +++ b/simutrans/trunk/gui/script_tool_frame.h @@ -0,0 +1,48 @@ +/* + * 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 "../tpl/vector_tpl.h" + +class scripted_tool_info_t; + + +class script_tool_frame_t : public savegame_frame_t +{ +private: + // pointers to info structures + vector_tpl infos; + +protected: + /** + * Action that's started by the press of a button. + */ + bool item_action(const char *fullpath) OVERRIDE; + + /** + * Returns extra file info: title of tool from description.tab + */ + const char *get_info(const char *path) OVERRIDE; + + // true, if valid + bool check_file( const char *filename, const char *suffix ) OVERRIDE; + +public: + script_tool_frame_t(); + + ~script_tool_frame_t(); + + /** + * Set the window associated helptext + * @return the filename for the helptext, or NULL + */ + const char * get_help_filename() const OVERRIDE { return "script_tool.txt"; } +}; + +#endif diff --git a/simutrans/trunk/script/api/api_command.cc b/simutrans/trunk/script/api/api_command.cc index 129f63554..b79d0f0ed 100644 --- a/simutrans/trunk/script/api/api_command.cc +++ b/simutrans/trunk/script/api/api_command.cc @@ -35,11 +35,17 @@ SQInteger command_constructor(HSQUIRRELVM vm) if (id & GENERAL_TOOL) { // we do not want scripts to open dialogues or quitting the game etc - if (tool_t *tool = create_tool(id)) { - my_tool_t* mtool = new my_tool_t(tool); - attach_instance(vm, 1, mtool); + if (id == (TOOL_EXEC_TWO_CLICK_SCRIPT | GENERAL_TOOL)) { + // do not create & attach instance, will be done separately return 0; } + else { + if (tool_t *tool = create_tool(id)) { + my_tool_t* mtool = new my_tool_t(tool); + attach_instance(vm, 1, mtool); + return 0; + } + } } return sq_raise_error(vm, "Invalid tool called (%d / 0x%x)", id & 0xfff, id); } @@ -145,7 +151,7 @@ SQInteger command_work(HSQUIRRELVM vm) bool twoclick = top>4; // save & set default_param - my_tool_t *mtool = get_attached_instance(vm, 1, (void*)param::get); + my_tool_t *mtool = get_attached_instance(vm, 1, param::tag()); if (mtool == NULL) { return sq_raise_error(vm, "Called from an instance different to tool_x"); } diff --git a/simutrans/trunk/script/api/api_doc.h b/simutrans/trunk/script/api/api_doc.h index 07aca46fa..0dfae467f 100644 --- a/simutrans/trunk/script/api/api_doc.h +++ b/simutrans/trunk/script/api/api_doc.h @@ -99,6 +99,38 @@ * addons/ai/myai/ * * + * @section s_scripted_tools Scripted tools + * + * See also @ref tool_skel and @ref tool_only. + * + * @subsection sec_dir_tools Recommended directory structure + * + * The script file (tool.nut) as well as the configuration file (description.tab) go into + * + * + * pak-something/tool/mytool/ + * + * + * Related pak-files have to be placed in + * + * + * pak-something/ + * + * + * directly. + * + * @subsection sec_description_tab The file description.tab + * + * This is a plain text file with the following entries:
+ * + * title=Name of tool to be shown in tool selection dialog
+ * type=one_click or two_click
+ * tooltip=Tooltip for the icon in the toolbar
+ * restart=Set to 1 if the virtual machine has to be restarted after each call to the work functions.
+ * menu=Parameter that can be used to load tools with menuconf.tab
+ * icon=Name of cursor object (loaded from some pak-file), used images: 0 = cursor, 1 = icon, 2 = marker image
+ *
+ * * @section s_general_advice General scripting advice * * Check out the sections on the Modules page. @@ -297,6 +329,22 @@ * */ +/** + * @defgroup tool_skel Tool interface + * + * The following methods are vital for the functioning of scripted tools. + * They will be called from simutrans to interact with the script. You should consider + * implementing them. + * + */ + +/** + * @defgroup tool_only Tool only functions + * + * These classes and methods are only available for scripted scenarios. + * + */ + /** * @defgroup game_cmd Functions to alter the state of the game and map * diff --git a/simutrans/trunk/script/api/api_skeleton.cc b/simutrans/trunk/script/api/api_skeleton.cc index 29c7ee597..96b837148 100644 --- a/simutrans/trunk/script/api/api_skeleton.cc +++ b/simutrans/trunk/script/api/api_skeleton.cc @@ -276,4 +276,87 @@ register_function("is_schedule_allowed"); */ register_function("is_convoy_allowed"); +/** + * Initializes the tool. + * @returns true upon success. + * + * @param pl player instance to use this tool. + * @ingroup tool_skel + * @typemask bool(player_x) + */ +register_function("init"); + +/** + * Exits the tool. Do cleanup here. + * @returns true upon success. + * + * @param pl player instance to use this tool. + * @ingroup tool_skel + * @typemask bool(player_x) + */ +register_function("exit"); + +/** + * Does the work (for tools of one-click type). + * @returns null upon success, an error message otherwise. + * + * @param pl player instance to use this tool. + * @param pos tile clicked by user, here the work should be done. + * @ingroup tool_skel + * @typemask string(player_x, coord3d) + */ +register_function("work"); + +/** + * Does the work (for tools of two-click type). + * @returns null upon success, an error message otherwise. + * + * @param pl player instance to use this tool. + * @param start first tile clicked by user. + * @param end second tile clicked by user. + * @ingroup tool_skel + * @typemask string(player_x, coord3d, coord3d) + */ +register_function("do_work"); + +/** + * Mark tiles for working (for tools of two-click type). + * Call @ref mark_tile from here. + * + * @param pl player instance to use this tool. + * @param start first tile clicked by user. + * @param end second tile clicked by user. + * @ingroup tool_skel + * @typemask void(player_x, coord3d, coord3d) + */ +register_function("mark_tiles"); + +/** + * Place marker image of scripted tool at @p pos. + * Marker images will be deleted automatically. + * @returns true if succesfull + * + * @param pos position to be marked + * @ingroup tool_only + * @typemask bool(coord3d) + */ +// see simtool.scripted.cc +register_function("mark_tile"); + +/** + * Can the tool start/end on @p pos? If it is the second click, @p start is the position of the first click. + * Possible return values: + * 0 = no + * 1 = This tool can work on this tile (with single click) + * 2 = On this tile can dragging start/end + * 3 = Both (1 and 2) + * + * @param pl player instance to use this tool. + * @param pos position to test + * @param start first tile clicked by user + * @ingroup tool_skel + * @typemask void(player_x, coord3d, coord3d) + */ +register_function("is_valid_pos"); + #endif diff --git a/simutrans/trunk/script/script_tool_manager.cc b/simutrans/trunk/script/script_tool_manager.cc new file mode 100644 index 000000000..48a1b7eaf --- /dev/null +++ b/simutrans/trunk/script/script_tool_manager.cc @@ -0,0 +1,127 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "script_tool_manager.h" + +#include "../dataobj/tabfile.h" + +#include "../simdebug.h" +#include "../simtool-scripted.h" +#include "../simskin.h" +#include "../descriptor/skin_desc.h" +#include "../sys/simsys.h" +#include "../gui/tool_selector.h" +#include "../utils/cbuffer_t.h" +#include "../utils/searchfolder.h" + + +vector_tpl script_tool_manager_t::one_click_script_tools; +vector_tpl script_tool_manager_t::two_click_script_tools; + + +bool script_tool_manager_t::check_file( const char *path ) +{ + cbuffer_t buf; + buf.printf("%s/tool.nut", path ); + if (FILE* const f = dr_fopen(buf, "r")) { + fclose(f); + return true; + } + return false; +} + + +const scripted_tool_info_t* script_tool_manager_t::get_script_info(const char* path) +{ + scripted_tool_info_t* info = new scripted_tool_info_t(); + info->path = path; + + // read description.tab + cbuffer_t buf; + buf.printf("%s/description.tab", path); + tabfile_t file; + + if ( file.open(buf) ) { + tabfileobj_t contents; + file.read( contents ); + info->title = contents.get_string("title", path); + info->menu_arg = contents.get_string("menu", ""); + info->tooltip = contents.get_string("tooltip", ""); + + printf("Path %s Title %s\n", path, info->title.c_str()); + + const char* skin_name = contents.get_string("icon", ""); + info->desc = skinverwaltung_t::get_extra(skin_name, strlen(skin_name), skinverwaltung_t::cursor); + info->is_one_click = !( strcmp(contents.get_string("type", "one_click"), "two_click")==0 ); + } + else { + // no description.tab, use default values + info->title = path; + } + return info; +} + + +tool_t* script_tool_manager_t::load_tool(char const* path, tool_t* tool) +{ + if (!check_file(path)) { + return tool; + } + const scripted_tool_info_t* info = get_script_info(path); + + if (tool) { + // reinitialize existing tool + exec_script_base_t* esb = dynamic_cast(tool); + assert(esb); + esb->set_info(info); + } + else { + // create new tool + if (info->is_one_click) { + tool = new tool_exec_script_t(info); + } + else { + tool = new tool_exec_two_click_script_t(info); + } + } + return tool; +} + + +void script_tool_manager_t::load_scripts(char const* path) +{ + searchfolder_t find; + find.search(path, "", true, false); + FOR(searchfolder_t, const &name, find) { + cbuffer_t fullname; + fullname.printf("%s%s",path,name); + + tool_t *tool = load_tool(fullname); + + if (tool_exec_script_t* ot = dynamic_cast(tool)) { + one_click_script_tools.append(ot); + } + else if (tool_exec_two_click_script_t* tt = dynamic_cast(tool)) { + two_click_script_tools.append(tt); + } + } +} + + +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); + } + } + for(uint32 i=0; iget_menu_arg(), arg)==0 ) { + tool_selector->add_tool_selector(tool); + } + } +} diff --git a/simutrans/trunk/script/script_tool_manager.h b/simutrans/trunk/script/script_tool_manager.h new file mode 100644 index 000000000..0f76716d8 --- /dev/null +++ b/simutrans/trunk/script/script_tool_manager.h @@ -0,0 +1,61 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef SCRIPT_SCRIPT_TOOL_MANAGER_H +#define SCRIPT_SCRIPT_TOOL_MANAGER_H + + +#include "../tpl/vector_tpl.h" + +class exec_script_base_t; +class scripted_tool_info_t; +class tool_t; +class tool_selector_t; +class tool_exec_script_t; +class tool_exec_two_click_script_t; + +/** + * There's no need to construct an instance since everything is static here. + */ +class script_tool_manager_t +{ +private: + /// All one-click script tools + static vector_tpl one_click_script_tools; + /// All two-click script tools + static vector_tpl two_click_script_tools; + +public: + + /// Looks for tool.nut in path + static bool check_file(char const* path); + + /** + * Reads description.tab at path. + * If no description is found, use default values. + * @returns filled info structure. + */ + static const scripted_tool_info_t* get_script_info(const char* path); + + /** + * Loads tool from path. + * If @p tool is not NULL it is reinitialized with the scripted tool. + * @returns tool + */ + static tool_t* load_tool(char const* path, tool_t* tool = NULL); + + /** + * Reads all tools from directory @p path. + */ + static void load_scripts(char const* path); + + /** + * Fills toolbar with scripted tools. + * Select only tools with menu entry equal to @p arg. + */ + static void fill_menu(tool_selector_t* tool_selector, char const* arg, sint16 sound_ok); +}; + +#endif diff --git a/simutrans/trunk/simmain.cc b/simutrans/trunk/simmain.cc index f12c1ebc5..ec2532e70 100644 --- a/simutrans/trunk/simmain.cc +++ b/simutrans/trunk/simmain.cc @@ -81,6 +81,7 @@ #include "utils/simrandom.h" #include "bauer/vehikelbauer.h" +#include "script/script_tool_manager.h" #include "vehicle/simvehicle.h" #include "vehicle/simroadtraffic.h" @@ -1107,15 +1108,25 @@ int simu_main(int argc, char** argv) pakset_info_t::calculate_checksum(); pakset_info_t::debug(); - if( !overlaid_warning.empty() ) { - overlaid_warning.append( "

Continue by ESC, SPACE, or BACKSPACE.
" ); - help_frame_t *win = new help_frame_t(); - win->set_text( overlaid_warning.c_str() ); - modal_dialogue( win, magic_pakset_info_t, NULL, wait_for_key ); - destroy_all_win(true); +// if( !overlaid_warning.empty() ) { +// overlaid_warning.append( "

Continue by ESC, SPACE, or BACKSPACE.
" ); +// help_frame_t *win = new help_frame_t(); +// win->set_text( overlaid_warning.c_str() ); +// modal_dialogue( win, magic_pakset_info_t, NULL, wait_for_key ); +// destroy_all_win(true); +// } + + // load tool scripts + dbg->message("simmain()","Reading tool scripts ..."); + dr_chdir( env_t::program_dir ); + script_tool_manager_t::load_scripts((env_t::objfilename + "tool/").c_str()); + if( env_t::default_settings.get_with_private_paks() ) { + dr_chdir( env_t::user_dir ); + script_tool_manager_t::load_scripts(("addons/" + env_t::objfilename + "tool/").c_str()); } dbg->message("simmain()","Reading menu configuration ..."); + dr_chdir( env_t::program_dir ); tool_t::read_menu(env_t::objfilename); // reread theme diff --git a/simutrans/trunk/simmenu.cc b/simutrans/trunk/simmenu.cc index dfc729a9f..cddd464de 100644 --- a/simutrans/trunk/simmenu.cc +++ b/simutrans/trunk/simmenu.cc @@ -15,6 +15,7 @@ #include "simmenu.h" #include "simtool.h" #include "simtool-dialogs.h" +#include "simtool-scripted.h" #include "simskin.h" #include "simsound.h" @@ -22,6 +23,7 @@ #include "bauer/wegbauer.h" #include "bauer/brueckenbauer.h" #include "bauer/tunnelbauer.h" +#include "script/script_tool_manager.h" #include "descriptor/building_desc.h" #include "descriptor/bridge_desc.h" @@ -118,6 +120,8 @@ 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; + case TOOL_EXEC_TWO_CLICK_SCRIPT: tool = new tool_exec_two_click_script_t(); break; default: dbg->error("create_general_tool()","cannot satisfy request for general_tool[%i]!",toolnr); return NULL; @@ -219,6 +223,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; @@ -895,6 +900,13 @@ 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(")) { + const char* end = strchr(c, ')'); + char buf[1000]; + size_t len = end ? min(lengthof(buf)-1, end-c) : lengthof(buf)-1; + tstrncpy(buf, c, len+1); + 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 ); @@ -1218,7 +1230,7 @@ void two_click_tool_t::cleanup( bool delete_start_marker ) } -image_id two_click_tool_t::get_marker_image() +image_id two_click_tool_t::get_marker_image() const { return skinverwaltung_t::bauigelsymbol->get_image_id(0); } diff --git a/simutrans/trunk/simmenu.h b/simutrans/trunk/simmenu.h index 104824643..96bd9d453 100644 --- a/simutrans/trunk/simmenu.h +++ b/simutrans/trunk/simmenu.h @@ -74,6 +74,8 @@ enum { TOOL_SET_CLIMATE, TOOL_ROTATE_BUILDING, TOOL_MERGE_STOP, + TOOL_EXEC_SCRIPT, + TOOL_EXEC_TWO_CLICK_SCRIPT, GENERAL_TOOL_COUNT, GENERAL_TOOL = 0x1000 }; @@ -156,6 +158,7 @@ enum { DIALOG_SCENARIO_INFO, DIALOG_LIST_DEPOT, DIALOG_LIST_VEHICLE, + DIALOG_SCRIPT_TOOL, DIALOGE_TOOL_COUNT, DIALOGE_TOOL = 0x4000 }; @@ -411,7 +414,7 @@ private: */ virtual uint8 is_valid_pos( player_t *, const koord3d &pos, const char *&error, const koord3d &start ) = 0; - virtual image_id get_marker_image(); + virtual image_id get_marker_image() const; bool first_click_var; koord3d start; diff --git a/simutrans/trunk/simskin.cc b/simutrans/trunk/simskin.cc index 5111fe624..69ffc0ecd 100644 --- a/simutrans/trunk/simskin.cc +++ b/simutrans/trunk/simskin.cc @@ -78,7 +78,8 @@ const skin_desc_t* skinverwaltung_t::pumpe = NULL; const skin_desc_t* skinverwaltung_t::senke = NULL; const skin_desc_t* skinverwaltung_t::tunnel_texture = NULL; -slist_tplskinverwaltung_t::extra_obj; +slist_tplskinverwaltung_t::extra_menu_obj; +slist_tplskinverwaltung_t::extra_cursor_obj; static special_obj_tpl const misc_objekte[] = { @@ -191,20 +192,26 @@ bool skinverwaltung_t::register_desc(skintyp_t type, const skin_desc_t* desc) case nothing: return true; default: return false; } - if( !::register_desc(sd, desc) ) { - // currently no misc objects allowed ... - if( !(type==cursor || type==symbol) ) { - if( type==menu ) { - extra_obj.insert( desc ); - dbg->message( "skinverwaltung_t::register_desc()","Extra object %s added.", desc->get_name() ); - } - else { - dbg->warning("skinverwaltung_t::register_desc()","Spurious object '%s' loaded (will not be referenced anyway)!", desc->get_name() ); - } + if( ::register_desc(sd, desc) ) { + return true; + } + else if( type==cursor || type==symbol ) { + if( ::register_desc( fakultative_objekte, desc ) ) { + return true; + } + } + // currently no misc objects allowed ... + if( type==cursor || type==menu ) { + if( type==cursor ) { + extra_cursor_obj.insert( desc ); } else { - return ::register_desc( fakultative_objekte, desc ); + extra_menu_obj.insert( desc ); } + dbg->message( "skinverwaltung_t::register_desc()","Extra object %s added.", desc->get_name() ); + } + else { + dbg->warning("skinverwaltung_t::register_desc()","Spurious object '%s' loaded (will not be referenced anyway)!", desc->get_name() ); } return true; } @@ -212,10 +219,15 @@ bool skinverwaltung_t::register_desc(skintyp_t type, const skin_desc_t* desc) // return the extra_obj with this name -const skin_desc_t *skinverwaltung_t::get_extra( const char *str, int len ) +const skin_desc_t *skinverwaltung_t::get_extra( const char *str, int len, skintyp_t type ) { - FOR(slist_tpl, const s, skinverwaltung_t::extra_obj) { - if (strncmp(str, s->get_name(), len) == 0) { + if( type!=menu && type!=cursor ) { + // illegal type + return NULL; + } + FOR(slist_tpl, const s, + (type==menu ? skinverwaltung_t::extra_menu_obj : skinverwaltung_t::extra_cursor_obj)) { + if ( strncmp(str, s->get_name(), len) == 0 ) { return s; } } diff --git a/simutrans/trunk/simskin.h b/simutrans/trunk/simskin.h index 54789d202..b8ef09ede 100644 --- a/simutrans/trunk/simskin.h +++ b/simutrans/trunk/simskin.h @@ -152,11 +152,12 @@ public: * @param len length of string * @return pointer to skin object or NULL if nothing found */ - static const skin_desc_t *get_extra( const char *str, int len ); + static const skin_desc_t *get_extra( const char *str, int len, skintyp_t type = menu ); private: - /// holds objects from paks with type 'menu' - static slist_tplextra_obj; + /// holds objects from paks with type 'menu' and 'cursor' + static slist_tplextra_menu_obj; + static slist_tplextra_cursor_obj; }; #endif diff --git a/simutrans/trunk/simtool-dialogs.h b/simutrans/trunk/simtool-dialogs.h index a282bc6cc..6767d06d7 100644 --- a/simutrans/trunk/simtool-dialogs.h +++ b/simutrans/trunk/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 scripted-tools 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/simutrans/trunk/simtool-scripted.cc b/simutrans/trunk/simtool-scripted.cc new file mode 100644 index 000000000..8bdc95dc7 --- /dev/null +++ b/simutrans/trunk/simtool-scripted.cc @@ -0,0 +1,390 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "simtool-scripted.h" +#include "player/simplay.h" + +#include "script/script_tool_manager.h" +#include "script/script.h" +#include "script/script_loader.h" +#include "script/api/api.h" +#include "script/api_class.h" +#include "script/api_param.h" + +#include "simskin.h" +#include "simworld.h" +#include "obj/zeiger.h" + +void export_scripted_tools(HSQUIRRELVM vm); + +// -- callback to receive error messages from work/do_work calls +static bool exec_script_base_work_callback(exec_script_base_t *esb, player_t* player, const char* err) +{ + tool_t *tool = esb ? dynamic_cast(esb) : NULL; + if (tool && player) { + player->tell_tool_result(tool, koord3d::invalid, err); + } + if (tool_exec_two_click_script_t* tct = dynamic_cast(esb)) { + tct->waiting_for_do_work = false; + // we cannot call init() here directly, because it may destroy the vm that called us + tct->needs_call_to_init = true; + } + return true; +} + +// -- transfer pointers to tools between squirrel and c++ +namespace script_api { + /** + * Mechanic to transfer exec_script_base_t* pointers between squirrel and c++, + * used for callbacks. + */ + declare_specialized_param(exec_script_base_t*, "x", "scripted_tool_t"); + + SQInteger param::push(HSQUIRRELVM vm, exec_script_base_t* const& v) + { + push_instance(vm, param::squirrel_type()); + sq_setinstanceup(vm, -1, v); + return 1; + } + exec_script_base_t* param::get(HSQUIRRELVM vm, SQInteger index) + { + return get_attached_instance(vm, index, param::tag()); + } + void* param::tag() { return (void*)¶m::get; } + + /** + * Mechanic to transfer tool_exec_two_click_script_t* pointers between squirrel and c++, + * used for marking tiles in two-click tools. + */ + template<> struct param { + static tool_exec_two_click_script_t* get(HSQUIRRELVM vm, SQInteger index) + { + tool_t* tool = param::get(vm, index); + return tool ? dynamic_cast(tool) : NULL; + } + + static SQInteger push(HSQUIRRELVM vm, tool_exec_two_click_script_t* const& tool) + { + SQInteger res = push_instance(vm, "command_x", (uint32)(TOOL_EXEC_TWO_CLICK_SCRIPT | GENERAL_TOOL) ); + if (SQ_SUCCEEDED(res)) { + my_tool_t* mtool = new my_tool_t(tool); + attach_instance(vm, -1, mtool); + } + return res; + } + }; +}; + +// -- basic script handling -- +exec_script_base_t::~exec_script_base_t() +{ + delete info; + delete script; +} + + +void exec_script_base_t::set_info(const scripted_tool_info_t *i) +{ + delete info; + info = i; +} + + +bool exec_script_base_t::init_vm(player_t* player) +{ + if (get_info() == NULL) { + // tool probably read from menuconf.tab + // path in default_param, initialize + if (tool_t *tool = dynamic_cast(this)) { + script_tool_manager_t::load_tool(tool->get_default_param(), tool); + } + } + if( script==NULL || (info && info->restart) ) { + load_script(info->path, player); + } + return script != NULL; +} + + +void exec_script_base_t::load_script(const char* path, player_t* player) +{ + cbuffer_t buf; + buf.printf("script-exec-%d.log", player->get_player_nr()); + if( script ) { + // if vm already exists, delete it. + delete script; + script = NULL; + } + // start vm + script = script_loader_t::start_vm("tool_base.nut", buf, path, false); + if (script == NULL) { + return; + } + // set my player number + script->set_my_player(player->get_player_nr()); + // export tool-pointer handling + export_scripted_tools(script->get_vm()); + // callback + script->register_callback(exec_script_base_work_callback, "exec_script_base_work_callback"); + // call script to initialize it + buf.clear(); + buf.printf( "%s/tool.nut", path ); + if (const char* err = script->call_script(buf)) { + if (strcmp(err, "suspended")) { + dbg->error("tool_exec_script_t::load_script", "error [%s] calling %s", err, (const char*)buf); + delete script; + script = NULL; + } + } +} + + +void export_scripted_tools(HSQUIRRELVM vm) +{ + script_api::create_class(vm, script_api::param::squirrel_type()); + script_api::end_class(vm); +} + + +#define check_script() \ + if (!script) { \ + dbg->warning("tool_exec_script_t::call_function", "script vm is not available."); \ + return ""; \ + } +#define check_error() \ + if (err && strcmp(err, "suspended")==0) {\ + dbg->warning("tool_exec_script_t::call_function", "error calling %s: %s", function, err);\ + } + +template +const char* exec_script_base_t::call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret) +{ + check_script(); + const char* err = script->call_function(ct, function, ret, player); + check_error(); + return err; +} + +template +const char* exec_script_base_t::call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret, A1 arg1) +{ + check_script(); + const char* err = script->call_function(ct, function, ret, player, arg1); + check_error(); + return err; +} + +template +const char* exec_script_base_t::call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret, A1 arg1, A2 arg2) +{ + check_script(); + const char* err = script->call_function(ct, function, ret, player, arg1, arg2); + check_error(); + return err; +} + + +void exec_script_base_t::step(player_t* player) +{ + if (script) { + script->call_function(script_vm_t::QUEUE, "step"); + + if (tool_exec_two_click_script_t *tt = dynamic_cast(this)) { + if (tt->needs_call_to_init) { + tt->init(player); + } + } + } +} + + +void exec_script_base_t::init_images(tool_t *tool) const +{ + if (info && info->desc) { + if (info->desc->get_image_id(0) != IMG_EMPTY) { + tool->cursor = info->desc->get_image_id(0); + } + tool->set_icon(info->desc->get_image_id(1)); + } +} + + +tool_exec_script_t::tool_exec_script_t(const scripted_tool_info_t *info) : tool_t(TOOL_EXEC_SCRIPT | GENERAL_TOOL), exec_script_base_t(info) +{ + init_images(this); +} + + +bool tool_exec_script_t::init(player_t* player) +{ + bool res = false; + return init_vm(player) && call_function(script_vm_t::FORCE, "init", player, res)== NULL && res; +} + + +bool tool_exec_script_t::exit(player_t* player) +{ + bool res, res2 = false; + // exit script + res = call_function(script_vm_t::FORCE, "exit", player, res2) == NULL; + // shut down vm + delete script; + script = NULL; + return res && res2; +} + + +const char* tool_exec_script_t::work(player_t* player, koord3d pos) +{ + static plainstring res; + // callback + script->prepare_callback("exec_script_base_work_callback", 3, (exec_script_base_t*)this, player, ""); + // now call + const char* err = call_function(script_vm_t::QUEUE, "work", player, res, pos); + if (err && strcmp(err, "suspended")==0) { + // suspended + } + else { + // no callback necessary + script->clear_pending_callback(); + } + return res.c_str(); +} + + + +tool_exec_two_click_script_t::tool_exec_two_click_script_t(const scripted_tool_info_t *info) : two_click_tool_t(TOOL_EXEC_TWO_CLICK_SCRIPT | GENERAL_TOOL), exec_script_base_t(info) +{ + set_marker(IMG_EMPTY); + waiting_for_do_work = false; + needs_call_to_init = true; + + init_images(this); + + if (info && info->desc) { + set_marker(info->desc->get_image(2) ? info->desc->get_image_id(2) : cursor); + } +} + + +SQInteger script_mark_tile(HSQUIRRELVM vm); // see below + +bool tool_exec_two_click_script_t::init(player_t* player) +{ + // remove marker images + bool res = two_click_tool_t::init(player); + if (waiting_for_do_work) { + return res; + } + + needs_call_to_init = false; + + res = res && init_vm(player); + if (res) { + HSQUIRRELVM vm = script->get_vm(); + // put pointer to this tool into registry + sq_pushregistrytable(vm); + script_api::create_slot(vm, "my_two_click_tool", this); + sq_poptop(vm); + // export marker function + sq_pushroottable(vm); + script_api::register_function(vm, script_mark_tile, "mark_tile", 2, ". t|x|y", false /* static */); + sq_poptop(vm); + } + return res && call_function(script_vm_t::FORCE, "init", player, res)== NULL && res; +} + + +bool tool_exec_two_click_script_t::exit(player_t* player) +{ + bool res, res2 = false; + // exit script + res = two_click_tool_t::exit(player) && call_function(script_vm_t::FORCE, "exit", player, res2) == NULL; + // shut down vm + delete script; + script = NULL; + return res && res2; +} + + +const char* tool_exec_two_click_script_t::do_work(player_t* player, const koord3d &start, const koord3d &end) +{ + if (waiting_for_do_work) { + return ""; + } + static plainstring res; + // callback + script->prepare_callback("exec_script_base_work_callback", 3, (exec_script_base_t*)this, player, ""); + // now call + const char* err = call_function(script_vm_t::QUEUE, "do_work", player, res, start, end); + if (err && strcmp(err, "suspended")==0) { + // suspended + waiting_for_do_work = true; + } + else { + // no callback necessary + script->clear_pending_callback(); + } + return res.c_str(); +} + + +void tool_exec_two_click_script_t::mark_tiles(player_t* player, const koord3d &start, const koord3d &end) +{ + if (waiting_for_do_work) { + return; + } + bool dummy; + // try to mark; if script is busy, do nothing + call_function(script_vm_t::TRY, "mark_tiles", player, dummy, start, end); +} + + +uint8 tool_exec_two_click_script_t::is_valid_pos(player_t* player, const koord3d &pos, const char *&error, const koord3d &start) +{ + error = NULL; + uint8 res = 2; // allow dragging + const char* err = call_function(script_vm_t::FORCEX, "is_valid_pos", player, res, pos, start); + // script error? signal 'start dragging is allowed here' + if (err) { + res = 2; // allow dragging + } + return res; +} + +// mark_tile(pos) +SQInteger script_mark_tile(HSQUIRRELVM vm) +{ + koord3d pos = script_api::param::get(vm, 2); + player_t* player = script_api::get_my_player(vm); + // tool + sq_pushregistrytable(vm); + tool_exec_two_click_script_t* tool = NULL; + if (!SQ_SUCCEEDED(script_api::get_slot(vm, "my_two_click_tool", tool)) || tool == NULL) { + return SQ_ERROR; + } + sq_poptop(vm); + // now call the method + return script_api::param::push(vm, tool->mark_tile(player, pos) ); +} + + +bool tool_exec_two_click_script_t::mark_tile(player_t* player, const koord3d &pos) +{ + grund_t *gr = welt->lookup(pos); + if (gr) { + zeiger_t *mark = new zeiger_t(pos, player ); + gr->obj_add(mark); + mark->set_image(get_marker_image()); + marked.insert(mark); + } + return gr != NULL; +} + + +image_id tool_exec_two_click_script_t::get_marker_image() const +{ + return marker != IMG_EMPTY ? marker : (cursor != IMG_EMPTY ? cursor : skinverwaltung_t::bauigelsymbol->get_image_id(0)); +} diff --git a/simutrans/trunk/simtool-scripted.h b/simutrans/trunk/simtool-scripted.h new file mode 100644 index 000000000..a52816753 --- /dev/null +++ b/simutrans/trunk/simtool-scripted.h @@ -0,0 +1,131 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef SIMTOOL_SCRIPTED_H +#define SIMTOOL_SCRIPTED_H + +#include "simmenu.h" +#include "script/script.h" +#include "utils/plainstring.h" + +class script_vm_t; +class skin_desc_t; + + +/** + * Information about scripted tools, extracted from description.tab. + * - data from description.tab: + * + * plainstring path + * plainstring title=tool for test 2 + * bool type=one_click or two_click + * bool restart=1 + * plainstring menu=my_script + * skin_desc_t* icon=one_click_test: cursor, icon, marker + * + */ +struct scripted_tool_info_t { + plainstring path; ///< path to files + plainstring title; ///< name of tool (used for dialog) + plainstring tooltip; + plainstring menu_arg; ///< menu name, where this tool should appear (used in menuconf.tab) + const skin_desc_t* desc; ///< skin object used for cursor (0), icon (1), and maybe marker (2) + bool restart; ///< true, if script vm has to be restarted after work() + bool is_one_click; ///< true, if tool is one-click (otherwise needs two clicks/coordinates to work) + /// sets default values + scripted_tool_info_t() + { + desc = NULL; + restart = true; + is_one_click = true; + } +}; + +class exec_script_base_t { +private: + /// information about tool, take ownership of pointer + const scripted_tool_info_t *info; + + void load_script(const char* path, player_t* player); +protected: + + void init_images(tool_t *tool) const; + /// the vm, will be initialized in init() + script_vm_t *script; + /// starts vm, sets our_player, returns true if successful + bool init_vm(player_t* player); + + template + const char* call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret); + template + const char* call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret, A1 arg1); + template + const char* call_function(script_vm_t::call_type_t ct, const char* function, player_t* player, R& ret, A1 arg1, A2 arg2); +public: + exec_script_base_t(const scripted_tool_info_t *i) : info(i), script(NULL) {} + virtual ~exec_script_base_t(); + + void set_info(const scripted_tool_info_t *i); + const scripted_tool_info_t* get_info() const { return info; }; + + const char* get_menu_arg() const { return info ? info->menu_arg.c_str() : ""; } + + /// has to be called if the tool is active, to resume script if a work-command gets suspended + void step(player_t* player); + + const char *get_tooltip(const player_t *) const { return info ? info->tooltip.c_str() : ""; } +}; + + +class tool_exec_script_t : public tool_t, public exec_script_base_t { +protected: + +public: + tool_exec_script_t(const scripted_tool_info_t *info = NULL); + /// is network-safe, as calls to work-commands will be properly handled in network mode + bool is_init_network_save() const OVERRIDE { return true; } + bool is_work_network_save() const OVERRIDE { return true; } + + bool init(player_t* player) OVERRIDE; + bool exit(player_t* player) OVERRIDE; + + const char *work(player_t* player, koord3d pos) OVERRIDE; + + using exec_script_base_t::get_tooltip; +}; + +class tool_exec_two_click_script_t : public two_click_tool_t, public exec_script_base_t { + + image_id marker; +public: + tool_exec_two_click_script_t(const scripted_tool_info_t *info = NULL); + /// is network-safe, as calls to work-commands will be properly handled in network mode + bool is_work_network_save() const OVERRIDE { return true; } + bool is_init_network_save() const OVERRIDE { return true; } + + bool init(player_t* player) OVERRIDE; + bool exit(player_t* player) OVERRIDE; + + uint8 is_valid_pos(player_t* player, const koord3d &pos, const char *&error, const koord3d &start) OVERRIDE; + const char *do_work(player_t* player, const koord3d &start, const koord3d &end) OVERRIDE; + void mark_tiles(player_t* player, const koord3d &start, const koord3d &end) OVERRIDE; + + bool mark_tile(player_t* player, const koord3d &start); + // two_click_tool_t calls init() after do_work(), + // because it assumes all work is done. + // We have to block this. + bool waiting_for_do_work; + // instead, init() will be called in step() + // if necessary + bool needs_call_to_init; + + void set_marker(image_id m) { marker = m; } + + virtual image_id get_marker_image() const OVERRIDE; + + using exec_script_base_t::get_tooltip; +}; + +#endif diff --git a/simutrans/trunk/simtool.cc b/simutrans/trunk/simtool.cc index 4052ede01..25d6ed957 100644 --- a/simutrans/trunk/simtool.cc +++ b/simutrans/trunk/simtool.cc @@ -92,7 +92,6 @@ #include "simtool.h" #include "player/finance.h" - #define is_scenario() welt->get_scenario()->is_scripted() #define CHECK_FUNDS() \ @@ -5695,7 +5694,7 @@ const char *tool_build_factory_t::work( player_t *player, koord3d pos ) /** * link tool: links products of factory one with factory two (if possible) */ -image_id tool_link_factory_t::get_marker_image() +image_id tool_link_factory_t::get_marker_image() const { return cursor; } @@ -5989,7 +5988,7 @@ const char *tool_forest_t::do_work( player_t *player, const koord3d &start, cons } -image_id tool_stop_mover_t::get_marker_image() +image_id tool_stop_mover_t::get_marker_image() const { return cursor; } @@ -6339,7 +6338,7 @@ const char *tool_make_stop_public_t::work( player_t *player, koord3d p ) /* merge stop */ -image_id tool_merge_stop_t::get_marker_image() +image_id tool_merge_stop_t::get_marker_image() const { return cursor; } @@ -6443,6 +6442,7 @@ const char *tool_merge_stop_t::do_work( player_t *player, const koord3d &last_po return NULL; } + bool tool_show_trees_t::init( player_t * ) { env_t::hide_trees = !env_t::hide_trees; diff --git a/simutrans/trunk/simtool.h b/simutrans/trunk/simtool.h index 5f2539c04..5b17d27b1 100644 --- a/simutrans/trunk/simtool.h +++ b/simutrans/trunk/simtool.h @@ -535,7 +535,7 @@ private: char const* do_work(player_t*, koord3d const&, koord3d const&) OVERRIDE; void mark_tiles(player_t*, koord3d const&, koord3d const&) OVERRIDE {} uint8 is_valid_pos(player_t*, koord3d const&, char const*&, koord3d const&) OVERRIDE; - image_id get_marker_image() OVERRIDE; + image_id get_marker_image() const OVERRIDE; }; class tool_headquarter_t : public kartenboden_tool_t { @@ -598,7 +598,7 @@ private: char const* do_work(player_t*, koord3d const&, koord3d const&) OVERRIDE; void mark_tiles(player_t*, koord3d const&, koord3d const&) OVERRIDE {} uint8 is_valid_pos(player_t*, koord3d const&, char const*&, koord3d const&) OVERRIDE; - image_id get_marker_image() OVERRIDE; + image_id get_marker_image() const OVERRIDE; void read_start_position(player_t *player, const koord3d &pos); }; @@ -631,7 +631,7 @@ private: char const* do_work(player_t*, koord3d const&, koord3d const&) OVERRIDE; void mark_tiles(player_t*, koord3d const&, koord3d const&) OVERRIDE; uint8 is_valid_pos(player_t*, koord3d const&, char const*&, koord3d const&) OVERRIDE; - image_id get_marker_image() OVERRIDE; + image_id get_marker_image() const OVERRIDE; }; @@ -645,6 +645,7 @@ public: char const* work(player_t*, koord3d) OVERRIDE { return default_param ? default_param : ""; } }; + /********************* one click tools ****************************/ class tool_pause_t : public tool_t { diff --git a/simutrans/trunk/simutrans/script/tool_base.nut b/simutrans/trunk/simutrans/script/tool_base.nut new file mode 100644 index 000000000..40f099011 --- /dev/null +++ b/simutrans/trunk/simutrans/script/tool_base.nut @@ -0,0 +1,44 @@ +// stubs for functions needed by scripted tools +function init(player) +{ + return true +} + +function exit(player) +{ + return true +} +// one-click tools +function work(player, pos) +{ + return null +} + +// two-click tools +function do_work(player, start, end) +{ + return null +} + +function mark_tiles(player, start, end) +{ +} + +/** + * Can the tool start/end on pos? If it is the second click, start is the position of the first click + * 0 = no + * 1 = This tool can work on this tile (with single click) + * 2 = On this tile can dragging start/end + * 3 = Both (1 and 2) + * + */ +function is_valid_pos(player, start, pos) +{ + return 2 +} + +// dummy auxiliary function, will be regularly called +function step() +{ + // do not implement +} diff --git a/simutrans/trunk/simworld.cc b/simutrans/trunk/simworld.cc index f4d19d4a3..a0cc93b1e 100644 --- a/simutrans/trunk/simworld.cc +++ b/simutrans/trunk/simworld.cc @@ -34,7 +34,7 @@ #include "simunits.h" #include "simversion.h" #include "display/simview.h" -#include "simtool.h" +#include "simtool-scripted.h" #include "gui/simwin.h" #include "simworld.h" #include "sys/simsys.h" @@ -4204,6 +4204,13 @@ void karte_t::step() if( get_scenario()->is_scripted() ) { get_scenario()->step(); } + + if (selected_tool[active_player_nr]) { + if (exec_script_base_t* esb = dynamic_cast(selected_tool[active_player_nr])) { + esb->step(get_active_player()); + } + } + DBG_DEBUG4("karte_t::step", "end"); }