From fc094e2dbca2c352cf42a38048aed17db1f15b7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 21 Jul 2015 16:26:41 -0400 Subject: [PATCH 001/109] OS X: Check for existence of SDL.framework before building --- library/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 845f8dd07..16cf03d1a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -298,6 +298,9 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) IF(APPLE) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) + IF(NOT EXISTS ${SDL_LIBRARY}) + MESSAGE(FATAL_ERROR "SDL framework not found. Make sure CMAKE_INSTALL_PREFIX is specified and correct.") + ENDIF() SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) From c34e9bb657c9fdefa7a6e46dd4c9c2c13705da20 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 22 Jul 2015 17:27:57 -0400 Subject: [PATCH 002/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5c4d627d7..5850f66fa 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5c4d627d7306b8425493f8ebbfe66c0f347f6871 +Subproject commit 5850f66fa5e37401b1536b574a48d645993520e2 From ccb3b34c475598208c3016037144f97d6b9382c4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 16:16:59 -0400 Subject: [PATCH 003/109] Update NEWS --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 4dd16cada..fdcf9fb51 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ DFHack Future The console on Linux and OS X now recognizes keyboard input between prompts JSON libraries available (C++ and Lua) More build information available in plugins + Fixed a rare overflow issue that could cause crashes on Linux and OS X + Stopped DF window from receiving input when unfocused on OS X Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() @@ -50,6 +52,9 @@ DFHack Future gui/gm-editor: Pointers can now be displaced gui/hack-wish: renamed to gui/create-item "keybinding list" accepts a context + lever: + - Lists lever names + - "lever pull" can be used to pull the currently-selected lever memview: Fixed display issue nyan: Can now be stopped with dfhack-run quicksave: Restricted to fortress mode From 27295a9b6ff4b90134f2f6710fc10c8b56f1ae8a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 23:24:00 -0400 Subject: [PATCH 004/109] Log DFHack and save-specific information on world (un)load events --- library/Core.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 11af466ce..29644f535 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -73,6 +73,7 @@ using namespace DFHack; #include #include #include "tinythread.h" +#include "md5wrapper.h" #include "SDL_events.h" @@ -1755,6 +1756,68 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve void Core::onStateChange(color_ostream &out, state_change_event event) { + using df::global::gametype; + static md5wrapper md5w; + static std::string ostype = ""; + + if (!ostype.size()) + { + ostype = "unknown OS"; + if (vinfo) { + switch (vinfo->getOS()) + { + case OS_WINDOWS: + ostype = "Windows"; + break; + case OS_APPLE: + ostype = "OS X"; + break; + case OS_LINUX: + ostype = "Linux"; + break; + default: + break; + } + } + } + + switch (event) + { + case SC_WORLD_LOADED: + case SC_WORLD_UNLOADED: + case SC_MAP_LOADED: + case SC_MAP_UNLOADED: + if (world && world->cur_savegame.save_dir.size()) + { + std::string evtlogpath = "data/save/" + world->cur_savegame.save_dir + "/events-dfhack.log"; + std::ofstream evtlog; + evtlog.open(evtlogpath, std::ios_base::app); // append + if (evtlog.fail()) + { + out.printerr("Could not append to %s\n", evtlogpath.c_str()); + } + else + { + char timebuf[30]; + time_t rawtime = time(NULL); + struct tm * timeinfo = localtime(&rawtime); + strftime(timebuf, sizeof(timebuf), "[%Y-%m-%dT%H:%M:%S%z] ", timeinfo); + evtlog << timebuf; + evtlog << "DFHack " << Version::git_description() << " on " << ostype << "; "; + evtlog << "cwd md5: " << md5w.getHashFromString(getHackPath()).substr(0, 10) << "; "; + evtlog << "save: " << world->cur_savegame.save_dir << "; "; + evtlog << sc_event_name(event) << "; "; + if (gametype) + evtlog << "game type " << ENUM_KEY_STR(game_type, *gametype) << " (" << *gametype << ")"; + else + evtlog << "game type unavailable"; + evtlog << std::endl; + } + } + default: + break; + } + EventManager::onStateChange(out, event); buildings_onStateChange(out, event); From 03aa2399a0bf0a0078c6c4fd9447ad504a1282c8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 23 Jul 2015 23:25:01 -0400 Subject: [PATCH 005/109] Add environment variables to skip global and vtable initialization --- library/VersionInfoFactory.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 1b66b583a..2336b8d6c 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -81,6 +81,8 @@ VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) { + bool no_vtables = getenv("DFHACK_NO_VTABLES"); + bool no_globals = getenv("DFHACK_NO_GLOBALS"); TiXmlElement* pMemEntry; const char *cstr_name = entry->Attribute("name"); if (!cstr_name) @@ -136,6 +138,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) cerr << "Dummy symbol table entry: " << cstr_key << endl; continue; } + if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) + continue; uint32_t addr = strtol(cstr_value, 0, 0); if (is_vtable) mem->setVTable(cstr_key, addr); From bbe2002a28266505758cbd3db48156d62aad4397 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 10:49:17 -0400 Subject: [PATCH 006/109] Update Compile.rst --- Compile.rst | 113 ++++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/Compile.rst b/Compile.rst index ce4449524..260af3b13 100644 --- a/Compile.rst +++ b/Compile.rst @@ -4,28 +4,43 @@ Building DFHACK .. contents:: - - -===== -Linux -===== -On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD. - +=================== How to get the code =================== DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -Having a 'git' package installed is the minimal requirement, but some sort of git gui or git integration for your favorite text editor/IDE will certainly help. +The code resides here: https://github.com/DFHack/dfhack + +On Linux and OS X, having a 'git' package installed is the minimal requirement (see below for OS X instructions), +but some sort of git gui or git integration for your favorite text editor/IDE will certainly help. + +On Windows, you will need some sort of Windows port of git, or a GUI. Some examples: + + * http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply. + * http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit The code resides here: https://github.com/DFHack/dfhack -If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:: +To get the code:: + + git clone --recursive https://github.com/DFHack/dfhack + cd dfhack + +If your version of git does not support the ``--recursive`` flag, you will need to omit it and run +``git submodule update --init`` after entering the dfhack directory. + +If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address instead:: - git clone git://github.com/DFHack/dfhack.git + git clone --recursive git://github.com/DFHack/dfhack.git cd dfhack - git submodule init - git submodule update -If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). +The tortoisegit GUI should have the equivalent options included. + +If you want to get really involved with the development, create an account on Github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). + +===== +Linux +===== +On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD. Dependencies ============ @@ -46,7 +61,7 @@ To build Stonesense, you'll also need OpenGL headers. Build ===== -Building is fairly straightforward. Enter the ``build`` folder and start the build like this:: +Building is fairly straightforward. Enter the ``build`` folder (or create an empty folder in the DFHack directory to use instead) and start the build like this:: cd build cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF @@ -72,8 +87,8 @@ Fixing the libstdc++ version bug When compiling dfhack yourself, it builds against your system libc. When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which -is usually way older, and incompatible with your dfhack. This manifests with -the error message:: +comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions. +This manifests itself with the error message:: ./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so) @@ -84,18 +99,33 @@ to your system lib and everything will work fine:: cd /path/to/DF/ rm libs/libstdc++.so.6 +Alternatively, this issue can be avoided by compiling DFHack with GCC 4.5. + ======== Mac OS X ======== -If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST. +DFHack functions similarly on OS X and Linux, and the majority of the +information above regarding the build process (cmake and make) applies here +as well. + +* If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST. +* If you are building on 10.10+, read the "Yosemite Changes" subsection before building. 1. Download and unpack a copy of the latest DF 2. Install Xcode from Mac App Store 3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. 4. Install dependencies - Option 1: Using MacPorts: + Option 1: Using Homebrew: + + * `Install Homebrew `_ and run: + * ``brew tap homebrew/versions`` + * ``brew install git`` + * ``brew install cmake`` + * ``brew install gcc45`` + + Option 2: Using MacPorts: * `Install MacPorts `_ * Run ``sudo port install gcc45 +universal cmake +universal git-core +universal`` @@ -103,14 +133,6 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa At some point during this process, it may ask you to install a Java environment; let it do so. - Option 2: Using Homebrew: - - * `Install Homebrew `_ and run: - * ``brew tap homebrew/versions`` - * ``brew install git`` - * ``brew install cmake`` - * ``brew install gcc45 --enable-multilib`` - 5. Install perl dependencies 1. ``sudo cpan`` @@ -123,23 +145,21 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa 6. Get the dfhack source:: - git clone git://github.com/DFHack/dfhack.git + git clone --recursive https://github.com/DFHack/dfhack.git cd dfhack - git submodule init - git submodule update 7. Set environment variables: + Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: + + export CC=/usr/local/bin/gcc-4.5 + export CXX=/usr/local/bin/g++-4.5 + Macports:: export CC=/opt/local/bin/gcc-mp-4.5 export CXX=/opt/local/bin/g++-mp-4.5 - Homebrew:: - - export CC=/usr/local/bin/gcc-4.5 - export CXX=/usr/local/bin/g++-4.5 - 8. Build dfhack:: mkdir build-osx @@ -172,27 +192,6 @@ Windows ======= On Windows, DFHack replaces the SDL library distributed with DF. -How to get the code -=================== -DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -You will need some sort of Windows port of git, or a GUI. Some examples: - - * http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply. - * http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit :) - -The code resides here: https://github.com/DFHack/dfhack - -If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:: - - git clone git://github.com/DFHack/dfhack.git - cd dfhack - git submodule init - git submodule update - -The tortoisegit GUI should have the equivalent options included. - -If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). - Dependencies ============ First, you need ``cmake``. Get the win32 installer version from the official @@ -264,7 +263,7 @@ The most important parts of DFHack are the Core, Console, Modules and Plugins. * Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF. * Console is a thread-safe console that can be used to invoke commands exported by Plugins. -* Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. Higl-level is mostly method calls, low-level publicly visible pointers to DF's data structures. +* Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. High-level is mostly method calls, low-level publicly visible pointers to DF's data structures. * Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick. Rudimentary API documentation can be built using doxygen (see build options with ``ccmake`` or ``cmake-gui``). @@ -280,7 +279,7 @@ DF data structure definitions DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule. -Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code. +Data structure layouts are described in files following the df.\*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code. Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime. From 6c55164be4c1d830f3df9a593fa6e7abd12eb19f Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 10:54:59 -0400 Subject: [PATCH 007/109] Fix a potential crash in tweak farm-plot-select Related to #396 --- plugins/tweak/tweaks/farm-plot-select.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 063180c75..11d772450 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -53,9 +53,9 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { df::building_farmplotst* farm_plot = getFarmPlot(); - if (farm_plot) + if (farm_plot && ui->selected_farm_crops.size() > 0) { - if (input->count(interface_key::SELECT_ALL) && ui->selected_farm_crops.size() > 0) + if (input->count(interface_key::SELECT_ALL)) { int32_t crop_id = getSelectedCropId(); for (int season = 0; season < 4; season++) @@ -80,7 +80,7 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); - if (!getFarmPlot()) + if (!getFarmPlot() || !ui->selected_farm_crops.size()) return; auto dims = Gui::getDwarfmodeViewDims(); int x = dims.menu_x1 + 1, From f1a6e5fbe9890de7b5a2cb2b4a8b64575e61d107 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 11:13:13 -0400 Subject: [PATCH 008/109] tweak farm-plot-select: Don't require crops to be discovered Crops whose seeds haven't been discovered are still listed in menus as of 0.40.10 --- NEWS | 4 +++- plugins/tweak/tweaks/farm-plot-select.h | 32 +++++++++++-------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index fdcf9fb51..358e6b19a 100644 --- a/NEWS +++ b/NEWS @@ -64,7 +64,9 @@ DFHack Future - Supports fortress mode loo[k] menu - Recognizes ? and ; keys teleport: Fixed cursor recognition - tweak: debug output now logged to stderr.log instead of console + tweak: + - debug output now logged to stderr.log instead of console - makes DFHack start faster + - farm-plot-select: Fixed issues with selecting undiscovered crops workflow: Improved handling of plant reactions Removed diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 11d772450..38b0596bd 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -23,26 +23,22 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { { // Adapted from autofarm using namespace df::enums::plant_raw_flags; - // Discovered? - if (ui->tasks.discovered_plants[crop_id]) + // Possible to plant? + df::plant_raw* raws = world->raws.plants.all[crop_id]; + if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) { - // Possible to plant? - df::plant_raw* raws = world->raws.plants.all[crop_id]; - if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) + // Right depth? + DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z); + MapExtras::MapCache mc; + MapExtras::Block * b = mc.BlockAt(cursor / 16); + if (!b || !b->is_valid()) + return false; + auto &block = *b->getRaw(); + df::tile_designation &des = + block.designation[farm_plot->centerx % 16][farm_plot->centery % 16]; + if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean) { - // Right depth? - DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z); - MapExtras::MapCache mc; - MapExtras::Block * b = mc.BlockAt(cursor / 16); - if (!b || !b->is_valid()) - return false; - auto &block = *b->getRaw(); - df::tile_designation &des = - block.designation[farm_plot->centerx % 16][farm_plot->centery % 16]; - if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean) - { - return true; - } + return true; } } return false; From 726ffd6417ac67b779adc5a1a390f7b8523a0b57 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 24 Jul 2015 13:58:09 -0400 Subject: [PATCH 009/109] gm-editor: Define some aliases for useful variables --- NEWS | 5 ++++- scripts/gui/gm-editor.lua | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 358e6b19a..7a1b512e1 100644 --- a/NEWS +++ b/NEWS @@ -49,7 +49,10 @@ DFHack Future - weather display now separated from the date display - New mouse cursor widget full-heal: "-r" option removes corpses - gui/gm-editor: Pointers can now be displaced + gui/gm-editor + - Pointers can now be displaced + - Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc. + - Now avoids errors with unrecognized types gui/hack-wish: renamed to gui/create-item "keybinding list" accepts a context lever: diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 344dd7e07..5a4bc5dab 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -228,6 +228,8 @@ function GmEditorUi:editSelected(index,choice) self:updateTarget(true) elseif trg_type == 'userdata' or trg_type == 'table' then self:pushTarget(trg.target[trg_key]) + elseif trg_type == 'nil' or trg_type == 'function' then + -- ignore else print("Unknown type:"..trg_type) pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end) @@ -379,21 +381,45 @@ function GmEditorUi:popTarget() self:updateTarget() end function show_editor(trg) + if not trg then + qerror('Target not found') + end local screen = GmEditorUi{target=trg} screen:show() end +eval_env = {} +setmetatable(eval_env, {__index = function(_, k) + if k == 'scr' or k == 'screen' then + return dfhack.gui.getCurViewscreen() + elseif k == 'bld' or k == 'building' then + return dfhack.gui.getSelectedBuilding() + elseif k == 'item' then + return dfhack.gui.getSelectedItem() + elseif k == 'job' then + return dfhack.gui.getSelectedJob() + elseif k == 'wsjob' or k == 'workshop_job' then + return dfhack.gui.getSelectedWorkshopJob() + elseif k == 'unit' then + return dfhack.gui.getSelectedUnit() + else + return _G[k] + end +end}) +function eval(s) + local f, err = load("return " .. s, "expression", "t", eval_env) + if err then qerror(err) end + return f() +end if #args~=0 then if args[1]=="dialog" then function thunk(entry) - local t=load("return "..entry)() - show_editor(t) + show_editor(eval(entry)) end dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) elseif args[1]=="free" then show_editor(df.reinterpret_cast(df[args[2]],args[3])) else - local t=load("return "..args[1])() - show_editor(t) + show_editor(eval(args[1])) end else show_editor(getTargetFromScreens()) From 175edf501aa2fe77d0a3893737b063343b8e5349 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Jun 2015 21:49:22 -0400 Subject: [PATCH 010/109] Add "confirm" plugin - implements a few confirmation dialogs See #577 --- plugins/CMakeLists.txt | 1 + plugins/confirm.cpp | 255 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 plugins/confirm.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f5a82e916..628c521de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp) + DFHACK_PLUGIN(confirm confirm.cpp) DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(deramp deramp.cpp) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp new file mode 100644 index 000000000..3c1497c87 --- /dev/null +++ b/plugins/confirm.cpp @@ -0,0 +1,255 @@ +#include +#include +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "uicommon.h" +#include "df/viewscreen_tradegoodsst.h" + +using namespace DFHack; + +DFHACK_PLUGIN("confirm"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +typedef std::set ikey_set; +command_result df_confirm (color_ostream &out, std::vector & parameters); + +static std::multimap hooks; + +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ +static cls cls##_instance; \ +struct cls##_hooks : cls::screen_type { \ + typedef cls::screen_type interpose_base; \ + DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ + { \ + cls##_instance.screen = this; \ + if (!cls##_instance.feed(input)) \ + INTERPOSE_NEXT(feed)(input); \ + } \ + DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ + { \ + cls##_instance.screen = this; \ + INTERPOSE_NEXT(render)(); \ + cls##_instance.render(); \ + } \ + DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ + { \ + return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, feed); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, render); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, key_conflict); + +template +class confirmation { +public: + enum cstate { INACTIVE, ACTIVE, SELECTED }; + typedef T screen_type; + screen_type *screen; + bool feed (ikey_set *input) { + if (state == INACTIVE) + { + for (auto it = input->begin(); it != input->end(); ++it) + { + if (intercept_key(*it)) + { + last_key = *it; + state = ACTIVE; + return true; + } + } + return false; + } + else if (state == ACTIVE) + { + if (input->count(df::interface_key::LEAVESCREEN)) + state = INACTIVE; + else if (input->count(df::interface_key::SELECT)) + state = SELECTED; + return true; + } + return false; + } + bool key_conflict (df::interface_key key) + { + if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN) + return false; + return state == ACTIVE; + } + void render() { + static std::vector lines; + Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK); + if (state == ACTIVE) + { + split_string(&lines, get_message(), "\n"); + size_t max_length = 30; + for (auto it = lines.begin(); it != lines.end(); ++it) + max_length = std::max(max_length, it->size()); + int width = max_length + 4; + int height = lines.size() + 4; + int x1 = (gps->dimx / 2) - (width / 2); + int x2 = x1 + width - 1; + int y1 = (gps->dimy / 2) - (height / 2); + int y2 = y1 + height - 1; + for (int x = x1; x <= x2; x++) + { + Screen::paintTile(border_ud, x, y1); + Screen::paintTile(border_ud, x, y2); + } + for (int y = y1; y <= y2; y++) + { + Screen::paintTile(border_lr, x1, y); + Screen::paintTile(border_lr, x2, y); + } + Screen::paintTile(corner_ul, x1, y1); + Screen::paintTile(corner_ur, x2, y1); + Screen::paintTile(corner_dl, x1, y2); + Screen::paintTile(corner_dr, x2, y2); + std::string title = " " + get_title() + " "; + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), + (gps->dimx / 2) - (title.size() / 2), y1, title); + int x = x1 + 2; + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y2, ": Cancel"); + x = x2 - 2 - 4 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); + OutputString(COLOR_WHITE, x, y2, ": Ok"); + Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); + for (size_t i = 0; i < lines.size(); i++) + { + Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]); + } + } + else if (state == SELECTED) + { + ikey_set tmp; + tmp.insert(last_key); + screen->feed(&tmp); + state = INACTIVE; + } + } + virtual bool intercept_key (df::interface_key key) = 0; + virtual std::string get_title() { return "Confirm"; } + virtual std::string get_message() = 0; + virtual UIColor get_color() { return COLOR_YELLOW; } +protected: + cstate state; + df::interface_key last_key; +}; + +class trade_seize_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } + virtual std::string get_id() { return "trade-seize"; } + virtual std::string get_title() { return "Confirm seize"; } + virtual std::string get_message() { return "Are you sure you want to sieze these goods?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); + +class trade_offer_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } + virtual std::string get_id() { return "trade-offer"; } + virtual std::string get_title() { return "Confirm offer"; } + virtual std::string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); + +#define CHOOK(cls) \ + HOOK_ACTION(cls, render) \ + HOOK_ACTION(cls, feed) \ + HOOK_ACTION(cls, key_conflict) + +#define CHOOKS \ + CHOOK(trade_seize_confirmation) \ + CHOOK(trade_offer_confirmation) + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + std::vector hook_names; +#define HOOK_ACTION(cls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(cls##_hooks, method))); \ + if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); + CHOOKS +#undef HOOK_ACTION + std::string help = + " confirmation enable|disable option|all ...\n" + "Available options:\n " + join_strings(", ", hook_names); + commands.push_back(PluginCommand( + "confirm", + "Confirmation dialogs", + df_confirm, + false, //allow non-interactive use + help.c_str() + )); + return CR_OK; +} + +DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) +{ + if (is_enabled != enable) + { +#define HOOK_ACTION(cls, method) !INTERPOSE_HOOK(cls##_hooks, method).apply(enable) || + if (CHOOKS 0) + return CR_FAILURE; +#undef HOOK_ACTION + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ +#define HOOK_ACTION(cls, method) INTERPOSE_HOOK(cls##_hooks, method).remove(); + CHOOKS; +#undef HOOK_ACTION + return CR_OK; +} + +void enable_conf (color_ostream &out, std::string name, bool state) +{ + bool found = false; + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + if (it->first == name) + { + found = true; + it->second.apply(state); + } + } + if (!found) + out.printerr("Unrecognized option: %s\n", name.c_str()); +} + +command_result df_confirm (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + bool state = true; + for (auto it = parameters.begin(); it != parameters.end(); ++it) + { + if (*it == "enable") + state = true; + else if (*it == "disable") + state = false; + else if (*it == "all") + { + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + it->second.apply(state); + } + } + else + enable_conf(out, *it, state); + } + return CR_OK; +} From 35f229c578ce3688ed8e4e7d2c696e423095b13a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Jun 2015 10:31:12 -0400 Subject: [PATCH 011/109] Add confirmations for hauling route/stop deletion and depot removal --- plugins/confirm.cpp | 103 +++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 3c1497c87..91db60d81 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -7,18 +7,26 @@ #include "PluginManager.h" #include "VTableInterpose.h" #include "uicommon.h" +#include "modules/Gui.h" + +#include "df/building_tradedepotst.h" +#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; +using namespace df::enums; +using std::string; +using std::vector; DFHACK_PLUGIN("confirm"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); typedef std::set ikey_set; -command_result df_confirm (color_ostream &out, std::vector & parameters); +command_result df_confirm (color_ostream &out, vector & parameters); -static std::multimap hooks; +static std::multimap hooks; #define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ static cls cls##_instance; \ @@ -82,7 +90,7 @@ public: return state == ACTIVE; } void render() { - static std::vector lines; + static vector lines; Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); @@ -115,7 +123,9 @@ public: Screen::paintTile(corner_ur, x2, y1); Screen::paintTile(corner_dl, x1, y2); Screen::paintTile(corner_dr, x2, y2); - std::string title = " " + get_title() + " "; + string title = " " + get_title() + " "; + Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK), + x2 - 6, y1, "DFHack"); Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (gps->dimx / 2) - (title.size() / 2), y1, title); int x = x1 + 2; @@ -139,8 +149,8 @@ public: } } virtual bool intercept_key (df::interface_key key) = 0; - virtual std::string get_title() { return "Confirm"; } - virtual std::string get_message() = 0; + virtual string get_title() { return "Confirm"; } + virtual string get_message() = 0; virtual UIColor get_color() { return COLOR_YELLOW; } protected: cstate state; @@ -150,38 +160,83 @@ protected: class trade_seize_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } - virtual std::string get_id() { return "trade-seize"; } - virtual std::string get_title() { return "Confirm seize"; } - virtual std::string get_message() { return "Are you sure you want to sieze these goods?"; } + virtual string get_id() { return "trade-seize"; } + virtual string get_title() { return "Confirm seize"; } + virtual string get_message() { return "Are you sure you want to sieze these goods?"; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); class trade_offer_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } - virtual std::string get_id() { return "trade-offer"; } - virtual std::string get_title() { return "Confirm offer"; } - virtual std::string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } + virtual string get_id() { return "trade-offer"; } + virtual string get_title() { return "Confirm offer"; } + virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); +class hauling_route_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (ui->main.mode == ui_sidebar_mode::Hauling && ui->hauling.view_routes.size()) + return key == df::interface_key::D_HAULING_REMOVE; + return false; + } + virtual string get_id() { return "haul-delete"; } + virtual string get_title() { return "Confirm deletion"; } + virtual string get_message() + { + std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route"; + return std::string("Are you sure you want to delete this ") + type + "?"; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation); + +class depot_remove_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + df::building_tradedepotst *depot = virtual_cast(Gui::getAnyBuilding(screen)); + if (depot && key == df::interface_key::DESTROYBUILDING) + { + for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it) + { + if ((**it).time_remaining) + return true; + } + } + return false; + } + virtual string get_id() { return "depot-remove"; } + virtual string get_title() { return "Confirm depot removal"; } + virtual string get_message() + { + return "Are you sure you want to remove this depot?\n" + "Merchants are present and will lose profits."; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); + #define CHOOK(cls) \ - HOOK_ACTION(cls, render) \ - HOOK_ACTION(cls, feed) \ - HOOK_ACTION(cls, key_conflict) + HOOK_ACTION(cls, cls##_hooks, render) \ + HOOK_ACTION(cls, cls##_hooks, feed) \ + HOOK_ACTION(cls, cls##_hooks, key_conflict) #define CHOOKS \ CHOOK(trade_seize_confirmation) \ - CHOOK(trade_offer_confirmation) + CHOOK(trade_offer_confirmation) \ + CHOOK(hauling_route_delete_confirmation) \ + CHOOK(depot_remove_confirmation) -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { - std::vector hook_names; -#define HOOK_ACTION(cls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(cls##_hooks, method))); \ + vector hook_names; +#define HOOK_ACTION(cls, hookcls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(hookcls, method))); \ if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); CHOOKS #undef HOOK_ACTION - std::string help = + string help = " confirmation enable|disable option|all ...\n" "Available options:\n " + join_strings(", ", hook_names); commands.push_back(PluginCommand( @@ -198,7 +253,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { if (is_enabled != enable) { -#define HOOK_ACTION(cls, method) !INTERPOSE_HOOK(cls##_hooks, method).apply(enable) || +#define HOOK_ACTION(cls, hookcls, method) !INTERPOSE_HOOK(hookcls, method).apply(enable) || if (CHOOKS 0) return CR_FAILURE; #undef HOOK_ACTION @@ -210,13 +265,13 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_shutdown (color_ostream &out) { -#define HOOK_ACTION(cls, method) INTERPOSE_HOOK(cls##_hooks, method).remove(); +#define HOOK_ACTION(cls, hookcls, method) INTERPOSE_HOOK(hookcls, method).remove(); CHOOKS; #undef HOOK_ACTION return CR_OK; } -void enable_conf (color_ostream &out, std::string name, bool state) +void enable_conf (color_ostream &out, string name, bool state) { bool found = false; for (auto it = hooks.begin(); it != hooks.end(); ++it) @@ -231,7 +286,7 @@ void enable_conf (color_ostream &out, std::string name, bool state) out.printerr("Unrecognized option: %s\n", name.c_str()); } -command_result df_confirm (color_ostream &out, std::vector & parameters) +command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; From ed3cbe2e605c011701b1297281481687d59aa3cb Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 9 Jun 2015 13:52:37 -0400 Subject: [PATCH 012/109] Add several trade-related confirmations --- plugins/confirm.cpp | 83 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 91db60d81..419887af5 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -11,6 +11,7 @@ #include "df/building_tradedepotst.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; @@ -28,7 +29,8 @@ command_result df_confirm (color_ostream &out, vector & parameters); static std::multimap hooks; -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) +#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ static cls cls##_instance; \ struct cls##_hooks : cls::screen_type { \ typedef cls::screen_type interpose_base; \ @@ -49,9 +51,9 @@ struct cls##_hooks : cls::screen_type { \ return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ } \ }; \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, feed); \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, render); \ -IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, key_conflict); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); template class confirmation { @@ -131,7 +133,7 @@ public: int x = x1 + 2; OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); OutputString(COLOR_WHITE, x, y2, ": Cancel"); - x = x2 - 2 - 4 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); + x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); OutputString(COLOR_WHITE, x, y2, ": Ok"); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); @@ -157,6 +159,34 @@ protected: df::interface_key last_key; }; +class trade_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_TRADE; } + virtual string get_id() { return "trade"; } + virtual string get_title() { return "Confirm trade"; } + virtual string get_message() { return "Are you sure you want to trade the selected goods?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); + +class trade_cancel_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (key == df::interface_key::LEAVESCREEN) + { + #define check_list(list) for (auto it = screen->list.begin(); it != screen->list.end(); ++it) if (*it) return true + check_list(trader_selected); + check_list(broker_selected); + #undef check_list + } + return false; + } + virtual string get_id() { return "trade-cancel"; } + virtual string get_title() { return "Cancel trade"; } + virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; } +}; +IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); + class trade_seize_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } @@ -218,16 +248,57 @@ public: }; IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); +class squad_disband_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return screen->num_squads && key == df::interface_key::D_MILITARY_DISBAND_SQUAD; + } + virtual string get_id() { return "squad-disband"; } + virtual string get_title() { return "Disband squad"; } + virtual string get_message() { return "Are you sure you want to disband this squad?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation); + +class note_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE; + } + virtual string get_id() { return "note-delete"; } + virtual string get_title() { return "Delete note"; } + virtual string get_message() { return "Are you sure you want to delete this note?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation); + +class route_delete_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE; + } + virtual string get_id() { return "route-delete"; } + virtual string get_title() { return "Delete route"; } + virtual string get_message() { return "Are you sure you want to delete this route?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); + #define CHOOK(cls) \ HOOK_ACTION(cls, cls##_hooks, render) \ HOOK_ACTION(cls, cls##_hooks, feed) \ HOOK_ACTION(cls, cls##_hooks, key_conflict) #define CHOOKS \ + CHOOK(trade_confirmation) \ + CHOOK(trade_cancel_confirmation) \ CHOOK(trade_seize_confirmation) \ CHOOK(trade_offer_confirmation) \ CHOOK(hauling_route_delete_confirmation) \ - CHOOK(depot_remove_confirmation) + CHOOK(depot_remove_confirmation) \ + CHOOK(squad_disband_confirmation) \ + CHOOK(note_delete_confirmation) \ + CHOOK(route_delete_confirmation) DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { From 6352a6e266f226e4d4e7bbf8497e6ee974f09187 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 12:03:29 -0400 Subject: [PATCH 013/109] Expand confirm plugin and improve safety of adding/removing hooks * New option: trade-select-all: Prompts when selecting all goods when some goods are already selected * Improved trade confirmation message depending on selected goods * Made seize and offer confirmations only display when goods in the appropriate columns are selected * States of each option are now listed by "confirm help" --- plugins/confirm.cpp | 241 +++++++++++++++++++++++++++++++++----------- 1 file changed, 182 insertions(+), 59 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 419887af5..73d92b894 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -10,6 +10,8 @@ #include "modules/Gui.h" #include "df/building_tradedepotst.h" +#include "df/general_ref.h" +#include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_tradegoodsst.h" @@ -27,33 +29,52 @@ REQUIRE_GLOBAL(ui); typedef std::set ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); -static std::multimap hooks; +struct conf_wrapper; +static std::map confirmations; -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) -#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ -static cls cls##_instance; \ -struct cls##_hooks : cls::screen_type { \ - typedef cls::screen_type interpose_base; \ - DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ - { \ - cls##_instance.screen = this; \ - if (!cls##_instance.feed(input)) \ - INTERPOSE_NEXT(feed)(input); \ - } \ - DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ - { \ - cls##_instance.screen = this; \ - INTERPOSE_NEXT(render)(); \ - cls##_instance.render(); \ - } \ - DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ +template +bool in_vector (std::vector &vec, FT item) +{ + return std::find(vec.begin(), vec.end(), item) != vec.end(); +} + +#define goods_selected_func(list) \ +static bool list##_goods_selected(df::viewscreen_tradegoodsst *screen) \ +{ \ + for (auto it = screen->list##_selected.begin(); it != screen->list##_selected.end(); ++it) \ + if (*it) return true; \ + return false; \ +} +goods_selected_func(trader); +goods_selected_func(broker); +#undef goods_selected_func + +#define goods_all_selected_func(list) \ +static bool list##_goods_all_selected(df::viewscreen_tradegoodsst *screen) \ +{ \ + for (size_t i = 0; i < screen->list##_selected.size(); ++i) \ { \ - return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + if (!screen->list##_selected[i]) \ + { \ + std::vector *refs = &screen->list##_items[i]->general_refs; \ + bool in_container = false; \ + for (auto it = refs->begin(); it != refs->end(); ++it) \ + { \ + if (virtual_cast(*it)) \ + { \ + in_container = true; \ + break; \ + } \ + } \ + if (!in_container) \ + return false; \ + } \ } \ -}; \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); + return true; \ +} +goods_all_selected_func(trader); +goods_all_selected_func(broker); +#undef goods_all_selected_func template class confirmation { @@ -159,12 +180,82 @@ protected: df::interface_key last_key; }; +struct conf_wrapper { + bool enabled; + std::set hooks; + + conf_wrapper() + :enabled(false) + {} + void add_hook(VMethodInterposeLinkBase *hook) + { + if (!hooks.count(hook)) + hooks.insert(hook); + } + bool apply (bool state) { + if (state == enabled) + return true; + for (auto h = hooks.begin(); h != hooks.end(); ++h) + { + if (!(**h).apply(state)) + return false; + } + enabled = state; + return true; + } +}; + +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) +#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ +static cls cls##_instance; \ +struct cls##_hooks : cls::screen_type { \ + typedef cls::screen_type interpose_base; \ + DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ + { \ + cls##_instance.screen = this; \ + if (!cls##_instance.feed(input)) \ + INTERPOSE_NEXT(feed)(input); \ + } \ + DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ + { \ + cls##_instance.screen = this; \ + INTERPOSE_NEXT(render)(); \ + cls##_instance.render(); \ + } \ + DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ + { \ + return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); + class trade_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_TRADE; } + virtual bool intercept_key (df::interface_key key) + { + return key == df::interface_key::TRADE_TRADE; + } virtual string get_id() { return "trade"; } virtual string get_title() { return "Confirm trade"; } - virtual string get_message() { return "Are you sure you want to trade the selected goods?"; } + virtual string get_message() + { + if (trader_goods_selected(screen) && broker_goods_selected(screen)) + return "Are you sure you want to trade the selected goods?"; + else if (trader_goods_selected(screen)) + return "You are not giving any items. This is likely\n" + "to irritate the merchants.\n" + "Attempt to trade anyway?"; + else if (broker_goods_selected(screen)) + return "You are not receiving any items. You may want to\n" + "offer these items instead or choose items to receive.\n" + "Attempt to trade anyway?"; + else + return "No items are selected. This is likely\n" + "to irritate the merchants.\n" + "Attempt to trade anyway?"; + } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); @@ -172,14 +263,8 @@ class trade_cancel_confirmation : public confirmationlist.begin(); it != screen->list.end(); ++it) if (*it) return true - check_list(trader_selected); - check_list(broker_selected); - #undef check_list - } - return false; + return key == df::interface_key::LEAVESCREEN && + (trader_goods_selected(screen) || broker_goods_selected(screen)); } virtual string get_id() { return "trade-cancel"; } virtual string get_title() { return "Cancel trade"; } @@ -189,7 +274,10 @@ IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); class trade_seize_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } + virtual bool intercept_key (df::interface_key key) + { + return trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE; + } virtual string get_id() { return "trade-seize"; } virtual string get_title() { return "Confirm seize"; } virtual string get_message() { return "Are you sure you want to sieze these goods?"; } @@ -198,13 +286,39 @@ IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); class trade_offer_confirmation : public confirmation { public: - virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } + virtual bool intercept_key (df::interface_key key) + { + return broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER; + } virtual string get_id() { return "trade-offer"; } virtual string get_title() { return "Confirm offer"; } virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } }; IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); +class trade_select_all_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + if (key == df::interface_key::SEC_SELECT) + { + if (screen->in_right_pane && broker_goods_selected(screen) && !broker_goods_all_selected(screen)) + return true; + else if (!screen->in_right_pane && trader_goods_selected(screen) && !trader_goods_all_selected(screen)) + return true; + } + return false; + } + virtual string get_id() { return "trade-select-all"; } + virtual string get_title() { return "Confirm selection"; } + virtual string get_message() + { + return "Selecting all goods will overwrite your current selection\n" + "and cannot be undone. Continue?"; + } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation); + class hauling_route_delete_confirmation : public confirmation { public: virtual bool intercept_key (df::interface_key key) @@ -285,15 +399,16 @@ public: IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); #define CHOOK(cls) \ - HOOK_ACTION(cls, cls##_hooks, render) \ - HOOK_ACTION(cls, cls##_hooks, feed) \ - HOOK_ACTION(cls, cls##_hooks, key_conflict) + HOOK_ACTION(cls, render) \ + HOOK_ACTION(cls, feed) \ + HOOK_ACTION(cls, key_conflict) #define CHOOKS \ CHOOK(trade_confirmation) \ CHOOK(trade_cancel_confirmation) \ CHOOK(trade_seize_confirmation) \ CHOOK(trade_offer_confirmation) \ + CHOOK(trade_select_all_confirmation) \ CHOOK(hauling_route_delete_confirmation) \ CHOOK(depot_remove_confirmation) \ CHOOK(squad_disband_confirmation) \ @@ -302,55 +417,54 @@ IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { - vector hook_names; -#define HOOK_ACTION(cls, hookcls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(hookcls, method))); \ - if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); +#define HOOK_ACTION(cls, method) \ + if (confirmations.find(cls##_instance.get_id()) == confirmations.end()) \ + confirmations[cls##_instance.get_id()] = new conf_wrapper; \ + confirmations[cls##_instance.get_id()]->add_hook(&INTERPOSE_HOOK(cls##_hooks, method)); CHOOKS #undef HOOK_ACTION - string help = - " confirmation enable|disable option|all ...\n" - "Available options:\n " + join_strings(", ", hook_names); commands.push_back(PluginCommand( "confirm", "Confirmation dialogs", df_confirm, false, //allow non-interactive use - help.c_str() + + " confirmation enable|disable option|all ...\n" + " confirmation help|status\n" )); return CR_OK; } -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { if (is_enabled != enable) { -#define HOOK_ACTION(cls, hookcls, method) !INTERPOSE_HOOK(hookcls, method).apply(enable) || - if (CHOOKS 0) - return CR_FAILURE; -#undef HOOK_ACTION + for (auto c = confirmations.begin(); c != confirmations.end(); ++c) + { + if (!c->second->apply(enable)) + return CR_FAILURE; + } is_enabled = enable; } - return CR_OK; } DFhackCExport command_result plugin_shutdown (color_ostream &out) { -#define HOOK_ACTION(cls, hookcls, method) INTERPOSE_HOOK(hookcls, method).remove(); - CHOOKS; -#undef HOOK_ACTION + if (plugin_enable(out, false) != CR_OK) + return CR_FAILURE; return CR_OK; } void enable_conf (color_ostream &out, string name, bool state) { bool found = false; - for (auto it = hooks.begin(); it != hooks.end(); ++it) + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) { if (it->first == name) { found = true; - it->second.apply(state); + it->second->apply(state); } } if (!found) @@ -361,6 +475,15 @@ command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; + if (in_vector(parameters, "help") || in_vector(parameters, "status")) + { + out << "Available options: \n"; + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + { + out << " " << it->first << ": " << (it->second->enabled ? "enabled" : "disabled") << std::endl; + } + return CR_OK; + } for (auto it = parameters.begin(); it != parameters.end(); ++it) { if (*it == "enable") @@ -369,9 +492,9 @@ command_result df_confirm (color_ostream &out, vector & parameters) state = false; else if (*it == "all") { - for (auto it = hooks.begin(); it != hooks.end(); ++it) + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) { - it->second.apply(state); + it->second->apply(state); } } else From 11027c2c75f58f10c670d0064cd4bbf91c7dd02f Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 13:01:14 -0400 Subject: [PATCH 014/109] Document and enable confirm plugin --- NEWS | 1 + Readme.rst | 11 +++++++++++ dfhack.init-example | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 7a1b512e1..1581d47a3 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ DFHack Future New internal commands kill-lua: Interrupt running Lua scripts New plugins + confirm: Adds confirmation dialogs for several potentially dangerous actions New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/Readme.rst b/Readme.rst index ca886907b..cd2c3717f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -311,6 +311,17 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p Game interface ============== +confirm +------- + +Implements several confirmation dialogs for potentially destructive actions +(for example, seizing goods from traders or deleting hauling routes). + +Usage: + +* ``enable confirm`` or ``confirm enable all``: Enable all confirmations (replace with ``disable`` to disable) +* ``confirm enable option1 [option2...]``: Enable (or disable) specific confirmations. Run ``confirm help`` for a complete list of options. + follow ------ Makes the game view follow the currently highlighted unit after you exit from diff --git a/dfhack.init-example b/dfhack.init-example index daed81041..501ebb830 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -201,8 +201,8 @@ enable search enable automaterial # Other interface improvement tools -# enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop stockpiles enable \ + confirm \ dwarfmonitor \ mousequery \ automelt \ From 889c76b1473bbb6be0f80b1f3c9846b78ae261b3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 14:05:26 -0400 Subject: [PATCH 015/109] buildingplan: Don't assume building and item type names match There are a few building types (e.g. hatch covers) where this is not the case. Fixes #604 --- NEWS | 1 + plugins/buildingplan-lib.cpp | 89 +++++++++++++++++------------------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/NEWS b/NEWS index 1581d47a3..ec0b663fa 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ DFHack Future Fixed default arguments in Lua gametype detection functions Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() + buildingplan: Now supports hatch covers gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index a25ee7b93..c933ce11b 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -9,6 +9,7 @@ void debug(const string &msg) color_ostream_proxy out(Core::getInstance().getConsole()); out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; } +#define dbg Core::getInstance().getConsole() void enable_quickfort_fn(pair& pair) { pair.second = true; } @@ -521,51 +522,47 @@ void Planner::reset(color_ostream &out) void Planner::initialize() { - std::vector item_names; - typedef df::enum_traits item_types; - int size = item_types::last_item_value - item_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - is_relevant_item_type[(df::item_type) (i-1)] = false; - std::string item_name = toLower(item_types::key_table[i]); - std::string item_name_clean; - for (auto c = item_name.begin(); c != item_name.end(); c++) - { - if (*c == '_') - continue; - item_name_clean += *c; - } - item_names.push_back(item_name_clean); - } - - typedef df::enum_traits building_types; - size = building_types::last_item_value - building_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - auto building_type = (df::building_type) (i-1); - if (building_type == building_type::Weapon || building_type == building_type::Floodgate) - continue; - - std::string building_name = toLower(building_types::key_table[i]); - for (size_t j = 0; j < item_names.size(); j++) - { - if (building_name == item_names[j]) - { - auto btype = (df::building_type) (i-1); - auto itype = (df::item_type) j; - - item_for_building_type[btype] = itype; - default_item_filters[btype] = ItemFilter(); - available_item_vectors[itype] = std::vector(); - is_relevant_item_type[itype] = true; - - if (planmode_enabled.find(btype) == planmode_enabled.end()) - { - planmode_enabled[btype] = false; - } - } - } - } +#define add_building_type(btype, itype) \ + item_for_building_type[df::building_type::btype] = df::item_type::itype; \ + default_item_filters[df::building_type::btype] = ItemFilter(); \ + available_item_vectors[df::item_type::itype] = std::vector(); \ + is_relevant_item_type[df::item_type::itype] = true; \ + if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ + planmode_enabled[df::building_type::btype] = false + + FOR_ENUM_ITEMS(item_type, it) + is_relevant_item_type[it] = false; + + add_building_type(Armorstand, ARMORSTAND); + add_building_type(Bed, BED); + add_building_type(Chair, CHAIR); + add_building_type(Coffin, COFFIN); + add_building_type(Door, DOOR); + // add_building_type(Floodgate, FLOODGATE); not displayed before or after being built + add_building_type(Hatch, HATCH_COVER); + // not displayed before or after being built: + // add_building_type(GrateWall, GRATE); + // add_building_type(GrateFloor, GRATE); + // add_building_type(BarsVertical, BAR); + // add_building_type(BarsFloor, BAR); + add_building_type(Cabinet, CABINET); + add_building_type(Box, BOX); + // skip kennels, farm plot + add_building_type(Weaponrack, WEAPONRACK); + add_building_type(Statue, STATUE); + add_building_type(Slab, SLAB); + add_building_type(Table, TABLE); + // skip roads ... furnaces + add_building_type(WindowGlass, WINDOW); + // skip gem window ... support + add_building_type(AnimalTrap, ANIMALTRAP); + add_building_type(Chain, CHAIN); + add_building_type(Cage, CAGE); + // skip archery target + add_building_type(TractionBench, TRACTION_BENCH); + // skip nest box, hive (tools) + +#undef add_building_type } void Planner::doCycle() @@ -657,4 +654,4 @@ void Planner::cycleDefaultQuality(df::building_type type) *quality = static_cast(*quality + 1); if (*quality == item_quality::Artifact) (*quality) = item_quality::Ordinary; -} \ No newline at end of file +} From c3c625e7bd1bc56d062ed65824a26b16b6c62c1a Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 18:22:18 -0400 Subject: [PATCH 016/109] Remove embark-tools nano (implemented in 0.40.24) --- NEWS | 1 + Readme.rst | 1 - plugins/embark-tools.cpp | 73 ---------------------------------------- 3 files changed, 1 insertion(+), 74 deletions(-) diff --git a/NEWS b/NEWS index ec0b663fa..406b2c6a3 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,7 @@ DFHack Future - farm-plot-select: Fixed issues with selecting undiscovered crops workflow: Improved handling of plant reactions Removed + embark-tools nano: 1x1 embarks are now possible in vanilla 0.40.24 DFHack 0.40.24-r3 Internals diff --git a/Readme.rst b/Readme.rst index cd2c3717f..84fc475a5 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2155,7 +2155,6 @@ Tools: * ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution. * ``mouse``: Implements mouse controls (currently in the local embark region only) -* ``nano``: An implementation of nano embark - allows resizing below 2x2 when enabled. * ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators. * ``sticky``: Maintains the selected local area while navigating the world map diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 13884dc2d..37794c875 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -61,37 +61,6 @@ void set_embark_pos (df::viewscreen_choose_start_sitest * screen, int a, b, c, d, e, f; \ get_embark_pos(screen, a, b, c, d, e, f); -void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy) -{ - /* Reproduces DF's embark resizing functionality - * Local area resizes up and to the right, unless it's already touching the edge - */ - GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); - if (x1 == x2 && dx == -1) - dx = 0; - if (y1 == y2 && dy == -1) - dy = 0; - - x2 += dx; // Resize right - while (x2 > 15) - { - x2--; - x1--; - } - x1 = std::max(0, x1); - - y1 -= dy; // Resize up - while (y1 < 0) - { - y1++; - y2++; - } - y2 = std::min(15, y2); - - set_embark_pos(screen, x1, x2, y1, y2); - update_embark_sidebar(screen); -} - typedef df::viewscreen_choose_start_sitest start_sitest; typedef std::set ikey_set; @@ -160,47 +129,6 @@ public: }; }; -class NanoEmbark : public EmbarkTool -{ -public: - virtual std::string getId() { return "nano"; } - virtual std::string getName() { return "Nano embark"; } - virtual std::string getDesc() { return "Allows the embark size to be decreased below 2x2"; } - virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_N; } - virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) - { - for (auto iter = input->begin(); iter != input->end(); iter++) - { - df::interface_key key = *iter; - bool is_resize = true; - int dx = 0, dy = 0; - switch (key) - { - case df::interface_key::SETUP_LOCAL_Y_UP: - dy = 1; - break; - case df::interface_key::SETUP_LOCAL_Y_DOWN: - dy = -1; - break; - case df::interface_key::SETUP_LOCAL_X_UP: - dx = 1; - break; - case df::interface_key::SETUP_LOCAL_X_DOWN: - dx = -1; - break; - default: - is_resize = false; - } - if (is_resize) - { - cancel = true; - resize_embark(screen, dx, dy); - return; - } - } - }; -}; - class SandIndicator : public EmbarkTool { protected: @@ -816,7 +744,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Mon, 27 Jul 2015 20:32:33 -0400 Subject: [PATCH 017/109] Cache result of getPath() on darwin --- library/Process-darwin.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 230809cd3..a485e196d 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -259,18 +259,24 @@ uint32_t Process::getTickCount() string Process::getPath() { + static string cached_path = ""; + if (cached_path.size()) + return cached_path; char path[1024]; char *real_path; uint32_t size = sizeof(path); if (getcwd(path, size)) - return string(path); + { + cached_path = string(path); + return cached_path; + } if (_NSGetExecutablePath(path, &size) == 0) { real_path = realpath(path, NULL); } std::string path_string(real_path); int last_slash = path_string.find_last_of("/"); - std::string directory = path_string.substr(0,last_slash); - return directory; + cached_path = path_string.substr(0,last_slash); + return cached_path; } int Process::getPID() From 5ca5feb8558c162c557bd6adb8e345a0fee11bfa Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 21:38:53 -0400 Subject: [PATCH 018/109] Stop embark-tools from crashing on unload Also enable sand indicator and mouse controls by default and refactor --- dfhack.init-example | 3 ++ plugins/embark-tools.cpp | 76 +++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 501ebb830..780655c0a 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -222,6 +222,9 @@ enable \ # allow the fortress bookkeeper to queue jobs through the manager enable stockflow +# enable mouse controls and sand indicator in embark screen +embark-tools enable sand mouse + ########### # Scripts # ########### diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 37794c875..3ff38def5 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -8,6 +8,7 @@ #include "modules/Screen.h" #include "modules/Gui.h" #include +#include #include #include @@ -85,7 +86,7 @@ public: virtual void after_feed(start_sitest* screen, ikey_set* input) { }; virtual void after_mouse_event(start_sitest* screen) { }; }; -std::vector tools; +std::map tools; /* @@ -575,6 +576,8 @@ public: max_y = min_y + height; Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1); + std::string title = " Embark tools (DFHack) "; + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (max_x - min_x + title.size()) / 2, min_y, title); x = min_x + 2; y = max_y - 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); @@ -584,7 +587,7 @@ public: y = min_y + 2; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; + EmbarkTool* t = iter->second; x = min_x + 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey())); OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": "); @@ -605,7 +608,7 @@ public: df::interface_key key = *iter; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; + EmbarkTool* t = iter->second; if (t->getToggleKey() == key) { t->toggleEnabled(); @@ -615,25 +618,20 @@ public: }; }; +void add_tool (EmbarkTool *t) +{ + tools[t->getId()] = t; +} + bool tool_exists (std::string tool_name) { - FOR_ITER_TOOLS(iter) - { - EmbarkTool* tool = *iter; - if (tool->getId() == tool_name) - return true; - } - return false; + return tools.find(tool_name) != tools.end(); } bool tool_enabled (std::string tool_name) { - FOR_ITER_TOOLS(iter) - { - EmbarkTool* tool = *iter; - if (tool->getId() == tool_name) - return tool->getEnabled(); - } + if (tool_exists(tool_name)) + return tools[tool_name]->getEnabled(); return false; } @@ -642,7 +640,7 @@ bool tool_enable (std::string tool_name, bool enable_state) int n = 0; FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getId() == tool_name || tool_name == "all") { tool->setEnabled(enable_state); @@ -666,8 +664,9 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest std::vector parts; FOR_ITER_TOOLS(it) { - if ((*it)->getEnabled()) - parts.push_back((*it)->getName()); + EmbarkTool *t = it->second; + if (t->getEnabled()) + parts.push_back(t->getName()); } if (parts.size()) { @@ -698,7 +697,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest bool cancel = false; FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->before_feed(this, input, cancel); } @@ -709,7 +708,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest display_settings(); FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->after_feed(this, input); } @@ -718,7 +717,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest { FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->before_render(this); } @@ -726,7 +725,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest display_tool_status(); FOR_ITER_TOOLS(iter) { - EmbarkTool* tool = *iter; + EmbarkTool* tool = iter->second; if (tool->getEnabled()) tool->after_render(this); } @@ -742,16 +741,16 @@ command_result embark_tools_cmd (color_ostream &out, std::vector & DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - tools.push_back(new EmbarkAnywhere); - tools.push_back(new MouseControl); - tools.push_back(new SandIndicator); - tools.push_back(new StablePosition); + add_tool(new EmbarkAnywhere); + add_tool(new MouseControl); + add_tool(new SandIndicator); + add_tool(new StablePosition); std::string help = ""; help += "embark-tools (enable/disable) tool [tool...]\n" "Tools:\n"; FOR_ITER_TOOLS(iter) { - help += (" " + (*iter)->getId() + ": " + (*iter)->getDesc() + "\n"); + help += (" " + iter->second->getId() + ": " + iter->second->getDesc() + "\n"); } commands.push_back(PluginCommand( "embark-tools", @@ -782,6 +781,19 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) return CR_OK; } +DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) +{ + if (evt == SC_BEGIN_UNLOAD) + { + if (Gui::getCurFocus() == "dfhack/embark-tools/options") + { + out.printerr("Settings screen active.\n"); + return CR_NOT_FOUND; + } + } + return CR_OK; +} + DFhackCExport command_result plugin_onupdate (color_ostream &out) { static int8_t mask = 0; @@ -801,8 +813,8 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) { FOR_ITER_TOOLS(iter) { - if ((*iter)->getEnabled()) - (*iter)->after_mouse_event(screen); + if (iter->second->getEnabled()) + iter->second->after_mouse_event(screen); } } mask = new_mask; @@ -842,8 +854,8 @@ command_result embark_tools_cmd (color_ostream &out, std::vector & out << "Tool status:" << std::endl; FOR_ITER_TOOLS(iter) { - EmbarkTool* t = *iter; - out << t->getName() << " (" << t->getId() << "): " + EmbarkTool* t = iter->second; + out << " " << t->getName() << " (" << t->getId() << "): " << (t->getEnabled() ? "Enabled" : "Disabled") << std::endl; } } From e51779b08ccc5d03837e7b767a3660edd368d2d9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 27 Jul 2015 21:40:04 -0400 Subject: [PATCH 019/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5850f66fa..ccca7b721 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5850f66fa5e37401b1536b574a48d645993520e2 +Subproject commit ccca7b721bef0cbddb54694cf16927e5fe668b7f From b73e859a37dea80b32e90bc4667392687597f2f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 14:37:49 -0400 Subject: [PATCH 020/109] Reorganize stocks viewscreen to fit a 25-row resolution --- plugins/stocks.cpp | 49 +++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index b6bf68d05..bf7969f6c 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -698,7 +698,7 @@ public: hide_flags.bits.dump = !hide_flags.bits.dump; populateItems(); } - else if (input->count(interface_key::CUSTOM_CTRL_R)) + else if (input->count(interface_key::CUSTOM_CTRL_E)) { hide_flags.bits.on_fire = !hide_flags.bits.on_fire; populateItems(); @@ -897,30 +897,32 @@ public: y = 2; x = left_margin; - OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); - OutputString(COLOR_LIGHTRED, x, y, "Press Ctrl-Hotkey to toggle", true, left_margin); - OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); + OutputString(COLOR_BROWN, x, y, "Filters ", false, left_margin); + OutputString(COLOR_LIGHTRED, x, y, "(Ctrl+Key toggles)", true, left_margin); + OutputFilterString(x, y, "In Job ", "J", !hide_flags.bits.in_job, false, left_margin, COLOR_LIGHTBLUE); OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); - OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); + OutputFilterString(x, y, "Owned ", "O", !hide_flags.bits.owned, false, left_margin, COLOR_GREEN); OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); - OutputFilterString(x, y, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA); - OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); - OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); + OutputFilterString(x, y, "Dump ", "D", !hide_flags.bits.dump, false, left_margin, COLOR_LIGHTMAGENTA); + OutputFilterString(x, y, "On Fire", "E", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); + OutputFilterString(x, y, "Melt ", "M", !hide_flags.bits.melt, false, left_margin, COLOR_BLUE); OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE); - OutputFilterString(x, y, "Caged", "C", !extra_hide_flags.hide_in_cages, true, left_margin, COLOR_LIGHTRED); + OutputFilterString(x, y, "Caged ", "C", !extra_hide_flags.hide_in_cages, false, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN); OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY); - ++y; + if (gps->dimy > 26) + ++y; OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); OutputHotkeyString(x, y, "Toggle Grouping", "TAB", true, left_margin); ++y; - OutputHotkeyString(x, y, "Min Qual: ", "-+"); - OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); - OutputHotkeyString(x, y, "Max Qual: ", "/*"); - OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); - ++y; + OutputString(COLOR_WHITE, x, y, "Qual: "); + OutputHotkeyString(x, y, "Min: ", "-+"); + OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), false, left_margin); + ++x; + OutputHotkeyString(x, y, "Max: ", "/*"); + OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); @@ -928,16 +930,16 @@ public: OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); - OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); + OutputHotkeyString(x, y, "Zoom ", "Shift-Z", false, left_margin); + OutputHotkeyString(x, y, "Dump", "-D", true, left_margin); + OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin); + OutputHotkeyString(x, y, "Melt", "-M", true, left_margin); OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); - OutputHotkeyString(x, y, "Dump", "Shift-D", true, left_margin); - OutputHotkeyString(x, y, "Forbid", "Shift-F", true, left_margin); - OutputHotkeyString(x, y, "Melt", "Shift-M", true, left_margin); if (depot_info.canTrade()) OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin); - y = gps->dimy - 6; + y = gps->dimy - 5; OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin); OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin); } @@ -1307,7 +1309,6 @@ struct stocks_hook : public df::viewscreen_storesst if (input->count(interface_key::CUSTOM_E)) { Screen::dismiss(this); - Screen::dismiss(Gui::getCurViewscreen(true)); Screen::show(new ViewscreenStocks()); return; } @@ -1320,7 +1321,7 @@ struct stocks_hook : public df::viewscreen_storesst auto dim = Screen::getWindowSize(); int x = 40; int y = dim.y - 2; - OutputHotkeyString(x, y, "Enhanced View", "e"); + OutputHotkeyString(x, y, "Enhanced View", "e", false, 0, COLOR_WHITE, COLOR_LIGHTRED); } }; @@ -1440,6 +1441,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: ViewscreenStocks::reset(); break; + case SC_BEGIN_UNLOAD: + if (Gui::getCurFocus().find("dfhack/stocks") == 0) + return CR_FAILURE; + break; default: break; } From dd7c20c848cbee5a1e71446536e9ea65b3f22655 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:34:29 -0400 Subject: [PATCH 021/109] Add safer helpers to get STRING_X keys from interface_key sets A few plugins were using input->rbegin() previously, which crashes if input is empty and isn't strictly guaranteed to return a STRING_X key. --- NEWS | 1 + plugins/buildingplan.cpp | 2 +- plugins/search.cpp | 14 +++----------- plugins/uicommon.h | 22 ++++++++++++++++++---- plugins/zone.cpp | 9 ++------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index 406b2c6a3..0588784b9 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ DFHack Future gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job + stocks: fixed a crash when right-clicking steam-engine: - fixed a crash on arena load - number keys (e.g. 2/8) take priority over cursor keys when applicable diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index d3cdef923..ddc804897 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -189,7 +189,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest else if (isInNobleRoomQueryMode()) { auto np = getNoblePositionOfSelectedBuildingOwner(); - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) { int selection = last_token - interface_key::STRING_A048; diff --git a/plugins/search.cpp b/plugins/search.cpp index 4c0773906..7877bc207 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -5,6 +5,8 @@ #include +#include "uicommon.h" + #include "df/ui_look_list.h" #include "df/viewscreen_announcelistst.h" #include "df/viewscreen_petst.h" @@ -65,12 +67,6 @@ to use. */ -void OutputString(int8_t color, int &x, int y, const std::string &text) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - x += text.length(); -} - void make_text_dim(int x1, int x2, int y) { for (int x = x1; x <= x2; x++) @@ -225,11 +221,7 @@ public: { // Query typing mode - if (input->empty()) - { - return false; - } - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if (charcode >= 32 && charcode <= 126) { diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 56519c260..5f5414b72 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -206,6 +206,20 @@ static string pad_string(string text, const int size, const bool front = true, c } } +static df::interface_key get_string_key(const std::set *input) +{ + for (auto it = input->begin(); it != input->end(); ++it) + { + if (DFHack::Screen::keyToChar(*it) >= 0) + return *it; + } + return df::interface_key::NONE; +} + +static char get_string_input(const std::set *input) +{ + return DFHack::Screen::keyToChar(get_string_key(input)); +} /* * Utility Functions @@ -370,7 +384,7 @@ protected: y2 = sp->room.y + sp->room.height; } -private: +protected: int x1, x2, y1, y2, z; }; @@ -414,7 +428,7 @@ public: DFHack::World::DeletePersistentData(config); } -private: +protected: PersistentDataItem config; string persistence_key; }; @@ -799,7 +813,7 @@ public: else if (allow_search) { // Search query typing mode always on - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if ((charcode >= 96 && charcode <= 123) || charcode == 32) { @@ -873,7 +887,7 @@ public: return display_list.size(); } -private: +protected: static void clear_fn(ListEntry &e) { e.selected = false; } static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 10ffcd3c8..b9044b6fd 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -41,6 +41,7 @@ using namespace std; #include "Export.h" #include "PluginManager.h" #include "MiscUtils.h" +#include "uicommon.h" #include "LuaTools.h" #include "DataFuncs.h" @@ -3705,12 +3706,6 @@ DFHACK_PLUGIN_LUA_COMMANDS { //START zone filters -void OutputString(int8_t color, int &x, int y, const std::string &text) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - x += text.length(); -} - class zone_filter { public: @@ -3856,7 +3851,7 @@ public: return false; } - df::interface_key last_token = *input->rbegin(); + df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); if (charcode >= 32 && charcode <= 126) { From 786086e5c5c55995b536b8194a024c186662ee06 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:36:20 -0400 Subject: [PATCH 022/109] Make non-CR_OK return values for SC_BEGIN_UNLOAD events abort unload Returning CR_FAILURE, for example, is more consistent with plugin_shutdown(). --- library/PluginManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0d6442699..418b5a57b 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -326,7 +326,7 @@ bool Plugin::unload(color_ostream &con) EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && - plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) + plugin_onstatechange(con, SC_BEGIN_UNLOAD) != CR_OK) { con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); access->unlock(); From f8dd680a07284522e8fa30bae2f011dead70d0b8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 18:48:13 -0400 Subject: [PATCH 023/109] Fix zone entry in NEWS --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0588784b9..0b42a35e0 100644 --- a/NEWS +++ b/NEWS @@ -41,7 +41,7 @@ DFHack Future tweak fps-min fixed workflow: Fixed some issues with stuck jobs - Note: Existing stuck jobs must be cancelled and re-added - zone: Fixed a crash in the unit list after cancelling a job (and several other potential crashes) + zone: Fixed a crash when using "zone set" (and a few other potential crashes) Misc Improvements autolabor: - Stopped modification of labors that shouldn't be modified for brokers/diplomats From 825d46af51694fa45120a04532a2fd84313a898a Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jul 2015 21:48:00 -0400 Subject: [PATCH 024/109] Move ListColumn class to a separate header --- plugins/autochop.cpp | 1 + plugins/autodump.cpp | 4 +- plugins/automelt.cpp | 4 +- plugins/autotrade.cpp | 4 +- plugins/buildingplan-lib.h | 3 +- plugins/dwarfmonitor.cpp | 1 + plugins/hotkeys.cpp | 1 + plugins/listcolumn.h | 469 +++++++++++++++++++++++++++++ plugins/manipulator.cpp | 1 + plugins/mousequery.cpp | 5 +- plugins/remotefortressreader.cpp | 2 +- plugins/stockflow.cpp | 5 +- plugins/stockpiles/stockpiles.cpp | 1 + plugins/stocks.cpp | 1 + plugins/uicommon.h | 472 ------------------------------ 15 files changed, 484 insertions(+), 490 deletions(-) create mode 100644 plugins/listcolumn.h diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 3009877f1..f7ba3d300 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -1,6 +1,7 @@ // automatically chop trees #include "uicommon.h" +#include "listcolumn.h" #include "Core.h" #include "Console.h" diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 944288001..3fbe1ae77 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -37,6 +37,7 @@ using MapExtras::MapCache; using df::building_stockpilest; DFHACK_PLUGIN("autodump"); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); // Stockpile interface START @@ -254,9 +255,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { if (!INTERPOSE_HOOK(dump_hook, feed).apply(enable) || diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 620ed622e..0aacce233 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -18,6 +18,7 @@ using df::building_stockpilest; DFHACK_PLUGIN("automelt"); #define PLUGIN_VERSION 0.3 +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(ui); @@ -282,9 +283,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { if (!INTERPOSE_HOOK(melt_hook, feed).apply(enable) || diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index aa7a35494..a645d579e 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -22,6 +22,7 @@ using df::building_stockpilest; DFHACK_PLUGIN("autotrade"); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(ui); @@ -465,9 +466,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (enable != is_enabled) { depot_info.reset(); diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 7b2be66cb..7ec0c41d4 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -2,6 +2,7 @@ #define BUILDINGPLAN_H #include "uicommon.h" +#include "listcolumn.h" #include @@ -492,4 +493,4 @@ static Planner planner; static RoomMonitor roomMonitor; -#endif \ No newline at end of file +#endif diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 798d4e36d..e70bb8399 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include "DataDefs.h" diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 7709c0719..45ca07cbd 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include "df/viewscreen_dwarfmodest.h" #include "df/ui.h" diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h new file mode 100644 index 000000000..e90f2f5fb --- /dev/null +++ b/plugins/listcolumn.h @@ -0,0 +1,469 @@ +#include "uicommon.h" + +using df::global::enabler; +using df::global::gps; + +/* + * List classes + */ +template +class ListEntry +{ +public: + T elem; + string text, keywords; + bool selected; + UIColor color; + + ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : + elem(elem), text(text), selected(false), keywords(keywords), color(color) + { + } +}; + +template +class ListColumn +{ +public: + int highlighted_index; + int display_start_offset; + unsigned short text_clip_at; + int32_t bottom_margin, search_margin, left_margin; + bool multiselect; + bool allow_null; + bool auto_select; + bool allow_search; + bool feed_mouse_set_highlight; + bool feed_changed_highlight; + + ListColumn() + { + bottom_margin = 3; + clear(); + left_margin = 2; + search_margin = 63; + highlighted_index = 0; + text_clip_at = 0; + multiselect = false; + allow_null = true; + auto_select = false; + allow_search = true; + feed_mouse_set_highlight = false; + feed_changed_highlight = false; + } + + void clear() + { + list.clear(); + display_list.clear(); + display_start_offset = 0; + if (highlighted_index != -1) + highlighted_index = 0; + max_item_width = title.length(); + resize(); + } + + void resize() + { + display_max_rows = gps->dimy - 4 - bottom_margin; + } + + void add(ListEntry &entry) + { + list.push_back(entry); + if (entry.text.length() > max_item_width) + max_item_width = entry.text.length(); + } + + void add(const string &text, const T &elem) + { + list.push_back(ListEntry(text, elem)); + if (text.length() > max_item_width) + max_item_width = text.length(); + } + + int fixWidth() + { + if (text_clip_at > 0 && max_item_width > text_clip_at) + max_item_width = text_clip_at; + + for (auto it = list.begin(); it != list.end(); it++) + { + it->text = pad_string(it->text, max_item_width, false); + } + + return getMaxItemWidth(); + } + + int getMaxItemWidth() + { + return left_margin + max_item_width; + } + + virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} + + void display(const bool is_selected_column) const + { + int32_t y = 2; + paint_text(COLOR_TITLE, left_margin, y, title); + + int last_index_able_to_display = display_start_offset + display_max_rows; + for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) + { + ++y; + UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; + UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; + + string item_label = display_list[i]->text; + if (text_clip_at > 0 && item_label.length() > text_clip_at) + item_label.resize(text_clip_at); + + paint_text(fg_color, left_margin, y, item_label, bg_color); + int x = left_margin + display_list[i]->text.length() + 1; + display_extras(display_list[i]->elem, x, y); + } + + if (is_selected_column && allow_search) + { + y = gps->dimy - 3; + int32_t x = search_margin; + OutputHotkeyString(x, y, "Search" ,"S"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString(COLOR_WHITE, x, y, search_string); + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + } + + void filterDisplay() + { + ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; + display_list.clear(); + + search_string = toLower(search_string); + vector search_tokens; + if (!search_string.empty()) + split_string(&search_tokens, search_string, " "); + + for (size_t i = 0; i < list.size(); i++) + { + ListEntry *entry = &list[i]; + + bool include_item = true; + if (!search_string.empty()) + { + string item_string = toLower(list[i].text); + for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) + { + if (!si->empty() && item_string.find(*si) == string::npos && + list[i].keywords.find(*si) == string::npos) + { + include_item = false; + break; + } + } + } + + if (include_item) + { + display_list.push_back(entry); + if (entry == prev_selected) + highlighted_index = display_list.size() - 1; + } + else if (auto_select) + { + entry->selected = false; + } + } + changeHighlight(0); + feed_changed_highlight = true; + } + + void selectDefaultEntry() + { + for (size_t i = 0; i < display_list.size(); i++) + { + if (display_list[i]->selected) + { + highlighted_index = i; + break; + } + } + } + + void centerSelection() + { + if (display_list.size() == 0) + return; + display_start_offset = highlighted_index - (display_max_rows / 2); + validateDisplayOffset(); + validateHighlight(); + } + + void validateHighlight() + { + set_to_limit(highlighted_index, display_list.size() - 1); + + if (highlighted_index < display_start_offset) + display_start_offset = highlighted_index; + else if (highlighted_index >= display_start_offset + display_max_rows) + display_start_offset = highlighted_index - display_max_rows + 1; + + if (auto_select || (!allow_null && list.size() == 1)) + display_list[highlighted_index]->selected = true; + + feed_changed_highlight = true; + } + + void changeHighlight(const int highlight_change, const int offset_shift = 0) + { + if (!initHighlightChange()) + return; + + highlighted_index += highlight_change + offset_shift * display_max_rows; + + display_start_offset += offset_shift * display_max_rows; + validateDisplayOffset(); + validateHighlight(); + } + + void validateDisplayOffset() + { + set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); + } + + void setHighlight(const int index) + { + if (!initHighlightChange()) + return; + + highlighted_index = index; + validateHighlight(); + } + + bool initHighlightChange() + { + if (display_list.size() == 0) + return false; + + if (auto_select && !multiselect) + { + for (auto it = list.begin(); it != list.end(); it++) + { + it->selected = false; + } + } + + return true; + } + + void toggleHighlighted() + { + if (display_list.size() == 0) + return; + + if (auto_select) + return; + + ListEntry *entry = display_list[highlighted_index]; + if (!multiselect || !allow_null) + { + int selected_count = 0; + for (size_t i = 0; i < list.size(); i++) + { + if (!multiselect && !entry->selected) + list[i].selected = false; + if (!allow_null && list[i].selected) + selected_count++; + } + + if (!allow_null && entry->selected && selected_count == 1) + return; + } + + entry->selected = !entry->selected; + } + + vector getSelectedElems(bool only_one = false) + { + vector results; + for (auto it = list.begin(); it != list.end(); it++) + { + if ((*it).selected) + { + results.push_back(it->elem); + if (only_one) + break; + } + } + + return results; + } + + T getFirstSelectedElem() + { + vector results = getSelectedElems(true); + if (results.size() == 0) + return (T)nullptr; + else + return results[0]; + } + + void clearSelection() + { + for_each_(list, clear_fn); + } + + void selectItem(const T elem) + { + int i = 0; + for (; i < display_list.size(); i++) + { + if (display_list[i]->elem == elem) + { + setHighlight(i); + break; + } + } + } + + void clearSearch() + { + search_string.clear(); + filterDisplay(); + } + + size_t getDisplayListSize() + { + return display_list.size(); + } + + vector*> &getDisplayList() + { + return display_list; + } + + size_t getBaseListSize() + { + return list.size(); + } + + bool feed(set *input) + { + feed_mouse_set_highlight = feed_changed_highlight = false; + if (input->count(interface_key::CURSOR_UP)) + { + changeHighlight(-1); + } + else if (input->count(interface_key::CURSOR_DOWN)) + { + changeHighlight(1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) + { + changeHighlight(0, -1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + { + changeHighlight(0, 1); + } + else if (input->count(interface_key::SELECT) && !auto_select) + { + toggleHighlighted(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + { + clearSearch(); + } + else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) + { + return setHighlightByMouse(); + } + else if (allow_search) + { + // Search query typing mode always on + df::interface_key last_token = get_string_key(input); + int charcode = Screen::keyToChar(last_token); + if ((charcode >= 96 && charcode <= 123) || charcode == 32) + { + // Standard character + search_string += char(charcode); + filterDisplay(); + centerSelection(); + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + filterDisplay(); + centerSelection(); + } + } + else + { + return false; + } + + return true; + } + else + { + return false; + } + + return true; + } + + bool setHighlightByMouse() + { + if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && + gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) + { + int new_index = display_start_offset + gps->mouse_y - 3; + if (new_index < display_list.size()) + { + setHighlight(new_index); + feed_mouse_set_highlight = true; + } + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + + return true; + } + + return false; + } + + void sort(bool force_sort = false) + { + if (force_sort || list.size() < 100) + std::sort(list.begin(), list.end(), sort_fn); + + filterDisplay(); + } + + void setTitle(const string t) + { + title = t; + if (title.length() > max_item_width) + max_item_width = title.length(); + } + + size_t getDisplayedListSize() + { + return display_list.size(); + } + +protected: + static void clear_fn(ListEntry &e) { e.selected = false; } + static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } + + vector> list; + vector*> display_list; + string search_string; + string title; + int display_max_rows; + int max_item_width; +}; + diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index f675793df..6a69d7996 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -33,6 +33,7 @@ #include "df/entity_raw.h" #include "uicommon.h" +#include "listcolumn.h" using std::stringstream; using std::set; diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 4547a62d0..b0835832f 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -22,6 +22,8 @@ #include "DataFuncs.h" DFHACK_PLUGIN("mousequery"); +REQUIRE_GLOBAL(enabler); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); @@ -815,9 +817,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { - if (!gps) - return CR_FAILURE; - if (is_enabled != enable) { last_clicked_x = last_clicked_y = last_clicked_z = -1; diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6629e7893..bc9bdbb08 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1159,4 +1159,4 @@ static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessa } } return CR_OK; -} \ No newline at end of file +} diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index 34dfc1bd7..75d09e1e8 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -22,12 +22,9 @@ using df::building_stockpilest; DFHACK_PLUGIN("stockflow"); #define AUTOENABLE false -#ifdef DFHACK_PLUGIN_IS_ENABLED DFHACK_PLUGIN_IS_ENABLED(enabled); -#else -bool enabled = false; -#endif +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 936052a15..f02d19e45 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -40,6 +40,7 @@ using namespace google::protobuf; using namespace dfstockpiles; DFHACK_PLUGIN ( "stockpiles" ); +REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(selection_rect); diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index bf7969f6c..0f6039bfc 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 5f5414b72..a54b3aa04 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -37,10 +37,6 @@ using std::set; using namespace DFHack; using namespace df::enums; -using df::global::enabler; -using df::global::gps; - - #ifndef HAVE_NULLPTR #define nullptr 0L #endif @@ -432,471 +428,3 @@ protected: PersistentDataItem config; string persistence_key; }; - - - -/* - * List classes - */ -template -class ListEntry -{ -public: - T elem; - string text, keywords; - bool selected; - UIColor color; - - ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : - elem(elem), text(text), selected(false), keywords(keywords), color(color) - { - } -}; - -template -class ListColumn -{ -public: - int highlighted_index; - int display_start_offset; - unsigned short text_clip_at; - int32_t bottom_margin, search_margin, left_margin; - bool multiselect; - bool allow_null; - bool auto_select; - bool allow_search; - bool feed_mouse_set_highlight; - bool feed_changed_highlight; - - ListColumn() - { - bottom_margin = 3; - clear(); - left_margin = 2; - search_margin = 63; - highlighted_index = 0; - text_clip_at = 0; - multiselect = false; - allow_null = true; - auto_select = false; - allow_search = true; - feed_mouse_set_highlight = false; - feed_changed_highlight = false; - } - - void clear() - { - list.clear(); - display_list.clear(); - display_start_offset = 0; - if (highlighted_index != -1) - highlighted_index = 0; - max_item_width = title.length(); - resize(); - } - - void resize() - { - display_max_rows = gps->dimy - 4 - bottom_margin; - } - - void add(ListEntry &entry) - { - list.push_back(entry); - if (entry.text.length() > max_item_width) - max_item_width = entry.text.length(); - } - - void add(const string &text, const T &elem) - { - list.push_back(ListEntry(text, elem)); - if (text.length() > max_item_width) - max_item_width = text.length(); - } - - int fixWidth() - { - if (text_clip_at > 0 && max_item_width > text_clip_at) - max_item_width = text_clip_at; - - for (auto it = list.begin(); it != list.end(); it++) - { - it->text = pad_string(it->text, max_item_width, false); - } - - return getMaxItemWidth(); - } - - int getMaxItemWidth() - { - return left_margin + max_item_width; - } - - virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} - - void display(const bool is_selected_column) const - { - int32_t y = 2; - paint_text(COLOR_TITLE, left_margin, y, title); - - int last_index_able_to_display = display_start_offset + display_max_rows; - for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) - { - ++y; - UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; - UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; - - string item_label = display_list[i]->text; - if (text_clip_at > 0 && item_label.length() > text_clip_at) - item_label.resize(text_clip_at); - - paint_text(fg_color, left_margin, y, item_label, bg_color); - int x = left_margin + display_list[i]->text.length() + 1; - display_extras(display_list[i]->elem, x, y); - } - - if (is_selected_column && allow_search) - { - y = gps->dimy - 3; - int32_t x = search_margin; - OutputHotkeyString(x, y, "Search" ,"S"); - OutputString(COLOR_WHITE, x, y, ": "); - OutputString(COLOR_WHITE, x, y, search_string); - OutputString(COLOR_LIGHTGREEN, x, y, "_"); - } - } - - void filterDisplay() - { - ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; - display_list.clear(); - - search_string = toLower(search_string); - vector search_tokens; - if (!search_string.empty()) - split_string(&search_tokens, search_string, " "); - - for (size_t i = 0; i < list.size(); i++) - { - ListEntry *entry = &list[i]; - - bool include_item = true; - if (!search_string.empty()) - { - string item_string = toLower(list[i].text); - for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) - { - if (!si->empty() && item_string.find(*si) == string::npos && - list[i].keywords.find(*si) == string::npos) - { - include_item = false; - break; - } - } - } - - if (include_item) - { - display_list.push_back(entry); - if (entry == prev_selected) - highlighted_index = display_list.size() - 1; - } - else if (auto_select) - { - entry->selected = false; - } - } - changeHighlight(0); - feed_changed_highlight = true; - } - - void selectDefaultEntry() - { - for (size_t i = 0; i < display_list.size(); i++) - { - if (display_list[i]->selected) - { - highlighted_index = i; - break; - } - } - } - - void centerSelection() - { - if (display_list.size() == 0) - return; - display_start_offset = highlighted_index - (display_max_rows / 2); - validateDisplayOffset(); - validateHighlight(); - } - - void validateHighlight() - { - set_to_limit(highlighted_index, display_list.size() - 1); - - if (highlighted_index < display_start_offset) - display_start_offset = highlighted_index; - else if (highlighted_index >= display_start_offset + display_max_rows) - display_start_offset = highlighted_index - display_max_rows + 1; - - if (auto_select || (!allow_null && list.size() == 1)) - display_list[highlighted_index]->selected = true; - - feed_changed_highlight = true; - } - - void changeHighlight(const int highlight_change, const int offset_shift = 0) - { - if (!initHighlightChange()) - return; - - highlighted_index += highlight_change + offset_shift * display_max_rows; - - display_start_offset += offset_shift * display_max_rows; - validateDisplayOffset(); - validateHighlight(); - } - - void validateDisplayOffset() - { - set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); - } - - void setHighlight(const int index) - { - if (!initHighlightChange()) - return; - - highlighted_index = index; - validateHighlight(); - } - - bool initHighlightChange() - { - if (display_list.size() == 0) - return false; - - if (auto_select && !multiselect) - { - for (auto it = list.begin(); it != list.end(); it++) - { - it->selected = false; - } - } - - return true; - } - - void toggleHighlighted() - { - if (display_list.size() == 0) - return; - - if (auto_select) - return; - - ListEntry *entry = display_list[highlighted_index]; - if (!multiselect || !allow_null) - { - int selected_count = 0; - for (size_t i = 0; i < list.size(); i++) - { - if (!multiselect && !entry->selected) - list[i].selected = false; - if (!allow_null && list[i].selected) - selected_count++; - } - - if (!allow_null && entry->selected && selected_count == 1) - return; - } - - entry->selected = !entry->selected; - } - - vector getSelectedElems(bool only_one = false) - { - vector results; - for (auto it = list.begin(); it != list.end(); it++) - { - if ((*it).selected) - { - results.push_back(it->elem); - if (only_one) - break; - } - } - - return results; - } - - T getFirstSelectedElem() - { - vector results = getSelectedElems(true); - if (results.size() == 0) - return (T)nullptr; - else - return results[0]; - } - - void clearSelection() - { - for_each_(list, clear_fn); - } - - void selectItem(const T elem) - { - int i = 0; - for (; i < display_list.size(); i++) - { - if (display_list[i]->elem == elem) - { - setHighlight(i); - break; - } - } - } - - void clearSearch() - { - search_string.clear(); - filterDisplay(); - } - - size_t getDisplayListSize() - { - return display_list.size(); - } - - vector*> &getDisplayList() - { - return display_list; - } - - size_t getBaseListSize() - { - return list.size(); - } - - bool feed(set *input) - { - feed_mouse_set_highlight = feed_changed_highlight = false; - if (input->count(interface_key::CURSOR_UP)) - { - changeHighlight(-1); - } - else if (input->count(interface_key::CURSOR_DOWN)) - { - changeHighlight(1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) - { - changeHighlight(0, -1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) - { - changeHighlight(0, 1); - } - else if (input->count(interface_key::SELECT) && !auto_select) - { - toggleHighlighted(); - } - else if (input->count(interface_key::CUSTOM_SHIFT_S)) - { - clearSearch(); - } - else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) - { - return setHighlightByMouse(); - } - else if (allow_search) - { - // Search query typing mode always on - df::interface_key last_token = get_string_key(input); - int charcode = Screen::keyToChar(last_token); - if ((charcode >= 96 && charcode <= 123) || charcode == 32) - { - // Standard character - search_string += char(charcode); - filterDisplay(); - centerSelection(); - } - else if (last_token == interface_key::STRING_A000) - { - // Backspace - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - filterDisplay(); - centerSelection(); - } - } - else - { - return false; - } - - return true; - } - else - { - return false; - } - - return true; - } - - bool setHighlightByMouse() - { - if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && - gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) - { - int new_index = display_start_offset + gps->mouse_y - 3; - if (new_index < display_list.size()) - { - setHighlight(new_index); - feed_mouse_set_highlight = true; - } - - enabler->mouse_lbut = enabler->mouse_rbut = 0; - - return true; - } - - return false; - } - - void sort(bool force_sort = false) - { - if (force_sort || list.size() < 100) - std::sort(list.begin(), list.end(), sort_fn); - - filterDisplay(); - } - - void setTitle(const string t) - { - title = t; - if (title.length() > max_item_width) - max_item_width = title.length(); - } - - size_t getDisplayedListSize() - { - return display_list.size(); - } - -protected: - static void clear_fn(ListEntry &e) { e.selected = false; } - static bool sort_fn(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } - - vector> list; - vector*> display_list; - string search_string; - string title; - int display_max_rows; - int max_item_width; -}; - - From f387eb2960bdf32f47d580790bd8bb35a7303718 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 09:34:04 -0400 Subject: [PATCH 025/109] Fix an issue where Ctrl-*-A and Ctrl-*-Z keybindings were not properly detected --- NEWS | 1 + library/Core.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 0b42a35e0..dd33be7a4 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ DFHack Future More build information available in plugins Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X + Fixed issues with keybindings involving Ctrl-A and Ctrl-Z Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/library/Core.cpp b/library/Core.cpp index 29644f535..65c87b292 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1917,7 +1917,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke) } // convert A-Z to their a-z counterparts: - if('A' < unicode && unicode < 'Z') + if('A' <= unicode && unicode <= 'Z') { unicode += 'a' - 'A'; } From cc5045d6df4c055aed793719fb4c1a04a2296420 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 10:32:19 -0400 Subject: [PATCH 026/109] Fix Alt-E/U/N bindings on OS X --- NEWS | 2 +- library/Core.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index dd33be7a4..8a3d81c4e 100644 --- a/NEWS +++ b/NEWS @@ -7,7 +7,7 @@ DFHack Future More build information available in plugins Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X - Fixed issues with keybindings involving Ctrl-A and Ctrl-Z + Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/library/Core.cpp b/library/Core.cpp index 65c87b292..62b63411f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1963,7 +1963,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev) // Use unicode so Windows gives the correct value for the // user's Input Language - if((ke->ksym.unicode & 0xff80) == 0) + if(ke->ksym.unicode && ((ke->ksym.unicode & 0xff80) == 0)) { int key = UnicodeAwareSym(*ke); SelectHotkey(key, modstate); From 7fc682b199814caec1720330a06edb8e65dbbd45 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 11:22:38 -0400 Subject: [PATCH 027/109] Fix dismissal of current screen when zooming to items --- plugins/stocks.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 0f6039bfc..d6611e1de 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -802,6 +802,12 @@ public: return; Screen::dismiss(this); + auto vs = Gui::getCurViewscreen(true); + while (vs && !virtual_cast(vs)) + { + Screen::dismiss(vs); + vs = vs->parent; + } // Could be clever here, if item is in a container, to look inside the container. // But that's different for built containers vs bags/pots in stockpiles. send_key(interface_key::D_LOOK); From 4e45cc5bbb7d45eebcfc65ff53cc9b94a42ecfd7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 13:44:32 -0400 Subject: [PATCH 028/109] stocks: Use ^ and $ to match the beginning/end of item names Closes #624 --- plugins/listcolumn.h | 54 +++++++++++------- plugins/stocks.cpp | 133 +++++++++++++++++++++++++++++++++++++++---- plugins/uicommon.h | 12 ++++ 3 files changed, 166 insertions(+), 33 deletions(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index e90f2f5fb..0576ca4e4 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -134,6 +134,29 @@ public: } } + virtual void tokenizeSearch (vector *dest, const string search) + { + if (!search.empty()) + split_string(dest, search, " "); + } + + virtual bool showEntry(const ListEntry *entry, const vector &search_tokens) + { + if (!search_tokens.empty()) + { + string item_string = toLower(entry->text); + for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) + { + if (!si->empty() && item_string.find(*si) == string::npos && + entry->keywords.find(*si) == string::npos) + { + return false; + } + } + } + return true; + } + void filterDisplay() { ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; @@ -141,29 +164,13 @@ public: search_string = toLower(search_string); vector search_tokens; - if (!search_string.empty()) - split_string(&search_tokens, search_string, " "); + tokenizeSearch(&search_tokens, search_string); for (size_t i = 0; i < list.size(); i++) { ListEntry *entry = &list[i]; - bool include_item = true; - if (!search_string.empty()) - { - string item_string = toLower(list[i].text); - for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) - { - if (!si->empty() && item_string.find(*si) == string::npos && - list[i].keywords.find(*si) == string::npos) - { - include_item = false; - break; - } - } - } - - if (include_item) + if (showEntry(entry, search_tokens)) { display_list.push_back(entry); if (entry == prev_selected) @@ -347,14 +354,19 @@ public: return list.size(); } + virtual bool validSearchInput (unsigned char c) + { + return (c >= 'a' && c <= 'z') || c == ' '; + } + bool feed(set *input) { feed_mouse_set_highlight = feed_changed_highlight = false; - if (input->count(interface_key::CURSOR_UP)) + if (input->count(interface_key::STANDARDSCROLL_UP)) { changeHighlight(-1); } - else if (input->count(interface_key::CURSOR_DOWN)) + else if (input->count(interface_key::STANDARDSCROLL_DOWN)) { changeHighlight(1); } @@ -383,7 +395,7 @@ public: // Search query typing mode always on df::interface_key last_token = get_string_key(input); int charcode = Screen::keyToChar(last_token); - if ((charcode >= 96 && charcode <= 123) || charcode == 32) + if (charcode >= 0 && validSearchInput((unsigned char)charcode)) { // Standard character search_string += char(charcode); diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index d6611e1de..85fedb9f2 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -599,8 +599,115 @@ class StockListColumn : public ListColumn OutputString(color, x, y, get_quality_name(quality)); } } + + virtual bool validSearchInput (unsigned char c) + { + switch (c) + { + case '(': + case ')': + return true; + break; + default: + break; + } + string &search_string = ListColumn::search_string; + if (c == '^' && !search_string.size()) + return true; + else if (c == '$' && search_string.size()) + { + if (search_string == "^") + return false; + if (search_string[search_string.size() - 1] != '$') + return true; + } + return ListColumn::validSearchInput(c); + } + + std::string getRawSearch(const std::string s) + { + string raw_search = s; + if (raw_search.size() && raw_search[0] == '^') + raw_search.erase(0, 1); + if (raw_search.size() && raw_search[raw_search.size() - 1] == '$') + raw_search.erase(raw_search.size() - 1, 1); + return toLower(raw_search); + } + + virtual void tokenizeSearch (vector *dest, const string search) + { + string raw_search = getRawSearch(search); + ListColumn::tokenizeSearch(dest, raw_search); + } + + virtual bool showEntry (const ListEntry *entry, const vector &search_tokens) + { + string &search_string = ListColumn::search_string; + if (!search_string.size()) + return true; + + bool match_start = false, match_end = false; + string raw_search = getRawSearch(search_string); + if (search_string.size() && search_string[0] == '^') + match_start = true; + if (search_string.size() && search_string[search_string.size() - 1] == '$') + match_end = true; + + if (!ListColumn::showEntry(entry, search_tokens)) + return false; + + string item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false)); + + if ((match_start || match_end) && raw_search.size() > item_name.size()) + return false; + if (match_start && item_name.compare(0, raw_search.size(), raw_search) != 0) + return false; + if (match_end && item_name.compare(item_name.size() - raw_search.size(), raw_search.size(), raw_search) != 0) + return false; + + return true; + } }; +class search_help : public dfhack_viewscreen +{ +public: + void feed (std::set *input) + { + if (input->count(interface_key::HELP)) + return; + if (Screen::isDismissed(this)) + return; + Screen::dismiss(this); + if (!input->count(interface_key::LEAVESCREEN) && !input->count(interface_key::SELECT)) + parent->feed(input); + } + void render() + { + static std::string text = + "\7 Flag names can be\n" + " searched for - e.g. job,\n" + " inventory, dump, forbid\n" + "\n" + "\7 Use ^ to match the start\n" + " of a name, and/or $ to\n" + " match the end of a name"; + if (Screen::isDismissed(this)) + return; + parent->render(); + int left_margin = gps->dimx - SIDEBAR_WIDTH; + int x = left_margin, y = 2; + Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, gps->dimx - 2, gps->dimy - 4); + Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, left_margin - 1, gps->dimy - 2); + OutputString(COLOR_WHITE, x, y, "Search help", true, left_margin); + ++y; + vector lines; + split_string(&lines, text, "\n"); + for (auto line = lines.begin(); line != lines.end(); ++line) + OutputString(COLOR_WHITE, x, y, line->c_str(), true, left_margin); + } + std::string getFocusString() { return "stocks_view/search_help"; } +}; class ViewscreenStocks : public dfhack_viewscreen { @@ -662,6 +769,10 @@ public: Screen::dismiss(this); return; } + else if (input->count(interface_key::HELP)) + { + Screen::show(new search_help); + } bool key_processed = false; switch (selected_column) @@ -921,19 +1032,18 @@ public: ++y; OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); - OutputHotkeyString(x, y, "Toggle Grouping", "TAB", true, left_margin); + OutputHotkeyString(x, y, "Toggle Grouping", interface_key::CHANGETAB, true, left_margin); ++y; - OutputString(COLOR_WHITE, x, y, "Qual: "); - OutputHotkeyString(x, y, "Min: ", "-+"); - OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), false, left_margin); - ++x; - OutputHotkeyString(x, y, "Max: ", "/*"); + OutputHotkeyString(x, y, "Min Qual: ", "-+"); + OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); + OutputHotkeyString(x, y, "Max Qual: ", "/*"); OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); - ++y; + if (gps->dimy > 27) + ++y; OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); @@ -941,14 +1051,13 @@ public: OutputHotkeyString(x, y, "Dump", "-D", true, left_margin); OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin); OutputHotkeyString(x, y, "Melt", "-M", true, left_margin); + OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin, + depot_info.canTrade() ? COLOR_WHITE : COLOR_DARKGREY); OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); - if (depot_info.canTrade()) - OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin); - y = gps->dimy - 5; - OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin); - OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin); + y = gps->dimy - 4; + OutputHotkeyString(x, y, "Search help", interface_key::HELP, true, left_margin); } std::string getFocusString() { return "stocks_view"; } diff --git a/plugins/uicommon.h b/plugins/uicommon.h index a54b3aa04..7182ee2f2 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -112,6 +112,12 @@ static void OutputHotkeyString(int &x, int &y, const char *text, const char *hot OutputString(text_color, x, y, display, newline, left_margin); } +static void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, + bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) +{ + OutputHotkeyString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), newline, left_margin, text_color, hotkey_color); +} + static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { @@ -142,6 +148,12 @@ static void OutputToggleString(int &x, int &y, const char *text, const char *hot OutputString(COLOR_GREY, x, y, "Off", newline, left_margin); } +static void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, + int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) +{ + OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color); +} + inline string int_to_string(const int n) { return static_cast( &(ostringstream() << n) )->str(); From a90f3c3d6752d8fb0eba0c355cae98e436d6dd80 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:17:14 -0400 Subject: [PATCH 029/109] stockpiles: Restrict characters in filenames when saving Previously, it was possible to save outside of the stocksettings directory or fail to save in a nonexistent subdirectory (e.g. when a stockpile name had slashes in it). Resolves #621 --- plugins/lua/stockpiles.lua | 48 +++++++++++++++++++++++++++++-- plugins/stockpiles/stockpiles.cpp | 6 +--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 815a551b7..ca8c28cd4 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -96,6 +96,48 @@ function tablify(iterableObject) return t end +local filename_invalid_regex = '[^A-Za-z0-9 ._-]' + +function valid_filename(filename) + return not filename:match(filename_invalid_regex) +end + +function sanitize_filename(filename) + local ret = '' + for i = 1, #filename do + local ch = filename:sub(i, i) + if valid_filename(ch) then + ret = ret .. ch + else + ret = ret .. '-' + end + end + return ret +end + +FilenameInputBox = defclass(FilenameInputBox, dlg.InputBox) +function FilenameInputBox:onInput(keys) + if not valid_filename(string.char(keys._STRING or 0)) and not keys.STRING_A000 then + keys._STRING = nil + end + FilenameInputBox.super.onInput(self, keys) +end + +function showFilenameInputPrompt(title, text, tcolor, input, min_width) + FilenameInputBox{ + frame_title = title, + text = text, + text_pen = tcolor, + input = input, + frame_width = min_width, + on_input = script.mkresume(true), + on_cancel = script.mkresume(false), + on_close = script.qresume(nil) + }:show() + + return script.wait() +end + function load_settings() init() local path = get_path() @@ -132,16 +174,16 @@ function save_settings(stockpile) if #suggested == 0 then suggested = 'Stock1' end + suggested = sanitize_filename(suggested) local path = get_path() - local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested) + local sok,filename = showFilenameInputPrompt('Stockpile Settings', 'Enter filename', COLOR_WHITE, suggested) if sok then - if filename == nil or filename == '' then + if filename == nil or filename == '' or not valid_filename(filename) then script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED) else if not dfhack.filesystem.exists(path) then dfhack.filesystem.mkdir(path) end - print("saving...", path..'/'..filename) stockpiles_save(path..'/'..filename) end end diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index f02d19e45..bfba7e1e4 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -246,7 +246,7 @@ static command_result savestock ( color_ostream &out, vector & paramete if ( !is_dfstockfile ( file ) ) file += ".dfstock"; if ( !cereal.serialize_to_file ( file ) ) { - out.printerr ( "serialize failed\n" ); + out.printerr ( "could not save to %s\n", file.c_str() ); return CR_FAILURE; } return CR_OK; @@ -510,11 +510,9 @@ static int stockpiles_list_settings ( lua_State *L ) static void stockpiles_load ( color_ostream &out, std::string filename ) { - out << "stockpiles_load " << filename << " "; std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); - out << " result = "<< r << endl; if ( r != CR_OK ) show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); } @@ -522,11 +520,9 @@ static void stockpiles_load ( color_ostream &out, std::string filename ) static void stockpiles_save ( color_ostream &out, std::string filename ) { - out << "stockpiles_save " << filename << " "; std::vector params; params.push_back ( filename ); command_result r = savestock ( out, params ); - out << " result = "<< r << endl; if ( r != CR_OK ) show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); } From f3080b4fb54b87dbe412118b8a6725ea2699e1fc Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:21:03 -0400 Subject: [PATCH 030/109] Update NEWS --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 8a3d81c4e..25a9870a5 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,7 @@ DFHack Future gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly search: fixed crash in unit list after cancelling a job + stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking steam-engine: - fixed a crash on arena load @@ -70,6 +71,7 @@ DFHack Future - Supports noble suggestion screen (e.g. suggesting a baron) - Supports fortress mode loo[k] menu - Recognizes ? and ; keys + stocks: can now match beginning and end of item names teleport: Fixed cursor recognition tweak: - debug output now logged to stderr.log instead of console - makes DFHack start faster From d02e67267a2dd5a74834ac7f22c7924660d70bb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 14:47:55 -0400 Subject: [PATCH 031/109] Replace jsonxx with jsoncpp --- .gitmodules | 3 - CMakeLists.txt | 5 +- LICENSE | 27 +- depends/CMakeLists.txt | 2 +- depends/jsoncpp/CMakeLists.txt | 2 + depends/jsoncpp/jsoncpp.cpp | 5089 ++++++++++++++++++++++++++++++++ depends/jsoncpp/jsoncpp.h | 2031 +++++++++++++ depends/jsonxx | 1 - library/CMakeLists.txt | 2 +- plugins/CMakeLists.txt | 2 +- plugins/dwarfmonitor.cpp | 2 - 11 files changed, 7140 insertions(+), 26 deletions(-) create mode 100644 depends/jsoncpp/CMakeLists.txt create mode 100644 depends/jsoncpp/jsoncpp.cpp create mode 100644 depends/jsoncpp/jsoncpp.h delete mode 160000 depends/jsonxx diff --git a/.gitmodules b/.gitmodules index 212095b0c..b20d189ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git -[submodule "depends/jsonxx"] - path = depends/jsonxx - url = git://github.com/lethosor/jsonxx.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 64cf0717e..5a8dabd13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") endif() # make sure all the necessary submodules have been set up -if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt - OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/jsonxx/CMakeLists.txt) +if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt) message(FATAL_ERROR "One or more required submodules could not be found! Run 'git submodule update --init' from the root DFHack directory. (See the section 'Getting the Code' in Compile.rst or Compile.html)") endif() @@ -136,7 +135,7 @@ find_package(ZLIB REQUIRED) include_directories(depends/protobuf) include_directories(depends/lua/include) include_directories(depends/md5) -include_directories(depends/jsonxx) +include_directories(depends/jsoncpp) include_directories(depends/tinyxml) include_directories(depends/tthread) include_directories(${ZLIB_INCLUDE_DIRS}) diff --git a/LICENSE b/LICENSE index 3908e04d6..fd4f7abcb 100644 --- a/LICENSE +++ b/LICENSE @@ -164,30 +164,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- -jsonxx license +jsoncpp license -Copyright (c) 2010 Hong Jiang +Copyright (c) 2007-2010 Baptiste Lepilleur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -------------------------------------------------------------------------------- JSON.lua license diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 804ca09de..bf0345bfb 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(md5) add_subdirectory(protobuf) add_subdirectory(tinyxml) add_subdirectory(tthread) -add_subdirectory(jsonxx) +add_subdirectory(jsoncpp) # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) diff --git a/depends/jsoncpp/CMakeLists.txt b/depends/jsoncpp/CMakeLists.txt new file mode 100644 index 000000000..112903ee3 --- /dev/null +++ b/depends/jsoncpp/CMakeLists.txt @@ -0,0 +1,2 @@ +PROJECT(jsoncpp) +ADD_LIBRARY(jsoncpp STATIC jsoncpp.cpp) diff --git a/depends/jsoncpp/jsoncpp.cpp b/depends/jsoncpp/jsoncpp.cpp new file mode 100644 index 000000000..eb38217ef --- /dev/null +++ b/depends/jsoncpp/jsoncpp.cpp @@ -0,0 +1,5089 @@ +/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "jsoncpp.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { + +/// Converts a unicode code-point to UTF-8. +static inline std::string codePointToUTF8(unsigned int cp) { + std::string result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast(cp); + } else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + +/// Returns true if ch is a control character (in range [1,31]). +static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; } + +enum { + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + +/** Converts an unsigned integer to string. + * @param value Unsigned interger to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void uintToString(LargestUInt value, char*& current) { + *--current = 0; + do { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); +} + +/** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ +static inline void fixNumericLocale(char* begin, char* end) { + while (begin < end) { + if (*begin == ',') { + *begin = '.'; + } + ++begin; + } +} + +} // namespace Json { + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below +#define snprintf _snprintf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +static int const stackLimit_g = 1000; +static int stackDepth_g = 0; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L +typedef std::unique_ptr CharReaderPtr; +#else +typedef std::auto_ptr CharReaderPtr; +#endif + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() + : allowComments_(true), strictRoot_(false), + allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} + +Features Features::all() { return Features(); } + +Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + +static bool containsNewLine(Reader::Location begin, Reader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(Features::all()), + collectComments_() {} + +Reader::Reader(const Features& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool +Reader::parse(const std::string& document, Value& root, bool collectComments) { + document_ = document; + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); +} + +bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { + // std::istream_iterator begin(sin); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since std::string is reference-counted, this at least does not + // create an extra copy. + std::string doc; + std::getline(sin, doc, (char)EOF); + return parse(doc, root, collectComments); +} + +bool Reader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + stackDepth_g = 0; // Yes, this is bad coding, but options are limited. + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool Reader::readValue() { + // This is a non-reentrant way to support a stackLimit. Terrible! + // But this deprecated class has a security problem: Bad input can + // cause a seg-fault. This seems like a fair, binary-compatible way + // to prevent the problem. + if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + ++stackDepth_g; + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_ = ""; + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + --stackDepth_g; + return successful; +} + +void Reader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool Reader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void Reader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool Reader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool Reader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +static std::string normalizeEOL(Reader::Location begin, Reader::Location end) { + std::string normalized; + normalized.reserve(end - begin); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void +Reader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const std::string& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool Reader::readCStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void Reader::readNumber() { + const char *p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : 0; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } +} + +bool Reader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool Reader::readObject(Token& tokenStart) { + Token tokenName; + std::string name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name = ""; + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover( + "Missing ':' after object member name", colon, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover( + "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover( + "Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool Reader::readArray(Token& tokenStart) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (*current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token token; + // Accept Comment after last item in the array. + ok = readToken(token); + while (token.type_ == tokenComment && ok) { + ok = readToken(token); + } + bool badTokenType = + (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover( + "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + } + if (token.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(c - '0'); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + std::string buffer(token.start_, token.end_); + std::istringstream is(buffer); + if (!(is >> value)) + return addError("'" + std::string(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) { + std::string decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, std::string& decoded) { + decoded.reserve(token.end_ - token.start_ - 2); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool Reader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, + current); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++) == 'u') { + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, + current); + } + return true; +} + +bool Reader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, + current); + } + return true; +} + +bool +Reader::addError(const std::string& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool Reader::recoverFromError(TokenType skipUntilToken) { + int errorCount = int(errors_.size()); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool Reader::addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& Reader::currentValue() { return *(nodes_.top()); } + +Reader::Char Reader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void Reader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +std::string Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) +#if defined(WINCE) + _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#else + sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif +#else + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif + return buffer; +} + +// Deprecated. Preserved for backward compatibility +std::string Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); +} + +std::string Reader::getFormattedErrorMessages() const { + std::string formattedMessage; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector Reader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool Reader::pushError(const Value& value, const std::string& message) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool Reader::pushError(const Value& value, const std::string& message, const Value& extra) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool Reader::good() const { + return !errors_.size(); +} + +// exact copy of Features +class OurFeatures { +public: + static OurFeatures all(); + OurFeatures(); + bool allowComments_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + int stackLimit_; +}; // OurFeatures + +// exact copy of Implementation of class Features +// //////////////////////////////// + +OurFeatures::OurFeatures() + : allowComments_(true), strictRoot_(false) + , allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) + , allowSingleQuotes_(false) + , failIfExtra_(false) +{ +} + +OurFeatures OurFeatures::all() { return OurFeatures(); } + +// Implementation of class Reader +// //////////////////////////////// + +// exact copy of Reader, renamed to OurReader +class OurReader { +public: + typedef char Char; + typedef const Char* Location; + struct StructuredError { + size_t offset_start; + size_t offset_limit; + std::string message; + }; + + OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + std::string getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; + bool pushError(const Value& value, const std::string& message); + bool pushError(const Value& value, const std::string& message, const Value& extra); + bool good() const; + +private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, std::string& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const std::string& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + std::string getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + std::string commentsBefore_; + int stackDepth_; + + OurFeatures const features_; + bool collectComments_; +}; // OurReader + +// complete copy of Read impl, for OurReader + +OurReader::OurReader(OurFeatures const& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool OurReader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + stackDepth_ = 0; + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_) { + if (token.type_ != tokenError && token.type_ != tokenEndOfStream) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + } + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool OurReader::readValue() { + if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + ++stackDepth_; + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_ = ""; + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + --stackDepth_; + return successful; +} + +void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool OurReader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + break; + } // else continue + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool OurReader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool OurReader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +void +OurReader::addComment(Location begin, Location end, CommentPlacement placement) { + assert(collectComments_); + const std::string& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != 0); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void OurReader::readNumber() { + const char *p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : 0; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : 0; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : 0; + } +} +bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + + +bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& tokenStart) { + Token tokenName; + std::string name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name = ""; + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover( + "Missing ':' after object member name", colon, tokenObjectEnd); + } + if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + std::string msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover( + msg, tokenName, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover( + "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover( + "Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool OurReader::readArray(Token& tokenStart) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(tokenStart.start_ - begin_); + skipSpaces(); + if (*current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token token; + // Accept Comment after last item in the array. + ok = readToken(token); + while (token.type_ == tokenComment && ok) { + ok = readToken(token); + } + bool badTokenType = + (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover( + "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + } + if (token.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(-Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + Value::UInt digit(c - '0'); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const int bufferSize = 32; + int count; + int length = int(token.end_ - token.start_); + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError("Unable to parse token length", token); + } + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if (length <= bufferSize) { + Char buffer[bufferSize + 1]; + memcpy(buffer, token.start_, length); + buffer[length] = 0; + count = sscanf(buffer, format, &value); + } else { + std::string buffer(token.start_, token.end_); + count = sscanf(buffer.c_str(), format, &value); + } + + if (count != 1) + return addError("'" + std::string(token.start_, token.end_) + + "' is not a number.", + token); + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) { + std::string decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, std::string& decoded) { + decoded.reserve(token.end_ - token.start_ - 2); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool OurReader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, + current); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++) == 'u') { + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, + current); + } + return true; +} + +bool OurReader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", + token, + current); + unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, + current); + } + return true; +} + +bool +OurReader::addError(const std::string& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) { + int errorCount = int(errors_.size()); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool OurReader::addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& OurReader::currentValue() { return *(nodes_.top()); } + +OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void OurReader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +std::string OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) +#if defined(WINCE) + _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#else + sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif +#else + snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); +#endif + return buffer; +} + +std::string OurReader::getFormattedErrorMessages() const { + std::string formattedMessage; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector OurReader::getStructuredErrors() const { + std::vector allErrors; + for (Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError) { + const ErrorInfo& error = *itError; + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool OurReader::pushError(const Value& value, const std::string& message) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = 0; + errors_.push_back(info); + return true; +} + +bool OurReader::pushError(const Value& value, const std::string& message, const Value& extra) { + size_t length = end_ - begin_; + if(value.getOffsetStart() > length + || value.getOffsetLimit() > length + || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool OurReader::good() const { + return !errors_.size(); +} + + +class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; +public: + OurCharReader( + bool collectComments, + OurFeatures const& features) + : collectComments_(collectComments) + , reader_(features) + {} + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() +{ + setDefaults(&settings_); +} +CharReaderBuilder::~CharReaderBuilder() +{} +CharReader* CharReaderBuilder::newCharReader() const +{ + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); + features.stackLimit_ = settings_["stackLimit"].asInt(); + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + return new OurCharReader(collectComments, features); +} +static void getValidReaderKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); + valid_keys->insert("allowSingleQuotes"); + valid_keys->insert("stackLimit"); + valid_keys->insert("failIfExtra"); + valid_keys->insert("rejectDupKeys"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& CharReaderBuilder::operator[](std::string key) +{ + return settings_[key]; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) +{ +//! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; +//! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) +{ +//! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; +//! [CharReaderBuilderDefaults] +} + +////////////////////////////////// +// global functions + +bool parseFromStream( + CharReader::Factory const& fact, std::istream& sin, + Value* root, std::string* errs) +{ + std::ostringstream ssin; + ssin << sin.rdbuf(); + std::string doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + +std::istream& operator>>(std::istream& sin, Value& root) { + CharReaderBuilder b; + std::string errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + fprintf(stderr, + "Error from reader: %s", + errs.c_str()); + + throwRuntimeError("reader error"); + } + return sin; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() + : current_(), isNull_(true) { +} + +ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + +Value& ValueIteratorBase::deref() const { + return current_->second; +} + +void ValueIteratorBase::increment() { + ++current_; +} + +void ValueIteratorBase::decrement() { + --current_; +} + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance(const SelfType& other) const { +#ifdef JSON_USE_CPPTL_SMALLMAP + return other.current_ - current_; +#else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) { + return 0; + } + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; + ++it) { + ++myDistance; + } + return myDistance; +#endif +} + +bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; +} + +void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; +} + +Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); +} + +UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); +} + +std::string ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) return std::string(); + return std::string(keey, end); +} + +char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; +} + +char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = NULL; + return NULL; + } + *end = cname + (*current_).first.length(); + return cname; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() {} + +ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueConstIterator& ValueConstIterator:: +operator=(const ValueIteratorBase& other) { + copy(other); + return *this; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() {} + +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) {} + +ValueIterator::ValueIterator(const ValueIterator& other) + : ValueIteratorBase(other) {} + +ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +#include +#endif +#include // size_t +#include // min() + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + +// This is a walkaround to avoid the static initialization of Value::null. +// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of +// 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif +static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; +const unsigned char& kNullRef = kNull[0]; +const Value& Value::null = reinterpret_cast(kNullRef); +const Value& Value::nullRef = null; + +const Int Value::minInt = Int(~(UInt(-1) / 2)); +const Int Value::maxInt = Int(UInt(-1) / 2); +const UInt Value::maxUInt = UInt(-1); +#if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); +const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); +const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template +static inline bool InRange(double d, T min, U max) { + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) { + return static_cast(Int64(value / 2)) * 2.0 + Int64(value & 1); +} + +template static inline double integerToDouble(T value) { + return static_cast(value); +} + +template +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char* duplicateStringValue(const char* value, + size_t length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= (size_t)Value::maxInt) + length = Value::maxInt - 1; + + char* newString = static_cast(malloc(length + 1)); + if (newString == NULL) { + throwRuntimeError( + "in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; +} + +/* Record the length as a prefix. + */ +static inline char* duplicateAndPrefixStringValue( + const char* value, + unsigned int length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= (unsigned)Value::maxInt - sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; + char* newString = static_cast(malloc(actualLength)); + if (newString == 0) { + throwRuntimeError( + "in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + return newString; +} +inline static void decodePrefixedString( + bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) +{ + if (!isPrefixed) { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } else { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } +} +/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +static inline void releaseStringValue(char* value) { free(value); } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +Exception::Exception(std::string const& msg) + : msg_(msg) +{} +Exception::~Exception() throw() +{} +char const* Exception::what() const throw() +{ + return msg_.c_str(); +} +RuntimeError::RuntimeError(std::string const& msg) + : Exception(msg) +{} +LogicError::LogicError(std::string const& msg) + : Exception(msg) +{} +void throwRuntimeError(std::string const& msg) +{ + throw RuntimeError(msg); +} +void throwLogicError(std::string const& msg) +{ + throw LogicError(msg); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +Value::CommentInfo::CommentInfo() : comment_(0) {} + +Value::CommentInfo::~CommentInfo() { + if (comment_) + releaseStringValue(comment_); +} + +void Value::CommentInfo::setComment(const char* text, size_t len) { + if (comment_) { + releaseStringValue(comment_); + comment_ = 0; + } + JSON_ASSERT(text != 0); + JSON_ASSERT_MESSAGE( + text[0] == '\0' || text[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue(text, len); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +// Notes: policy_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {} + +Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate) + : cstr_(str) +{ + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = ulength & 0x3FFFFFFF; +} + +Value::CZString::CZString(const CZString& other) + : cstr_(other.storage_.policy_ != noDuplication && other.cstr_ != 0 + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_) +{ + storage_.policy_ = (other.cstr_ + ? (static_cast(other.storage_.policy_) == noDuplication + ? noDuplication : duplicate) + : static_cast(other.storage_.policy_)); + storage_.length_ = other.storage_.length_; +} + +Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) + releaseStringValue(const_cast(cstr_)); +} + +void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); +} + +Value::CZString& Value::CZString::operator=(CZString other) { + swap(other); + return *this; +} + +bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) return index_ < other.index_; + //return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); +} + +bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) return index_ == other.index_; + //return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) return false; + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; +} + +ArrayIndex Value::CZString::index() const { return index_; } + +//const char* Value::CZString::c_str() const { return cstr_; } +const char* Value::CZString::data() const { return cstr_; } +unsigned Value::CZString::length() const { return storage_.length_; } +bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value(ValueType vtype) { + initBasic(vtype); + switch (vtype) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + value_.string_ = 0; + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; +} + +Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; +} +#if defined(JSON_HAS_INT64) +Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; +} +Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; +} + +Value::Value(const char* value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); +} + +Value::Value(const char* beginValue, const char* endValue) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(beginValue, static_cast(endValue - beginValue)); +} + +Value::Value(const std::string& value) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); +} + +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value::Value(const CppTL::ConstString& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); +} +#endif + +Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; +} + +Value::Value(Value const& other) + : type_(other.type_), allocated_(false) + , + comments_(0), start_(other.start_), limit_(other.limit_) +{ + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.allocated_) { + unsigned len; + char const* str; + decodePrefixedString(other.allocated_, other.value_.string_, + &len, &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + allocated_ = true; + } else { + value_.string_ = other.value_.string_; + allocated_ = false; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } + if (other.comments_) { + comments_ = new CommentInfo[numberOfCommentPlacement]; + for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { + const CommentInfo& otherComment = other.comments_[comment]; + if (otherComment.comment_) + comments_[comment].setComment( + otherComment.comment_, strlen(otherComment.comment_)); + } + } +} + +Value::~Value() { + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (allocated_) + releaseStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } + + if (comments_) + delete[] comments_; +} + +Value& Value::operator=(Value other) { + swap(other); + return *this; +} + +void Value::swapPayload(Value& other) { + ValueType temp = type_; + type_ = other.type_; + other.type_ = temp; + std::swap(value_, other.value_); + int temp2 = allocated_; + allocated_ = other.allocated_; + other.allocated_ = temp2 & 0x1; +} + +void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); +} + +ValueType Value::type() const { return type_; } + +int Value::compare(const Value& other) const { + if (*this < other) + return -1; + if (*this > other) + return 1; + return 0; +} + +bool Value::operator<(const Value& other) const { + int typeDelta = type_ - other.type_; + if (typeDelta) + return typeDelta < 0 ? true : false; + switch (type_) { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + if (other.value_.string_) return true; + else return false; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + unsigned min_len = std::min(this_len, other_len); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: { + int delta = int(value_.map_->size() - other.value_.map_->size()); + if (delta) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator<=(const Value& other) const { return !(other < *this); } + +bool Value::operator>=(const Value& other) const { return !(*this < other); } + +bool Value::operator>(const Value& other) const { return other < *this; } + +bool Value::operator==(const Value& other) const { + // if ( type_ != other.type_ ) + // GCC 2.95.3 says: + // attempt to take address of bit-field structure member `Json::Value::type_' + // Beats me, but a temp solves the problem. + int temp = other.type_; + if (type_ != temp) + return false; + switch (type_) { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: + { + if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + if (this_len != other_len) return false; + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator!=(const Value& other) const { return !(*this == other); } + +const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type_ == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return this_str; +} + +bool Value::getString(char const** str, char const** cend) const { + if (type_ != stringValue) return false; + if (value_.string_ == 0) return false; + unsigned length; + decodePrefixedString(this->allocated_, this->value_.string_, &length, str); + *cend = *str + length; + return true; +} + +std::string Value::asString() const { + switch (type_) { + case nullValue: + return ""; + case stringValue: + { + if (value_.string_ == 0) return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + return std::string(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } +} + +#ifdef JSON_USE_CPPTL +CppTL::ConstString Value::asConstString() const { + unsigned len; + char const* str; + decodePrefixedString(allocated_, value_.string_, + &len, &str); + return CppTL::ConstString(str, len); +} +#endif + +Value::Int Value::asInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + +Value::UInt Value::asUInt() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + +#if defined(JSON_HAS_INT64) + +Value::Int64 Value::asInt64() const { + switch (type_) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + +Value::UInt64 Value::asUInt64() const { + switch (type_) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +#endif // if defined(JSON_HAS_INT64) + +LargestInt Value::asLargestInt() const { +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + +LargestUInt Value::asLargestUInt() const { +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + +double Value::asDouble() const { + switch (type_) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float Value::asFloat() const { + switch (type_) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool Value::asBool() const { + switch (type_) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + // This is kind of strange. Not recommended. + return (value_.real_ != 0.0) ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type_ == booleanValue && value_.bool_ == false) || + (type_ == stringValue && asString() == "") || + (type_ == arrayValue && value_.map_->size() == 0) || + (type_ == objectValue && value_.map_->size() == 0) || + type_ == nullValue; + case intValue: + return isInt() || + (type_ == realValue && InRange(value_.real_, minInt, maxInt)) || + type_ == booleanValue || type_ == nullValue; + case uintValue: + return isUInt() || + (type_ == realValue && InRange(value_.real_, 0, maxUInt)) || + type_ == booleanValue || type_ == nullValue; + case realValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case booleanValue: + return isNumeric() || type_ == booleanValue || type_ == nullValue; + case stringValue: + return isNumeric() || type_ == booleanValue || type_ == stringValue || + type_ == nullValue; + case arrayValue: + return type_ == arrayValue || type_ == nullValue; + case objectValue: + return type_ == objectValue || type_ == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + +/// Number of values in array or object +ArrayIndex Value::size() const { + switch (type_) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; +} + +bool Value::empty() const { + if (isNull() || isArray() || isObject()) + return size() == 0u; + else + return false; +} + +bool Value::operator!() const { return isNull(); } + +void Value::clear() { + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue || + type_ == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type_) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } +} + +void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + (*this)[newSize - 1]; + else { + for (ArrayIndex index = newSize; index < oldSize; ++index) { + value_.map_->erase(index); + } + assert(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type_ == nullValue) + *this = Value(arrayValue); + CZString key(index); + ObjectValues::iterator it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) + return (*it).second; + + ObjectValues::value_type defaultValue(key, nullRef); + it = value_.map_->insert(it, defaultValue); + return (*it).second; +} + +Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type_ == nullValue) + return nullRef; + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullRef; + return (*it).second; +} + +const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +void Value::initBasic(ValueType vtype, bool allocated) { + type_ = vtype; + allocated_ = allocated; + comments_ = 0; + start_ = 0; + limit_ = 0; +} + +// Access an object value by name, create a null member if it does not exist. +// @pre Type of '*this' is object or null. +// @param key is null-terminated. +Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(strlen(key)), CZString::noDuplication); // NOTE! + ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullRef); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +// @param key is not null-terminated. +Value& Value::resolveReference(char const* key, char const* cend) +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type_ == nullValue) + *this = Value(objectValue); + CZString actualKey( + key, static_cast(cend-key), CZString::duplicateOnCopy); + ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullRef); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullRef ? defaultValue : *value; +} + +bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + +Value const* Value::find(char const* key, char const* cend) const +{ + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::find(key, end, found): requires objectValue or nullValue"); + if (type_ == nullValue) return NULL; + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) return NULL; + return &(*it).second; +} +const Value& Value::operator[](const char* key) const +{ + Value const* found = find(key, key + strlen(key)); + if (!found) return nullRef; + return *found; +} +Value const& Value::operator[](std::string const& key) const +{ + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) return nullRef; + return *found; +} + +Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); +} + +Value& Value::operator[](const std::string& key) { + return resolveReference(key.data(), key.data() + key.length()); +} + +Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value& Value::operator[](const CppTL::ConstString& key) { + return resolveReference(key.c_str(), key.end_c_str()); +} +Value const& Value::operator[](CppTL::ConstString const& key) const +{ + Value const* found = find(key.c_str(), key.end_c_str()); + if (!found) return nullRef; + return *found; +} +#endif + +Value& Value::append(const Value& value) { return (*this)[size()] = value; } + +Value Value::get(char const* key, char const* cend, Value const& defaultValue) const +{ + Value const* found = find(key, cend); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const +{ + return get(key, key + strlen(key), defaultValue); +} +Value Value::get(std::string const& key, Value const& defaultValue) const +{ + return get(key.data(), key.data() + key.length(), defaultValue); +} + + +bool Value::removeMember(const char* key, const char* cend, Value* removed) +{ + if (type_ != objectValue) { + return false; + } + CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); + ObjectValues::iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + *removed = it->second; + value_.map_->erase(it); + return true; +} +bool Value::removeMember(const char* key, Value* removed) +{ + return removeMember(key, key + strlen(key), removed); +} +bool Value::removeMember(std::string const& key, Value* removed) +{ + return removeMember(key.data(), key.data() + key.length(), removed); +} +Value Value::removeMember(const char* key) +{ + JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type_ == nullValue) + return nullRef; + + Value removed; // null + removeMember(key, key + strlen(key), &removed); + return removed; // still null if removeMember() did nothing +} +Value Value::removeMember(const std::string& key) +{ + return removeMember(key.c_str()); +} + +bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type_ != arrayValue) { + return false; + } + CZString key(index); + ObjectValues::iterator it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i){ + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + ObjectValues::iterator itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; +} + +#ifdef JSON_USE_CPPTL +Value Value::get(const CppTL::ConstString& key, + const Value& defaultValue) const { + return get(key.c_str(), key.end_c_str(), defaultValue); +} +#endif + +bool Value::isMember(char const* key, char const* cend) const +{ + Value const* value = find(key, cend); + return NULL != value; +} +bool Value::isMember(char const* key) const +{ + return isMember(key, key + strlen(key)); +} +bool Value::isMember(std::string const& key) const +{ + return isMember(key.data(), key.data() + key.length()); +} + +#ifdef JSON_USE_CPPTL +bool Value::isMember(const CppTL::ConstString& key) const { + return isMember(key.c_str(), key.end_c_str()); +} +#endif + +Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type_ == nullValue || type_ == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type_ == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(std::string((*it).first.data(), + (*it).first.length())); + } + return members; +} +// +//# ifdef JSON_USE_CPPTL +// EnumMemberNames +// Value::enumMemberNames() const +//{ +// if ( type_ == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +// EnumValues +// Value::enumValues() const +//{ +// if ( type_ == objectValue || type_ == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + +bool Value::isNull() const { return type_ == nullValue; } + +bool Value::isBool() const { return type_ == booleanValue; } + +bool Value::isInt() const { + switch (type_) { + case intValue: + return value_.int_ >= minInt && value_.int_ <= maxInt; + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isUInt() const { + switch (type_) { + case intValue: + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); + case uintValue: + return value_.uint_ <= maxUInt; + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type_) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isIntegral() const { +#if defined(JSON_HAS_INT64) + return isInt64() || isUInt64(); +#else + return isInt() || isUInt(); +#endif +} + +bool Value::isDouble() const { return type_ == realValue || isIntegral(); } + +bool Value::isNumeric() const { return isIntegral() || isDouble(); } + +bool Value::isString() const { return type_ == stringValue; } + +bool Value::isArray() const { return type_ == arrayValue; } + +bool Value::isObject() const { return type_ == objectValue; } + +void Value::setComment(const char* comment, size_t len, CommentPlacement placement) { + if (!comments_) + comments_ = new CommentInfo[numberOfCommentPlacement]; + if ((len > 0) && (comment[len-1] == '\n')) { + // Always discard trailing newline, to aid indentation. + len -= 1; + } + comments_[placement].setComment(comment, len); +} + +void Value::setComment(const char* comment, CommentPlacement placement) { + setComment(comment, strlen(comment), placement); +} + +void Value::setComment(const std::string& comment, CommentPlacement placement) { + setComment(comment.c_str(), comment.length(), placement); +} + +bool Value::hasComment(CommentPlacement placement) const { + return comments_ != 0 && comments_[placement].comment_ != 0; +} + +std::string Value::getComment(CommentPlacement placement) const { + if (hasComment(placement)) + return comments_[placement].comment_; + return ""; +} + +void Value::setOffsetStart(size_t start) { start_ = start; } + +void Value::setOffsetLimit(size_t limit) { limit_ = limit; } + +size_t Value::getOffsetStart() const { return start_; } + +size_t Value::getOffsetLimit() const { return limit_; } + +std::string Value::toStyledString() const { + StyledWriter writer; + return writer.write(*this); +} + +Value::const_iterator Value::begin() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return const_iterator(); +} + +Value::const_iterator Value::end() const { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return const_iterator(); +} + +Value::iterator Value::begin() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); +} + +Value::iterator Value::end() { + switch (type_) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); +} + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {} + +PathArgument::PathArgument(ArrayIndex index) + : key_(), index_(index), kind_(kindIndex) {} + +PathArgument::PathArgument(const char* key) + : key_(key), index_(), kind_(kindKey) {} + +PathArgument::PathArgument(const std::string& key) + : key_(key.c_str()), index_(), kind_(kindKey) {} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path(const std::string& path, + const PathArgument& a1, + const PathArgument& a2, + const PathArgument& a3, + const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); +} + +void Path::makePath(const std::string& path, const InArgs& in) { + const char* current = path.c_str(); + const char* end = current + path.length(); + InArgs::const_iterator itInArg = in.begin(); + while (current != end) { + if (*current == '[') { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *current++ != ']') + invalidPath(path, int(current - path.c_str())); + } else if (*current == '%') { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } else if (*current == '.') { + ++current; + } else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(std::string(beginName, current)); + } + } +} + +void Path::addPathInArg(const std::string& /*path*/, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind) { + if (itInArg == in.end()) { + // Error: missing argument %d + } else if ((*itInArg)->kind_ != kind) { + // Error: bad argument type + } else { + args_.push_back(**itInArg); + } +} + +void Path::invalidPath(const std::string& /*path*/, int /*location*/) { + // Error: invalid path. +} + +const Value& Path::resolve(const Value& root) const { + const Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) { + // Error: unable to resolve path (array value expected at position... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + } + node = &((*node)[arg.key_]); + if (node == &Value::nullRef) { + // Error: unable to resolve path (object has no member named '' at + // position...) + } + } + } + return *node; +} + +Value Path::resolve(const Value& root, const Value& defaultValue) const { + const Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + return defaultValue; + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) + return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullRef) + return defaultValue; + } + } + return *node; +} + +Value& Path::make(Value& root) const { + Value* node = &root; + for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { + const PathArgument& arg = *it; + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray()) { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 +#include +#define isfinite _finite +#elif defined(__sun) && defined(__SVR4) //Solaris +#include +#define isfinite finite +#else +#include +#define isfinite std::isfinite +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below +#define snprintf _snprintf +#elif defined(__ANDROID__) +#define snprintf snprintf +#elif __cplusplus >= 201103L +#define snprintf std::snprintf +#endif + +#if defined(__BORLANDC__) +#include +#define isfinite _finite +#define snprintf _snprintf +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L +typedef std::unique_ptr StreamWriterPtr; +#else +typedef std::auto_ptr StreamWriterPtr; +#endif + +static bool containsControlCharacter(const char* str) { + while (*str) { + if (isControlCharacter(*(str++))) + return true; + } + return false; +} + +static bool containsControlCharacter0(const char* str, unsigned len) { + char const* end = str + len; + while (end != str) { + if (isControlCharacter(*str) || 0==*str) + return true; + ++str; + } + return false; +} + +std::string valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; +} + +std::string valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; +} + +#if defined(JSON_HAS_INT64) + +std::string valueToString(Int value) { + return valueToString(LargestInt(value)); +} + +std::string valueToString(UInt value) { + return valueToString(LargestUInt(value)); +} + +#endif // # if defined(JSON_HAS_INT64) + +std::string valueToString(double value) { + // Allocate a buffer that is more than large enough to store the 16 digits of + // precision requested below. + char buffer[32]; + int len = -1; + +// Print into the buffer. We need not request the alternative representation +// that always has a decimal point because JSON doesn't distingish the +// concepts of reals and integers. +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with + // visual studio 2005 to + // avoid warning. +#if defined(WINCE) + len = _snprintf(buffer, sizeof(buffer), "%.17g", value); +#else + len = sprintf_s(buffer, sizeof(buffer), "%.17g", value); +#endif +#else + if (isfinite(value)) { + len = snprintf(buffer, sizeof(buffer), "%.17g", value); + } else { + // IEEE standard states that NaN values will not compare to themselves + if (value != value) { + len = snprintf(buffer, sizeof(buffer), "null"); + } else if (value < 0) { + len = snprintf(buffer, sizeof(buffer), "-1e+9999"); + } else { + len = snprintf(buffer, sizeof(buffer), "1e+9999"); + } + // For those, we do not need to call fixNumLoc, but it is fast. + } +#endif + assert(len >= 0); + fixNumericLocale(buffer, buffer + len); + return buffer; +} + +std::string valueToString(bool value) { return value ? "true" : "false"; } + +std::string valueToQuotedString(const char* value) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && + !containsControlCharacter(value)) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = + strlen(value) * 2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + for (const char* c = value; *c != 0; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something. + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp +static char const* strnpbrk(char const* s, char const* accept, size_t n) { + assert((s || !n) && accept); + + char const* const end = s + n; + for (char const* cur = s; cur < end; ++cur) { + int const c = *cur; + for (char const* a = accept; *a; ++a) { + if (*a == c) { + return cur; + } + } + } + return NULL; +} +static std::string valueToQuotedStringN(const char* value, unsigned length) { + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL && + !containsControlCharacter0(value, length)) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = + length * 2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } else { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() {} + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + : yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false), + omitEndingLineFeed_(false) {} + +void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; } + +void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + +void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + +std::string FastWriter::write(const Value& root) { + document_ = ""; + writeValue(root); + if (!omitEndingLineFeed_) + document_ += "\n"; + return document_; +} + +void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + int size = value.size(); + for (int index = 0; index < size; ++index) { + if (index > 0) + document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } break; + case objectValue: { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (Value::Members::iterator it = members.begin(); it != members.end(); + ++it) { + const std::string& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); + document_ += yamlCompatiblityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } +} + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() + : rightMargin_(74), indentSize_(3), addChildValues_() {} + +std::string StyledWriter::write(const Value& root) { + document_ = ""; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += "\n"; + return document_; +} + +void StyledWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + const std::string& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + writeIndent(); + writeValue(childValue); + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + +bool StyledWriter::isMultineArray(const Value& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const std::string& value) { + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; +} + +void StyledWriter::writeIndent() { + if (!document_.empty()) { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + +void StyledWriter::writeWithIndent(const std::string& value) { + writeIndent(); + document_ += value; +} + +void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); } + +void StyledWriter::unindent() { + assert(int(indentString_.size()) >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); +} + +void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += "\n"; + writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += "\n"; +} + +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += "\n"; + document_ += root.getComment(commentAfter); + document_ += "\n"; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter(std::string indentation) + : document_(NULL), rightMargin_(74), indentation_(indentation), + addChildValues_() {} + +void StyledStreamWriter::write(std::ostream& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_ = ""; + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = NULL; // Forget the stream, for safety. +} + +void StyledStreamWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + const std::string& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledStreamWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + +bool StyledStreamWriter::isMultineArray(const Value& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const std::string& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const std::string& value) { + if (!indented_) writeIndent(); + *document_ << value; + indented_ = false; +} + +void StyledStreamWriter::indent() { indentString_ += indentation_; } + +void StyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; +} + +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; +} + +bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +////////////////////////// +// BuiltStyledStreamWriter + +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + +struct BuiltStyledStreamWriter : public StreamWriter +{ + BuiltStyledStreamWriter( + std::string const& indentation, + CommentStyle::Enum cs, + std::string const& colonSymbol, + std::string const& nullSymbol, + std::string const& endingLineFeedSymbol); + virtual int write(Value const& root, std::ostream* sout); +private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultineArray(Value const& value); + void pushValue(std::string const& value); + void writeIndent(); + void writeWithIndent(std::string const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + CommentStyle::Enum cs_; + std::string colonSymbol_; + std::string nullSymbol_; + std::string endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter( + std::string const& indentation, + CommentStyle::Enum cs, + std::string const& colonSymbol, + std::string const& nullSymbol, + std::string const& endingLineFeedSymbol) + : rightMargin_(74) + , indentation_(indentation) + , cs_(cs) + , colonSymbol_(colonSymbol) + , nullSymbol_(nullSymbol) + , endingLineFeedSymbol_(endingLineFeedSymbol) + , addChildValues_(false) + , indented_(false) +{ +} +int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout) +{ + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_ = ""; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = NULL; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL is possible for value.string_? + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); + else pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) { + std::string const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN(name.data(), static_cast(name.length()))); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ", "; + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { + int size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (int index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = + isMultiLine || ((childValue.isArray() || childValue.isObject()) && + childValue.size() > 0); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (int index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += int(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(std::string const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) { + if (!indented_) writeIndent(); + *sout_ << value; + indented_ = false; +} + +void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + +void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) writeIndent(); + const std::string& comment = root.getComment(commentBefore); + std::string::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && + (iter != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; +} + +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { + if (cs_ == CommentStyle::None) return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } +} + +// static +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +/////////////// +// StreamWriter + +StreamWriter::StreamWriter() + : sout_(NULL) +{ +} +StreamWriter::~StreamWriter() +{ +} +StreamWriter::Factory::~Factory() +{} +StreamWriterBuilder::StreamWriterBuilder() +{ + setDefaults(&settings_); +} +StreamWriterBuilder::~StreamWriterBuilder() +{} +StreamWriter* StreamWriterBuilder::newStreamWriter() const +{ + std::string indentation = settings_["indentation"].asString(); + std::string cs_str = settings_["commentStyle"].asString(); + bool eyc = settings_["enableYAMLCompatibility"].asBool(); + bool dnp = settings_["dropNullPlaceholders"].asBool(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + std::string colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } else if (indentation.empty()) { + colonSymbol = ":"; + } + std::string nullSymbol = "null"; + if (dnp) { + nullSymbol = ""; + } + std::string endingLineFeedSymbol = ""; + return new BuiltStyledStreamWriter( + indentation, cs, + colonSymbol, nullSymbol, endingLineFeedSymbol); +} +static void getValidWriterKeys(std::set* valid_keys) +{ + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); + valid_keys->insert("enableYAMLCompatibility"); + valid_keys->insert("dropNullPlaceholders"); +} +bool StreamWriterBuilder::validate(Json::Value* invalid) const +{ + Json::Value my_invalid; + if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + std::string const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return 0u == inv.size(); +} +Value& StreamWriterBuilder::operator[](std::string key) +{ + return settings_[key]; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) +{ + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + //! [StreamWriterBuilderDefaults] +} + +std::string writeString(StreamWriter::Factory const& builder, Value const& root) { + std::ostringstream sout; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); +} + +std::ostream& operator<<(std::ostream& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/depends/jsoncpp/jsoncpp.h b/depends/jsoncpp/jsoncpp.h new file mode 100644 index 000000000..da3807d31 --- /dev/null +++ b/depends/jsoncpp/jsoncpp.h @@ -0,0 +1,2031 @@ +/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGATED_H_INCLUDED +# define JSON_AMALGATED_H_INCLUDED +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +# define JSON_VERSION_H_INCLUDED + +# define JSONCPP_VERSION_STRING "1.6.5" +# define JSONCPP_VERSION_MAJOR 1 +# define JSONCPP_VERSION_MINOR 6 +# define JSONCPP_VERSION_PATCH 5 +# define JSONCPP_VERSION_QUALIFIER +# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 +// Microsoft Visual Studio 6 only support conversion from __int64 to double +// (no conversion from unsigned __int64). +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' +// characters in the debug information) +// All projects I've ever seen with VS6 were using this globally (not bothering +// with pragma push/pop). +#pragma warning(disable : 4786) +#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 + +#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 +/// Indicates that the following function is deprecated. +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#elif defined(__clang__) && defined(__has_feature) +#if __has_feature(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef long long int Int64; +typedef unsigned long long int UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) +} // end namespace Json + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +#define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +/** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ +class JSON_API Features { +public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_; +}; + +} // namespace Json + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +#define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include +#else +#include +#endif +#ifdef JSON_USE_CPPTL +#include +#endif + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(std::string const& msg); + virtual ~Exception() throw(); + virtual char const* what() const throw(); +protected: + std::string const msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(std::string const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(std::string const& msg); +}; + +/// used internally +void throwRuntimeError(std::string const& msg); +/// used internally +void throwLogicError(std::string const& msg); + +/** \brief Type of the value held by a Value object. + */ +enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). +}; + +enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement +}; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + +/** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignement takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ +class JSON_API StaticString { +public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of a #objectValue values using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; +public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +#if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + static const Value& null; ///< We regret this reference to a global instance; prefer the simpler Value(). + static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + +private: +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); + ~CZString(); + CZString& operator=(CZString other); + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + //const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_: 2; + unsigned length_: 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +#else + typedef CppTL::SmallMap ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. +This is useful since clear() and resize() will not alter types. + + Examples: +\code +Json::Value null_value; // null +Json::Value arr_value(Json::arrayValue); // [] +Json::Value obj_value(Json::objectValue); // {} +\endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + /// Deep copy. + Value(const Value& other); + ~Value(); + + /// Deep copy, then swap(other). + /// \note Over-write existing comments. To preserve comments, use #swapPayload(). + Value& operator=(Value other); + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! + std::string asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString( + char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return isNull() + bool operator!() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to size elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex size); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](int index); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](int index) const; + + /// If the array contains at least index+1 elements, returns the element + /// value, + /// otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const std::string& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const std::string& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has no entry for that name, then the member name used to store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); +#ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value& operator[](const CppTL::ConstString& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const CppTL::ConstString& key) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const std::string& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value const* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \return the removed Value, or null. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + /// \deprecated + Value removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + /// \deprecated + Value removeMember(const std::string& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(std::string const& key, Value* removed); + /// Same as removeMember(std::string const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true iff removed (no exceptions) + */ + bool removeIndex(ArrayIndex i, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const std::string& key) const; + /// Same as isMember(std::string const& key)const + bool isMember(const char* begin, const char* end) const; +#ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember(const CppTL::ConstString& key) const; +#endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + //# ifdef JSON_USE_CPPTL + // EnumMemberNames enumMemberNames() const; + // EnumValues enumValues() const; + //# endif + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.") + void setComment(const char* comment, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const std::string& comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + std::string getComment(CommentPlacement placement) const; + + std::string toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(size_t start); + void setOffsetLimit(size_t limit); + size_t getOffsetStart() const; + size_t getOffsetLimit() const; + +private: + void initBasic(ValueType type, bool allocated = false); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + struct CommentInfo { + CommentInfo(); + ~CommentInfo(); + + void setComment(const char* text, size_t len); + + char* comment_; + }; + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ + ObjectValues* map_; + } value_; + ValueType type_ : 8; + unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. + // If not allocated_, string_ must be null-terminated. + CommentInfo* comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + size_t start_; + size_t limit_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const std::string& key); + +private: + enum Kind { + kindNone = 0, + kindIndex, + kindKey + }; + std::string key_; + ArrayIndex index_; + Kind kind_; +}; + +/** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ +class JSON_API Path { +public: + Path(const std::string& path, + const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + +private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath(const std::string& path, const InArgs& in); + void addPathInArg(const std::string& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + void invalidPath(const std::string& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + std::string name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + +protected: + Value& deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + +private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + //typedef unsigned int size_t; + //typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +} // namespace Json + + +namespace std { +/// Specialize std::swap() for Json::Value. +template<> +inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } +} + + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +#define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +/** \brief Unserialize a JSON document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError { + size_t offset_start; + size_t offset_limit; + std::string message; + }; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + Reader(const Features& features); + + /** \brief Read a Value from a JSON + * document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + * back during + * serialization, \c false to discard comments. + * This parameter is ignored if + * Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool + parse(const std::string& document, Value& root, bool collectComments = true); + + /** \brief Read a Value from a JSON + document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + back during + * serialization, \c false to discard comments. + * This parameter is ignored if + Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(std::istream& is, Value& root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + std::string getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + */ + std::string getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate + * multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector getStructuredErrors() const; + + /** \brief Add a semantic error message. + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const std::string& message); + + /** \brief Add a semantic error message with extra context. + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const std::string& message, const Value& extra); + + /** \brief Return whether there are any errors. + * \return \c true if there are no errors to report \c false if + * errors have occurred. + */ + bool good() const; + +private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, std::string& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const std::string& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + std::string getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + std::string commentsBefore_; + Features features_; + bool collectComments_; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() {} + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) = 0; + + class Factory { + public: + virtual ~Factory() {} + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + std::string errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an object. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + virtual ~CharReaderBuilder(); + + virtual CharReader* newCharReader() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream( + CharReader::Factory const&, + std::istream&, + Value* root, std::string* errs); + +/** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() +*/ +JSON_API std::istream& operator>>(std::istream&, Value&); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + std::ostream* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the stream instead.) + \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, std::ostream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); + + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "" + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's Javascript, it makes for smaller output and the + browser can handle the output just fine. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + virtual ~StreamWriterBuilder(); + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSON_API Writer { +public: + virtual ~Writer(); + + virtual std::string write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in JSON format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be usefull to support feature such as RPC where bandwith is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API FastWriter : public Writer { + +public: + FastWriter(); + virtual ~FastWriter() {} + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's Javascript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + +public: // overridden from Writer + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + + std::string document_; + bool yamlCompatiblityEnabled_; + bool dropNullPlaceholders_; + bool omitEndingLineFeed_; +}; + +/** \brief Writes a Value in JSON format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledWriter : public Writer { +public: + StyledWriter(); + virtual ~StyledWriter() {} + +public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string document_; + std::string indentString_; + int rightMargin_; + int indentSize_; + bool addChildValues_; +}; + +/** \brief Writes a Value in JSON format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \param indentation Each level will be indented by this amount extra. + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledStreamWriter { +public: + StyledStreamWriter(std::string indentation = "\t"); + ~StyledStreamWriter() {} + +public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(std::ostream& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::ostream* document_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; + +#if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(Int value); +std::string JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(LargestInt value); +std::string JSON_API valueToString(LargestUInt value); +std::string JSON_API valueToString(double value); +std::string JSON_API valueToString(bool value); +std::string JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API std::ostream& operator<<(std::ostream&, const Value& root); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +# define JSON_ASSERT(condition) \ + {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} + +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +# define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGATED_H_INCLUDED diff --git a/depends/jsonxx b/depends/jsonxx deleted file mode 160000 index 9a0b18acb..000000000 --- a/depends/jsonxx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9a0b18acb0967bfc523dca7af2cb34030d97c02b diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 16cf03d1a..270948e1a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -310,7 +310,7 @@ IF(APPLE) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) ENDIF() -TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsonxx dfhack-version ${PROJECT_LIBS}) +TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp dfhack-version ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 628c521de..fdc6e931c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -113,7 +113,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) DFHACK_PLUGIN(drybuckets drybuckets.cpp) - DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx lua) + DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index e70bb8399..bfbc1168a 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -44,8 +44,6 @@ #include "df/descriptor_shape.h" #include "df/descriptor_color.h" -#include "jsonxx.h" - using std::deque; DFHACK_PLUGIN("dwarfmonitor"); From 071f67f2aabb2aeba70d3f74ee578b0e70e25f54 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jul 2015 20:09:55 -0400 Subject: [PATCH 032/109] Add some JSON helper functions --- depends/jsoncpp/jsoncpp-ex.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 depends/jsoncpp/jsoncpp-ex.h diff --git a/depends/jsoncpp/jsoncpp-ex.h b/depends/jsoncpp/jsoncpp-ex.h new file mode 100644 index 000000000..fc1174133 --- /dev/null +++ b/depends/jsoncpp/jsoncpp-ex.h @@ -0,0 +1,31 @@ +#include "jsoncpp.h" +#pragma once + +namespace JsonEx { + + template bool is (const Json::Value &val) { return false; } + template T as (const Json::Value &val); + template T get (const Json::Value &val, const std::string &key, const T &default_) { return default_; } + +#define define_helpers(type, is_func, as_func) \ + template<> inline bool is (const Json::Value &val) { return val.is_func(); } \ + template<> inline type as (const Json::Value &val) { return val.as_func(); } \ + template<> inline type get (const Json::Value &val, const std::string &key, const type &default_) \ + { Json::Value x = val[key]; return is(x) ? as(x) : default_; } + define_helpers(bool, isBool, asBool); + define_helpers(Json::Int, isInt, asInt); + define_helpers(Json::UInt, isUInt, asUInt); + define_helpers(Json::Int64, isInt64, asInt64); + define_helpers(Json::UInt64, isUInt64, asUInt64); + define_helpers(float, isDouble, asFloat); + define_helpers(double, isDouble, asDouble); + define_helpers(std::string, isString, asString); +#undef define_helpers + + inline std::string toSimpleString (const Json::Value &val) + { + Json::FastWriter w; + return w.write(val); + } + +} From 01e04c24c593c8ebff31a4ce4a8f42067ad08722 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 31 Jul 2015 14:25:26 -0400 Subject: [PATCH 033/109] Use a separate lua state in dwarfmonitor render hook --- plugins/dwarfmonitor.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index e70bb8399..216664a93 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -44,8 +44,6 @@ #include "df/descriptor_shape.h" #include "df/descriptor_color.h" -#include "jsonxx.h" - using std::deque; DFHACK_PLUGIN("dwarfmonitor"); @@ -157,6 +155,7 @@ static void open_stats_srceen(); namespace dm_lua { static color_ostream_proxy *out; + static lua_State *state; typedef int(*initializer)(lua_State*); int no_args (lua_State *L) { return 0; } void cleanup() @@ -166,26 +165,26 @@ namespace dm_lua { delete out; out = NULL; } + lua_close(state); } - bool init_call (lua_State *L, const char *func) + bool init_call (const char *func) { if (!out) out = new color_ostream_proxy(Core::getInstance().getConsole()); - return Lua::PushModulePublic(*out, L, "plugins.dwarfmonitor", func); + return Lua::PushModulePublic(*out, state, "plugins.dwarfmonitor", func); } - bool safe_call (lua_State *L, int nargs) + bool safe_call (int nargs) { - return Lua::SafeCall(*out, L, nargs, 0); + return Lua::SafeCall(*out, state, nargs, 0); } bool call (const char *func, initializer init = no_args) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - if (!init_call(L, func)) + Lua::StackUnwinder top(state); + if (!init_call(func)) return false; - int nargs = init(L); - return safe_call(L, nargs); + int nargs = init(state); + return safe_call(nargs); } template @@ -1977,6 +1976,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Fri, 31 Jul 2015 16:46:00 -0400 Subject: [PATCH 034/109] Fix manipulator crash when selecting from empty custom profession list --- NEWS | 1 + plugins/manipulator.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 25a9870a5..19c3f9f09 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,7 @@ DFHack Future buildingplan: Now supports hatch covers gui/hack-wish now properly assigns quality to items. gui/gm-editor handles lua tables properly + manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 6a69d7996..d6d2cb222 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -998,6 +998,8 @@ public: } void select_profession(size_t selected) { + if (selected >= manager.templates.size()) + return; ProfessionTemplate prof = manager.templates[selected - 1]; for (auto it = units.begin(); it != units.end(); ++it) @@ -1013,6 +1015,11 @@ public: Screen::clear(); int x = 2, y = 2; Screen::drawBorder(" Dwarf Manipulator - Custom Profession "); + if (!manager.templates.size()) + { + OutputString(COLOR_LIGHTRED, x, y, "No saved professions"); + return; + } if (selection_empty) { OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!"); From d7b1714a6ce4a06873c0149861bc8c5eb5385a3b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 2 Aug 2015 10:07:57 -0400 Subject: [PATCH 035/109] gui/create-item: Improve quality assignment and make "Esc" work --- NEWS | 2 +- scripts/gui/create-item.lua | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 19c3f9f09..1fba80908 100644 --- a/NEWS +++ b/NEWS @@ -32,7 +32,7 @@ DFHack Future Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() buildingplan: Now supports hatch covers - gui/hack-wish now properly assigns quality to items. + gui/create-item now properly assigns quality to items. gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job diff --git a/scripts/gui/create-item.lua b/scripts/gui/create-item.lua index 05cc2fc42..2339c28c2 100644 --- a/scripts/gui/create-item.lua +++ b/scripts/gui/create-item.lua @@ -3,6 +3,7 @@ -- author Putnam -- edited by expwnent +--@module = true local function getGenderString(gender) local genderStr @@ -112,9 +113,9 @@ end local function createItem(mat,itemType,quality,creator,description) local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) - if pcall(function() print(item.quality) end) then - item.quality=quality-1 - end + assert(item, 'failed to create item') + quality = math.max(0, math.min(5, quality - 1)) + item:setQuality(quality) if df.item_type[itemType[1]]=='SLAB' then item.description=description end @@ -175,23 +176,29 @@ function hackWish(unit) local amountok, amount local matok,mattype,matindex,matFilter local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true) + if not itemok then return end if not args.notRestrictive then matFilter=getMatFilter(itemtype) end if not usesCreature(itemtype) then matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter) + if not matok then return end else local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList()) + if not creatureok then return end mattype,matindex=getCreatureRaceAndCaste(creatureTable[3]) end local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable()) + if not qualityok then return end local description if df.item_type[itemtype]=='SLAB' then local descriptionok descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE) + if not descriptionok then return end end if args.multi then repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) + if not amountok then return end if mattype and itemtype then if df.item_type.attrs[itemtype].is_stackable then local proper_item=df.item.find(dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)) From 2f1bd12312c98f2671675d9002fd4dd547dc8dea Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 2 Aug 2015 10:09:36 -0400 Subject: [PATCH 036/109] Update NEWS --- NEWS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 1fba80908..f87d901dc 100644 --- a/NEWS +++ b/NEWS @@ -32,7 +32,9 @@ DFHack Future Circular lua dependencies (reqscript/script_environment) fixed Prevented crash in Items::createItem() buildingplan: Now supports hatch covers - gui/create-item now properly assigns quality to items. + gui/create-item: + - fixed assigning quality to items + - made "esc" work properly gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found search: fixed crash in unit list after cancelling a job From 090f542d49be3891e03b42ceff8df3648336ead0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 13:33:53 -0400 Subject: [PATCH 037/109] Allow for per-plugin compile flags --- plugins/Plugins.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 1e8ef8936..c801570db 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -59,7 +59,7 @@ ENDMACRO() MACRO(DFHACK_PLUGIN) PARSE_ARGUMENTS(PLUGIN - "LINK_LIBRARIES;DEPENDS;PROTOBUFS" + "LINK_LIBRARIES;DEPENDS;PROTOBUFS;COMPILE_FLAGS;COMPILE_FLAGS_GCC;COMPILE_FLAGS_MSVC" "SOME_OPT" ${ARGN} ) @@ -95,6 +95,13 @@ MACRO(DFHACK_PLUGIN) TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) ENDIF() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") + IF(UNIX) + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_GCC}") + ELSE() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_MSVC}") + ENDIF() + IF(APPLE) SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "") ELSEIF(UNIX) From 44287fc3a09816180c5bceebc034def699c4ea4c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 13:42:13 -0400 Subject: [PATCH 038/109] remotefortressreader: Check map validity before reading map info Fixes JapaMala/armok-vision#5 --- NEWS | 1 + plugins/remotefortressreader.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/NEWS b/NEWS index f87d901dc..9094dec76 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ DFHack Future - made "esc" work properly gui/gm-editor handles lua tables properly manipulator: fixed crash when selecting custom professions when none are found + remotefortressreader: fixed crash when attempting to send map info when no map was loaded search: fixed crash in unit list after cancelling a job stockpiles: now checks/sanitizes filenames when saving stocks: fixed a crash when right-clicking diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index bc9bdbb08..d3fba0827 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -931,6 +931,8 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) { + if (!Maps::IsValid()) + return CR_FAILURE; uint32_t size_x, size_y, size_z; int32_t pos_x, pos_y, pos_z; Maps::getSize(size_x, size_y, size_z); From a67326ad0058619ab6de3ab37e90a887999ae003 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:01:11 -0400 Subject: [PATCH 039/109] git-describe: always use long format --- library/git-describe.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 5cc2bbb67..7ccd2d91d 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,4 +1,4 @@ -execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags +execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION) From de49befdbbf4f015a0d12f3767031fce9218be9c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:30:51 -0400 Subject: [PATCH 040/109] Improve version information * Add reimplementations of old DFHACK_VERSION-style macros * Expose full git commit ID * Expose all DFHack::Version functions to Lua --- Lua API.rst | 14 ++++++++++++++ NEWS | 2 +- library/DFHackVersion.cpp | 5 +++++ library/LuaApi.cpp | 8 ++++++++ library/git-describe.cmake | 8 +++++++- library/include/DFHackVersion.h | 9 +++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 86233470a..3f92ad4db 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -785,6 +785,20 @@ can be omitted. Returns the DF version string from ``symbols.xml``. +* ``getDFHackVersion()`` +* ``getDFHackRelease()`` +* ``getCompiledDFVersion()`` +* ``getGitDescription()`` +* ``getGitCommit()`` + + Return information about the DFHack build in use. + + **Note:** ``getCompiledDFVersion()`` returns the DF version specified at compile time, + while ``getDFVersion()`` returns the version and typically the OS as well. + These do not necessarily match - for example, DFHack 0.34.11-r5 worked with + DF 0.34.10 and 0.34.11, so the former function would always return ``0.34.11`` + while the latter would return ``v0.34.10 `` or ``v0.34.11 ``. + * ``dfhack.getDFPath()`` Returns the DF directory path. diff --git a/NEWS b/NEWS index 9094dec76..10178f501 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,7 @@ DFHack Future Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable The console on Linux and OS X now recognizes keyboard input between prompts JSON libraries available (C++ and Lua) - More build information available in plugins + More DFHack build information used in plugin version checks and available to plugins and lua scripts Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index df05779fb..cc56af180 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -1,3 +1,4 @@ +#define NO_DFHACK_VERSION_MACROS #include "DFHackVersion.h" #include "git-describe.h" #include "Export.h" @@ -19,5 +20,9 @@ namespace DFHack { { return DFHACK_GIT_DESCRIPTION; } + const char *git_commit() + { + return DFHACK_GIT_COMMIT; + } } } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ee2ca3702..0e507dafe 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -38,6 +38,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "DataFuncs.h" +#include "DFHackVersion.h" #include "modules/World.h" #include "modules/Gui.h" @@ -1396,6 +1397,8 @@ static std::string df2utf(std::string s) { return DF2UTF(s); } static std::string utf2df(std::string s) { return UTF2DF(s); } static std::string df2console(std::string s) { return DF2CONSOLE(s); } +#define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function) + static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), @@ -1408,6 +1411,11 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(df2utf), WRAP(utf2df), WRAP(df2console), + WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version), + WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release), + WRAP_VERSION_FUNC(getCompiledDFVersion, df_version), + WRAP_VERSION_FUNC(getGitDescription, git_description), + WRAP_VERSION_FUNC(getGitCommit, git_commit), { NULL, NULL } }; diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 7ccd2d91d..9c5e7a874 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,9 +1,15 @@ execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) +execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" + OUTPUT_VARIABLE DFHACK_GIT_COMMIT) string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION) file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h - "#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"") + "#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"\n") +string(STRIP ${DFHACK_GIT_COMMIT} DFHACK_GIT_COMMIT) +file(APPEND ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h + "#define DFHACK_GIT_COMMIT \"${DFHACK_GIT_COMMIT}\"") execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h ${dfhack_SOURCE_DIR}/library/include/git-describe.h) diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index aa528ccbb..359ed0eba 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -5,5 +5,14 @@ namespace DFHack { const char *df_version(); const char *dfhack_release(); const char *git_description(); + const char *git_commit(); } } + +#ifndef NO_DFHACK_VERSION_MACROS +#define DF_VERSION DFHack::Version::df_version() +#define DFHACK_RELEASE DFHack::Version::dfhack_release() +#define DFHACK_VERSION DFHack::Version::dfhack_version() +#define DFHACK_GIT_DESCRIPTION DFHack::Version::git_description() +#define DFHACK_GIT_COMMIT DFHack::Version::git_commit() +#endif From afce8d810b637b4ddfe7ffe9ed8e31e667b0b1c2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 17:32:42 -0400 Subject: [PATCH 041/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index ccca7b721..e512e1845 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ccca7b721bef0cbddb54694cf16927e5fe668b7f +Subproject commit e512e184568991deb28704c42ab87c60cecc4e68 From f46d3d137f72c4e5c619033253f5045fa0212c88 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 20:41:54 -0400 Subject: [PATCH 042/109] Allow multiple contexts to be specified when adding keybindings --- NEWS | 1 + Readme.rst | 5 +++-- library/Core.cpp | 26 ++++++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 10178f501..5eee8c72e 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ DFHack Future Fixed a rare overflow issue that could cause crashes on Linux and OS X Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X + Multiple contexts can now be specified when adding keybindings Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/Readme.rst b/Readme.rst index 84fc475a5..fec765dd1 100644 --- a/Readme.rst +++ b/Readme.rst @@ -212,7 +212,7 @@ Possible ways to call the command: The ** parameter above has the following *case-sensitive* syntax:: - [Ctrl-][Alt-][Shift-]KEY[@context] + [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. @@ -227,7 +227,8 @@ the ``keybinding`` command among other things prints the current context string. Only bindings with a *context* tag that either matches the current context fully, or is a prefix ending at a '/' boundary would be considered for execution, i.e. for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``, -``@foo`` or none. +``@foo`` or none. Multiple contexts can be specified by separating them with a +pipe (``|``) - for example, ``@foo|bar|baz/foo``. Enabling plugins ================ diff --git a/library/Core.cpp b/library/Core.cpp index 62b63411f..d48f45199 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2105,6 +2105,23 @@ bool Core::ClearKeyBindings(std::string keyspec) bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) { + size_t at_pos = keyspec.find('@'); + if (at_pos != std::string::npos) + { + std::string raw_spec = keyspec.substr(0, at_pos); + std::string raw_focus = keyspec.substr(at_pos + 1); + if (raw_focus.find('|') != std::string::npos) + { + std::vector focus_strings; + split_string(&focus_strings, raw_focus, "|"); + for (size_t i = 0; i < focus_strings.size(); i++) + { + if (!AddKeyBinding(raw_spec + "@" + focus_strings[i], cmdline)) + return false; + } + return true; + } + } int sym; KeyBinding binding; if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus)) @@ -2156,11 +2173,12 @@ std::vector Core::ListKeyBindings(std::string keyspec) return rv; } -//////////////// -// ClassNamCheck -//////////////// -// Since there is no Process.cpp, put ClassNamCheck stuff in Core.cpp +///////////////// +// ClassNameCheck +///////////////// + +// Since there is no Process.cpp, put ClassNameCheck stuff in Core.cpp static std::set known_class_names; static std::map known_vptrs; From 418d2831ef8fcf24e33d0889d04449e7dc8ab891 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 21:50:01 -0400 Subject: [PATCH 043/109] Add a framework for including 3rd-party script repos Repos need to include a CMakeLists.txt file with calls to the DFHACK_SCRIPTS macro, which functions similarly to DFHACK_PLUGIN. The `open-legends` script from lethosor/dfhack-scripts is included as an example. --- .gitmodules | 3 +++ CMakeLists.txt | 2 ++ library/CMakeLists.txt | 1 + scripts/3rdparty/lethosor | 1 + scripts/CMakeLists.txt | 2 ++ scripts/Scripts.cmake | 19 +++++++++++++++++++ 6 files changed, 28 insertions(+) create mode 160000 scripts/3rdparty/lethosor create mode 100644 scripts/CMakeLists.txt create mode 100644 scripts/Scripts.cmake diff --git a/.gitmodules b/.gitmodules index b20d189ec..275772e0e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "depends/clsocket"] path = depends/clsocket url = git://github.com/DFHack/clsocket.git +[submodule "scripts/3rdparty/lethosor"] + path = scripts/3rdparty/lethosor + url = https://github.com/lethosor/dfhack-scripts diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a8dabd13..50fca68f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ IF(BUILD_PLUGINS) add_subdirectory (plugins) endif() +add_subdirectory(scripts) + # Packaging with CPack! IF(UNIX) if(APPLE) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 270948e1a..73d6188a3 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -377,6 +377,7 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts DESTINATION ${DFHACK_DATA_DESTINATION} FILES_MATCHING PATTERN "*.lua" PATTERN "*.rb" + PATTERN "3rdparty" EXCLUDE ) install(DIRECTORY ${dfhack_SOURCE_DIR}/patches diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor new file mode 160000 index 000000000..213826bd6 --- /dev/null +++ b/scripts/3rdparty/lethosor @@ -0,0 +1 @@ +Subproject commit 213826bd6feba0b16a582e0c4a74c5678ddc3be8 diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt new file mode 100644 index 000000000..ed5cc1ffc --- /dev/null +++ b/scripts/CMakeLists.txt @@ -0,0 +1,2 @@ +include(Scripts.cmake) +DFHACK_3RDPARTY_SCRIPT_REPO(lethosor) diff --git a/scripts/Scripts.cmake b/scripts/Scripts.cmake new file mode 100644 index 000000000..8ef0d1161 --- /dev/null +++ b/scripts/Scripts.cmake @@ -0,0 +1,19 @@ +include(../plugins/Plugins.cmake) + +MACRO(DFHACK_SCRIPTS) + PARSE_ARGUMENTS(SCRIPT + "SUBDIRECTORY" + "SOME_OPT" + ${ARGN} + ) + CAR(SCRIPT_SUBDIRECTORY ${SCRIPT_SUBDIRECTORY}) + install(FILES ${SCRIPT_DEFAULT_ARGS} + DESTINATION ${DFHACK_DATA_DESTINATION}/scripts/${SCRIPT_SUBDIRECTORY}) +ENDMACRO() + +MACRO(DFHACK_3RDPARTY_SCRIPT_REPO repo_path) + if(NOT EXISTS ${dfhack_SOURCE_DIR}/scripts/3rdparty/${repo_path}/CMakeLists.txt) + MESSAGE(FATAL_ERROR "Script submodule scripts/3rdparty/${repo_path} does not exist - run `git submodule update`.") + endif() + add_subdirectory(3rdparty/${repo_path}) +ENDMACRO() From 9a58332aedcb1b1eb655766ca990583e9fa08ef5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 6 Aug 2015 21:59:33 -0400 Subject: [PATCH 044/109] Update lethosor/dfhack-scripts submodule (whitespace) --- scripts/3rdparty/lethosor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor index 213826bd6..ceed207e3 160000 --- a/scripts/3rdparty/lethosor +++ b/scripts/3rdparty/lethosor @@ -1 +1 @@ -Subproject commit 213826bd6feba0b16a582e0c4a74c5678ddc3be8 +Subproject commit ceed207e38220e21067a91b8d6f7b9680a476f69 From bce9d98633d56054138bdee51b1b41f343322c73 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 7 Aug 2015 14:33:38 -0400 Subject: [PATCH 045/109] New plugin: fix-unit-occupancy (fixes bug 3499) --- NEWS | 1 + Readme.rst | 16 +++ plugins/CMakeLists.txt | 1 + plugins/fix-unit-occupancy.cpp | 236 +++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 plugins/fix-unit-occupancy.cpp diff --git a/NEWS b/NEWS index 5eee8c72e..ca285d1d6 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ DFHack Future kill-lua: Interrupt running Lua scripts New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions + fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/Readme.rst b/Readme.rst index fec765dd1..2c4d5d203 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1264,6 +1264,22 @@ This command adds the Guild Representative position to all Human civilizations, allowing them to make trade agreements (just as they did back in 0.28.181.40d and earlier) in case you haven't already modified your raws accordingly. +fix-unit-occupancy +------------------ +This plugin fixes issues with unit occupancy, notably issues with phantom +"unit blocking tile" messages (`Bug 3499`_). It can be run manually, or +periodically when enabled with the built-in enable/disable commands: + +* ``fix-unit-occupancy``: Run the plugin immediately. Available options: + + * ``-h``, ``here``, ``cursor``: Only operate on the tile at the cursor + * ``-n``, ``dry``, ``dry-run``: Do not write changes to map + +* ``fix-unit-occupancy interval X``: Run the plugin every ``X`` ticks (when enabled). + The default is 1200 ticks, or 1 day. Ticks are only counted when the game is unpaused. + +.. _`Bug 3499`: http://bay12games.com/dwarves/mantisbt/view.php?id=3499 + fixveins -------- Removes invalid references to mineral inclusions and restores missing ones. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fdc6e931c..2d726186d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -121,6 +121,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(filltraffic filltraffic.cpp) DFHACK_PLUGIN(fix-armory fix-armory.cpp) DFHACK_PLUGIN(fixpositions fixpositions.cpp) + DFHACK_PLUGIN(fix-unit-occupancy fix-unit-occupancy.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(follow follow.cpp) diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp new file mode 100644 index 000000000..c55870bea --- /dev/null +++ b/plugins/fix-unit-occupancy.cpp @@ -0,0 +1,236 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Units.h" +#include "modules/Translation.h" +#include "modules/World.h" + +#include "df/map_block.h" +#include "df/unit.h" +#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("fix-unit-occupancy"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(world); + +static int run_interval = 1200; // daily + +inline float getClock() +{ + return (float)clock() / (float)CLOCKS_PER_SEC; +} + +static std::string get_unit_description(df::unit *unit) +{ + if (!unit) + return ""; + std::string desc; + auto name = Units::getVisibleName(unit); + if (name->has_name) + desc = Translation::TranslateName(name, false); + desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too + + return desc; +} + +df::unit *findUnit(int x, int y, int z) +{ + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z) + return *u; + } + return NULL; +} + +struct uo_opts { + bool dry_run; + bool use_cursor; + uo_opts (bool dry_run = false, bool use_cursor = false) + :dry_run(dry_run), use_cursor(use_cursor) + {} +}; + +command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector & parameters); + +unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts) +{ + if (!Core::getInstance().isWorldLoaded()) + return 0; + + if (opts.use_cursor && cursor->x < 0) + out.printerr("No cursor\n"); + + unsigned count = 0; + float time1 = getClock(); + for (size_t i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block *block = world->map.map_blocks[i]; + int map_z = block->map_pos.z; + if (opts.use_cursor && (map_z != cursor->z || block->map_pos.y != (cursor->y / 16) * 16 || block->map_pos.x != (cursor->x / 16) * 16)) + continue; + for (int x = 0; x < 16; x++) + { + int map_x = x + block->map_pos.x; + for (int y = 0; y < 16; y++) + { + if (block->designation[x][y].bits.hidden) + continue; + int map_y = y + block->map_pos.y; + if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y)) + continue; + bool cur_occupancy = block->occupancy[x][y].bits.unit; + bool fixed_occupancy = cur_occupancy; + df::unit *cur_unit = findUnit(map_x, map_y, map_z); + if (cur_occupancy && !cur_unit) + { + out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n", + opts.dry_run ? "(Dry run) " : "", + map_x, map_y, map_z); + fixed_occupancy = false; + } + // else if (!cur_occupancy && cur_unit) + // { + // out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str()); + // fixed_occupancy = true; + // } + if (cur_occupancy != fixed_occupancy && !opts.dry_run) + { + ++count; + block->occupancy[x][y].bits.unit = fixed_occupancy; + } + } + } + } + float time2 = getClock(); + std::cerr << "fix-unit-occupancy: elapsed time: " << time2 - time1 << " secs" << endl; + if (count) + out << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl; + return count; +} + +unsigned fix_unit_occupancy (color_ostream &out) +{ + uo_opts tmp; + return fix_unit_occupancy(out, tmp); +} + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "fix-unit-occupancy", + "Fix unit occupancy issues such as phantom 'creature blocking site' messages (bug 3499)", + cmd_fix_unit_occupancy, + false, //allow non-interactive use + " enable fix-unit-occupancy: Enable the plugin\n" + " disable fix-unit-occupancy fix-unit-occupancy: Disable the plugin\n" + " fix-unit-occupancy: Run the plugin immediately. Available options:\n" + " -h|here|cursor: Only operate on the tile at the cursor\n" + " -n|dry|dry-run: Do not write changes to map\n" + " fix-unit-occupancy interval X: Run the plugin every X ticks (when enabled).\n" + " Default is 1200, or 1 day. Ticks are only counted when the game is unpaused.\n" + + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + is_enabled = enable; + if (is_enabled) + fix_unit_occupancy(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + static unsigned tick = UINT_MAX; + static decltype(world->frame_counter) old_world_frame = 0; + if (is_enabled && World::isFortressMode()) + { + // only increment tick when the world has changed + if (old_world_frame != world->frame_counter) + { + old_world_frame = world->frame_counter; + tick++; + } + if (tick > run_interval) + { + tick = 0; + fix_unit_occupancy(out); + } + } + else + { + tick = INT_MAX; + old_world_frame = 0; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) +{ + if (evt == SC_MAP_LOADED && is_enabled && World::isFortressMode()) + fix_unit_occupancy(out); + return CR_OK; +} + +command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + uo_opts opts; + bool ok = true; + + if (parameters.size() >= 1 && (parameters[0] == "-i" || parameters[0].find("interval") != std::string::npos)) + { + if (parameters.size() >= 2) + { + int new_interval = atoi(parameters[1].c_str()); + if (new_interval < 100) + { + out.printerr("Invalid interval - minimum is 100 ticks\n"); + return CR_WRONG_USAGE; + } + run_interval = new_interval; + if (!is_enabled) + out << "note: Plugin not enabled (use `enable fix-unit-occupancy` to enable)" << endl; + return CR_OK; + } + else + return CR_WRONG_USAGE; + } + + for (auto opt = parameters.begin(); opt != parameters.end(); ++opt) + { + if (*opt == "-n" || opt->find("dry") != std::string::npos) + opts.dry_run = true; + else if (*opt == "-h" || opt->find("cursor") != std::string::npos || opt->find("here") != std::string::npos) + opts.use_cursor = true; + else if (opt->find("enable") != std::string::npos) + plugin_enable(out, true); + else if (opt->find("disable") != std::string::npos) + plugin_enable(out, false); + else + { + out.printerr("Unknown parameter: %s\n", opt->c_str()); + ok = false; + } + } + if (!ok) + return CR_WRONG_USAGE; + + fix_unit_occupancy(out, opts); + + return CR_OK; +} From dc747d59df7a7fa83923caf5d7b86969917550fb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 9 Aug 2015 13:20:28 -0400 Subject: [PATCH 046/109] Link dfhack-md5 on Windows --- library/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 270948e1a..328443f48 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -240,14 +240,13 @@ IF(UNIX) ENDIF() ENDIF() -IF(UNIX) +IF(APPLE) + SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) +ELSEIF(UNIX) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) - IF(APPLE) - SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) - ENDIF() ELSE(WIN32) #FIXME: do we really need psapi? - SET(PROJECT_LIBS psapi dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS psapi dfhack-md5 dfhack-tinyxml dfhack-tinythread) ENDIF() ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) From fcd15bfd7313446ebbce1b28745c96952cb3c84a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 9 Aug 2015 13:37:26 -0400 Subject: [PATCH 047/109] Move vshook plugin to title-version and enable by default --- NEWS | 1 + dfhack.init-example | 3 ++ plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/devel/vshook.cpp | 71 ------------------------------------ plugins/title-version.cpp | 66 +++++++++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 72 deletions(-) delete mode 100644 plugins/devel/vshook.cpp create mode 100644 plugins/title-version.cpp diff --git a/NEWS b/NEWS index ca285d1d6..77f62bc99 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,7 @@ DFHack Future New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) + title-version (formerly vshook): Display DFHack version on title screen New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves diff --git a/dfhack.init-example b/dfhack.init-example index 780655c0a..bbf938981 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -191,6 +191,9 @@ tweak tradereq-pet-gender # Globally acting plugins # ########################### +# Display DFHack version on title screen +enable title-version + # Dwarf Manipulator (simple in-game Dwarf Therapist replacement) enable manipulator diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2d726186d..258b0f5de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -161,6 +161,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) + DFHACK_PLUGIN(title-version title-version.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index e03d4c4b7..74c198645 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,7 +19,6 @@ DFHACK_PLUGIN(stepBetween stepBetween.cpp) DFHACK_PLUGIN(stockcheck stockcheck.cpp) DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp) -DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(zoom zoom.cpp) IF(UNIX) diff --git a/plugins/devel/vshook.cpp b/plugins/devel/vshook.cpp deleted file mode 100644 index a0e2a5e59..000000000 --- a/plugins/devel/vshook.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "df/graphic.h" -#include "df/viewscreen_titlest.h" - -using std::vector; -using std::string; -using std::stack; -using namespace DFHack; - -using df::global::gps; - -DFHACK_PLUGIN("vshook"); - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -struct title_hook : df::viewscreen_titlest { - typedef df::viewscreen_titlest interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - Screen::Pen pen(' ',COLOR_WHITE,COLOR_BLACK); - Screen::paintString(pen, 0, 0, "DFHack "); - Screen::paintString(pen, 7, 0, Version::dfhack_version()); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render); - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) -{ - if (!gps) - return CR_FAILURE; - - if (enable != is_enabled) - { - if (!INTERPOSE_HOOK(title_hook, render).apply(enable)) - return CR_FAILURE; - - is_enabled = enable; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - // DON'T DO THIS IN NON-EXAMPLE PLUGINS - plugin_enable(out, true); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - INTERPOSE_HOOK(title_hook, render).remove(); - return CR_OK; -} diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp new file mode 100644 index 000000000..c52a7a9f5 --- /dev/null +++ b/plugins/title-version.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "VTableInterpose.h" +#include "DFHackVersion.h" + +#include "df/graphic.h" +#include "df/viewscreen_titlest.h" +#include "uicommon.h" + +using std::vector; +using std::string; +using namespace DFHack; + +DFHACK_PLUGIN("title-version"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +struct title_version_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 0, y = 0; + OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render); + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + if (!gps) + return CR_FAILURE; + + if (enable != is_enabled) + { + if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + INTERPOSE_HOOK(title_version_hook, render).remove(); + return CR_OK; +} From fec19b97006f56e1e7e80f8e06a0c74011917b75 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Mon, 10 Aug 2015 10:12:40 -0400 Subject: [PATCH 048/109] Compile.rst: Link to "Linux dependencies" wiki page --- Compile.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Compile.rst b/Compile.rst index 260af3b13..36563e881 100644 --- a/Compile.rst +++ b/Compile.rst @@ -59,6 +59,8 @@ You should be able to find them in your distro repositories (on Arch linux 'perl To build Stonesense, you'll also need OpenGL headers. +Some additional dependencies for other distros are listed on the `wiki `_. + Build ===== Building is fairly straightforward. Enter the ``build`` folder (or create an empty folder in the DFHack directory to use instead) and start the build like this:: From 9b5f42e3a918b8167a76752c5b86c9cfdaca6bc8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 10:41:47 -0400 Subject: [PATCH 049/109] tweak farm-plot-select: Only show controls when plots are fully built Fixes #521 --- NEWS | 1 + plugins/tweak/tweaks/farm-plot-select.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 77f62bc99..a47a18934 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,7 @@ DFHack Future - fixed a crash on arena load - number keys (e.g. 2/8) take priority over cursor keys when applicable tweak fps-min fixed + tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built workflow: Fixed some issues with stuck jobs - Note: Existing stuck jobs must be cancelled and re-added zone: Fixed a crash when using "zone set" (and a few other potential crashes) diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 38b0596bd..696c7ca2e 100644 --- a/plugins/tweak/tweaks/farm-plot-select.h +++ b/plugins/tweak/tweaks/farm-plot-select.h @@ -76,7 +76,10 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); - if (!getFarmPlot() || !ui->selected_farm_crops.size()) + auto farm_plot = getFarmPlot(); + if (!farm_plot || !ui->selected_farm_crops.size()) + return; + if (farm_plot->getBuildStage() != farm_plot->getMaxBuildStage()) return; auto dims = Gui::getDwarfmodeViewDims(); int x = dims.menu_x1 + 1, From d09cdc83baf69bbe68b2b6d70514c355e1a00db5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 11:25:15 -0400 Subject: [PATCH 050/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index e512e1845..7cfed040f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e512e184568991deb28704c42ab87c60cecc4e68 +Subproject commit 7cfed040fe83b94fb6b97afec1c5d86ccf1cb81e From 05f36856f4abd6339b6f44ac188d5acabf853811 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 Aug 2015 11:30:21 -0400 Subject: [PATCH 051/109] fix-unit-occupancy: Always provide feedback when running command --- plugins/fix-unit-occupancy.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index c55870bea..4675b5cfb 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -230,7 +230,9 @@ command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector Date: Mon, 10 Aug 2015 12:44:27 -0400 Subject: [PATCH 052/109] Make lua listdir functions return useful error messages --- library/LuaApi.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0e507dafe..c23cfe22a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2209,7 +2209,14 @@ static int filesystem_listdir(lua_State *L) luaL_checktype(L,1,LUA_TSTRING); std::string dir=lua_tostring(L,1); std::vector files; - DFHack::Filesystem::listdir(dir, files); + int err = DFHack::Filesystem::listdir(dir, files); + if (err) + { + lua_pushnil(L); + lua_pushstring(L, strerror(err)); + lua_pushinteger(L, err); + return 3; + } lua_newtable(L); for(int i=0;i Date: Mon, 10 Aug 2015 23:24:24 -0400 Subject: [PATCH 053/109] typo s/ot a forum/to a forum/ --- dfhack.init-example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dfhack.init-example b/dfhack.init-example index 1bb3f8acf..0c322d850 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -45,7 +45,7 @@ keybinding add Ctrl-Shift-P command-prompt keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" -# export a Dwarf's preferences screen in BBCode to post ot a forum +# export a Dwarf's preferences screen in BBCode to post to a forum keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves ############################## From 2aba2da56df5e1b9683b7fbc8b46c8f083af901c Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 11 Aug 2015 22:59:19 -0400 Subject: [PATCH 054/109] Avoid DataStaticsFields dependency on PluginManager.h --- library/LuaApi.cpp | 1 + library/LuaTools.cpp | 1 + library/LuaTypes.cpp | 1 + library/include/LuaWrapper.h | 8 +++++--- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c23cfe22a..d601b198a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -39,6 +39,7 @@ distribution. #include "DataIdentity.h" #include "DataFuncs.h" #include "DFHackVersion.h" +#include "PluginManager.h" #include "modules/World.h" #include "modules/Gui.h" diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 8c0e5ef1a..7992373a9 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -49,6 +49,7 @@ distribution. #include "MiscUtils.h" #include "DFHackVersion.h" +#include "PluginManager.h" #include "df/job.h" #include "df/job_item.h" diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 410c25f88..0904ff8f9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -40,6 +40,7 @@ distribution. #include "LuaTools.h" #include "DataFuncs.h" +#include "PluginManager.h" #include "MiscUtils.h" #include diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index ab694c4a8..4604ed451 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -30,7 +30,6 @@ distribution. #include #include "DataDefs.h" -#include "PluginManager.h" #include #include @@ -39,7 +38,10 @@ distribution. * Internal header file of the lua wrapper. */ -namespace DFHack { namespace LuaWrapper { + +namespace DFHack { + struct FunctionReg; +namespace LuaWrapper { struct LuaToken; /** @@ -232,7 +234,7 @@ namespace DFHack { namespace LuaWrapper { /** * Wrap functions and add them to the table on the top of the stack. */ - using DFHack::FunctionReg; + typedef DFHack::FunctionReg FunctionReg; void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); int method_wrapper_core(lua_State *state, function_identity_base *id); From a60c8d8e7c4a9ffad615499901dc25589344b935 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 13 Aug 2015 11:25:59 +0530 Subject: [PATCH 055/109] Added conditionals to remotefortressreader.cpp to make compiling for v 0.24.11 easier. --- plugins/remotefortressreader.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6629e7893..ea0e19422 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,8 +1,4 @@ -//define which version of DF this is being built for. -#define DF_VER_040 -//#define DF_VER_034 - // some headers required for a plugin. Nothing special, just the basics. #include "Core.h" #include @@ -27,8 +23,10 @@ #include "df/builtin_mats.h" #include "df/map_block_column.h" #include "df/plant.h" +#ifndef DFHACK_VERSION="0.34.11-r5" #include "df/plant_tree_info.h" #include "df/plant_growth.h" +#endif #include "df/itemdef.h" #include "df/building_def_workshopst.h" #include "df/building_def_furnacest.h" @@ -67,7 +65,11 @@ using namespace RemoteFortressReader; using namespace std; DFHACK_PLUGIN("RemoteFortressReader"); +#ifdef DFHACK_VERSION="0.34.11-r5" +using namespace df::global; +#else REQUIRE_GLOBAL(world); +#endif // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom @@ -236,6 +238,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m case df::enums::tiletype_material::RIVER: return RemoteFortressReader::RIVER; break; +#ifndef DFHACK_VERSION="0.34.11-r5" case df::enums::tiletype_material::ROOT: return RemoteFortressReader::ROOT; break; @@ -248,6 +251,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m case df::enums::tiletype_material::UNDERWORLD_GATE: return RemoteFortressReader::UNDERWORLD_GATE; break; +#endif default: return RemoteFortressReader::NO_MATERIAL; break; @@ -295,9 +299,11 @@ RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special spec case df::enums::tiletype_special::TRACK: return RemoteFortressReader::TRACK; break; +#ifndef DFHACK_VERSION="0.34.11-r5" case df::enums::tiletype_special::SMOOTH_DEAD: return RemoteFortressReader::SMOOTH_DEAD; break; +#endif default: return RemoteFortressReader::NO_SPECIAL; break; @@ -351,20 +357,25 @@ RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) case df::enums::tiletype_shape::BROOK_TOP: return RemoteFortressReader::BROOK_TOP; break; +#ifndef DFHACK_VERSION="0.34.11-r5" case df::enums::tiletype_shape::BRANCH: return RemoteFortressReader::BRANCH; break; -#ifdef DF_VER_034 +#endif +#ifdef DFHACK_VERSION="0.34.11-r5" case df::enums::tiletype_shape::TREE: - return RemoteFortressReader::TREE; + return RemoteFortressReader::TREE_SHAPE; break; #endif +#ifndef DFHACK_VERSION="0.34.11-r5" + case df::enums::tiletype_shape::TRUNK_BRANCH: return RemoteFortressReader::TRUNK_BRANCH; break; case df::enums::tiletype_shape::TWIG: return RemoteFortressReader::TWIG; break; +#endif case df::enums::tiletype_shape::SAPLING: return RemoteFortressReader::SAPLING; break; @@ -622,6 +633,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i basePlant->set_name(pp->name); basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_index(i); +#ifndef DFHACK_VERSION="0.34.11-r5" for (int g = 0; g < pp->growths.size(); g++) { df::plant_growth* growth = pp->growths[g]; @@ -636,6 +648,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i out_growth->mutable_mat_pair()->set_mat_index(i); } } +#endif } return CR_OK; } @@ -860,6 +873,9 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in int max_y = in->max_y() / 3; int max_z = in->max_z(); +#ifdef DFHACK_VERSION="0.34.11-r5" + //plants are gotten differently here +#else for (int xx = min_x; xx < max_x; xx++) for (int yy = min_y; yy < max_y; yy++) { @@ -894,6 +910,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in out_plant->set_pos_z(plant->pos.z); } } +#endif return CR_OK; } From 22374e6d86d434ad5fbf7a8b8e4ace49d470a02a Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 13 Aug 2015 15:59:15 +0530 Subject: [PATCH 056/109] Fixed remotefortressreader.cpp DF version detection. --- plugins/remotefortressreader.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 3a11e1816..2522836f6 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,3 +1,4 @@ +#define DF_VERSION 40024 // some headers required for a plugin. Nothing special, just the basics. #include "Core.h" @@ -23,7 +24,7 @@ #include "df/builtin_mats.h" #include "df/map_block_column.h" #include "df/plant.h" -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 #include "df/plant_tree_info.h" #include "df/plant_growth.h" #endif @@ -65,7 +66,7 @@ using namespace RemoteFortressReader; using namespace std; DFHACK_PLUGIN("RemoteFortressReader"); -#ifdef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION < 40024 using namespace df::global; #else REQUIRE_GLOBAL(world); @@ -238,7 +239,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m case df::enums::tiletype_material::RIVER: return RemoteFortressReader::RIVER; break; -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 case df::enums::tiletype_material::ROOT: return RemoteFortressReader::ROOT; break; @@ -299,7 +300,7 @@ RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special spec case df::enums::tiletype_special::TRACK: return RemoteFortressReader::TRACK; break; -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 case df::enums::tiletype_special::SMOOTH_DEAD: return RemoteFortressReader::SMOOTH_DEAD; break; @@ -357,17 +358,17 @@ RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) case df::enums::tiletype_shape::BROOK_TOP: return RemoteFortressReader::BROOK_TOP; break; -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 case df::enums::tiletype_shape::BRANCH: return RemoteFortressReader::BRANCH; break; #endif -#ifdef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION < 40001 case df::enums::tiletype_shape::TREE: return RemoteFortressReader::TREE_SHAPE; break; #endif -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 case df::enums::tiletype_shape::TRUNK_BRANCH: return RemoteFortressReader::TRUNK_BRANCH; @@ -633,7 +634,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i basePlant->set_name(pp->name); basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_index(i); -#ifndef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION > 40001 for (int g = 0; g < pp->growths.size(); g++) { df::plant_growth* growth = pp->growths[g]; @@ -873,7 +874,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in int max_y = in->max_y() / 3; int max_z = in->max_z(); -#ifdef DFHACK_VERSION="0.34.11-r5" +#if DF_VERSION < 40001 //plants are gotten differently here #else for (int xx = min_x; xx < max_x; xx++) From 776af1904537afc48aee36b02dda0557d9a18881 Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Fri, 14 Aug 2015 11:54:38 +1000 Subject: [PATCH 057/109] Add and document points.lua A trivial script to choose the number of points available at embark. --- NEWS | 3 ++- Readme.rst | 6 ++++++ scripts/points.lua | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 scripts/points.lua diff --git a/NEWS b/NEWS index a47a18934..f364bdb3e 100644 --- a/NEWS +++ b/NEWS @@ -22,8 +22,9 @@ DFHack Future New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) fix-ster: changes fertility/sterility of animals or dwarves - view-item-info: adds information and customisable descriptions to item viewscreens + view-item-info: adds information and customisable descriptions to item viewscreens warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found + points: set number of points available at embark screen New tweaks kitchen-keys: Fixes DF kitchen meal keybindings kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences diff --git a/Readme.rst b/Readme.rst index 2c4d5d203..6843fb71e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2589,6 +2589,12 @@ Example:: multicmd locate-ore iron ; digv +points +====== +Sets available points at the embark screen to the specified number. Eg. +``points 1000000`` would allow you to buy everything, or ``points 0`` would +make life quite difficult. + position ======== Reports the current time: date, clock time, month, and season. Also reports diff --git a/scripts/points.lua b/scripts/points.lua new file mode 100644 index 000000000..f39009c8a --- /dev/null +++ b/scripts/points.lua @@ -0,0 +1,3 @@ +-- by Meph +-- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005 +df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...) \ No newline at end of file From 55b703a5aae2f56af895f19b69e621aad786635b Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Fri, 14 Aug 2015 12:30:45 +1000 Subject: [PATCH 058/109] Document hide, show, startdwarf --- NEWS | 1 + Readme.rst | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/NEWS b/NEWS index f364bdb3e..11935062d 100644 --- a/NEWS +++ b/NEWS @@ -75,6 +75,7 @@ DFHack Future memview: Fixed display issue nyan: Can now be stopped with dfhack-run quicksave: Restricted to fortress mode + README: documented show/hide command, startdwarf.rb remotefortressreader: Exposes more information search: - Supports noble suggestion screen (e.g. suggesting a baron) diff --git a/Readme.rst b/Readme.rst index 6843fb71e..41fb0d935 100644 --- a/Readme.rst +++ b/Readme.rst @@ -291,6 +291,11 @@ control of the game. * Activate with 'forcepause 1' * Deactivate with 'forcepause 0' +hide / show +----------- +Hides or shows the DFHack terminal window, respectively. To use ``show``, use +the in-game console (default keybinding Ctrl-Shift-P). Only available on Windows. + nopause ------- Disables pausing (both manual and automatic) with the exception of pause forced @@ -2676,6 +2681,13 @@ Other options available: ``del``, ``clear``, ``list``. This plugin also shortens the 'sleeping' and 'on break' periods of targets. +startdwarf +========== +Use at the embark screen to embark with the specified number of dwarves. Eg. +``startdwarf 500`` would lead to a severe food shortage and FPS issues, while +``startdwarf 10`` would just allow a few more warm bodies to dig in. +Trying a number less than 7 may have strange effects. + stripcaged ========== For dumping items inside cages. Will mark selected items for dumping, then From 15c8ae187b9245e5f463a81415e1b49731554c29 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Fri, 14 Aug 2015 13:20:22 +1000 Subject: [PATCH 059/109] Clarify startdwarf documentation --- Readme.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.rst b/Readme.rst index 41fb0d935..e50fea72e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2686,7 +2686,7 @@ startdwarf Use at the embark screen to embark with the specified number of dwarves. Eg. ``startdwarf 500`` would lead to a severe food shortage and FPS issues, while ``startdwarf 10`` would just allow a few more warm bodies to dig in. -Trying a number less than 7 may have strange effects. +The number must be 7 or greater. stripcaged ========== From a71a6d45cc50e9d36eac308e9a71e4a7844ce5f1 Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 14 Aug 2015 15:36:36 +0530 Subject: [PATCH 060/109] Added a function to pull the world map from remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 64 ++++++++++++++++++++++++ plugins/remotefortressreader.cpp | 44 +++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 5597e7fdb..b8056cb7d 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -84,6 +84,14 @@ enum TiletypeVariant VAR_4 = 3; }; +enum WorldPoles +{ + NO_POLES = 0; + NORTH_POLE = 1; + SOUTH_POLE = 2; + BOTH_POLES = 3; +} + message Tiletype { required int32 id = 1; @@ -231,3 +239,59 @@ message MapInfo optional string world_name_english = 8; optional string save_name = 9; } + +enum FrontType +{ + FRONT_NONE = 0; + FRONT_WARM = 1; + FRONT_COLD = 2; + FRONT_OCCLUDED = 3; +} +enum CumulusType +{ + CUMULUS_NONE = 0; + CUMULUS_MEDIUM = 1; + CUMULUS_MULTI = 2; + CUMULUS_NIMBUS = 3; +} +enum StratusType +{ + STRATUS_NONE = 0; + STRATUS_ALTO = 1; + STRATUS_PROPER = 2; + STRATUS_NIMBUS = 3; +} +enum FogType +{ + FOG_NONE = 0; + FOG_MIST = 1; + FOG_NORMAL = 2; + F0G_THICK = 3; +} + +message Cloud +{ + optional FrontType front = 1; + optional CumulusType cumulus = 2; + optional bool cirrus = 3; + optional StratusType stratus = 4; + optional FogType fog = 5; +} + +message WorldMap +{ + required int32 world_width = 1; + required int32 world_height = 2; + optional string name = 3; + optional string name_english = 4; + repeated int32 elevation = 5; + repeated int32 rainfall = 6; + repeated int32 vegetation = 7; + repeated int32 temperature = 8; + repeated int32 evilness = 9; + repeated int32 drainage = 10; + repeated int32 volcanism = 11; + repeated int32 savagery = 12; + repeated Cloud clouds = 13; + repeated int32 salinity = 14; +} diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 2522836f6..47ad5e251 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -38,7 +38,9 @@ #include "df/physical_attribute_type.h" #include "df/mental_attribute_type.h" -#include +#include "df/color_modifier_raw.h" + +#include "df/region_map_entry.h" #include "df/unit.h" @@ -87,6 +89,7 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); +static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -135,6 +138,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("ResetMapHashes", ResetMapHashes); svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); + svc->addFunction("GetWorldMap", GetWorldMap); return svc; } @@ -1180,3 +1184,41 @@ static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessa } return CR_OK; } + +static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out) +{ + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx ++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + out->add_elevation(map_entry->elevation); + out->add_rainfall(map_entry->rainfall); + out->add_vegetation(map_entry->vegetation); + out->add_temperature(map_entry->temperature); + out->add_evilness(map_entry->evilness); + out->add_drainage(map_entry->drainage); + out->add_volcanism(map_entry->volcanism); + out->add_savagery(map_entry->savagery); + out->add_salinity(map_entry->salinity); + auto clouds = out->add_clouds(); + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); + } + return CR_OK; +} \ No newline at end of file From 1bf3a6a37a23cbecdf4e55b1d4adc3e66ac6c5fb Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Aug 2015 10:46:05 -0400 Subject: [PATCH 061/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 7cfed040f..fd62449d1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7cfed040fe83b94fb6b97afec1c5d86ccf1cb81e +Subproject commit fd62449d10f58726996c26cf31f02912e7ef692d From 4fc6cb6f175aebc96bf7ec4629a90cd05b71e8ee Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Aug 2015 16:11:23 -0400 Subject: [PATCH 062/109] Several PluginManager improvements * load/unload/reload are no longer restricted to plugins that exist on startup * Names passed to DFHACK_PLUGIN must match the plugin's filename (remotefortressreader vs RemoteFortressReader, counters vs probe) * "plug" output lists all plugins and state/command information * Deleted plugins can be reloaded again if they are replaced * load/unload/reload don't fail silently with broken plugins * Built-in commands are recognized internally (e.g. "help help" does not display "help is not a recognized command"), although help for them is not yet implemented * New command: "type" (bash-like) - shows where/how a command is implemented * "plug" can accept multiple plugin names * "ls" displays more information about unloaded/unrecognized plugins * "load all" changed to "load -all" (or "load --all", "load -a", ...) --- library/Core.cpp | 316 +++++++++++++++++++------------ library/PluginManager.cpp | 291 +++++++++++++++++++++------- library/include/PluginManager.h | 82 ++++---- plugins/devel/counters.cpp | 4 +- plugins/remotefortressreader.cpp | 2 +- plugins/stockflow.cpp | 4 +- 6 files changed, 477 insertions(+), 222 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index d48f45199..86fbcc83d 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -386,12 +386,12 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: std::vector possible; auto plug_mgr = Core::getInstance().getPluginManager(); - for(size_t i = 0; i < plug_mgr->size(); i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); + const Plugin * plug = it->second; for (size_t j = 0; j < plug->size(); j++) { - const PluginCommand &pcmd = plug->operator[](j); + const PluginCommand &pcmd = (*plug)[j]; if (pcmd.isHotkeyCommand()) continue; if (pcmd.name.substr(0, first.size()) == first) @@ -485,6 +485,43 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } +string getBuiltinCommand(std::string cmd) +{ + std::string builtin = ""; + if (cmd == "ls" || + cmd == "help" || + cmd == "type" || + cmd == "load" || + cmd == "unload" || + cmd == "reload" || + cmd == "enable" || + cmd == "disable" || + cmd == "plug" || + cmd == "type" || + cmd == "keybinding" || + cmd == "fpause" || + cmd == "cls" || + cmd == "die" || + cmd == "kill-lua" || + cmd == "script" || + cmd == "hide" || + cmd == "show" || + cmd == "sc-script" + ) + builtin = cmd; + + else if (cmd == "?" || cmd == "man") + builtin = "help"; + + else if (cmd == "dir") + builtin = "ls"; + + else if (cmd == "clear") + builtin = "cls"; + + return builtin; +} + command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; @@ -499,8 +536,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v first[i] = '/'; } } + // let's see what we actually got - if(first=="help" || first == "?" || first == "man") + string builtin = getBuiltinCommand(first); + if (builtin == "help") { if(!parts.size()) { @@ -516,21 +555,26 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v " help|?|man - This text.\n" " help COMMAND - Usage help for the given command.\n" " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls - Clear the console.\n" + " cls|clear - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" " keybinding - Modify bindings of commands to keys\n" "Plugin management (useful for developers):\n" " plug [PLUGIN|v] - List plugin state and description.\n" - " load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" + " load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n" + " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n" + " reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n" ); con.print("\nDFHack version %s.\n", Version::dfhack_version()); } else if (parts.size() == 1) { + if (getBuiltinCommand(parts[0]).size()) + { + con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl; + return CR_NOT_IMPLEMENTED; + } Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); if (plug) { for (size_t j = 0; j < plug->size();j++) @@ -564,97 +608,58 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } con.printerr("Unknown command: %s\n", parts[0].c_str()); + return CR_FAILURE; } else { con.printerr("not implemented yet\n"); + return CR_NOT_IMPLEMENTED; } } - else if( first == "load" ) + else if (builtin == "load" || builtin == "unload" || builtin == "reload") { - if(parts.size()) - { - string & plugname = parts[0]; - if(plugname == "all") - { - for(size_t i = 0; i < plug_mgr->size();i++) - { - Plugin * plug = (plug_mgr->operator[](i)); - plug->load(con); - } - } - else - { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } - else - { - plug->load(con); - } - } - } - } - else if( first == "reload" ) - { - if(parts.size()) + bool all = false; + bool load = (builtin == "load"); + bool unload = (builtin == "unload"); + if (parts.size()) { - string & plugname = parts[0]; - if(plugname == "all") + for (auto p = parts.begin(); p != parts.end(); p++) { - for(size_t i = 0; i < plug_mgr->size();i++) + if (p->size() && (*p)[0] == '-') { - Plugin * plug = (plug_mgr->operator[](i)); - plug->reload(con); + if (p->find('a') != string::npos) + all = true; } } - else + if (all) { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } + if (load) + plug_mgr->loadAll(); + else if (unload) + plug_mgr->unloadAll(); else - { - plug->reload(con); - } - } - } - } - else if( first == "unload" ) - { - if(parts.size()) - { - string & plugname = parts[0]; - if(plugname == "all") - { - for(size_t i = 0; i < plug_mgr->size();i++) - { - Plugin * plug = (plug_mgr->operator[](i)); - plug->unload(con); - } + plug_mgr->reloadAll(); + return CR_OK; } - else + for (auto p = parts.begin(); p != parts.end(); p++) { - Plugin * plug = plug_mgr->getPluginByName(plugname); - if(!plug) - { - con.printerr("No such plugin\n"); - } + if (!p->size() || (*p)[0] == '-') + continue; + if (load) + plug_mgr->load(*p); + else if (unload) + plug_mgr->unload(*p); else - { - plug->unload(con); - } + plug_mgr->reload(*p); } } + else + con.printerr("%s: no arguments\n", builtin.c_str()); } - else if( first == "enable" || first == "disable" ) + else if( builtin == "enable" || builtin == "disable" ) { CoreSuspender suspend; - bool enable = (first == "enable"); + bool enable = (builtin == "enable"); if(parts.size()) { @@ -673,7 +678,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } - Plugin * plug = plug_mgr->getPluginByName(part); + Plugin * plug = (*plug_mgr)[part]; if(!plug) { @@ -691,14 +696,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v else if (!plug->can_set_enabled()) { res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); + con.printerr("Cannot %s plugin: %s\n", builtin.c_str(), part.c_str()); } else { res = plug->set_enabled(con, enable); if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); + con.printerr("Could not %s plugin: %s\n", builtin.c_str(), part.c_str()); } } @@ -706,9 +711,9 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else { - for(size_t i = 0; i < plug_mgr->size();i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - Plugin * plug = (plug_mgr->operator[](i)); + Plugin * plug = it->second; if (!plug->can_be_enabled()) continue; con.print( @@ -720,7 +725,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } } - else if(first == "ls" || first == "dir") + else if (builtin == "ls" || builtin == "dir") { bool all = false; if (parts.size() && parts[0] == "-a") @@ -731,10 +736,18 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(parts.size()) { string & plugname = parts[0]; - const Plugin * plug = plug_mgr->getPluginByName(plugname); + const Plugin * plug = (*plug_mgr)[plugname]; if(!plug) { - con.printerr("There's no plugin called %s!\n",plugname.c_str()); + con.printerr("There's no plugin called %s!\n", plugname.c_str()); + } + else if (plug->getState() != Plugin::PS_LOADED) + { + con.printerr("Plugin %s is not loaded.\n", plugname.c_str()); + } + else if (!plug->size()) + { + con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str()); } else for (size_t j = 0; j < plug->size();j++) { @@ -749,27 +762,28 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { con.print( "builtin:\n" - " help|?|man - This text or help specific to a plugin.\n" - " ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " kill-lua - Stop an active Lua script\n" - " keybinding - Modify bindings of commands to keys\n" - " script FILENAME - Run the commands specified in a file.\n" - " sc-script - Automatically run specified scripts on state change events\n" - " plug [PLUGIN|v] - List plugin state and detailed description.\n" - " load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" - " enable/disable PLUGIN - Enable or disable a plugin if supported.\n" + " help|?|man - This text or help specific to a plugin.\n" + " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately\n" + " kill-lua - Stop an active Lua script\n" + " keybinding - Modify bindings of commands to keys\n" + " script FILENAME - Run the commands specified in a file.\n" + " sc-script - Automatically run specified scripts on state change events\n" + " plug [PLUGIN|v] - List plugin state and detailed description.\n" + " load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n" + " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n" + " reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n" + " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n" + " type COMMAND - Display information about where a command is implemented\n" "\n" "plugins:\n" ); std::set out; - for(size_t i = 0; i < plug_mgr->size();i++) + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); + const Plugin * plug = it->second; if(!plug->size()) continue; for (size_t j = 0; j < plug->size();j++) @@ -795,17 +809,87 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } } - else if(first == "plug") + else if (builtin == "plug") { - for(size_t i = 0; i < plug_mgr->size();i++) + const char *header_format = "%25s %10s %4s\n"; + const char *row_format = "%25s %10s %4i\n"; + con.print(header_format, "Name", "State", "Cmds"); + + vector plugins; + if (parts.size()) + plugins = parts; + else + plugins = plug_mgr->listPlugins(); + for (auto f = plugins.begin(); f != plugins.end(); ++f) { - const Plugin * plug = (plug_mgr->operator[](i)); - if(!plug->size()) + const Plugin * plug = plug_mgr->getPluginByName(*f); + if (!plug) continue; - con.print("%s\n", plug->getName().c_str()); + color_value color; + switch (plug->getState()) + { + case Plugin::PS_LOADED: + color = COLOR_LIGHTGREEN; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; + } + con.color(color); + con.print(row_format, + f->c_str(), + Plugin::getStateDescription(plug->getState()), + plug->size() + ); + con.color(COLOR_RESET); + } + } + else if (builtin == "type") + { + if (!parts.size()) + { + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; + } + con << parts[0]; + string builtin_cmd = getBuiltinCommand(parts[0]); + Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin_cmd.size()) + { + con << " is a built-in command"; + if (builtin_cmd != parts[0]) + con << " (aliased to " << builtin_cmd << ")"; + con << "." << std::endl; + } + else if (plug) + { + con << " is part of plugin " << plug->getName() << "." << std::endl; + } + else if (findScript(this->p->getPath(), parts[0] + ".lua").size()) + { + con << " is a Lua script." << std::endl; + } + else if (findScript(this->p->getPath(), parts[0] + ".rb").size()) + { + con << " is a Ruby script." << std::endl; + } + else + { + con << " is not recognized." << std::endl; + return CR_FAILURE; } } - else if(first == "keybinding") + else if (builtin == "keybinding") { if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) { @@ -853,12 +937,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v << Gui::getFocusString(Core::getTopViewscreen()) << endl; } } - else if(first == "fpause") + else if (builtin == "fpause") { World::SetPauseState(true); con.print("The game was forced to pause!\n"); } - else if(first == "cls") + else if (builtin == "cls") { if (con.is_console()) ((Console&)con).clear(); @@ -868,11 +952,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_NEEDS_CONSOLE; } } - else if(first == "die") + else if (builtin == "die") { _exit(666); } - else if(first == "kill-lua") + else if (builtin == "kill-lua") { bool force = false; for (auto it = parts.begin(); it != parts.end(); ++it) @@ -883,7 +967,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if (!Lua::Interrupt(force)) con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); } - else if(first == "script") + else if (builtin == "script") { if(parts.size() == 1) { @@ -896,7 +980,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } - else if(first=="hide") + else if (builtin=="hide") { if (!getConsole().hide()) { @@ -905,7 +989,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } return CR_OK; } - else if(first=="show") + else if (builtin=="show") { if (!getConsole().show()) { @@ -914,7 +998,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } return CR_OK; } - else if(first == "sc-script") + else if (builtin == "sc-script") { if (parts.size() < 1) { @@ -1338,7 +1422,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); - plug_mgr->init(this); + plug_mgr->init(); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 418b5a57b..d0243647c 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -52,6 +52,31 @@ using namespace tthread; #include +#define MUTEX_GUARD(lock) auto lock_##__LINE__ = make_mutex_guard(lock); +template +tthread::lock_guard make_mutex_guard (T *mutex) +{ + return tthread::lock_guard(*mutex); +} + +#if defined(_LINUX) + static const string plugin_suffix = ".plug.so"; +#elif defined(_DARWIN) + static const string plugin_suffix = ".plug.dylib"; +#else + static const string plugin_suffix = ".plug.dll"; +#endif + +static string getPluginPath() +{ + return Core::getInstance().getHackPath() + "plugins/"; +} + +static string getPluginPath (std::string name) +{ + return getPluginPath() + name + plugin_suffix; +} + struct Plugin::RefLock { RefLock() @@ -156,18 +181,12 @@ struct Plugin::LuaEvent : public Lua::Event::Owner { } }; -Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) +Plugin::Plugin(Core * core, const std::string & path, + const std::string &name, PluginManager * pm) + :name(name), + path(path), + parent(pm) { - filename = filepath; - parent = pm; - name.reserve(_filename.size()); - for(size_t i = 0; i < _filename.size();i++) - { - char ch = _filename[i]; - if(ch == '.') - break; - name.append(1,ch); - } plugin_lib = 0; plugin_init = 0; plugin_globals = 0; @@ -191,6 +210,24 @@ Plugin::~Plugin() delete access; } +const char *Plugin::getStateDescription (plugin_state state) +{ + switch (state) + { +#define map(s, desc) case s: return desc; break; + map(PS_LOADED, "loaded") + map(PS_UNLOADED, "unloaded") + map(PS_BROKEN, "broken") + map(PS_LOADING, "loading") + map(PS_UNLOADING, "unloading") + map(PS_DELETED, "deleted") +#undef map + default: + return "unknown"; + break; + } +} + bool Plugin::load(color_ostream &con) { { @@ -199,8 +236,10 @@ bool Plugin::load(color_ostream &con) { return true; } - else if(state != PS_UNLOADED) + else if(state != PS_UNLOADED && state != PS_DELETED) { + if (state == PS_BROKEN) + con.printerr("Plugin %s is broken - cannot be loaded\n", name.c_str()); return false; } state = PS_LOADING; @@ -208,16 +247,23 @@ bool Plugin::load(color_ostream &con) // enter suspend CoreSuspender suspend; // open the library, etc - fprintf(stderr, "loading plugin %s\n", filename.c_str()); - DFLibrary * plug = OpenPlugin(filename.c_str()); + fprintf(stderr, "loading plugin %s\n", name.c_str()); + if (!Filesystem::isfile(path)) + { + con.printerr("Plugin %s does not exist on disk\n", name.c_str()); + RefAutolock lock(access); + state = PS_DELETED; + return false; + } + DFLibrary * plug = OpenPlugin(path.c_str()); if(!plug) { - con.printerr("Can't load plugin %s\n", filename.c_str()); + con.printerr("Can't load plugin %s\n", name.c_str()); RefAutolock lock(access); state = PS_BROKEN; return false; } - #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN + #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED #define plugin_check_symbol(sym) \ if (!LookupPlugin(plug, sym)) \ { \ @@ -226,14 +272,20 @@ bool Plugin::load(color_ostream &con) return false; \ } - plugin_check_symbol("name") - plugin_check_symbol("version") + plugin_check_symbol("plugin_name") + plugin_check_symbol("plugin_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") - const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); - const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); - const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "git_description"); + const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name"); + if (name != *plug_name) + { + con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name); + plugin_abort_load; + return false; + } + const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); + const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); const char *dfhack_version = Version::dfhack_version(); const char *dfhack_git_desc = Version::git_description(); @@ -252,10 +304,7 @@ bool Plugin::load(color_ostream &con) *plug_name, plug_git_desc, dfhack_git_desc); } else - { con.printerr("Warning: Plugin %s missing git information\n", *plug_name); - plug_git_desc = "unknown"; - } bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS")) { @@ -292,7 +341,6 @@ bool Plugin::load(color_ostream &con) plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); index_lua(plug); - this->name = *plug_name; plugin_lib = plug; commands.clear(); if(plugin_init(con,commands) == CR_OK) @@ -303,6 +351,7 @@ bool Plugin::load(color_ostream &con) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) con.printerr("Plugin %s has no enabled var!\n", name.c_str()); fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc); + fflush(stderr); return true; } else @@ -312,6 +361,7 @@ bool Plugin::load(color_ostream &con) plugin_onupdate = 0; reset_lua(); plugin_abort_load; + state = PS_BROKEN; return false; } } @@ -364,11 +414,13 @@ bool Plugin::unload(color_ostream &con) return false; } } - else if(state == PS_UNLOADED) + else if(state == PS_UNLOADED || state == PS_DELETED) { access->unlock(); return true; } + else if (state == PS_BROKEN) + con.printerr("Plugin %s is broken - cannot be unloaded\n", name.c_str()); access->unlock(); return false; } @@ -741,64 +793,144 @@ bool PluginExports::bind(DFLibrary *lib) return true; } -PluginManager::PluginManager(Core * core) +PluginManager::PluginManager(Core * core) : core(core) { + plugin_mutex = new recursive_mutex(); cmdlist_mutex = new mutex(); ruby = NULL; } PluginManager::~PluginManager() { - for(size_t i = 0; i < all_plugins.size();i++) + for (auto it = begin(); it != end(); ++it) { - delete all_plugins[i]; + Plugin *p = it->second; + delete p; } all_plugins.clear(); + delete plugin_mutex; delete cmdlist_mutex; } -void PluginManager::init(Core * core) +void PluginManager::init() { -#ifdef LINUX_BUILD - string path = core->getHackPath() + "plugins/"; -#ifdef _DARWIN - const string searchstr = ".plug.dylib"; -#else - const string searchstr = ".plug.so"; -#endif -#else - string path = core->getHackPath() + "plugins\\"; - const string searchstr = ".plug.dll"; -#endif - vector filez; - Filesystem::listdir(path, filez); - for(size_t i = 0; i < filez.size();i++) + loadAll(); +} + +bool PluginManager::addPlugin(string name) +{ + if (all_plugins.find(name) != all_plugins.end()) + { + Core::printerr("Plugin already exists: %s\n", name.c_str()); + return false; + } + string path = getPluginPath(name); + if (!Filesystem::isfile(path)) { - if(hasEnding(filez[i],searchstr)) + Core::printerr("Plugin does not exist: %s\n", name.c_str()); + return false; + } + Plugin * p = new Plugin(core, path, name, this); + all_plugins[name] = p; + return true; +} + +vector PluginManager::listPlugins() +{ + vector results; + vector files; + Filesystem::listdir(getPluginPath(), files); + for (auto file = files.begin(); file != files.end(); ++file) + { + if (hasEnding(*file, plugin_suffix)) { - Plugin * p = new Plugin(core, path + filez[i], filez[i], this); - all_plugins.push_back(p); - // make all plugins load by default (until a proper design emerges). - p->load(core->getConsole()); + string shortname = file->substr(0, file->find(plugin_suffix)); + results.push_back(shortname); } } + return results; +} + +bool PluginManager::load (const string &name) +{ + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name] && !addPlugin(name)) + return false; + Plugin *p = (*this)[name]; + if (!p) + { + Core::printerr("Plugin failed to load: %s\n", name.c_str()); + return false; + } + return p->load(core->getConsole()); } -Plugin *PluginManager::getPluginByName (const std::string & name) +bool PluginManager::loadAll() { - for(size_t i = 0; i < all_plugins.size(); i++) + MUTEX_GUARD(plugin_mutex); + auto files = listPlugins(); + bool ok = true; + // load all plugins in hack/plugins + for (auto f = files.begin(); f != files.end(); ++f) { - if(name == all_plugins[i]->name) - return all_plugins[i]; + if (!load(*f)) + ok = false; } - return 0; + return ok; +} + +bool PluginManager::unload (const string &name) +{ + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name]) + { + Core::printerr("Plugin does not exist: %s\n", name.c_str()); + return false; + } + return (*this)[name]->unload(core->getConsole()); +} + +bool PluginManager::unloadAll() +{ + MUTEX_GUARD(plugin_mutex); + bool ok = true; + // only try to unload plugins that are in all_plugins + for (auto it = begin(); it != end(); ++it) + { + if (!unload(it->first)) + ok = false; + } + return ok; +} + +bool PluginManager::reload (const string &name) +{ + // equivalent to "unload(name); load(name);" if plugin is recognized, + // "load(name);" otherwise + MUTEX_GUARD(plugin_mutex); + if (!(*this)[name]) + return load(name); + if (!unload(name)) + return false; + return load(name); +} + +bool PluginManager::reloadAll() +{ + MUTEX_GUARD(plugin_mutex); + bool ok = true; + if (!unloadAll()) + ok = false; + if (!loadAll()) + ok = false; + return ok; } Plugin *PluginManager::getPluginByCommand(const std::string &command) { tthread::lock_guard lock(*cmdlist_mutex); - map ::iterator iter = belongs.find(command); - if (iter != belongs.end()) + map ::iterator iter = command_map.find(command); + if (iter != command_map.end()) return iter->second; else return NULL; @@ -829,18 +961,14 @@ bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen * void PluginManager::OnUpdate(color_ostream &out) { - for(size_t i = 0; i < all_plugins.size(); i++) - { - all_plugins[i]->on_update(out); - } + for (auto it = begin(); it != end(); ++it) + it->second->on_update(out); } void PluginManager::OnStateChange(color_ostream &out, state_change_event event) { - for(size_t i = 0; i < all_plugins.size(); i++) - { - all_plugins[i]->on_state_change(out, event); - } + for (auto it = begin(); it != end(); ++it) + it->second->on_state_change(out, event); } // FIXME: doesn't check name collisions! @@ -848,15 +976,15 @@ void PluginManager::registerCommands( Plugin * p ) { cmdlist_mutex->lock(); vector & cmds = p->commands; - for(size_t i = 0; i < cmds.size();i++) + for (size_t i = 0; i < cmds.size();i++) { std::string name = cmds[i].name; - if (belongs.find(name) != belongs.end()) + if (command_map.find(name) != command_map.end()) { fprintf(stderr, "Plugin %s re-implements command \"%s\" (from plugin %s)\n", - p->getName().c_str(), name.c_str(), belongs[name]->getName().c_str()); + p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str()); } - belongs[name] = p; + command_map[name] = p; } if (p->plugin_eval_ruby) ruby = p; @@ -870,9 +998,34 @@ void PluginManager::unregisterCommands( Plugin * p ) vector & cmds = p->commands; for(size_t i = 0; i < cmds.size();i++) { - belongs.erase(cmds[i].name); + command_map.erase(cmds[i].name); } if (p->plugin_eval_ruby) ruby = NULL; cmdlist_mutex->unlock(); } + +Plugin *PluginManager::operator[] (std::string name) +{ + if (all_plugins.find(name) == all_plugins.end()) + { + if (Filesystem::isfile(getPluginPath(name))) + addPlugin(name); + } + return (all_plugins.find(name) != all_plugins.end()) ? all_plugins[name] : NULL; +} + +size_t PluginManager::size() +{ + return all_plugins.size(); +} + +std::map::iterator PluginManager::begin() +{ + return all_plugins.begin(); +} + +std::map::iterator PluginManager::end() +{ + return all_plugins.end(); +} diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 680262bad..a80e8d138 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -139,22 +139,25 @@ namespace DFHack struct RefLock; struct RefAutolock; struct RefAutoinc; - enum plugin_state - { - PS_UNLOADED, - PS_LOADED, - PS_BROKEN, - PS_LOADING, - PS_UNLOADING - }; friend class PluginManager; friend class RPCService; - Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); + Plugin(DFHack::Core* core, const std::string& filepath, + const std::string &plug_name, PluginManager * pm); ~Plugin(); command_result on_update(color_ostream &out); command_result on_state_change(color_ostream &out, state_change_event event); void detach_connection(RPCService *svc); public: + enum plugin_state + { + PS_UNLOADED, + PS_LOADED, + PS_BROKEN, + PS_LOADING, + PS_UNLOADING, + PS_DELETED + }; + static const char *getStateDescription (plugin_state state); bool load(color_ostream &out); bool unload(color_ostream &out); bool reload(color_ostream &out); @@ -183,6 +186,10 @@ namespace DFHack { return name; } + plugin_state getState() + { + return state; + } void open_lua(lua_State *state, int table); @@ -196,7 +203,7 @@ namespace DFHack RefLock * access; std::vector commands; std::vector services; - std::string filename; + std::string path; std::string name; DFLibrary * plugin_lib; PluginManager * parent; @@ -247,34 +254,41 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); - void init(Core* core); + void init(); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); void unregisterCommands( Plugin * p ); // PUBLIC METHODS public: - Plugin *getPluginByName (const std::string & name); + std::vector listPlugins(); + + bool load (const std::string &name); + bool loadAll(); + bool unload (const std::string &name); + bool unloadAll(); + bool reload (const std::string &name); + bool reloadAll(); + + Plugin *getPluginByName (const std::string &name) { return (*this)[name]; } Plugin *getPluginByCommand (const std::string &command); void *getPluginExports(const std::string &name); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); - Plugin* operator[] (std::size_t index) - { - if(index >= all_plugins.size()) - return 0; - return all_plugins[index]; - }; - std::size_t size() - { - return all_plugins.size(); - } + Plugin* operator[] (const std::string name); + std::size_t size(); Plugin *ruby; + + std::map::iterator begin(); + std::map::iterator end(); // DATA private: + Core *core; + bool addPlugin(std::string name); + tthread::recursive_mutex * plugin_mutex; tthread::mutex * cmdlist_mutex; - std::map belongs; - std::vector all_plugins; + std::map command_map; + std::map all_plugins; std::string plugin_path; }; @@ -287,10 +301,10 @@ namespace DFHack } }; -#define DFHACK_PLUGIN_AUX(plugin_name, is_dev) \ - DFhackDataExport const char * name = plugin_name;\ - DFhackDataExport const char * version = DFHack::Version::dfhack_version();\ - DFhackDataExport const char * git_description = DFHack::Version::git_description();\ +#define DFHACK_PLUGIN_AUX(m_plugin_name, is_dev) \ + DFhackDataExport const char * plugin_name = m_plugin_name;\ + DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ + DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\ DFhackDataExport Plugin *plugin_self = NULL;\ std::vector _plugin_globals;\ DFhackDataExport std::vector* plugin_globals = &_plugin_globals; \ @@ -298,9 +312,9 @@ namespace DFHack /// You have to include DFHACK_PLUGIN("plugin_name") in every plugin you write - just once. Ideally at the top of the main file. #ifdef DEV_PLUGIN -#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, true) +#define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, true) #else -#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, false) +#define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, false) #endif #define DFHACK_PLUGIN_IS_ENABLED(varname) \ @@ -330,7 +344,11 @@ namespace DFHack #define DFHACK_LUA_EVENT(name) { #name, &name##_event } #define DFHACK_LUA_END { NULL, NULL } -#define REQUIRE_GLOBAL(global_name) \ - using df::global::global_name; \ + +#define REQUIRE_GLOBAL_NO_USE(global_name) \ static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \ (plugin_globals->push_back(#global_name), 0); + +#define REQUIRE_GLOBAL(global_name) \ + using df::global::global_name; \ + REQUIRE_GLOBAL_NO_USE(global_name) diff --git a/plugins/devel/counters.cpp b/plugins/devel/counters.cpp index 332945677..c03aaed72 100644 --- a/plugins/devel/counters.cpp +++ b/plugins/devel/counters.cpp @@ -31,7 +31,7 @@ command_result df_counters (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("probe"); +DFHACK_PLUGIN("counters"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -44,4 +44,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parame desired = true; fast = true; } else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") { - out.print("%s: %s\nUsage:\n%s", name, tagline, usage); + out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); return CR_OK; } else if (parameters[0] == "list") { if (!enabled) { @@ -417,7 +417,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Fri, 14 Aug 2015 16:27:39 -0400 Subject: [PATCH 063/109] Update NEWS --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index a47a18934..2e12991c9 100644 --- a/NEWS +++ b/NEWS @@ -9,12 +9,14 @@ DFHack Future Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Multiple contexts can now be specified when adding keybindings + Plugin system is no longer restricted to plugins that exist on startup Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() Lua viewscreens can choose not to intercept the OPTIONS keybinding New internal commands kill-lua: Interrupt running Lua scripts + type: Show where a command is implemented New plugins confirm: Adds confirmation dialogs for several potentially dangerous actions fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499) @@ -39,6 +41,7 @@ DFHack Future - fixed assigning quality to items - made "esc" work properly gui/gm-editor handles lua tables properly + help: now recognizes built-in commands, like "help" manipulator: fixed crash when selecting custom professions when none are found remotefortressreader: fixed crash when attempting to send map info when no map was loaded search: fixed crash in unit list after cancelling a job @@ -73,6 +76,9 @@ DFHack Future - "lever pull" can be used to pull the currently-selected lever memview: Fixed display issue nyan: Can now be stopped with dfhack-run + plug: + - lists all plugins + - shows state and number of commands in plugins quicksave: Restricted to fortress mode remotefortressreader: Exposes more information search: From 8c93177f1d07a51ebeefbe19b3d657f245d4fa6c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Aug 2015 16:33:34 -0400 Subject: [PATCH 064/109] No need to document documentation updates --- NEWS | 1 - scripts/points.lua | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 11935062d..f364bdb3e 100644 --- a/NEWS +++ b/NEWS @@ -75,7 +75,6 @@ DFHack Future memview: Fixed display issue nyan: Can now be stopped with dfhack-run quicksave: Restricted to fortress mode - README: documented show/hide command, startdwarf.rb remotefortressreader: Exposes more information search: - Supports noble suggestion screen (e.g. suggesting a baron) diff --git a/scripts/points.lua b/scripts/points.lua index f39009c8a..0bedd2074 100644 --- a/scripts/points.lua +++ b/scripts/points.lua @@ -1,3 +1,3 @@ -- by Meph -- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005 -df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...) \ No newline at end of file +df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...) From 0b4d32d0e6c81fa6bc8382531f233286b30733db Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Aug 2015 16:40:38 -0400 Subject: [PATCH 065/109] Add @sethwoodworth to Contributors.rst see #669 --- Contributors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Contributors.rst b/Contributors.rst index a3225fe5c..7fb774085 100644 --- a/Contributors.rst +++ b/Contributors.rst @@ -80,6 +80,7 @@ melkor217 melkor217 acwatkins acwatkins Wes Malone wesQ3 Michon van Dooren MaienM +Seth Woodworth sethwoodworth ======================= ==================== =========================== And these are the cool people who made **Stonesense**. From a5f15b279ced1bb90b7d30eb275479fdcaafaead Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 14 Aug 2015 19:32:41 -0400 Subject: [PATCH 066/109] plug: Include deleted plugins in full list --- library/Core.cpp | 14 ++++++-------- library/PluginManager.cpp | 11 +++++++++++ library/include/PluginManager.h | 3 +++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 86fbcc83d..3cb86896a 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -815,16 +815,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v const char *row_format = "%25s %10s %4i\n"; con.print(header_format, "Name", "State", "Cmds"); - vector plugins; - if (parts.size()) - plugins = parts; - else - plugins = plug_mgr->listPlugins(); - for (auto f = plugins.begin(); f != plugins.end(); ++f) + plug_mgr->refresh(); + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = plug_mgr->getPluginByName(*f); + const Plugin * plug = it->second; if (!plug) continue; + if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) + continue; color_value color; switch (plug->getState()) { @@ -847,7 +845,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } con.color(color); con.print(row_format, - f->c_str(), + plug->getName().c_str(), Plugin::getStateDescription(plug->getState()), plug->size() ); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d0243647c..ae99b31dd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -851,6 +851,17 @@ vector PluginManager::listPlugins() return results; } +void PluginManager::refresh() +{ + MUTEX_GUARD(plugin_mutex); + auto files = listPlugins(); + for (auto f = files.begin(); f != files.end(); ++f) + { + if (!(*this)[*f]) + addPlugin(*f); + } +} + bool PluginManager::load (const string &name) { MUTEX_GUARD(plugin_mutex); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index a80e8d138..aba6aaa0f 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -261,7 +261,10 @@ namespace DFHack void unregisterCommands( Plugin * p ); // PUBLIC METHODS public: + // list names of all plugins present in hack/plugins std::vector listPlugins(); + // create Plugin instances for any plugins in hack/plugins that aren't present in all_plugins + void refresh(); bool load (const std::string &name); bool loadAll(); From 9b1f394032961b5aed0961516e45ea0aa8484789 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sat, 15 Aug 2015 16:43:33 +1000 Subject: [PATCH 067/109] Add remaining basic and fix/* scripts to readme --- Readme.rst | 237 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 176 insertions(+), 61 deletions(-) diff --git a/Readme.rst b/Readme.rst index e50fea72e..2f28dfd06 100644 --- a/Readme.rst +++ b/Readme.rst @@ -645,6 +645,39 @@ Options: :show X: Marks the selected map feature as discovered. :hide X: Marks the selected map feature as undiscovered. +fortplan +-------- +Usage: fortplan [filename] + +Designates furniture for building according to a .csv file with +quickfort-style syntax. Companion to digfort. + +The first line of the file must contain the following:: + + #build start(X; Y; ) + +...where X and Y are the offset from the top-left corner of the file's area +where the in-game cursor should be located, and +is an optional description of where that is. You may also leave a description +of the contents of the file itself following the closing parenthesis on the +same line. + +The syntax of the file itself is similar to digfort or quickfort. At present, +only buildings constructed of an item with the same name as the building +are supported. All other characters are ignored. For example:: + + `,`,d,`,` + `,f,`,t,` + `,s,b,c,` + +This section of a file would designate for construction a door and some +furniture inside a bedroom: specifically, clockwise from top left, a cabinet, +a table, a chair, a bed, and a statue. + +All of the building designation uses Planning Mode, so you do not need to +have the items available to construct all the buildings when you run +fortplan with the .csv file. + infiniteSky ----------- Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once. @@ -2120,6 +2153,7 @@ Advanced usage: :`autolabor reset-all`: Return all labors to the default handling. :`autolabor list`: List current status of all labors. :`autolabor status`: Show basic status information. +:`autolabor-artisans `: Run a command for labors where skill affects output quality *Examples:* @@ -2251,6 +2285,11 @@ scripts that are obscure, developer-oriented, or should be used as keybindings. The following scripts are distibuted with DFHack: +devel/* +======= +Scripts in this subdirectory are intended for developers, or still substantially +under development. If you don't already know what they do, best to leave them alone. + fix/* ===== Scripts in this subdirectory fix various bugs and issues, some of them obscure. @@ -2265,19 +2304,17 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure. on the same exact tile (bug 5991), designates the tile restricted traffic to hopefully avoid jamming it again, and unsuspends them. -* fix/cloth-stockpile - - Fixes erratic behavior of cloth stockpiles by scanning material objects - in memory and patching up some invalid reference fields. Needs to be run - every time a save game is loaded; putting ``fix/cloth-stockpile enable`` - in ``dfhack.init`` makes it run automatically. - * fix/dead-units Removes uninteresting dead units from the unit list. Doesn't seem to give any noticeable performance gain, but migrants normally stop if the unit list grows to around 3000 units, and this script reduces it back. +* fix/fat-dwarves + + Avoids 5-10% FPS loss due to constant recalculation of insulation for dwarves at + maximum fatness, by reducing the cap from 1,000,000 to 999,999. + * fix/feeding-timers Reset the GiveWater and GiveFood timers of all units as appropriate. @@ -2292,6 +2329,10 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure. Diagnoses and fixes issues with nonexistant 'items occupying site', usually caused by autodump bugs or other hacking mishaps. +* fix/loyaltycascade + + Aborts loyalty cascades by fixing units whose own civ is the enemy. + * fix/population-cap Run this after every migrant wave to ensure your population cap is not exceeded. @@ -2305,6 +2346,11 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure. the environment and stops temperature updates. In order to maintain this efficient state however, use ``tweak stable-temp`` and ``tweak fast-heat``. +* fix/stuckdoors + + Fix doors that are stuck open due to incorrect map occupancy flags, eg due to + incorrect use of teleport. + gui/* ===== @@ -2328,6 +2374,39 @@ directory. Don't forget to `enable stockpiles` and create the `stocksettings` directory in the DF folder before trying to use this plugin. +adaptation +========== +View or set level of cavern adaptation for the selected unit or the whole fort. +Usage: ``adaptation [value]``. The ``value`` must be +between 0 and 800,000 inclusive. + +add-thought +=========== +Adds a thought or emotion to the selected unit. Can be used by other scripts, +or the gui invoked by running ``add-thought gui`` with a unit selected. + +autofarm +======== +Automatically handle crop selection in farm plots based on current plant stocks. +Selects a crop for planting if current stock is below a threshold. +Selected crops are dispatched on all farmplots. + +Usage:: + + autofarm start + autofarm default 30 + autofarm threshold 150 helmet_plump tail_pig + +autounsuspend +============= +Automatically unsuspend all jobs, on a recurring basis. See ``unsuspend`` for one-off use. + +ban-cooking +=========== +A more convenient way to ban cooking various categories of foods than the +kitchen interface. Usage: ``ban-cooking ``. Valid types are ``booze``, +``honey``, ``tallow``, ``oil``, and ``seeds`` (non-tree plants with seeds). + binpatch ======== Checks, applies or removes binary patches directly in memory at runtime:: @@ -2370,6 +2449,16 @@ Examples:: create-items bar CREATURE:CAT:SOAP create-items bar adamantine +deathcause +========== +Focus a body part ingame, and this script will display the cause of death of +the creature. +Also works when selecting units from the (``u``) unitlist viewscreen. + +dfstatus +======== +Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. + digfort ======= A script to designate an area for digging according to a plan in csv format. @@ -2400,16 +2489,6 @@ drain-aquifer ============= Remove all 'aquifer' tag from the map blocks. Irreversible. -deathcause -========== -Focus a body part ingame, and this script will display the cause of death of -the creature. -Also works when selecting units from the (``u``) unitlist viewscreen. - -dfstatus -======== -Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. - exterminate =========== Kills any unit of a given race. @@ -2449,6 +2528,10 @@ To purify all elves on the map with fire (may have side-effects):: exterminate elve magma +fixnaked +======== +Removes all unhappy thoughts due to lack of clothing. + fix-ster ======== Utilizes the orientation tag to either fix infertile creatures or inflict @@ -2461,38 +2544,20 @@ or sterile. Optional arguments specify the target: no argument for the selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf creatures, or ``only:`` to only process matching creatures. -fortplan -======== -Usage: fortplan [filename] - -Designates furniture for building according to a .csv file with -quickfort-style syntax. Companion to digfort. - -The first line of the file must contain the following:: - - #build start(X; Y; ) - -...where X and Y are the offset from the top-left corner of the file's area -where the in-game cursor should be located, and -is an optional description of where that is. You may also leave a description -of the contents of the file itself following the closing parenthesis on the -same line. - -The syntax of the file itself is similar to digfort or quickfort. At present, -only buildings constructed of an item with the same name as the building -are supported. All other characters are ignored. For example:: - - `,`,d,`,` - `,f,`,t,` - `,s,b,c,` +forum-dwarves +============= +Saves a copy of a text screen, formatted in bbcode for posting to the Bay12 Forums. +Use ``forum-dwarves help`` for more information. -This section of a file would designate for construction a door and some -furniture inside a bedroom: specifically, clockwise from top left, a cabinet, -a table, a chair, a bed, and a statue. +full-heal +========= +Attempts to fully heal the selected unit. ``full-heal -r`` attempts to resurrect the unit. -All of the building designation uses Planning Mode, so you do not need to -have the items available to construct all the buildings when you run -fortplan with the .csv file. +gaydar +====== +Shows the sexual orientation of units, useful for social engineering or checking +the viability of livestock breeding programs. Use ``gaydar -help`` for information +on available filters for orientation, citizenship, species, etc. growcrops ========= @@ -2576,6 +2641,15 @@ There are the following ways to invoke this command: Parses and executes the lua statement like the interactive interpreter would. +make-monarch +============ +Make the selected unit King or Queen or your civilisation. + +markdown +======== +Save a copy of a text screen in markdown (for reddit among others). +Use 'markdown help' for more details. + masspit ======= Designate all creatures in cages on top of a pit/pond activity zone for pitting. @@ -2587,7 +2661,7 @@ or with the game cursor on top of the area. multicmd ======== Run multiple dfhack commands. The argument is split around the -character ; and all parts are run sequencially as independent +character ; and all parts are run sequentially as independent dfhack commands. Useful for hotkeys. Example:: @@ -2615,15 +2689,52 @@ quicksave If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save. +region-pops +=========== +Show or modify the populations of animals in the region. Use ``region-pops`` for details. + remove-stress ============= -Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or very stressed dwarves taking on negative or greater values respectively. Applies to the selected unit, or use "remove-stress -all" to apply to all units. +Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or +very stressed dwarves taking on negative or greater values respectively. +Applies to the selected unit, or use "remove-stress -all" to apply to all units. + +remove-wear +=========== +Sets the wear on all items in your fort to zero. + +repeat +====== +Repeatedly calls a lua script at the specified interval. + +This allows neat background changes to the function of the game, especially when +invoked from an init file. For detailed usage instructions, use ``repeat -help``. + +Usage examples:: + + repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ] + repeat -time 1 -timeUnits months -command [ multicmd cleanowned scattered x; clean all ] -name clean + +The first example is abstract; the second will regularly remove all contaminants +and worn items from the game. + +``-name`` sets the name for the purposes of cancelling and making sure you don't schedule the +same repeating event twice. If not specified, it's set to the first argument after ``-command``. +``-time delay -timeUnits units``; delay is some positive integer, and units is some valid time +unit for ``dfhack.timeout(delay,timeUnits,function)``. ``-command [ ... ]`` specifies the +command to be run. setfps ====== Run ``setfps `` to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :) +show-unit-syndromes +=================== +Show syndromes affecting units and the remaining and maximum duration, along +with (optionally) substantial detail on the effects. Call +``show-unit-syndromes help`` for further options. + siren ===== Wakes up sleeping units, cancels breaks and stops parties either everywhere, @@ -2669,18 +2780,6 @@ Ex:: source add magma 7 - magma source source add water 0 - water drain -superdwarf -========== -Similar to fastdwarf, per-creature. - -To make any creature superfast, target it ingame using 'v' and:: - - superdwarf add - -Other options available: ``del``, ``clear``, ``list``. - -This plugin also shortens the 'sleeping' and 'on break' periods of targets. - startdwarf ========== Use at the embark screen to embark with the specified number of dwarves. Eg. @@ -2708,6 +2807,18 @@ alternatively pass cage IDs as arguments:: stripcaged weapons 25321 34228 +superdwarf +========== +Similar to fastdwarf, per-creature. + +To make any creature superfast, target it ingame using 'v' and:: + + superdwarf add + +Other options available: ``del``, ``clear``, ``list``. + +This plugin also shortens the 'sleeping' and 'on break' periods of targets. + teleport ======== Teleports a unit to given coordinates. @@ -2722,6 +2833,10 @@ undump-buildings ================ Undesignates building base materials for dumping. +unsuspend +========= +Unsuspend all jobs, on a one-off basis. See ``autounsuspend`` for regular use. + view-item-info ============== A script to extend the item or unit viewscreen with additional information From 84a1edc3b8d7b9e1d0994c3b7eb623dc17d3129b Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sat, 15 Aug 2015 16:43:50 +1000 Subject: [PATCH 068/109] Add initial comments to some scripts --- scripts/autounsuspend.rb | 2 ++ scripts/ban-cooking.rb | 4 ++-- scripts/fixnaked.lua | 2 ++ scripts/unsuspend.rb | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb index 2c474e993..94c6b1f26 100644 --- a/scripts/autounsuspend.rb +++ b/scripts/autounsuspend.rb @@ -1,3 +1,5 @@ +# un-suspend all jobs, on a recurring basis + class AutoUnsuspend attr_accessor :running diff --git a/scripts/ban-cooking.rb b/scripts/ban-cooking.rb index 9f21bb01a..544447277 100644 --- a/scripts/ban-cooking.rb +++ b/scripts/ban-cooking.rb @@ -1,3 +1,5 @@ +# convenient way to ban cooking categories of food + already_banned = {} kitchen = df.ui.kitchen kitchen.item_types.length.times { |i| @@ -84,5 +86,3 @@ $script_args.each do |arg| puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)" end end - -# vim: et:sw=4:ts=4 diff --git a/scripts/fixnaked.lua b/scripts/fixnaked.lua index 3f1ee6fdd..6e154440f 100644 --- a/scripts/fixnaked.lua +++ b/scripts/fixnaked.lua @@ -1,3 +1,5 @@ +--removes unhappy thoughts due to lack of clothing + function fixnaked() local total_fixed = 0 local total_removed = 0 diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb index 690ce6f03..0e1f52f35 100644 --- a/scripts/unsuspend.rb +++ b/scripts/unsuspend.rb @@ -1,3 +1,5 @@ +# un-suspend all jobs, one time only + joblist = df.world.job_list.next count = 0 From f8f7194f822ca51bf15e617e80cfd4bf2160e905 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sat, 15 Aug 2015 16:44:12 +1000 Subject: [PATCH 069/109] Make exportlegends compatible with open-legends --- scripts/exportlegends.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 7c8c539fb..a7178361f 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -564,7 +564,8 @@ function export_site_maps() end -- main() -if dfhack.gui.getCurFocus() == "legends" then +if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/lua/legends" then + -- either native legends mode, or using the open-legends.lua script if args[1] == "all" then export_legends_info() export_site_maps() From d9c50d677f51240cbae68c551eac562ee48bb443 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 15 Aug 2015 15:09:12 +0300 Subject: [PATCH 070/109] A lua interface for csockets in a spirit of luasocket --- plugins/lua/luasocket.lua | 67 ++++++++ plugins/luasocket.cpp | 352 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 plugins/lua/luasocket.lua create mode 100644 plugins/luasocket.cpp diff --git a/plugins/lua/luasocket.lua b/plugins/lua/luasocket.lua new file mode 100644 index 000000000..34cfeed38 --- /dev/null +++ b/plugins/lua/luasocket.lua @@ -0,0 +1,67 @@ +local _ENV = mkmodule('plugins.luasocket') +local _funcs={} +for k,v in pairs(_ENV) do + if type(v)=="function" then + _funcs[k]=v + _ENV[k]=nil + end +end + +local socket=defclass(socket) +socket.ATTRS={ + server_id=-1, + client_id=-1, +} + +function socket:close( ) + if self.client_id==-1 then + _funcs.lua_server_close(self.server_id) + else + _funcs.lua_client_close(self.server_id,self.client_id) + end +end +function socket:setTimeout( sec,msec ) + msec=msec or 0 + _funcs.lua_socket_set_timeout(self.server_id,self.client_id,sec,msec) +end + +local client=defclass(client,socket) +function client:receive( pattern ) + local pattern=pattern or "*l" + local bytes=-1 + if type(pattern)== number then + bytes=pattern + end + local ret=_funcs.lua_client_receive(self.server_id,self.client_id,bytes,pattern,false) + if ret=="" then + return + else + return ret + end +end +function client:send( data ) + _funcs.lua_client_send(self.server_id,self.client_id,data) +end + + +local server=defclass(server,socket) +function server:accept() + local id=_funcs.lua_server_accept(self.server_id,false) + if id~=nil then + return client{server_id=self.server_id,client_id=id} + else + return + end +end + +tcp={} +function tcp:bind( address,port ) + local id=_funcs.lua_socket_bind(address,port) + return server{server_id=id} +end +function tcp:connect( address,port ) + local id=_funcs.lua_socket_connect(address,port) + return client{client_id=id} +end +--TODO garbage collect stuff +return _ENV \ No newline at end of file diff --git a/plugins/luasocket.cpp b/plugins/luasocket.cpp new file mode 100644 index 000000000..e0a41a8b0 --- /dev/null +++ b/plugins/luasocket.cpp @@ -0,0 +1,352 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "DataDefs.h" + +#include +#include +#include +#include +#include +#include +#include "MiscUtils.h" +#include "LuaTools.h" +#include "DataFuncs.h" +#include //todo convert errors to lua-errors and co. Then remove this + +using namespace DFHack; +using namespace df::enums; +struct server +{ + CPassiveSocket *socket; + std::map clients; + int last_client_id; + void close(); +}; +std::map servers; +typedef std::map clients_map; +clients_map clients; //free clients, i.e. non-server spawned clients +DFHACK_PLUGIN("luasocket"); + +// The error messages are taken from the clsocket source code +const char * translate_socket_error(CSimpleSocket::CSocketError err) { + switch (err) { + case CSimpleSocket::SocketError: + return "Generic socket error translates to error below."; + case CSimpleSocket::SocketSuccess: + return "No socket error."; + case CSimpleSocket::SocketInvalidSocket: + return "Invalid socket handle."; + case CSimpleSocket::SocketInvalidAddress: + return "Invalid destination address specified."; + case CSimpleSocket::SocketInvalidPort: + return "Invalid destination port specified."; + case CSimpleSocket::SocketConnectionRefused: + return "No server is listening at remote address."; + case CSimpleSocket::SocketTimedout: + return "Timed out while attempting operation."; + case CSimpleSocket::SocketEwouldblock: + return "Operation would block if socket were blocking."; + case CSimpleSocket::SocketNotconnected: + return "Currently not connected."; + case CSimpleSocket::SocketEinprogress: + return "Socket is non-blocking and the connection cannot be completed immediately"; + case CSimpleSocket::SocketInterrupted: + return "Call was interrupted by a signal that was caught before a valid connection arrived."; + case CSimpleSocket::SocketConnectionAborted: + return "The connection has been aborted."; + case CSimpleSocket::SocketProtocolError: + return "Invalid protocol for operation."; + case CSimpleSocket::SocketFirewallError: + return "Firewall rules forbid connection."; + case CSimpleSocket::SocketInvalidSocketBuffer: + return "The receive buffer point outside the process's address space."; + case CSimpleSocket::SocketConnectionReset: + return "Connection was forcibly closed by the remote host."; + case CSimpleSocket::SocketAddressInUse: + return "Address already in use."; + case CSimpleSocket::SocketInvalidPointer: + return "Pointer type supplied as argument is invalid."; + case CSimpleSocket::SocketEunknown: + return "Unknown error please report to mark@carrierlabs.com"; + default: + return "No such CSimpleSocket error"; + } +} +void server::close() +{ + for(auto it=clients.begin();it!=clients.end();it++) + { + CActiveSocket* sock=it->second; + sock->Close(); + delete sock; + } + clients.clear(); + socket->Close(); + delete socket; +} +std::pair get_client(int server_id,int client_id) +{ + std::map* target=&clients; + if(server_id>0) + { + if(servers.count(server_id)==0) + { + throw std::runtime_error("Server with this id does not exist"); + } + server &cur_server=servers[server_id]; + target=&cur_server.clients; + } + + if(target->count(client_id)==0) + { + throw std::runtime_error("Client does with this id not exist"); + } + CActiveSocket *sock=(*target)[client_id]; + return std::make_pair(sock,target); +} +void handle_error(CSimpleSocket::CSocketError err,bool skip_timeout=true) +{ + if(err==CSimpleSocket::SocketSuccess) + return; + if(err==CSimpleSocket::SocketTimedout && skip_timeout) + return; + throw std::runtime_error(translate_socket_error(err)); +} +static int lua_socket_bind(std::string ip,int port) +{ + static int server_id=0; + CPassiveSocket* sock=new CPassiveSocket; + if(!sock->Initialize()) + { + CSimpleSocket::CSocketError err=sock->GetSocketError(); + delete sock; + handle_error(err,false); + } + sock->SetBlocking(); + if(!sock->Listen((uint8_t*)ip.c_str(),port)) + { + handle_error(sock->GetSocketError(),false); + } + server_id++; + server& cur_server=servers[server_id]; + cur_server.socket=sock; + cur_server.last_client_id=0; + return server_id; +} +static int lua_server_accept(int id,bool fail_on_timeout) +{ + if(servers.count(id)==0) + { + throw std::runtime_error("Server not bound"); + } + server &cur_server=servers[id]; + CActiveSocket* sock=cur_server.socket->Accept(); + if(!sock) + { + handle_error(sock->GetSocketError(),!fail_on_timeout); + return 0; + } + else + { + cur_server.last_client_id++; + cur_server.clients[cur_server.last_client_id]=sock; + return cur_server.last_client_id; + } +} +static void lua_client_close(int server_id,int client_id) +{ + auto info=get_client(server_id,client_id); + + CActiveSocket *sock=info.first; + std::map* target=info.second; + + target->erase(client_id); + CSimpleSocket::CSocketError err=CSimpleSocket::SocketSuccess; + if(!sock->Close()) + err=sock->GetSocketError(); + delete sock; + if(err!=CSimpleSocket::SocketSuccess) + { + throw std::runtime_error(translate_socket_error(err)); + } +} +static void lua_server_close(int server_id) +{ + if(servers.count(server_id)==0) + { + throw std::runtime_error("Server with this id does not exist"); + } + server &cur_server=servers[server_id]; + try{ + cur_server.close(); + } + catch(...) + { + servers.erase(server_id); + throw; + } +} +static std::string lua_client_receive(int server_id,int client_id,int bytes,std::string pattern,bool fail_on_timeout) +{ + auto info=get_client(server_id,client_id); + CActiveSocket *sock=info.first; + if(bytes>0) + { + if(sock->Receive(bytes)<=0) + { + throw std::runtime_error(translate_socket_error(sock->GetSocketError())); + } + return std::string((char*)sock->GetData(),bytes); + } + else + { + std::string ret; + if(pattern=="*a") //?? + { + while(true) + { + int received=sock->Receive(1); + if(received<0) + { + handle_error(sock->GetSocketError(),!fail_on_timeout); + return "";//maybe return partial string? + } + else if(received==0) + { + break; + } + ret+=(char)*sock->GetData(); + } + return ret; + } + else if (pattern=="" || pattern=="*l") + { + while(true) + { + + if(sock->Receive(1)<=0) + { + handle_error(sock->GetSocketError(),!fail_on_timeout); + return "";//maybe return partial string? + } + char rec=(char)*sock->GetData(); + if(rec=='\n') + break; + ret+=rec; + } + return ret; + } + else + { + throw std::runtime_error("Unsupported receive pattern"); + } + } +} +static void lua_client_send(int server_id,int client_id,std::string data) +{ + if(data.size()==0) + return; + std::map* target=&clients; + if(server_id>0) + { + if(servers.count(server_id)==0) + { + throw std::runtime_error("Server with this id does not exist"); + } + server &cur_server=servers[server_id]; + target=&cur_server.clients; + } + + if(target->count(client_id)==0) + { + throw std::runtime_error("Client does with this id not exist"); + } + CActiveSocket *sock=(*target)[client_id]; + if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size()) + { + throw std::runtime_error(translate_socket_error(sock->GetSocketError())); + } +} +static int lua_socket_connect(std::string ip,int port) +{ + static int last_client_id=0; + CActiveSocket* sock=new CActiveSocket; + if(!sock->Initialize()) + { + CSimpleSocket::CSocketError err=sock->GetSocketError(); + delete sock; + throw std::runtime_error(translate_socket_error(err)); + } + if(!sock->Open((const uint8_t*)ip.c_str(),port)) + { + CSimpleSocket::CSocketError err=sock->GetSocketError(); + delete sock; + throw std::runtime_error(translate_socket_error(err)); + } + last_client_id++; + clients[last_client_id]=sock; + return last_client_id; +} +static void lua_socket_set_timeout(int server_id,int client_id,int32_t sec,int32_t msec) +{ + std::map* target=&clients; + if(server_id>0) + { + if(servers.count(server_id)==0) + { + throw std::runtime_error("Server with this id does not exist"); + } + server &cur_server=servers[server_id]; + if(client_id==-1) + { + cur_server.socket->SetConnectTimeout(sec,msec); + cur_server.socket->SetReceiveTimeout(sec,msec); + cur_server.socket->SetSendTimeout(sec,msec); + return; + } + target=&cur_server.clients; + } + + if(target->count(client_id)==0) + { + throw std::runtime_error("Client does with this id not exist"); + } + CActiveSocket *sock=(*target)[client_id]; + sock->SetConnectTimeout(sec,msec); + sock->SetReceiveTimeout(sec,msec); + sock->SetSendTimeout(sec,msec); +} +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(lua_socket_bind), //spawn a server + DFHACK_LUA_FUNCTION(lua_socket_connect),//spawn a client (i.e. connection) + DFHACK_LUA_FUNCTION(lua_socket_set_timeout), + DFHACK_LUA_FUNCTION(lua_server_accept), + DFHACK_LUA_FUNCTION(lua_server_close), + DFHACK_LUA_FUNCTION(lua_client_close), + DFHACK_LUA_FUNCTION(lua_client_send), + DFHACK_LUA_FUNCTION(lua_client_receive), + DFHACK_LUA_END +}; +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + + return CR_OK; +} +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + for(auto it=clients.begin();it!=clients.end();it++) + { + CActiveSocket* sock=it->second; + sock->Close(); + delete sock; + } + clients.clear(); + for(auto it=servers.begin();it!=servers.end();it++) + { + it->second.close(); + } + servers.clear(); + return CR_OK; +} \ No newline at end of file From 5bb69cfe04769331b1d958b8dd7abe493882dab6 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 15 Aug 2015 15:12:13 +0300 Subject: [PATCH 071/109] Add luasocket to CMakeLists.txt --- plugins/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 258b0f5de..6c39b9bc0 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -135,6 +135,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) + DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(mode mode.cpp) #DFHACK_PLUGIN(misery misery.cpp) From fb702e0298ed8416d0426dd640153b5798dde095 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 15 Aug 2015 15:37:35 +0300 Subject: [PATCH 072/109] Add documentation to luasocket. --- Lua API.rst | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Lua API.rst b/Lua API.rst index 3f92ad4db..fb75bcbc0 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -3420,6 +3420,73 @@ Simple mechanical workshop:: } } +Luasocket +========= + +A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently +only subset of functions exist and only tcp mode is implemented. + +Socket class +------------ + +This is a base class for ``client`` and ``server`` sockets. You can not create it - it's like a virtual +base class in c++. + + +* ``socket:close()`` + + Closes the connection. + +* ``socket:setTimeout(sec,msec)`` + + Sets the operation timeout for this socket. It's possible to set timeout to 0. Then it performs like + a non-blocking socket. + +Client class +------------ + +Client is a connection socket to a server. You can get this object either from ``tcp:connect(address,port)`` or +from ``server:accept()``. It's a subclass of ``socket``. + +* ``client:receive(pattern)`` + + Receives data. If ``pattern`` is a number, it receives that much data. Other supported patterns: + + * ``*a`` + + Read all available data. + + * ``*l`` + + Read one line. This is the default mode (if pattern is nil). +* ``client:send(data)`` + + Sends data. Data is a string. + + +Server class +------------ + +Server is a socket that is waiting for clients. You can get this object from ``tcp:bind(address,port)``. + +* ``server:accept()`` + + Accepts an incoming connection if it exists. Returns a ``client`` object representing that socket. + +Tcp class +--------- + +A class with all the tcp functionality. + +* ``tcp:bind(address,port)`` + + Starts listening on that port for incoming connections. Returns ``server`` object. + +* ``tcp:connect(address,port)`` + + Tries connecting to that address and port. Returns ``client`` object. + + ======= Scripts ======= From d5513eac1bae3989192827e2768d0cbc04041a33 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Sun, 16 Aug 2015 13:15:11 +1000 Subject: [PATCH 073/109] Tweak readme based on feedback --- Readme.rst | 16 +++++++++------- scripts/autounsuspend.rb | 2 +- scripts/unsuspend.rb | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Readme.rst b/Readme.rst index 2f28dfd06..66ec5106c 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2361,6 +2361,10 @@ directory. A graphical interface for creating items. +* gui/dfstatus + + Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. + * gui/stockpiles Load and save stockpile settings from the 'q' menu. @@ -2399,7 +2403,8 @@ Usage:: autounsuspend ============= -Automatically unsuspend all jobs, on a recurring basis. See ``unsuspend`` for one-off use. +Automatically unsuspend construction jobs, on a recurring basis. +See ``unsuspend`` for one-off use, or ``resume all``. ban-cooking =========== @@ -2455,10 +2460,6 @@ Focus a body part ingame, and this script will display the cause of death of the creature. Also works when selecting units from the (``u``) unitlist viewscreen. -dfstatus -======== -Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. - digfort ======= A script to designate an area for digging according to a plan in csv format. @@ -2643,7 +2644,7 @@ There are the following ways to invoke this command: make-monarch ============ -Make the selected unit King or Queen or your civilisation. +Make the selected unit King or Queen of your civilisation. markdown ======== @@ -2835,7 +2836,8 @@ Undesignates building base materials for dumping. unsuspend ========= -Unsuspend all jobs, on a one-off basis. See ``autounsuspend`` for regular use. +Unsuspend construction jobs, on a one-off basis. See ``autounsuspend`` for regular use. +Equivalent to ``resume all``. view-item-info ============== diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb index 94c6b1f26..9613288ba 100644 --- a/scripts/autounsuspend.rb +++ b/scripts/autounsuspend.rb @@ -1,4 +1,4 @@ -# un-suspend all jobs, on a recurring basis +# un-suspend construction jobs, on a recurring basis class AutoUnsuspend attr_accessor :running diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb index 0e1f52f35..bd0c3ad78 100644 --- a/scripts/unsuspend.rb +++ b/scripts/unsuspend.rb @@ -1,4 +1,5 @@ -# un-suspend all jobs, one time only +# un-suspend construction jobs, one time only +# same as "resume all" joblist = df.world.job_list.next count = 0 From f60b3e81173b66931a37b45d5f665993faf4ded1 Mon Sep 17 00:00:00 2001 From: Japa Date: Sun, 16 Aug 2015 20:28:52 +0530 Subject: [PATCH 074/109] Added a function to get region tiles from remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 7 ++ plugins/remotefortressreader.cpp | 102 +++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index b8056cb7d..8b5fd5863 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -294,4 +294,11 @@ message WorldMap repeated int32 savagery = 12; repeated Cloud clouds = 13; repeated int32 salinity = 14; + optional int32 map_x = 15; + optional int32 map_y = 16; +} + +message RegionMaps +{ + repeated WorldMap world_maps = 1; } diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 47ad5e251..3a1d6ca87 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -41,6 +41,7 @@ #include "df/color_modifier_raw.h" #include "df/region_map_entry.h" +#include "df/world_region_details.h" #include "df/unit.h" @@ -90,6 +91,7 @@ static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage * static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); +static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -139,6 +141,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetWorldMap", GetWorldMap); + svc->addFunction("GetRegionMaps", GetRegionMaps); return svc; } @@ -1221,4 +1224,103 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); } return CR_OK; +} + +static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1, df::region_map_entry * e2, df::region_map_entry * e3, df::region_map_entry * e4) +{ + out->add_rainfall((e1->rainfall + e2->rainfall + e3->rainfall + e4->rainfall) / 4); + out->add_vegetation((e1->vegetation + e2->vegetation + e3->vegetation + e4->vegetation) / 4); + out->add_temperature((e1->temperature + e2->temperature + e3->temperature + e4->temperature) / 4); + out->add_evilness((e1->evilness + e2->evilness + e3->evilness + e4->evilness) / 4); + out->add_drainage((e1->drainage + e2->drainage + e3->drainage + e4->drainage) / 4); + out->add_volcanism((e1->volcanism + e2->volcanism + e3->volcanism + e4->volcanism) / 4); + out->add_savagery((e1->savagery + e2->savagery + e3->savagery + e4->savagery) / 4); + out->add_salinity((e1->salinity + e2->salinity + e3->salinity + e4->salinity) / 4); +} + +static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1, df::region_map_entry * e2) +{ + AddAveragedRegionTiles(out, e1, e1, e2, e2); +} + +static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1) +{ + AddAveragedRegionTiles(out, e1, e1, e1, e1); +} + +static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out) +{ + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + out->set_world_width(17); + out->set_world_height(17); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); + + df::region_map_entry * maps[] = + { + &worldData->region_map[pos_x][pos_y], &worldData->region_map[pos_x + 1][pos_y], + &worldData->region_map[pos_x][pos_y + 1], &worldData->region_map[pos_x + 1][pos_y + 1] + }; + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + out->add_elevation(worldRegionDetails->elevation[xx][yy]); + switch (worldRegionDetails->biome[xx][yy]) + { + case 1: + AddAveragedRegionTiles(out, maps[1]); + break; + case 2: + AddAveragedRegionTiles(out, maps[2], maps[3]); + break; + case 3: + AddAveragedRegionTiles(out, maps[3]); + break; + case 4: + AddAveragedRegionTiles(out, maps[0], maps[2]); + break; + case 5: + AddAveragedRegionTiles(out, maps[0], maps[1], maps[2], maps[3]); + break; + case 6: + AddAveragedRegionTiles(out, maps[1], maps[3]); + break; + case 7: + AddAveragedRegionTiles(out, maps[0]); + break; + case 8: + AddAveragedRegionTiles(out, maps[0], maps[1]); + break; + case 9: + AddAveragedRegionTiles(out, maps[2]); + break; + default: + AddAveragedRegionTiles(out, maps[0], maps[1], maps[2], maps[3]); + break; + } + } +} + +static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) +{ + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (int i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + WorldMap * regionMap = out->add_world_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; } \ No newline at end of file From bf7fc42710c5d777eae79a2ad55a785159a02202 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 17 Aug 2015 00:11:52 +0530 Subject: [PATCH 075/109] Made remotefortressreader.cpp get the elevations from the next region over for the last line. --- plugins/remotefortressreader.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 3a1d6ca87..e2a7911ce 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1261,6 +1261,22 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w out->set_name_english(name); out->set_name(name); + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (int i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + df::region_map_entry * maps[] = { &worldData->region_map[pos_x][pos_y], &worldData->region_map[pos_x + 1][pos_y], @@ -1270,7 +1286,16 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w for (int yy = 0; yy < 17; yy++) for (int xx = 0; xx < 17; xx++) { - out->add_elevation(worldRegionDetails->elevation[xx][yy]); + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + out->add_elevation(southEast->elevation[0][0]); + else if (xx == 16 && east != NULL) + out->add_elevation(east->elevation[0][yy]); + else if (yy == 16 && south != NULL) + out->add_elevation(south->elevation[xx][0]); + else + out->add_elevation(worldRegionDetails->elevation[xx][yy]); + switch (worldRegionDetails->biome[xx][yy]) { case 1: From 5717c2cdd4772b046b80e54de746788224ea26e6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 16 Aug 2015 17:02:53 -0400 Subject: [PATCH 076/109] Don't set plugin state to PS_BROKEN due to load failures --- library/PluginManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index ae99b31dd..87a9bec61 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -260,7 +260,7 @@ bool Plugin::load(color_ostream &con) { con.printerr("Can't load plugin %s\n", name.c_str()); RefAutolock lock(access); - state = PS_BROKEN; + state = PS_UNLOADED; return false; } #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED @@ -361,7 +361,6 @@ bool Plugin::load(color_ostream &con) plugin_onupdate = 0; reset_lua(); plugin_abort_load; - state = PS_BROKEN; return false; } } From 19f966aefcada4f1772b2e13460820de971614d1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 16 Aug 2015 17:28:31 -0400 Subject: [PATCH 077/109] Improve some plugin-related error handling --- library/PluginManager.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 87a9bec61..6a2b52c71 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -248,20 +248,21 @@ bool Plugin::load(color_ostream &con) CoreSuspender suspend; // open the library, etc fprintf(stderr, "loading plugin %s\n", name.c_str()); - if (!Filesystem::isfile(path)) - { - con.printerr("Plugin %s does not exist on disk\n", name.c_str()); - RefAutolock lock(access); - state = PS_DELETED; - return false; - } DFLibrary * plug = OpenPlugin(path.c_str()); if(!plug) { - con.printerr("Can't load plugin %s\n", name.c_str()); RefAutolock lock(access); - state = PS_UNLOADED; - return false; + if (!Filesystem::isfile(path)) + { + con.printerr("Plugin %s does not exist on disk\n", name.c_str()); + state = PS_DELETED; + return false; + } + else { + con.printerr("Can't load plugin %s\n", name.c_str()); + state = PS_UNLOADED; + return false; + } } #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED #define plugin_check_symbol(sym) \ @@ -869,7 +870,7 @@ bool PluginManager::load (const string &name) Plugin *p = (*this)[name]; if (!p) { - Core::printerr("Plugin failed to load: %s\n", name.c_str()); + Core::printerr("Plugin failed to register: %s\n", name.c_str()); return false; } return p->load(core->getConsole()); @@ -981,7 +982,6 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event) it->second->on_state_change(out, event); } -// FIXME: doesn't check name collisions! void PluginManager::registerCommands( Plugin * p ) { cmdlist_mutex->lock(); @@ -991,8 +991,9 @@ void PluginManager::registerCommands( Plugin * p ) std::string name = cmds[i].name; if (command_map.find(name) != command_map.end()) { - fprintf(stderr, "Plugin %s re-implements command \"%s\" (from plugin %s)\n", + core->printerr("Plugin %s re-implements command \"%s\" (from plugin %s)\n", p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str()); + continue; } command_map[name] = p; } @@ -1001,7 +1002,6 @@ void PluginManager::registerCommands( Plugin * p ) cmdlist_mutex->unlock(); } -// FIXME: doesn't check name collisions! void PluginManager::unregisterCommands( Plugin * p ) { cmdlist_mutex->lock(); From 763942045584504e6be78629c278ec6ad592de9d Mon Sep 17 00:00:00 2001 From: warmist Date: Mon, 17 Aug 2015 10:07:20 +0300 Subject: [PATCH 078/109] Update luasocket.lua --- plugins/lua/luasocket.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/lua/luasocket.lua b/plugins/lua/luasocket.lua index 34cfeed38..da0f0fe38 100644 --- a/plugins/lua/luasocket.lua +++ b/plugins/lua/luasocket.lua @@ -50,7 +50,7 @@ function server:accept() if id~=nil then return client{server_id=self.server_id,client_id=id} else - return + return end end @@ -64,4 +64,4 @@ function tcp:connect( address,port ) return client{client_id=id} end --TODO garbage collect stuff -return _ENV \ No newline at end of file +return _ENV From 29a0aee44c0955d4344598e9ca5eefe2b10bb8ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 24 Aug 2015 17:51:39 -0400 Subject: [PATCH 079/109] Implement strnlen on OS X <= 10.6 Fixes #182 --- library/LuaTypes.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 0904ff8f9..fa9445a81 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -49,6 +49,16 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; +#ifdef _DARWIN +#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_6 +size_t strnlen (const char *str, size_t max) +{ + const char *end = (const char*)memchr(str, 0, max); + return end ? (size_t)(end - str) : max; +} +#endif +#endif + /************************************** * Identity object read/write methods * **************************************/ From 8292f56f63af1f1e8038309f99f2d38cbc7a07a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 25 Aug 2015 10:12:31 -0400 Subject: [PATCH 080/109] New tweak: embark-profile-name --- NEWS | 1 + Readme.rst | 1 + plugins/tweak/tweak.cpp | 5 +++++ plugins/tweak/tweaks/embark-profile-name.h | 22 ++++++++++++++++++++++ 4 files changed, 29 insertions(+) create mode 100644 plugins/tweak/tweaks/embark-profile-name.h diff --git a/NEWS b/NEWS index 02eb33839..4e20592e2 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,7 @@ DFHack Future warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found points: set number of points available at embark screen New tweaks + embark-profile-name: Allows the use of lowercase letters when saving embark profiles kitchen-keys: Fixes DF kitchen meal keybindings kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs diff --git a/Readme.rst b/Readme.rst index 66ec5106c..d68755cb2 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1368,6 +1368,7 @@ Subcommands that persist until disabled or DF quits: :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :craft-age-wear: Fixes the behavior of crafted items wearing out over time (bug 6003). With this tweak, items made from cloth and leather will gain a level of wear every 20 years. +:embark-profile-name: Allows the use of lowercase letters when saving embark profiles :eggs-fertile: Displays a fertility indicator on nestboxes :farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus :fast-heat: Further improves temperature update performance by ensuring that 1 degree diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index fa2810d5d..98ec21eb3 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -81,6 +81,7 @@ #include "tweaks/civ-agreement-ui.h" #include "tweaks/craft-age-wear.h" #include "tweaks/eggs-fertile.h" +#include "tweaks/embark-profile-name.h" #include "tweaks/farm-plot-select.h" #include "tweaks/fast-heat.h" #include "tweaks/fast-trade.h" @@ -177,6 +178,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + int ch = -1; + for (auto it = input->begin(); ch == -1 && it != input->end(); ++it) + ch = Screen::keyToChar(*it); + if (in_save_profile && ch >= 32 && ch <= 126) + { + profile_name.push_back((char)ch); + } + else + { + if (input->count(df::interface_key::LEAVESCREEN)) + input->insert(df::interface_key::SETUPGAME_SAVE_PROFILE_ABORT); + INTERPOSE_NEXT(feed)(input); + } + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(embark_profile_name_hook, feed); From 81ef17df327984a01866342e954322b4299e79d0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 25 Aug 2015 10:23:59 -0400 Subject: [PATCH 081/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index fd62449d1..66b55a8d3 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fd62449d10f58726996c26cf31f02912e7ef692d +Subproject commit 66b55a8d39212267cd7a2e4cd34f2a47bb79dccc From 00d39e270ca17d13520080a1bcf86dc555e9370f Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 25 Aug 2015 14:17:45 -0400 Subject: [PATCH 082/109] Add hack/libs to DYLD_LIBRARY_PATH on OS X Fixes an issue in #649 --- package/darwin/dfhack | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 8040a837a..5a04bd591 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -4,11 +4,11 @@ cd "${PWD}" #thanks to Iriel for figuring this out OSREV=`uname -r | cut -d. -f1` if [ "$OSREV" -ge 11 ] ; then - export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs - export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs + export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs else - export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs - export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs + export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs fi old_tty_settings=$(stty -g) From c6ae3e11fd2df70f2f680e5dcc25564b9ae5ab3c Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 26 Aug 2015 16:40:59 -0400 Subject: [PATCH 083/109] Rename remotefortressreader to RemoteFortressReader --- plugins/CMakeLists.txt | 2 +- plugins/remotefortressreader.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 258b0f5de..eeb31a2b1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -145,7 +145,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(prospector prospector.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(regrass regrass.cpp) - DFHACK_PLUGIN(remotefortressreader remotefortressreader.cpp PROTOBUFS RemoteFortressReader) + DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp PROTOBUFS RemoteFortressReader) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) DFHACK_PLUGIN(resume resume.cpp) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index a6c571748..793ff2016 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -68,7 +68,7 @@ using namespace df::enums; using namespace RemoteFortressReader; using namespace std; -DFHACK_PLUGIN("remotefortressreader"); +DFHACK_PLUGIN("RemoteFortressReader"); #if DF_VERSION < 40024 using namespace df::global; #else From 906fba9b5416630d4399f245159564efd08e74ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 26 Aug 2015 16:41:04 -0400 Subject: [PATCH 084/109] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 66b55a8d3..2a49a0761 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 66b55a8d39212267cd7a2e4cd34f2a47bb79dccc +Subproject commit 2a49a0761e4413603d9a72542077755cea2ad56d From 7437400c185882205c664ca7b7c44fb832ac1059 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Aug 2015 19:38:41 -0400 Subject: [PATCH 085/109] command-prompt: Prevent unloading when prompt is active --- plugins/command-prompt.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index bc3b89610..f762ed713 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -330,6 +330,13 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 29 Aug 2015 19:42:32 -0400 Subject: [PATCH 086/109] Remove MacPool.* --- library/MacPool.h | 12 ------------ library/MacPool.mm | 22 ---------------------- 2 files changed, 34 deletions(-) delete mode 100644 library/MacPool.h delete mode 100644 library/MacPool.mm diff --git a/library/MacPool.h b/library/MacPool.h deleted file mode 100644 index f3b5dbebb..000000000 --- a/library/MacPool.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * MacPool.h - * Handles creation and destruction of autorelease pool for DFHack on the Mac - */ - -#ifndef MACPOOL_H -#define MACPOOL_H - -int create_pool(); -int destroy_pool(); - -#endif \ No newline at end of file diff --git a/library/MacPool.mm b/library/MacPool.mm deleted file mode 100644 index 8f26543e9..000000000 --- a/library/MacPool.mm +++ /dev/null @@ -1,22 +0,0 @@ -/* - * MacPool.m - * - */ - -#import -#import "MacPool.h" - -NSAutoreleasePool *thePool; - -int create_pool() { - fprintf(stderr,"Creating autorelease pool\n"); - thePool = [[NSAutoreleasePool alloc] init]; - return 1; -} - -int destroy_pool() { - fprintf(stderr,"Draining and releasing autorelease pool\n"); - [thePool drain]; - [thePool release]; - return 0; -} \ No newline at end of file From 6881aeac70a46e142204cb7705def575b31ee1a6 Mon Sep 17 00:00:00 2001 From: Japa Date: Thu, 3 Sep 2015 00:13:48 +0530 Subject: [PATCH 087/109] Enable sending of creature_raw in remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 29 ++++++++ plugins/remotefortressreader.cpp | 86 +++++++++++++++++++++++- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8b5fd5863..e0c95dac2 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -177,6 +177,7 @@ message UnitDefinition optional int32 pos_x = 3; optional int32 pos_y = 4; optional int32 pos_z = 5; + optional MatPair race = 6; } message UnitList @@ -302,3 +303,31 @@ message RegionMaps { repeated WorldMap world_maps = 1; } + +message CasteRaw +{ + optional int32 index = 1; + optional string caste_id = 2; + repeated string caste_name = 3; + repeated string baby_name = 4; + repeated string child_name = 5; +} + +message CreatureRaw +{ + optional int32 index = 1; + optional string creature_id = 2; + repeated string name = 3; + repeated string general_baby_name = 4; + repeated string general_child_name = 5; + optional int32 creature_tile = 6; + optional int32 creature_soldier_tile = 7; + optional ColorDefinition color = 8; + optional int32 adultsize = 9; + repeated CasteRaw caste = 10; +} + +message CreatureRawList +{ + repeated CreatureRaw creature_raws = 1; +} diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 793ff2016..4490b4144 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -44,6 +44,10 @@ #include "df/world_region_details.h" #include "df/unit.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" + +#include "df/enabler.h" //DFhack specific headers #include "modules/Maps.h" @@ -92,6 +96,7 @@ static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); +static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -141,8 +146,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetRegionMaps", GetRegionMaps); - return svc; + svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetCreatureRaws", GetCreatureRaws); + return svc; } // This is called right before the plugin library is removed from memory. @@ -173,6 +179,20 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes) return sum2 << 8 | sum1; } +void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) +{ + if (!df::global::enabler) + return; + + auto enabler = df::global::enabler; + + int index = in[0] + 8 * in[1]; + + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); +} + RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { switch (material) @@ -933,7 +953,9 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->set_pos_x(unit->pos.x); send_unit->set_pos_y(unit->pos.y); send_unit->set_pos_z(unit->pos.z); - } + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + } return CR_OK; } @@ -1349,3 +1371,61 @@ static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *i } return CR_OK; } + +static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) +{ + if (!df::global::world) + return CR_FAILURE; + + df::world * world = df::global::world; + + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; + + auto send_creature = out->add_creature_raws(); + + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); + + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); + + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + + send_creature->set_adultsize(orig_creature->adultsize); + + for (int j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); + + send_caste->set_index(j); + + send_caste->set_caste_id(orig_caste->caste_id); + + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); + + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); + + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + } + } + + return CR_OK; +} \ No newline at end of file From 1ea2da511b36760644b901d020a8c48163bf9a84 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Sep 2015 15:53:13 -0400 Subject: [PATCH 088/109] Fix whitespace --- plugins/remotefortressreader.cpp | 104 +++++++++++++++---------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 4490b4144..9e186eb47 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -146,9 +146,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetCreatureRaws", GetCreatureRaws); - return svc; + svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetCreatureRaws", GetCreatureRaws); + return svc; } // This is called right before the plugin library is removed from memory. @@ -181,16 +181,16 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes) void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) { - if (!df::global::enabler) - return; + if (!df::global::enabler) + return; - auto enabler = df::global::enabler; - - int index = in[0] + 8 * in[1]; + auto enabler = df::global::enabler; + + int index = in[0] + 8 * in[1]; - out->set_red((int)(enabler->ccolor[index][0] * 255)); - out->set_green((int)(enabler->ccolor[index][1] * 255)); - out->set_blue((int)(enabler->ccolor[index][2] * 255)); + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); } RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) @@ -953,9 +953,9 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->set_pos_x(unit->pos.x); send_unit->set_pos_y(unit->pos.y); send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - } + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + } return CR_OK; } @@ -1374,58 +1374,58 @@ static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *i static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) { - if (!df::global::world) - return CR_FAILURE; + if (!df::global::world) + return CR_FAILURE; - df::world * world = df::global::world; + df::world * world = df::global::world; - for (int i = 0; i < world->raws.creatures.all.size(); i++) - { - df::creature_raw * orig_creature = world->raws.creatures.all[i]; + for (int i = 0; i < world->raws.creatures.all.size(); i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; - auto send_creature = out->add_creature_raws(); + auto send_creature = out->add_creature_raws(); - send_creature->set_index(i); - send_creature->set_creature_id(orig_creature->creature_id); - send_creature->add_name(orig_creature->name[0]); - send_creature->add_name(orig_creature->name[1]); - send_creature->add_name(orig_creature->name[2]); + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); - send_creature->add_general_child_name(orig_creature->general_child_name[0]); - send_creature->add_general_child_name(orig_creature->general_child_name[1]); + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); - send_creature->set_creature_tile(orig_creature->creature_tile); - send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); - ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); - send_creature->set_adultsize(orig_creature->adultsize); + send_creature->set_adultsize(orig_creature->adultsize); - for (int j = 0; j < orig_creature->caste.size(); j++) - { - auto orig_caste = orig_creature->caste[j]; - if (!orig_caste) - continue; - auto send_caste = send_creature->add_caste(); + for (int j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); - send_caste->set_index(j); + send_caste->set_index(j); - send_caste->set_caste_id(orig_caste->caste_id); + send_caste->set_caste_id(orig_caste->caste_id); - send_caste->add_caste_name(orig_caste->caste_name[0]); - send_caste->add_caste_name(orig_caste->caste_name[1]); - send_caste->add_caste_name(orig_caste->caste_name[2]); + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); - send_caste->add_baby_name(orig_caste->baby_name[0]); - send_caste->add_baby_name(orig_caste->baby_name[1]); + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); - send_caste->add_child_name(orig_caste->child_name[0]); - send_caste->add_child_name(orig_caste->child_name[1]); - } - } + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + } + } - return CR_OK; + return CR_OK; } \ No newline at end of file From 9eac4585b15b6741f129f93fd3f2c5dcb08b61a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Sep 2015 15:56:53 -0400 Subject: [PATCH 089/109] keybinding: support 0-9, F10-F12 --- NEWS | 1 + Readme.rst | 4 ++-- library/Core.cpp | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 4e20592e2..be8be21cd 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ DFHack Future Stopped DF window from receiving input when unfocused on OS X Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X Multiple contexts can now be specified when adding keybindings + Keybindings can now use F10-F12 and 0-9 Plugin system is no longer restricted to plugins that exist on startup Lua Scripts can be enabled with the built-in enable/disable commands diff --git a/Readme.rst b/Readme.rst index d68755cb2..bb8e2bf7f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -197,7 +197,7 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file. -Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z. +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. Possible ways to call the command: @@ -214,7 +214,7 @@ The ** parameter above has the following *case-sensitive* syntax:: [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] -where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. +where the *KEY* part can be any recognized key and [] denote optional parts. When multiple commands are bound to the same key combination, DFHack selects the first applicable one. Later 'add' commands, and earlier entries within one diff --git a/library/Core.cpp b/library/Core.cpp index 3cb86896a..9f7709c3a 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2157,9 +2157,15 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { + *psym = SDL::K_0 + (keyspec[0]-'0'); + return true; } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') { *psym = SDL::K_F1 + (keyspec[1]-'1'); return true; + } else if (keyspec.size() == 3 && keyspec.substr(0, 2) == "F1" && keyspec[2] >= '0' && keyspec[2] <= '2') { + *psym = SDL::K_F10 + (keyspec[2]-'0'); + return true; } else if (keyspec == "Enter") { *psym = SDL::K_RETURN; return true; From f1391b63e2beae0bc0ec13d44f0a42557fc681b3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 3 Sep 2015 15:02:08 -0400 Subject: [PATCH 090/109] dfstatus: Allow sections and metal bars to be customized --- NEWS | 1 + Readme.rst | 1 + dfhack-config/dfstatus.lua | 28 +++++ scripts/gui/dfstatus.lua | 244 +++++++++++++++++++++++++++---------- 4 files changed, 207 insertions(+), 67 deletions(-) create mode 100644 dfhack-config/dfstatus.lua diff --git a/NEWS b/NEWS index be8be21cd..dd2acc430 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,7 @@ DFHack Future - widgets' positions, formats, etc. are now customizable (see Readme) - weather display now separated from the date display - New mouse cursor widget + dfstatus: Can enable/disable individual categories and customize metal bar list full-heal: "-r" option removes corpses gui/gm-editor - Pointers can now be displaced diff --git a/Readme.rst b/Readme.rst index bb8e2bf7f..dea56d40e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2365,6 +2365,7 @@ directory. * gui/dfstatus Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. + Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``. * gui/stockpiles diff --git a/dfhack-config/dfstatus.lua b/dfhack-config/dfstatus.lua new file mode 100644 index 000000000..f1e087d2f --- /dev/null +++ b/dfhack-config/dfstatus.lua @@ -0,0 +1,28 @@ +-- dfstatus config +-- the dfstatus script can be found in hack/scripts/gui/ +--[[ +The following variables can be set to true/false to enable/disable categories (all true by default) +* drink +* wood +* fuel +* prepared_meals +* tanned_hides +* cloth +* metals + +Example: +drink = false +fuel = true + +To add metals: +* metal 'IRON' +* metals "GOLD" 'SILVER' +* metal('COPPER') +* metals("BRONZE", 'HORN_SILVER') +Use '-' for a blank line: +* metal '-' +]] + +metals 'IRON' 'PIG_IRON' 'STEEL' +metals '-' +metals 'GOLD' 'SILVER' 'COPPER' diff --git a/scripts/gui/dfstatus.lua b/scripts/gui/dfstatus.lua index 8d7ceeb27..32758cfbf 100644 --- a/scripts/gui/dfstatus.lua +++ b/scripts/gui/dfstatus.lua @@ -1,8 +1,87 @@ --- dfstatus 1.5 - a quick access status screen. --- originally written by enjia2000@gmail.com +-- a quick access status screen +-- originally written by enjia2000@gmail.com (stolencatkarma) local gui = require 'gui' +function warn(msg) + dfhack.color(COLOR_LIGHTRED) + print(msg) + dfhack.color(nil) +end + +config = { + flags = { + drink = true, + wood = true, + fuel = true, + prepared_meals = true, + tanned_hides = true, + cloth = true, + metals = true, + }, + metal_ids = {}, +} + +function parse_config() + local metal_map = {} + for id, raw in pairs(df.global.world.raws.inorganics) do + if raw.material.flags.IS_METAL then + metal_map[raw.id:upper()] = id + metal_map[id] = raw.id:upper() + end + end + + local function add_metal(...) + for _, m in pairs({...}) do + id = metal_map[tostring(m):upper()] + if id ~= nil then + table.insert(config.metal_ids, id) + elseif m == '-' then + table.insert(config.metal_ids, '-') + else + warn('Invalid metal: ' .. tostring(m)) + end + end + return add_metal + end + + local env = {} + setmetatable(env, { + __index = function(_, k) + if k == 'metal' or k == 'metals' then + return add_metal + elseif k == 'flags' then + return config.flags + else + error('unknown name: ' .. k, 2) + end + end, + __newindex = function(_, k, v) + if config.flags[k] ~= nil then + if v ~= nil then + config.flags[k] = v + else + config.flags[k] = false + end + else + error('unknown flag: ' .. k, 2) + end + end, + }) + local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env) + if not f then + qerror('error loading config: ' .. err) + end + local ok, err = pcall(f) + if not ok then + qerror('error parsing config: ' .. err) + end +end + +function getInorganicName(id) + return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper)) +end + dfstatus = defclass(dfstatus, gui.FramedScreen) dfstatus.ATTRS = { frame_style = gui.GREY_LINE_FRAME, @@ -13,95 +92,126 @@ dfstatus.ATTRS = { focus_path = 'dfstatus', } -function dfstatus:onRenderBody(dc) +function dfstatus:init() + self.text = {} + self.start = 1 + local function write(line) + table.insert(self.text, line) + -- ensure that the window is wide enough for this line plus a scroll arrow + if #line + 1 > self.frame_width then + self.frame_width = #line + 1 + end + end + local function newline() write('') end + local f = config.flags + local drink = 0 local wood = 0 - --local meat = 0 - --local raw_fish = 0 - --local plants = 0 - local prepared_meals = 0 - local fuel = 0 - local pigiron = 0 - local iron = 0 - local steel = 0 - - local silver = 0 - local copper = 0 - local gold = 0 - local tannedhides = 0 + local prepared_meals = 0 + local tanned_hides = 0 local cloth = 0 + local metals = {} + for _, id in pairs(config.metal_ids) do + metals[id] = 0 + end + for _, item in ipairs(df.global.world.items.all) do if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then - if (item:getType() == df.item_type.WOOD) then wood = wood + item:getStackSize() - elseif (item:getType() == df.item_type.DRINK) then drink = drink + item:getStackSize() - elseif (item:getType() == df.item_type.SKIN_TANNED) then tannedhides = tannedhides + item:getStackSize() - elseif (item:getType() == df.item_type.CLOTH) then cloth = cloth + item:getStackSize() - --elseif (item:getType() == df.item_type.MEAT) then meat = meat + item:getStackSize() - --elseif (item:getType() == df.item_type.FISH_RAW) then raw_fish = raw_fish + item:getStackSize() - --elseif (item:getType() == df.item_type.PLANT) then plants = plants + item:getStackSize() - elseif (item:getType() == df.item_type.FOOD) then prepared_meals = prepared_meals + item:getStackSize() - elseif (item:getType() == df.item_type.BAR) then - for token in string.gmatch(dfhack.items.getDescription(item,0),"[^%s]+") do - if (token == "silver") then silver = silver + item:getStackSize() - elseif (token == "charcoal" or token == "coke") then fuel = fuel + item:getStackSize() - elseif (token == "iron") then iron = iron + item:getStackSize() - elseif (token == "pig") then pigiron = pigiron + item:getStackSize() - elseif (token == "copper") then copper = copper + item:getStackSize() - elseif (token == "gold") then gold = gold + item:getStackSize() - elseif (token == "steel") then steel = steel + item:getStackSize() + if item:getType() == df.item_type.WOOD then + wood = wood + item:getStackSize() + elseif item:getType() == df.item_type.DRINK then + drink = drink + item:getStackSize() + elseif item:getType() == df.item_type.SKIN_TANNED then + tanned_hides = tanned_hides + item:getStackSize() + elseif item:getType() == df.item_type.CLOTH then + cloth = cloth + item:getStackSize() + elseif item:getType() == df.item_type.FOOD then + prepared_meals = prepared_meals + item:getStackSize() + elseif item:getType() == df.item_type.BAR then + if item:getMaterial() == df.builtin_mats.COAL then + fuel = fuel + item:getStackSize() + elseif item:getMaterial() == df.builtin_mats.INORGANIC then + local mat_idx = item:getMaterialIndex() + if metals[mat_idx] ~= nil then + metals[mat_idx] = metals[mat_idx] + item:getStackSize() end - break -- only need to look at the 1st token of each item. end end end end + if f.drink then + write("Drinks: " .. drink) + end + if f.prepared_meals then + write("Meals: " .. prepared_meals) + end + if f.drink or f.prepared_meals then + newline() + end + if f.wood then + write("Wood: " .. wood) + end + if f.fuel then + write("Fuel: " .. fuel) + end + if f.wood or f.fuel then + newline() + end + if f.tanned_hides then + write("Hides: " .. tanned_hides) + end + if f.cloth then + write("Cloth: " .. cloth) + end + if f.tanned_hides or f.cloth then + newline() + end + if f.metals then + write("Metal bars:") + for _, id in pairs(config.metal_ids) do + if id == '-' then + newline() + else + write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id]) + end + end + end + self.start_min = 1 + self.start_max = #self.text - self.frame_height + 1 +end + +function dfstatus:onRenderBody(dc) dc:pen(COLOR_LIGHTGREEN) - dc:string("Drinks: " .. drink) - dc:newline(0) - dc:string("Meals: " .. prepared_meals) - dc:newline(0) - dc:newline(0) - dc:string("Wood: " .. wood) - dc:newline(0) - dc:newline(0) - dc:string("Hides: " .. tannedhides) - dc:newline(0) - dc:string("Cloth: " .. cloth) - dc:newline(0) - -- dc:string("Raw Fish: ".. raw_fish) - -- dc:newline(0) - -- dc:string("Plants: ".. plants) - -- dc:newline(0) - dc:newline(0) - dc:string("Bars:") - dc:newline(1) - dc:string("Fuel: " .. fuel) - dc:newline(1) - dc:string("Pig Iron: " .. pigiron) - dc:newline(1) - dc:string("Steel: " .. steel) - dc:newline(1) - dc:string("Iron: " .. iron) - dc:newline(1) - dc:newline(1) - dc:string("Copper: " .. copper) - dc:newline(1) - dc:string("Silver: " .. silver) - dc:newline(1) - dc:string("Gold: " .. gold) + for id, line in pairs(self.text) do + if id >= self.start then + dc:string(line):newline() + end + end + dc:pen(COLOR_LIGHTCYAN) + if self.start > self.start_min then + dc:seek(self.frame_width - 1, 0):char(24) + end + if self.start < self.start_max then + dc:seek(self.frame_width - 1, self.frame_height - 1):char(25) + end end function dfstatus:onInput(keys) if keys.LEAVESCREEN or keys.SELECT then self:dismiss() scr = nil + elseif keys.STANDARDSCROLL_UP then + self.start = math.max(self.start - 1, self.start_min) + elseif keys.STANDARDSCROLL_DOWN then + self.start = math.min(self.start + 1, self.start_max) end end if not scr then + parse_config() scr = dfstatus() scr:show() else From 66c7a461fd5233d346ea000c2d29d4ac4c949b38 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 3 Sep 2015 15:02:51 -0400 Subject: [PATCH 091/109] Don't attempt to copy non-files into dfhack-config --- library/Core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index 9f7709c3a..854f5b0d8 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1397,6 +1397,8 @@ bool Core::Init() if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end()) { std::string src_file = std::string("dfhack-config/default/") + filename; + if (!Filesystem::isfile(src_file)) + continue; std::string dest_file = std::string("dfhack-config/") + filename; std::ifstream src(src_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary); From 1c5428c3f04e0f7e060438a5046e15a1588e22ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 3 Sep 2015 15:03:17 -0400 Subject: [PATCH 092/109] Clean up linter initialization --- travis/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis/lint.py b/travis/lint.py index ac0f76d2f..ce28f4be3 100644 --- a/travis/lint.py +++ b/travis/lint.py @@ -85,7 +85,7 @@ class TabLinter(Linter): def fix_line(self, line): return line.replace('\t', ' ') -linters = [NewlineLinter(), TrailingWhitespaceLinter(), TabLinter()] +linters = [cls() for cls in Linter.__subclasses__()] def main(): root_path = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else '.') From 1a78cabd7c9193cae2ba202b532abf2c4f1b08e1 Mon Sep 17 00:00:00 2001 From: Japa Date: Sat, 5 Sep 2015 14:54:12 +0530 Subject: [PATCH 093/109] Add profession color and unit flags to remotefortressreader --- plugins/proto/RemoteFortressReader.proto | 5 +++++ plugins/remotefortressreader.cpp | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index e0c95dac2..d62573d90 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -178,6 +178,11 @@ message UnitDefinition optional int32 pos_y = 4; optional int32 pos_z = 5; optional MatPair race = 6; + optional ColorDefinition profession_color = 7; + optional uint32 flags1 = 8; + optional uint32 flags2 = 9; + optional uint32 flags3 = 10; + } message UnitList diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 4490b4144..54655bf59 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -57,6 +57,7 @@ #include "modules/Translation.h" #include "modules/Items.h" #include "modules/Buildings.h" +#include "modules/Units.h" #include "TileTypes.h" #include "MiscUtils.h" @@ -179,20 +180,25 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes) return sum2 << 8 | sum1; } -void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) +void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) { if (!df::global::enabler) return; auto enabler = df::global::enabler; - - int index = in[0] + 8 * in[1]; out->set_red((int)(enabler->ccolor[index][0] * 255)); out->set_green((int)(enabler->ccolor[index][1] * 255)); out->set_blue((int)(enabler->ccolor[index][2] * 255)); } +void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) +{ + int index = in[0] + 8 * in[1]; + ConvertDfColor(index, out); +} + + RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) { switch (material) @@ -955,6 +961,10 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->set_pos_z(unit->pos.z); send_unit->mutable_race()->set_mat_type(unit->race); send_unit->mutable_race()->set_mat_index(unit->caste); + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); } return CR_OK; } From 1bff73cc7a31c8c86b975a2d27e16341cd41b8ec Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sat, 5 Sep 2015 18:37:56 -0400 Subject: [PATCH 094/109] Update onReactionComplete documentation Changed in e5e0d93ef1e01386b45f241931dd50ffe04e0652 --- Lua API.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lua API.rst b/Lua API.rst index 3f92ad4db..9f3ee7598 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -3225,7 +3225,7 @@ on DF world events. List of events -------------- -1. ``onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)`` +1. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. From 9eb86c7e38c9c394e26e12e645ba10059ec560de Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 6 Sep 2015 16:23:02 -0400 Subject: [PATCH 095/109] Support additional script search paths These can currently be added/removed from C++ or through the Lua API. --- Lua API.rst | 27 ++++++++++++ library/Core.cpp | 89 ++++++++++++++++++++++++++++----------- library/LuaApi.cpp | 45 ++++++++++++++++++++ library/PluginManager.cpp | 1 + library/include/Core.h | 8 ++++ library/lua/dfhack.lua | 21 +-------- 6 files changed, 148 insertions(+), 43 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 3f92ad4db..f9390c777 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1964,6 +1964,33 @@ and are only documented here for completeness: Wraps strerror() - returns a string describing a platform-specific error code +* ``dfhack.internal.addScriptPath(path, search_before)`` + + Adds ``path`` to the list of paths searched for scripts (both in Lua and Ruby). + If ``search_before`` is passed and ``true``, the path will be searched before + the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will + be searched after. + + Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does + not exist or has already been registered). + +* ``dfhack.internal.removeScriptPath(path)`` + + Removes ``path`` from the script search paths and returns ``true`` if successful. + +* ``dfhack.internal.getScriptPaths()`` + + Returns the list of script paths in the order they are searched, including defaults. + (This can change if a world is loaded.) + +* ``dfhack.internal.findScript(name)`` + + Searches script paths for the script ``name`` and returns the path of the first + file found, or ``nil`` on failure. + + Note: This requires an extension to be specified (``.lua`` or ``.rb``) - + use ``dfhack.findScript()`` to include the ``.lua`` extension automatically. + Core interpreter context ======================== diff --git a/library/Core.cpp b/library/Core.cpp index 854f5b0d8..c5834875e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -427,24 +427,65 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: return false; } -string findScript(string path, string name) { - if (df::global::world) { - //first try the save folder if it exists - string save = World::ReadWorldFolder(); - if ( save != "" ) { - string file = path + "/data/save/" + save + "/raw/scripts/" + name; - if (fileExists(file)) { - return file; - } +bool Core::addScriptPath(string path, bool search_before) +{ + lock_guard lock(*script_path_mutex); + vector &vec = script_paths[search_before ? 0 : 1]; + if (std::find(vec.begin(), vec.end(), path) != vec.end()) + return false; + if (!Filesystem::isdir(path)) + return false; + vec.push_back(path); + return true; +} + +bool Core::removeScriptPath(string path) +{ + lock_guard lock(*script_path_mutex); + bool found = false; + for (int i = 0; i < 2; i++) + { + vector &vec = script_paths[i]; + while (1) + { + auto it = std::find(vec.begin(), vec.end(), path); + if (it == vec.end()) + break; + vec.erase(it); + found = true; } } - string file = path + "/raw/scripts/" + name; - if (fileExists(file)) { - return file; + return found; +} + +void Core::getScriptPaths(std::vector *dest) +{ + lock_guard lock(*script_path_mutex); + dest->clear(); + string df_path = this->p->getPath(); + for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) + dest->push_back(*it); + if (df::global::world) { + string save = World::ReadWorldFolder(); + if (save.size()) + dest->push_back(df_path + "/data/save/" + save + "/raw/scripts"); } - file = path + "/hack/scripts/" + name; - if (fileExists(file)) { - return file; + dest->push_back(df_path + "/raw/scripts"); + dest->push_back(df_path + "/hack/scripts"); + for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it) + dest->push_back(*it); +} + + +string Core::findScript(string name) +{ + vector paths; + getScriptPaths(&paths); + for (auto it = paths.begin(); it != paths.end(); ++it) + { + string path = *it + "/" + name; + if (Filesystem::isfile(path)) + return path; } return ""; } @@ -592,15 +633,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_OK; } } - string path = this->p->getPath(); - string file = findScript(path, parts[0] + ".lua"); + string file = findScript(parts[0] + ".lua"); if ( file != "" ) { string help = getScriptHelp(file, "-- "); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); return CR_OK; } if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { - file = findScript(path, parts[0] + ".rb"); + file = findScript(parts[0] + ".rb"); if ( file != "" ) { string help = getScriptHelp(file, "# "); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); @@ -682,7 +722,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(!plug) { - std::string lua = findScript(this->p->getPath(), part + ".lua"); + std::string lua = findScript(part + ".lua"); if (lua.size()) { res = enableLuaScript(con, part, enable); @@ -873,11 +913,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { con << " is part of plugin " << plug->getName() << "." << std::endl; } - else if (findScript(this->p->getPath(), parts[0] + ".lua").size()) + else if (findScript(parts[0] + ".lua").size()) { con << " is a Lua script." << std::endl; } - else if (findScript(this->p->getPath(), parts[0] + ".rb").size()) + else if (findScript(parts[0] + ".rb").size()) { con << " is a Ruby script." << std::endl; } @@ -1100,11 +1140,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(res == CR_NOT_IMPLEMENTED) { string completed; - string path = this->p->getPath(); - string filename = findScript(path, first + ".lua"); + string filename = findScript(first + ".lua"); bool lua = filename != ""; if ( !lua ) { - filename = findScript(path, first + ".rb"); + filename = findScript(first + ".rb"); } if ( lua ) res = runLuaScript(con, first, parts); @@ -1273,6 +1312,8 @@ Core::Core() server = NULL; color_ostream::log_errors_to_stderr = true; + + script_path_mutex = new mutex(); }; void Core::fatal (std::string output, bool deactivate) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d601b198a..698b4ba5a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2637,6 +2637,47 @@ static int internal_getModifiers(lua_State *L) return 1; } +static int internal_addScriptPath(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + bool search_before = (lua_gettop(L) > 1 && lua_toboolean(L, 2)); + lua_pushboolean(L, Core::getInstance().addScriptPath(path, search_before)); + return 1; +} + +static int internal_removeScriptPath(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + lua_pushboolean(L, Core::getInstance().removeScriptPath(path)); + return 1; +} + +static int internal_getScriptPaths(lua_State *L) +{ + int i = 1; + lua_newtable(L); + std::vector paths; + Core::getInstance().getScriptPaths(&paths); + for (auto it = paths.begin(); it != paths.end(); ++it) + { + lua_pushinteger(L, i++); + lua_pushstring(L, it->c_str()); + lua_settable(L, -3); + } + return 1; +} + +static int internal_findScript(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + std::string path = Core::getInstance().findScript(name); + if (path.size()) + lua_pushstring(L, path.c_str()); + else + lua_pushnil(L); + return 1; +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, @@ -2652,6 +2693,10 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getDir", filesystem_listdir }, { "runCommand", internal_runCommand }, { "getModifiers", internal_getModifiers }, + { "addScriptPath", internal_addScriptPath }, + { "removeScriptPath", internal_removeScriptPath }, + { "getScriptPaths", internal_getScriptPaths }, + { "findScript", internal_findScript }, { NULL, NULL } }; diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 6a2b52c71..306ad67f4 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -1017,6 +1017,7 @@ void PluginManager::unregisterCommands( Plugin * p ) Plugin *PluginManager::operator[] (std::string name) { + MUTEX_GUARD(plugin_mutex); if (all_plugins.find(name) == all_plugins.end()) { if (Filesystem::isfile(getPluginPath(name))) diff --git a/library/include/Core.h b/library/include/Core.h index 45d7fab35..2d74405e1 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -159,6 +159,11 @@ namespace DFHack command_result runCommand(color_ostream &out, const std::string &command); bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); + bool addScriptPath(std::string path, bool search_before = false); + bool removeScriptPath(std::string path); + std::string findScript(std::string name); + void getScriptPaths(std::vector *dest); + bool ClearKeyBindings(std::string keyspec); bool AddKeyBinding(std::string keyspec, std::string cmdline); std::vector ListKeyBindings(std::string keyspec); @@ -231,6 +236,9 @@ namespace DFHack std::vector allModules; DFHack::PluginManager * plug_mgr; + std::vector script_paths[2]; + tthread::mutex *script_path_mutex; + // hotkey-related stuff struct KeyBinding { int modifiers; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 142ae3685..fa3a3a975 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -420,24 +420,7 @@ local scripts = internal.scripts local hack_path = dfhack.getHackPath() function dfhack.findScript(name) - local file - file = dfhack.getSavePath() - if file then - file = file .. '/raw/scripts/' .. name .. '.lua' - if dfhack.filesystem.exists(file) then - return file - end - end - local path = dfhack.getDFPath() - file = path..'/raw/scripts/' .. name .. '.lua' - if dfhack.filesystem.exists(file) then - return file - end - file = path..'/hack/scripts/'..name..'.lua' - if dfhack.filesystem.exists(file) then - return file - end - return nil + return dfhack.internal.findScript(name .. '.lua') end local valid_script_flags = { @@ -477,7 +460,7 @@ function dfhack.script_environment(name, strict) if not scripts[path] or scripts[path]:needs_update() then local _, env = dfhack.run_script_with_env(nil, name, { module=true, - module_strict=strict and true or false -- ensure that this key is present if 'strict' is nil + module_strict=(strict and true or false) -- ensure that this key is present if 'strict' is nil }) return env else From 3b36a23404a587bbeeb6248f9bca136e6a3d83f6 Mon Sep 17 00:00:00 2001 From: Japa Date: Mon, 7 Sep 2015 22:41:24 +0530 Subject: [PATCH 096/109] Add soldier state to remotefortressreader.cpp --- plugins/proto/RemoteFortressReader.proto | 8 ++++---- plugins/remotefortressreader.cpp | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index d62573d90..c0d7104a7 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -179,10 +179,10 @@ message UnitDefinition optional int32 pos_z = 5; optional MatPair race = 6; optional ColorDefinition profession_color = 7; - optional uint32 flags1 = 8; - optional uint32 flags2 = 9; - optional uint32 flags3 = 10; - + optional uint32 flags1 = 8; + optional uint32 flags2 = 9; + optional uint32 flags3 = 10; + optional bool is_soldier = 11; } message UnitList diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index fc7ac9a5d..857c70b07 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -965,6 +965,7 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, send_unit->set_flags1(unit->flags1.whole); send_unit->set_flags2(unit->flags2.whole); send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); } return CR_OK; } From 804abcf9a8f9ad55c954273317bdcb9ec6c5a812 Mon Sep 17 00:00:00 2001 From: pf4public Date: Sat, 5 Sep 2015 22:57:26 +0300 Subject: [PATCH 097/109] typos --- Readme.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Readme.rst b/Readme.rst index dea56d40e..cfcf1609a 100644 --- a/Readme.rst +++ b/Readme.rst @@ -799,10 +799,10 @@ up. For more details, see the 'help' command while using this. -tiletypes-commands +tiletypes-command ------------------ Runs tiletypes commands, separated by ;. This makes it possible to change -tiletypes modes from a hotkey. +tiletypes modes from a hotkey or via dfhack-run. tiletypes-here -------------- @@ -1210,6 +1210,7 @@ Options: :-c: Clear designations instead of setting them :-x: Apply selected action to all plants except those specified (invert selection) +:-a: Select every type of plant (obeys -t/-s) Specifying both -t and -s will have no effect. If no plant IDs are specified, all valid plant IDs will be listed. From f3811c47722c92e9fa347c9ba858018523e4bb77 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 26 Jan 2015 22:35:46 +0200 Subject: [PATCH 098/109] advfort: siege weapon and trap fixes --- scripts/gui/advfort.lua | 56 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 1c8713d02..dce54d826 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,10 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.012 + version: 0.013 changelog: + *0.013 + - fixed siege weapons and traps (somewhat). Now you can load them with new menu :) *0.012 - fix for some jobs not finding correct building. *0.011 @@ -21,6 +23,10 @@ - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - gather plants still not working... Other jobs seem to work. - added new-and-improved waiting. Interestingly it could be improved to be interuptable. + todo list: + - document everything! Maybe somebody would understand what is happening then and help me :< + - when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer + --]==] --keybinding, change to your hearts content. Only the key part. @@ -720,7 +726,7 @@ function finish_item_assign(args) end end function AssignJobItems(args) - print("----") + --print("----") if settings.df_assign then --use df default logic and hope that it would work return true end @@ -773,13 +779,13 @@ function AssignJobItems(args) end end end - print("before block") + --print("before block") if settings.gui_item_select and #job.job_items>0 then local item_dialog=require('hack.scripts.gui.advfort_items') --local rr=require('gui.script').start(function() - print("before dialog") + --print("before dialog") local ret=item_dialog.showItemEditor(job,item_suitability) - print("post dialog",ret) + --print("post dialog",ret) --showItemEditor(job,item_suitability) if ret then finish_item_assign(args) @@ -1007,40 +1013,37 @@ function onWorkShopJobChosen(args,idx,choice) args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} makeJob(args) end -function siegeWeaponActionChosen(building,actionid) - local args - if actionid==1 then - building.facing=(building.facing+1)%4 - elseif actionid==2 then +function siegeWeaponActionChosen(args,actionid) + local building=args.building + if actionid==1 then --Tunr + building.facing=(args.building.facing+1)%4 + return + elseif actionid==2 then --Load local action=df.job_type.LoadBallista if building:getSubtype()==df.siegeengine_type.Catapult then action=df.job_type.LoadCatapult + args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1}}}),AssignJobItems} --TODO just boulders here + else + args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.SIEGEAMMO}}}),AssignJobItems} end - args={} args.job_type=action args.unit=df.global.world.units.active[0] local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} args.from_pos=from_pos args.pos=from_pos - args.pre_actions={dfhack.curry(setFiltersUp,{items={{}}})} - --issue a job... - elseif actionid==3 then + elseif actionid==3 then --Fire local action=df.job_type.FireBallista if building:getSubtype()==df.siegeengine_type.Catapult then action=df.job_type.FireCatapult end - args={} args.job_type=action args.unit=df.global.world.units.active[0] local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} args.from_pos=from_pos args.pos=from_pos - --another job? - end - if args~=nil then - args.post_actions={AssignBuildingRef} - makeJob(args) end + args.post_actions={AssignBuildingRef} + makeJob(args) end function putItemToBuilding(building,item) if building:getType()==df.building_type.Table then @@ -1063,8 +1066,9 @@ function usetool:openPutWindow(building) dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) end function usetool:openSiegeWindow(building) + local args={building=building,screen=self} dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, - dfhack.curry(siegeWeaponActionChosen,building)) + dfhack.curry(siegeWeaponActionChosen,args)) end function usetool:onWorkShopButtonClicked(building,index,choice) local adv=df.global.world.units.active[0] @@ -1159,17 +1163,17 @@ function usetool:armCleanTrap(building) end --building.trap_type==df.trap_type.PressurePlate then --settings/link - local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos, + local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos, building=building,job_type=df.job_type.CleanTrap} if building.trap_type==df.trap_type.CageTrap then args.job_type=df.job_type.LoadCageTrap local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} elseif building.trap_type==df.trap_type.StoneFallTrap then args.job_type=df.job_type.LoadStoneTrap local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} elseif building.trap_type==df.trap_type.WeaponTrap then qerror("TODO") else @@ -1181,10 +1185,10 @@ function usetool:armCleanTrap(building) end function usetool:hiveActions(building) local adv=df.global.world.units.active[0] - local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos, + local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos, from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} makeJob(args) --InstallColonyInHive, --CollectHiveProducts, From cd027afe9066090401870a5c5af0032db9e130da Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 31 Jan 2015 12:30:19 +0200 Subject: [PATCH 099/109] Add known bugs to advfort.lua --- scripts/gui/advfort.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index dce54d826..ae0b8cb29 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -26,6 +26,10 @@ todo list: - document everything! Maybe somebody would understand what is happening then and help me :< - when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer + bugs list: + - items blocking construction stuck the game + - burning charcoal crashed game + - gem thingies probably broken --]==] From 510fdd529c9f9dc28cde0fa5c2165785b99bb772 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 4 Feb 2015 00:04:12 +0200 Subject: [PATCH 100/109] Axle fix, lever linking, clutter display. --- scripts/gui/advfort.lua | 263 ++++++++++++++++++++++++---------- scripts/gui/advfort_items.lua | 23 ++- 2 files changed, 207 insertions(+), 79 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index ae0b8cb29..6515311a7 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,13 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.013 + version: 0.02 changelog: + *0.02 + - fixed axles not being able to be placed in other direction (thanks SyrusLD) + - added lever linking + - restructured advfort_items, don't forget to update that too! + - Added clutter view if shop is cluttered. *0.013 - fixed siege weapons and traps (somewhat). Now you can load them with new menu :) *0.012 @@ -30,6 +35,7 @@ - items blocking construction stuck the game - burning charcoal crashed game - gem thingies probably broken + - custom reactions semibroken --]==] @@ -40,13 +46,13 @@ prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, continue={key="A_WAIT",desc="Continue job if available"}, down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, +up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, workshop={key="CHANGETAB",desc="Show building menu"}, } -- building filters -build_filter={ +build_filter={ forbid_all=false, --this forbits all except the "allow" allow={"MetalSmithsForge"}, --ignored if forbit_all=false forbid={} --ignored if forbit_all==true @@ -94,7 +100,7 @@ function deon_filter(name,type_id,subtype_id,custom_id, parent) else return not hasValue(race_filter.forbid,name) end - else + else if build_filter.forbid_all then return hasValue(build_filter.allow,name) else @@ -114,7 +120,7 @@ for k,v in ipairs({...}) do --setting parsing settings.df_assign=false else mode_name=v - + end end @@ -164,7 +170,7 @@ end function inSite() local tx,ty=advGlobalPos() --print(tx,ty) - + for k,v in pairs(df.global.world.world_data.sites) do local tp={v.pos.x,v.pos.y} if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and @@ -470,24 +476,24 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog local all=makeset{"w","h","d"} local needs={[btype.FarmPlot]=area,[btype.Bridge]=all, [btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"}, - [btype.AxleHorizontal]=makeset{"w","h"},[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}} + [btype.AxleHorizontal]=all,[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}} local myneeds=needs[args.type] if myneeds==nil then return end if args.width==nil and myneeds.w then --args.width=3 - dialog.showInputPrompt("Building size:", "Input building width:", nil, "1", + dialog.showInputPrompt("Building size:", "Input building width:", nil, "1", function(txt) args.width=tonumber(txt);BuildingChosen(args) end) return true end if args.height==nil and myneeds.h then --args.height=4 - dialog.showInputPrompt("Building size:", "Input building height:", nil, "1", + dialog.showInputPrompt("Building size:", "Input building height:", nil, "1", function(txt) args.height=tonumber(txt);BuildingChosen(args) end) return true end if args.direction==nil and myneeds.d then --args.direction=0--? - dialog.showInputPrompt("Building size:", "Input building direction:", nil, "0", + dialog.showInputPrompt("Building size:", "Input building direction:", nil, "0", function(txt) args.direction=tonumber(txt);BuildingChosen(args) end) return true end @@ -497,7 +503,7 @@ end CheckAndFinishBuilding=nil function BuildingChosen(inp_args,type_id,subtype_id,custom_id) local args=inp_args or {} - + args.type=type_id or args.type args.subtype=subtype_id or args.subtype args.custom=custom_id or args.custom_id @@ -507,9 +513,9 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id) last_building.type=args.type last_building.subtype=args.subtype last_building.custom=args.custom - + if chooseBuildingWidthHeightDir(args) then - + return end --if settings.build_by_items then @@ -540,7 +546,7 @@ function isSuitableItem(job_item,item) --todo butcher test if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then - + return false, "type" elseif job_item.item_subtype~=-1 then if item:getSubtype()~=job_item.item_subtype then @@ -548,7 +554,7 @@ function isSuitableItem(job_item,item) end end end - + if job_item.mat_type~=-1 then if item:getActualMaterial()~= job_item.mat_type then --unless we would want to make hist-fig specific reactions return false, "material" @@ -568,7 +574,7 @@ function isSuitableItem(job_item,item) --print(matinfo:getCraftClass()) --print("Matching ",item," vs ",job_item) - if not matinfo:matches(job_item) then + if type(job_item) ~= "table" and not matinfo:matches(job_item) then --[[ local true_flags={} for k,v in pairs(job_item.flags1) do @@ -590,7 +596,7 @@ function isSuitableItem(job_item,item) print(v) end --]] - + return false,"matinfo" end -- some bonus checks: @@ -605,8 +611,8 @@ function isSuitableItem(job_item,item) end if job_item.min_dimension~=-1 then end - if #job_item.contains~=0 then - end + -- if #job_item.contains~=0 then + -- end if job_item.has_tool_use~=-1 then if not item:hasToolUse(job_item.has_tool_use) then return false,"tool use" @@ -729,39 +735,31 @@ function finish_item_assign(args) uncollected[1].is_fetching=1 end end -function AssignJobItems(args) - --print("----") - if settings.df_assign then --use df default logic and hope that it would work - return true - end - -- first find items that you want to use for the job - local job=args.job - local its +function EnumItems_with_settings( args ) if settings.check_inv then - its=EnumItems{pos=args.from_pos,unit=args.unit, + return EnumItems{pos=args.from_pos,unit=args.unit, inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn, [df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true} else - its=EnumItems{pos=args.from_pos} + return EnumItems{pos=args.from_pos} end - - --[[while(#job.items>0) do --clear old job items - job.items[#job.items-1]:delete() - job.items:erase(#job.items-1) - end]] +end +function find_suitable_items(job,items,job_items) + job_items=job_items or job.job_items local item_counts={} - for job_id, trg_job_item in ipairs(job.job_items) do + for job_id, trg_job_item in ipairs(job_items) do item_counts[job_id]=trg_job_item.quantity end + local item_suitability={} local used_item_id={} - for job_id, trg_job_item in ipairs(job.job_items) do + for job_id, trg_job_item in ipairs(job_items) do item_suitability[job_id]={} - - for _,cur_item in pairs(its) do + + for _,cur_item in pairs(items) do if not used_item_id[cur_item.id] then - + local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) if item_suitable or settings.build_by_items then table.insert(item_suitability[job_id],cur_item) @@ -771,7 +769,7 @@ function AssignJobItems(args) print(cur_item,msg) end --]] - if not settings.gui_item_select then + if not settings.gui_item_select then if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then --cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) @@ -783,21 +781,35 @@ function AssignJobItems(args) end end end - --print("before block") + return item_suitability,item_counts +end +function AssignJobItems(args) + --print("----") + if settings.df_assign then --use df default logic and hope that it would work + return true + end + -- first find items that you want to use for the job + local job=args.job + local its=EnumItems_with_settings(args) + + local item_suitability,item_counts=find_suitable_items(job,its) + --[[while(#job.items>0) do --clear old job items + job.items[#job.items-1]:delete() + job.items:erase(#job.items-1) + end]] + + + if settings.gui_item_select and #job.job_items>0 then local item_dialog=require('hack.scripts.gui.advfort_items') - --local rr=require('gui.script').start(function() - --print("before dialog") local ret=item_dialog.showItemEditor(job,item_suitability) - --print("post dialog",ret) - --showItemEditor(job,item_suitability) if ret then finish_item_assign(args) return true else print("Failed job, i'm confused...") end - + --end) return false,"Selecting items" else @@ -813,7 +825,7 @@ function AssignJobItems(args) return true end - + end @@ -825,7 +837,7 @@ CheckAndFinishBuilding=function (args,bld) break end end - + if args.job~=nil then args.pre_actions={AssignJobItems} else @@ -859,9 +871,9 @@ function BuildLast(args) return true end function CancelJob(unit) - local c_job=unit.job.current_job + local c_job=unit.job.current_job if c_job then - unit.job.current_job =nil --todo add real cancelation + unit.job.current_job =nil --todo add real cancelation for k,v in pairs(c_job.general_refs) do if df.general_ref_unit_workerst:is_instance(v) then v:delete() @@ -872,7 +884,7 @@ function CancelJob(unit) end end function ContinueJob(unit) - local c_job=unit.job.current_job + local c_job=unit.job.current_job --no job to continue if not c_job then return end --reset suspends... @@ -887,13 +899,112 @@ function ContinueJob(unit) --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! addJobAction(c_job,unit) end +-- function assign_link_refs(args ) +-- local job=args.job +-- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id}) +-- job.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=args.triggertarget.id}) +-- printall(job) +-- end +-- function assign_link_roles( args ) +-- if #args.job.items~=2 then +-- print("AAA FAILED!") +-- return false +-- end +-- args.job.items[0].role=df.job_item_ref.T_role.LinkToTarget +-- args.job.items[1].role=df.job_item_ref.T_role.LinkToTrigger +-- end +function fake_linking(lever,building,slots) + local item1=slots[1].items[1] + local item2=slots[2].items[1] + if not dfhack.items.moveToBuilding(item1,lever,2) then + qerror("failed to move item to building") + end + if not dfhack.items.moveToBuilding(item2,building,2) then + qerror("failed to move item2 to building") + end + item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id}) + item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id}) + + lever.linked_mechanisms:insert("#",item2) + --fixes... + if building:getType()==df.building_type.Door then + building.door_flags.operated_by_mechanisms=true + end + + dfhack.gui.showAnnouncement("Linked!",COLOR_YELLOW,true) +end +function LinkBuilding(args) + local bld=args.building or dfhack.buildings.findAtTile(args.pos) + args.building=bld + local lever_bld + if lever_id then --intentionally global! + lever_bld=df.building.find(lever_id) + if lever_bld==nil then + lever_id=nil + end + end + if lever_bld==nil then + if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then + lever_id=bld.id + dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true) + return + else + dfhack.gui.showAnnouncement("You first need a lever",COLOR_RED,true) + end + else + if lever_bld==bld then + dfhack.gui.showAnnouncement("Invalid target",COLOR_RED,true) --todo more invalid targets + return + end + -- args.job_type=df.job_type.LinkBuildingToTrigger + -- args.building=lever_bld + -- args.triggertarget=bld + -- args.pre_actions={ + -- dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.item_type.TRAPPARTS},{quantity=1,item_type=df.item_type.TRAPPARTS}}}), + -- AssignJobItems, + -- assign_link_refs,} + -- args.post_actions={AssignBuildingRef,assign_link_roles} + -- makeJob(args) + local input_filter_defaults = { --stolen from buildings lua to better customize... + item_type = df.item_type.TRAPPARTS, + item_subtype = -1, + mat_type = -1, + mat_index = -1, + flags1 = {}, + -- Instead of noting those that allow artifacts, mark those that forbid them. + -- Leaves actually enabling artifacts to the discretion of the API user, + -- which is the right thing because unlike the game UI these filters are + -- used in a way that does not give the user a chance to choose manually. + flags2 = { allow_artifact = true }, + flags3 = {}, + flags4 = 0, + flags5 = 0, + reaction_class = '', + has_material_reaction_product = '', + metal_ore = -1, + min_dimension = -1, + has_tool_use = -1, + quantity = 1 + } + local job_items={copyall(input_filter_defaults),copyall(input_filter_defaults)} + local its=EnumItems_with_settings(args) + local suitability=find_suitable_items(nil,its,job_items) + require('hack.scripts.gui.advfort_items').jobitemEditor{items=suitability,job_items=job_items,on_okay=dfhack.curry(fake_linking,lever_bld,bld)}:show() + lever_id=nil + end + --one item as LinkToTrigger role + --one item as LinkToTarget + --genref for holder(lever) + --genref for triggertarget + +end actions={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, {"CarveTrack" ,df.job_type.CarveTrack,{IsFloor,IsHardMaterial} - ,{SetCarveDir}}, + ,{SetCarveDir}}, {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, @@ -904,7 +1015,7 @@ actions={ {"Fish" ,df.job_type.Fish,{IsWater}}, --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, - {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, + {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, @@ -914,7 +1025,8 @@ actions={ {"BuildLast" ,BuildLast,{NoConstructedBuilding}}, {"Clean" ,df.job_type.Clean,{}}, {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, - + {"Link Buildings" ,LinkBuilding,{IsBuilding}}, + } for id,action in pairs(actions) do @@ -932,7 +1044,7 @@ function usetool:getModeName() else return actions[(mode or 0)+1][1] or " " end - + end function usetool:init(args) @@ -943,16 +1055,16 @@ function usetool:init(args) text={{key=keybinds.prevJob.key},{gap=1,text=self:callback("getModeName")},{gap=1,key=keybinds.nextJob.key}, } }, - + wid.Label{ view_id="shopLabel", frame = {l=35,xalign=0,yalign=0}, visible=false, text={ - {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} + {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}} }, - + wid.Label{ view_id="siteLabel", frame = {t=1,xalign=-1,yalign=0}, @@ -1058,7 +1170,7 @@ function putItemToBuilding(building,item) end end function usetool:openPutWindow(building) - + local adv=df.global.world.units.active[0] local items=EnumItems{pos=adv.pos,unit=adv, inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, @@ -1114,7 +1226,7 @@ function usetool:openShopWindowButtoned(building,no_reset) --]] end building:fillSidebarMenu() - + local list={} for id,choice in pairs(wui.choices_visible) do table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) @@ -1130,7 +1242,7 @@ function usetool:openShopWindowButtoned(building,no_reset) end function usetool:openShopWindow(building) local adv=df.global.world.units.active[0] - + local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) if filter_pile then local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building @@ -1161,7 +1273,7 @@ function usetool:armCleanTrap(building) LoadStoneTrap, LoadWeaponTrap, ]] - if building.trap_type==df.trap_type.Lever then + if building.trap_type==df.trap_type.Lever then --link return end @@ -1198,7 +1310,7 @@ function usetool:hiveActions(building) --CollectHiveProducts, end function usetool:operatePump(building) - + local adv=df.global.world.units.active[0] makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self} end @@ -1213,7 +1325,7 @@ function usetool:farmPlot(building) end end --check if there tile is without plantseeds,add job - + local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self} if do_harvest then args.job_type=df.job_type.HarvestPlants @@ -1302,10 +1414,15 @@ MODES={ input=usetool.chairActions, }, } -function usetool:shopMode(enable,mode,building) +function usetool:shopMode(enable,mode,building) self.subviews.shopLabel.visible=enable if mode then self.subviews.shopLabel:itemById("text1").text=mode.name + if building:getClutterLevel()<=1 then + self.subviews.shopLabel:itemById("clutter").text="" + else + self.subviews.shopLabel:itemById("clutter").text=" Clutter:"..tostring(building:getClutterLevel()) + end self.building=building end self.mode=mode @@ -1357,7 +1474,7 @@ function usetool:fieldInput(keys) break end end - + for _,p in pairs(cur_mode[3] or {}) do local ok,msg=p(state) if ok==false then @@ -1365,7 +1482,7 @@ function usetool:fieldInput(keys) failed=true end end - + if not failed then local ok,msg if type(cur_mode[2])=="function" then @@ -1373,9 +1490,9 @@ function usetool:fieldInput(keys) else makeJob(state) --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) - + end - + if code=="SELECT" then self:sendInputToParent("LEAVESCREEN") end @@ -1389,11 +1506,11 @@ function usetool:fieldInput(keys) end end end - + end function usetool:onInput(keys) local adv=df.global.world.units.active[0] - + if keys.LEAVESCREEN then if df.global.cursor.x~=-30000 then self:sendInputToParent("LEAVESCREEN") @@ -1420,13 +1537,13 @@ function usetool:onInput(keys) if keys[keybinds.workshop.key] then self.mode.input(self,self.building) end - self:fieldInput(keys) + self:fieldInput(keys) else self:fieldInput(keys) end end local site=inSite() - + if site then self.subviews.siteLabel.visible=true self.subviews.siteLabel:itemById("site").text=dfhack.TranslateName(site.name) diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua index 39a10367c..bbc7e6b50 100644 --- a/scripts/gui/advfort_items.lua +++ b/scripts/gui/advfort_items.lua @@ -12,6 +12,7 @@ jobitemEditor.ATTRS{ allow_remove=false, allow_any_item=false, job=DEFAULT_NIL, + job_items=DEFAULT_NIL, items=DEFAULT_NIL, on_okay=DEFAULT_NIL, } @@ -29,7 +30,7 @@ end --items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) function jobitemEditor:init(args) --self.job=args.job - if self.job==nil then qerror("This screen must have job target") end + if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end if self.items==nil then qerror("This screen must have item list") end self:addviews{ @@ -124,7 +125,15 @@ end function jobitemEditor:fill() self.slots={} for k,v in pairs(self.items) do - table.insert(self.slots,{job_item=self.job.job_items[k], id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots}) + local job_item + + if self.job then + job_item=self.job.job_items[k] + else + job_item=self.job_items[k] + end + + table.insert(self.slots,{job_item=job_item, id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots}) update_slot_text(self.slots[#self.slots]) end self.subviews.itemList:setChoices(self.slots) @@ -139,13 +148,15 @@ function jobitemEditor:jobValid() return true end function jobitemEditor:commit() - for _,slot in pairs(self.slots) do - for _1,cur_item in pairs(slot.items) do - self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id}) + if self.job then + for _,slot in pairs(self.slots) do + for _1,cur_item in pairs(slot.items) do + self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id}) + end end end self:dismiss() - if self.on_okay then self:on_okay() end + if self.on_okay then self.on_okay(self.slots) end end function showItemEditor(job,item_selections) From b52beb5aa47568352755bb82fdba8d10a3b2c4ea Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 13 Feb 2015 18:41:06 +0200 Subject: [PATCH 101/109] Add autofill. --- scripts/gui/advfort_items.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua index bbc7e6b50..b233dcf66 100644 --- a/scripts/gui/advfort_items.lua +++ b/scripts/gui/advfort_items.lua @@ -15,6 +15,7 @@ jobitemEditor.ATTRS{ job_items=DEFAULT_NIL, items=DEFAULT_NIL, on_okay=DEFAULT_NIL, + autofill=true, } function update_slot_text(slot) local items="" @@ -75,6 +76,9 @@ function jobitemEditor:init(args) } self.assigned={} self:fill() + if self.autofill then + self:fill_slots() + end end function jobitemEditor:get_slot() local idx,choice=self.subviews.itemList:getSelected() @@ -105,6 +109,23 @@ function jobitemEditor:add_item() end ) end +function jobitemEditor:fill_slots() + for i,v in ipairs(self.slots) do + while v.filled_amount Date: Sun, 22 Feb 2015 10:43:12 +0200 Subject: [PATCH 102/109] A bit more info what is new. --- scripts/gui/advfort.lua | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 6515311a7..b9c04808f 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,11 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.02 + version: 0.021 changelog: + *0.021 + - advfort_items now autofills items + - tried out few things to fix gather plants *0.02 - fixed axles not being able to be placed in other direction (thanks SyrusLD) - added lever linking @@ -301,7 +304,8 @@ function SetWebRef(args) local pos=args.pos for k,v in pairs(df.global.world.items.other.ANY_WEBS) do if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) + args.job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) + return end end end @@ -999,6 +1003,28 @@ function LinkBuilding(args) --genref for triggertarget end +--[[ Plant gathering attemped fix No. 35]] +function get_design_block_ev(blk) + for i,v in ipairs(blk.block_events) do + if v:getType()==df.block_square_event_type.designation_priority then + return v + end + end +end +function PlantGatherFix(args) + args.job.flags[17]=true --?? + + local pos=args.pos + local block=dfhack.maps.getTileBlock(pos) + local ev=get_design_block_ev(block) + if ev==nil then + block.block_events:insert("#",{new=df.block_square_event_designation_priorityst}) + ev=block.block_events[#block.block_events-1] + end + ev.priority[pos.x % 16][pos.y % 16]=bit32.bor(ev.priority[pos.x % 16][pos.y % 16],4000) + + args.job.item_category:assign{furniture=true,corpses=true,ammo=true} --this is actually required in fort mode +end actions={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, @@ -1016,7 +1042,7 @@ actions={ --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, - {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare}}, + {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, From 72b7a194255ebe31c368019114b140fd0b995e83 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 9 Sep 2015 22:14:06 +0300 Subject: [PATCH 103/109] Advfort improvements * forbid doing anything in non-sites unless you are (-c)heating * a bit more documentation and tidying * add a deadlock fix --- scripts/gui/advfort.lua | 127 ++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 38 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index b9c04808f..1f7150d91 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,12 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.021 + version: 0.03 changelog: + *0.03 + - forbid doing anything in non-sites unless you are (-c)heating + - a bit more documentation and tidying + - add a deadlock fix *0.021 - advfort_items now autofills items - tried out few things to fix gather plants @@ -39,6 +43,7 @@ - burning charcoal crashed game - gem thingies probably broken - custom reactions semibroken + - gathering plants still broken --]==] @@ -66,6 +71,7 @@ build_filter.HUMANish={ forbid={} } +--economic stone fix: just disable all of them --[[ FIXME: maybe let player select which to disable?]] for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end @@ -116,14 +122,16 @@ for k,v in ipairs({...}) do --setting parsing if v=="-c" or v=="--cheat" then settings.build_by_items=true settings.df_assign=false + settings.cheat=true elseif v=="-i" or v=="--inventory" then settings.check_inv=true settings.df_assign=false elseif v=="-a" or v=="--nodfassign" then settings.df_assign=false + elseif v=="-h" or v=="--help" then + settings.help=true else mode_name=v - end end @@ -158,6 +166,12 @@ function showHelp() Disclaimer(helptext) dialog.showMessage("Help!?!",helptext) end + +if settings.help then + showHelp() + return +end + --[[ Util functions ]]-- function advGlobalPos() local map=df.global.world.map @@ -785,10 +799,10 @@ function find_suitable_items(job,items,job_items) end end end + return item_suitability,item_counts end function AssignJobItems(args) - --print("----") if settings.df_assign then --use df default logic and hope that it would work return true end @@ -802,8 +816,6 @@ function AssignJobItems(args) job.items:erase(#job.items-1) end]] - - if settings.gui_item_select and #job.job_items>0 then local item_dialog=require('hack.scripts.gui.advfort_items') local ret=item_dialog.showItemEditor(job,item_suitability) @@ -829,8 +841,6 @@ function AssignJobItems(args) return true end - - end CheckAndFinishBuilding=function (args,bld) @@ -903,6 +913,7 @@ function ContinueJob(unit) --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! addJobAction(c_job,unit) end +--TODO: in far far future maybe add real linking? -- function assign_link_refs(args ) -- local job=args.job -- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id}) @@ -924,7 +935,7 @@ function fake_linking(lever,building,slots) qerror("failed to move item to building") end if not dfhack.items.moveToBuilding(item2,building,2) then - qerror("failed to move item2 to building") + qerror("failed to move item2 to building") end item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id}) item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id}) @@ -976,10 +987,6 @@ function LinkBuilding(args) mat_type = -1, mat_index = -1, flags1 = {}, - -- Instead of noting those that allow artifacts, mark those that forbid them. - -- Leaves actually enabling artifacts to the discretion of the API user, - -- which is the right thing because unlike the game UI these filters are - -- used in a way that does not give the user a chance to choose manually. flags2 = { allow_artifact = true }, flags3 = {}, flags4 = 0, @@ -1003,7 +1010,7 @@ function LinkBuilding(args) --genref for triggertarget end ---[[ Plant gathering attemped fix No. 35]] +--[[ Plant gathering attemped fix No. 35]] --[=[ still did not work!]=] function get_design_block_ev(blk) for i,v in ipairs(blk.block_events) do if v:getType()==df.block_square_event_type.designation_priority then @@ -1073,6 +1080,22 @@ function usetool:getModeName() end +function usetool:update_site() + local site=inSite() + self.current_site=site + local site_label=self.subviews.siteLabel + if site then + + site_label:itemById("site").text=dfhack.TranslateName(site.name) + else + if settings.cheat then + site_label:itemById("site").text="" + else + site_label:itemById("site").text="" + end + end +end + function usetool:init(args) self:addviews{ wid.Label{ @@ -1094,7 +1117,6 @@ function usetool:init(args) wid.Label{ view_id="siteLabel", frame = {t=1,xalign=-1,yalign=0}, - visible=false, text={ {id="text1", text="Site:"},{id="site", text="name"} } @@ -1104,6 +1126,7 @@ function usetool:init(args) for i,v in ipairs(labors) do labors[i]=true end + self:update_site() end MOVEMENT_KEYS = { A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, @@ -1216,9 +1239,7 @@ function usetool:onWorkShopButtonClicked(building,index,choice) local adv=df.global.world.units.active[0] local args={unit=adv,building=building} if df.interface_button_building_new_jobst:is_instance(choice.button) then - print("pre-click") choice.button:click() - print("post-click",#building.jobs) if #building.jobs>0 then local job=building.jobs[#building.jobs-1] args.job=job @@ -1484,6 +1505,13 @@ function usetool:setupFields() ui.site_id=site.id end end +function usetool:siteCheck() + if self.site ~= nil or settings.cheat then --TODO: add check if it's correct site (the persistant ones) + return true + end + return false, "You are not on site" +end +--movement and co... Also passes on allowed keys function usetool:fieldInput(keys) local adv=df.global.world.units.active[0] local cur_mode=actions[(mode or 0)+1] @@ -1491,9 +1519,18 @@ function usetool:fieldInput(keys) for code,_ in pairs(keys) do --print(code) if MOVEMENT_KEYS[code] then - local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], - from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_actions=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} - if code=="SELECT" then + + local state={ + unit=adv, + pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]), + dir=MOVEMENT_KEYS[code], + from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}, + post_actions=cur_mode[4], + pre_actions=cur_mode[5], + job_type=cur_mode[2], + screen=self} + + if code=="SELECT" then --do job in the distance, TODO: check if you can still cheat-mine (and co.) remotely if df.global.cursor.x~=-30000 then state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} else @@ -1501,11 +1538,18 @@ function usetool:fieldInput(keys) end end - for _,p in pairs(cur_mode[3] or {}) do - local ok,msg=p(state) - if ok==false then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true + --First check site + local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site? + if not ok then + dfhack.gui.showAnnouncement(msg,5,1) + failed=true + else + for _,p in pairs(cur_mode[3] or {}) do --then check predicates + local ok,msg=p(state) + if not ok then + dfhack.gui.showAnnouncement(msg,5,1) + failed=true + end end end @@ -1534,23 +1578,25 @@ function usetool:fieldInput(keys) end end + function usetool:onInput(keys) + + self:update_site() + local adv=df.global.world.units.active[0] if keys.LEAVESCREEN then - if df.global.cursor.x~=-30000 then - self:sendInputToParent("LEAVESCREEN") + if df.global.cursor.x~=-30000 then --if not poiting at anything + self:sendInputToParent("LEAVESCREEN") --leave poiting else - self:dismiss() + self:dismiss() --leave the adv-tools all together CancelJob(adv) end - elseif keys[keybinds.nextJob.key] then + elseif keys[keybinds.nextJob.key] then --next job with looping mode=(mode+1)%#actions - elseif keys[keybinds.prevJob.key] then + elseif keys[keybinds.prevJob.key] then --prev job with looping mode=mode-1 if mode<0 then mode=#actions-1 end - --elseif keys.A_LOOK then - -- self:sendInputToParent("A_LOOK") elseif keys["A_SHORT_WAIT"] then --ContinueJob(adv) self:sendInputToParent("A_SHORT_WAIT") @@ -1568,14 +1614,7 @@ function usetool:onInput(keys) self:fieldInput(keys) end end - local site=inSite() - if site then - self.subviews.siteLabel.visible=true - self.subviews.siteLabel:itemById("site").text=dfhack.TranslateName(site.name) - else - self.subviews.siteLabel.visible=false - end end function usetool:onIdle() @@ -1583,7 +1622,19 @@ function usetool:onIdle() local job_ptr=adv.job.current_job local job_action=findAction(adv,df.unit_action_type.Job) + if self.long_wait and self.long_wait_timer==nil then + self.long_wait_timer=1000 --TODO tweak this + end + if job_ptr and self.long_wait and not job_action then + + if self.long_wait_timer<=0 then --fix deadlocks with force-canceling of waiting + self.long_wait_timer=nil + self.long_wait=false + else + self.long_wait_timer=self.long_wait_timer-1 + end + if adv.job.current_job.completion_timer==-1 then self.long_wait=false end From 96e6582417d395247429b5ccd15ffe62e7e1724f Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 9 Sep 2015 22:30:00 +0300 Subject: [PATCH 104/109] Mostly whitespace changes. --- scripts/gui/advfort.lua | 48 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 1f7150d91..daa66c6b1 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -54,13 +54,13 @@ prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, continue={key="A_WAIT",desc="Continue job if available"}, down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, +up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, workshop={key="CHANGETAB",desc="Show building menu"}, } -- building filters -build_filter={ +build_filter={ forbid_all=false, --this forbits all except the "allow" allow={"MetalSmithsForge"}, --ignored if forbit_all=false forbid={} --ignored if forbit_all==true @@ -109,7 +109,7 @@ function deon_filter(name,type_id,subtype_id,custom_id, parent) else return not hasValue(race_filter.forbid,name) end - else + else if build_filter.forbid_all then return hasValue(build_filter.allow,name) else @@ -185,14 +185,13 @@ function advGlobalPos() return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48) end function inSite() + local tx,ty=advGlobalPos() - --print(tx,ty) - + for k,v in pairs(df.global.world.world_data.sites) do local tp={v.pos.x,v.pos.y} if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then - --print(k) return v end end @@ -499,19 +498,19 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog if myneeds==nil then return end if args.width==nil and myneeds.w then --args.width=3 - dialog.showInputPrompt("Building size:", "Input building width:", nil, "1", + dialog.showInputPrompt("Building size:", "Input building width:", nil, "1", function(txt) args.width=tonumber(txt);BuildingChosen(args) end) return true end if args.height==nil and myneeds.h then --args.height=4 - dialog.showInputPrompt("Building size:", "Input building height:", nil, "1", + dialog.showInputPrompt("Building size:", "Input building height:", nil, "1", function(txt) args.height=tonumber(txt);BuildingChosen(args) end) return true end if args.direction==nil and myneeds.d then --args.direction=0--? - dialog.showInputPrompt("Building size:", "Input building direction:", nil, "0", + dialog.showInputPrompt("Building size:", "Input building direction:", nil, "0", function(txt) args.direction=tonumber(txt);BuildingChosen(args) end) return true end @@ -521,7 +520,7 @@ end CheckAndFinishBuilding=nil function BuildingChosen(inp_args,type_id,subtype_id,custom_id) local args=inp_args or {} - + args.type=type_id or args.type args.subtype=subtype_id or args.subtype args.custom=custom_id or args.custom_id @@ -531,9 +530,8 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id) last_building.type=args.type last_building.subtype=args.subtype last_building.custom=args.custom - + if chooseBuildingWidthHeightDir(args) then - return end --if settings.build_by_items then @@ -564,7 +562,6 @@ function isSuitableItem(job_item,item) --todo butcher test if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then - return false, "type" elseif job_item.item_subtype~=-1 then if item:getSubtype()~=job_item.item_subtype then @@ -572,7 +569,7 @@ function isSuitableItem(job_item,item) end end end - + if job_item.mat_type~=-1 then if item:getActualMaterial()~= job_item.mat_type then --unless we would want to make hist-fig specific reactions return false, "material" @@ -774,10 +771,9 @@ function find_suitable_items(job,items,job_items) local used_item_id={} for job_id, trg_job_item in ipairs(job_items) do item_suitability[job_id]={} - + for _,cur_item in pairs(items) do if not used_item_id[cur_item.id] then - local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) if item_suitable or settings.build_by_items then table.insert(item_suitability[job_id],cur_item) @@ -825,7 +821,6 @@ function AssignJobItems(args) else print("Failed job, i'm confused...") end - --end) return false,"Selecting items" else @@ -851,7 +846,7 @@ CheckAndFinishBuilding=function (args,bld) break end end - + if args.job~=nil then args.pre_actions={AssignJobItems} else @@ -887,7 +882,7 @@ end function CancelJob(unit) local c_job=unit.job.current_job if c_job then - unit.job.current_job =nil --todo add real cancelation + unit.job.current_job =nil --todo add real cancelation for k,v in pairs(c_job.general_refs) do if df.general_ref_unit_workerst:is_instance(v) then v:delete() @@ -898,7 +893,7 @@ function CancelJob(unit) end end function ContinueJob(unit) - local c_job=unit.job.current_job + local c_job=unit.job.current_job --no job to continue if not c_job then return end --reset suspends... @@ -1037,7 +1032,7 @@ actions={ {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, {"CarveTrack" ,df.job_type.CarveTrack,{IsFloor,IsHardMaterial} - ,{SetCarveDir}}, + ,{SetCarveDir}}, {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, @@ -1059,7 +1054,6 @@ actions={ {"Clean" ,df.job_type.Clean,{}}, {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, {"Link Buildings" ,LinkBuilding,{IsBuilding}}, - } for id,action in pairs(actions) do @@ -1104,7 +1098,6 @@ function usetool:init(args) text={{key=keybinds.prevJob.key},{gap=1,text=self:callback("getModeName")},{gap=1,key=keybinds.nextJob.key}, } }, - wid.Label{ view_id="shopLabel", @@ -1113,7 +1106,7 @@ function usetool:init(args) text={ {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}} }, - + wid.Label{ view_id="siteLabel", frame = {t=1,xalign=-1,yalign=0}, @@ -1180,7 +1173,7 @@ function onWorkShopJobChosen(args,idx,choice) end function siegeWeaponActionChosen(args,actionid) local building=args.building - if actionid==1 then --Tunr + if actionid==1 then --Turn building.facing=(args.building.facing+1)%4 return elseif actionid==2 then --Load @@ -1219,7 +1212,6 @@ function putItemToBuilding(building,item) end end function usetool:openPutWindow(building) - local adv=df.global.world.units.active[0] local items=EnumItems{pos=adv.pos,unit=adv, inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, @@ -1320,7 +1312,7 @@ function usetool:armCleanTrap(building) LoadStoneTrap, LoadWeaponTrap, ]] - if building.trap_type==df.trap_type.Lever then + if building.trap_type==df.trap_type.Lever then --link return end @@ -1609,7 +1601,7 @@ function usetool:onInput(keys) if keys[keybinds.workshop.key] then self.mode.input(self,self.building) end - self:fieldInput(keys) + self:fieldInput(keys) else self:fieldInput(keys) end From a80f14de36f212bcb0b21e320d82f916a8687b77 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 9 Sep 2015 22:33:46 +0300 Subject: [PATCH 105/109] MOAR whitespace reductionisms included. --- scripts/gui/advfort.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index daa66c6b1..9809f17a7 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -772,7 +772,7 @@ function find_suitable_items(job,items,job_items) for job_id, trg_job_item in ipairs(job_items) do item_suitability[job_id]={} - for _,cur_item in pairs(items) do + for _,cur_item in pairs(items) do if not used_item_id[cur_item.id] then local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) if item_suitable or settings.build_by_items then @@ -783,7 +783,7 @@ function find_suitable_items(job,items,job_items) print(cur_item,msg) end --]] - if not settings.gui_item_select then + if not settings.gui_item_select then if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then --cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) @@ -880,7 +880,7 @@ function BuildLast(args) return true end function CancelJob(unit) - local c_job=unit.job.current_job + local c_job=unit.job.current_job if c_job then unit.job.current_job =nil --todo add real cancelation for k,v in pairs(c_job.general_refs) do @@ -930,7 +930,7 @@ function fake_linking(lever,building,slots) qerror("failed to move item to building") end if not dfhack.items.moveToBuilding(item2,building,2) then - qerror("failed to move item2 to building") + qerror("failed to move item2 to building") end item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id}) item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id}) @@ -954,7 +954,7 @@ function LinkBuilding(args) lever_id=nil end end - if lever_bld==nil then + if lever_bld==nil then if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then lever_id=bld.id dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true) @@ -1043,7 +1043,7 @@ actions={ {"Fish" ,df.job_type.Fish,{IsWater}}, --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, - {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, + {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, @@ -1453,7 +1453,7 @@ MODES={ input=usetool.chairActions, }, } -function usetool:shopMode(enable,mode,building) +function usetool:shopMode(enable,mode,building) self.subviews.shopLabel.visible=enable if mode then self.subviews.shopLabel:itemById("text1").text=mode.name From 5a39a1d86d4936e5d0291ef39dd5ddf2d13095e5 Mon Sep 17 00:00:00 2001 From: warmist Date: Thu, 10 Sep 2015 14:38:44 +0300 Subject: [PATCH 106/109] Make forbidding jobs in sites optional Activated with "-s" or "--safe" mode. --- scripts/gui/advfort.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 9809f17a7..08fe29e0a 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -3,6 +3,8 @@ --[==[ version: 0.03 changelog: + *0.031 + - make forbiding optional (-s)afe mode *0.03 - forbid doing anything in non-sites unless you are (-c)heating - a bit more documentation and tidying @@ -122,7 +124,8 @@ for k,v in ipairs({...}) do --setting parsing if v=="-c" or v=="--cheat" then settings.build_by_items=true settings.df_assign=false - settings.cheat=true + elseif v=="-s" or v=="--safe" then + settings.safe=true elseif v=="-i" or v=="--inventory" then settings.check_inv=true settings.df_assign=false @@ -1082,10 +1085,10 @@ function usetool:update_site() site_label:itemById("site").text=dfhack.TranslateName(site.name) else - if settings.cheat then - site_label:itemById("site").text="" - else + if settings.safe then site_label:itemById("site").text="" + else + site_label:itemById("site").text="" end end end @@ -1498,7 +1501,7 @@ function usetool:setupFields() end end function usetool:siteCheck() - if self.site ~= nil or settings.cheat then --TODO: add check if it's correct site (the persistant ones) + if self.site ~= nil or not settings.safe then --TODO: add check if it's correct site (the persistant ones) return true end return false, "You are not on site" From 9123f3cd3e5c83ee2ddcb05d572bd5ca1ef5ae0f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Sep 2015 19:55:38 -0400 Subject: [PATCH 107/109] gm-editor: Select enum items from a list by default SEC_SELECT can be used for the old (numerical editing) behavior --- scripts/gui/gm-editor.lua | 41 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 5a4bc5dab..2afca8f23 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -15,7 +15,7 @@ local keybindings={ start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"}, help={key="HELP",desc="Show this help"}, displace={key="STRING_A093",desc="Open reference offseted by index"}, - NOT_USED={key="SEC_SELECT",desc="Choose an enum value from a list"}, --not a binding... + NOT_USED={key="SEC_SELECT",desc="Edit selected entry as a number (for enums)"}, --not a binding... } function getTargetFromScreens() local my_trg @@ -72,7 +72,7 @@ function GmEditorUi:init(args) self.keys={} local helptext={{text="Help"},NEWLINE,NEWLINE} for k,v in pairs(keybindings) do - table.insert(helptext,{text=v.desc,key=v.key,key_sep=':'}) + table.insert(helptext,{text=v.desc,key=v.key,key_sep=': '}) table.insert(helptext,NEWLINE) end table.insert(helptext,NEWLINE) @@ -81,13 +81,13 @@ function GmEditorUi:init(args) local helpPage=widgets.Panel{ subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), - on_submit2=self:callback("editSelectedEnum"), + on_submit2=self:callback("editSelectedRaw"), text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} local mainPage=widgets.Panel{ subviews={ mainList, widgets.Label{text={{text="",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, - widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=":"}},frame={l=1,t=2}}, + widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}}, widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, --widgets.Label{text="BLAH2"} } @@ -170,19 +170,28 @@ end function GmEditorUi:currentTarget() return self.stack[#self.stack] end -function GmEditorUi:editSelectedEnum(index,choice) +function GmEditorUi:getSelectedEnumType() local trg=self:currentTarget() - local trg_key=trg.keys[index] - if trg.target._field==nil then qerror("not an enum") end + local trg_key=trg.keys[self.subviews.list_main:getSelected()] + if trg.target._field==nil then return nil end local enum=trg.target:_field(trg_key)._type - if enum._kind=="enum-type" then + return enum + else + return nil + end +end +function GmEditorUi:editSelectedEnum(index,choice) + local enum=self:getSelectedEnumType() + if enum then + local trg=self:currentTarget() + local trg_key=self:getSelectedKey() local list={} for i=enum._first_item, enum._last_item do - table.insert(list,{text=tostring(enum[i]),value=i}) + table.insert(list,{text=('%s (%i)'):format(tostring(enum[i]), i),value=i}) end guiScript.start(function() - local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list) + local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list,nil,true) if ret then trg.target[trg_key]=choice.value self:updateTarget(true) @@ -210,7 +219,11 @@ function GmEditorUi:openOffseted(index,choice) self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice))) end) end -function GmEditorUi:editSelected(index,choice) +function GmEditorUi:editSelectedRaw(index,choice) + self:editSelected(index, choice, {raw=true}) +end +function GmEditorUi:editSelected(index,choice,opts) + opts = opts or {} local trg=self:currentTarget() local trg_key=trg.keys[index] if trg.target and trg.target._kind and trg.target._kind=="bitfield" then @@ -219,7 +232,9 @@ function GmEditorUi:editSelected(index,choice) else --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") local trg_type=type(trg.target[trg_key]) - if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected + if self:getSelectedEnumType() and not opts.raw then + self:editSelectedEnum() + elseif trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE, tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) @@ -312,7 +327,7 @@ function getStringValue(trg,field) if obj._field ~= nil then local enum=obj:_field(field)._type if enum._kind=="enum-type" then - text=text.."("..tostring(enum[obj[field]])..")" + text=text.." ("..tostring(enum[obj[field]])..")" end end end) From b58ccc9e5509b4708c830ae79fac2c90d5db05df Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Sep 2015 20:28:42 -0400 Subject: [PATCH 108/109] Fix embark-tools title alignment --- plugins/embark-tools.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 3ff38def5..7fa9cafca 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -577,7 +577,7 @@ public: Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1); std::string title = " Embark tools (DFHack) "; - Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (max_x - min_x + title.size()) / 2, min_y, title); + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), min_x + ((max_x - min_x - title.size()) / 2), min_y, title); x = min_x + 2; y = max_y - 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); @@ -788,7 +788,7 @@ DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_cha if (Gui::getCurFocus() == "dfhack/embark-tools/options") { out.printerr("Settings screen active.\n"); - return CR_NOT_FOUND; + return CR_FAILURE; } } return CR_OK; From 2fcf751a44ec65c99a6efb67494a2a3a6c1695ae Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 14 Sep 2015 16:56:09 -0400 Subject: [PATCH 109/109] Use DFHack fork of lethosor-scripts --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 275772e0e..961850a67 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = git://github.com/DFHack/clsocket.git [submodule "scripts/3rdparty/lethosor"] path = scripts/3rdparty/lethosor - url = https://github.com/lethosor/dfhack-scripts + url = https://github.com/DFHack/lethosor-scripts