diff --git a/.gitmodules b/.gitmodules index 212095b0c..961850a67 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,6 @@ [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 +[submodule "scripts/3rdparty/lethosor"] + path = scripts/3rdparty/lethosor + url = https://github.com/DFHack/lethosor-scripts diff --git a/CMakeLists.txt b/CMakeLists.txt index 64cf0717e..50fca68f7 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}) @@ -185,6 +184,8 @@ IF(BUILD_PLUGINS) add_subdirectory (plugins) endif() +add_subdirectory(scripts) + # Packaging with CPack! IF(UNIX) if(APPLE) diff --git a/Compile.rst b/Compile.rst index ce4449524..36563e881 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 git://github.com/DFHack/dfhack.git + git clone --recursive https://github.com/DFHack/dfhack 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). +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 --recursive git://github.com/DFHack/dfhack.git + cd dfhack + +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 ============ @@ -44,9 +59,11 @@ 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 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 +89,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 +101,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 +135,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 +147,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 +194,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 +265,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 +281,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. 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**. 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/Lua API.rst b/Lua API.rst index 86233470a..345901ca6 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. @@ -1950,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 ======================== @@ -3211,7 +3252,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. @@ -3406,6 +3447,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 ======= diff --git a/Readme.rst b/Readme.rst index ca886907b..cfcf1609a 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: @@ -212,9 +212,9 @@ 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. +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 @@ -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 ================ @@ -290,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 @@ -311,6 +317,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 @@ -628,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. @@ -749,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 -------------- @@ -1160,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. @@ -1252,6 +1303,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. @@ -1302,6 +1369,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 @@ -2087,6 +2155,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:* @@ -2144,7 +2213,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 @@ -2219,6 +2287,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. @@ -2233,19 +2306,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. @@ -2260,6 +2331,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. @@ -2273,6 +2348,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/* ===== @@ -2283,6 +2363,11 @@ directory. A graphical interface for creating items. +* 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 Load and save stockpile settings from the 'q' menu. @@ -2296,6 +2381,40 @@ 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 construction jobs, on a recurring basis. +See ``unsuspend`` for one-off use, or ``resume all``. + +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:: @@ -2338,6 +2457,12 @@ 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. + digfort ======= A script to designate an area for digging according to a plan in csv format. @@ -2368,16 +2493,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. @@ -2417,6 +2532,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 @@ -2429,38 +2548,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 ========= @@ -2544,6 +2645,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 of 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. @@ -2555,13 +2665,19 @@ 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:: 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 @@ -2577,15 +2693,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, @@ -2631,17 +2784,12 @@ Ex:: source add magma 7 - magma source source add water 0 - water drain -superdwarf +startdwarf ========== -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. +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. +The number must be 7 or greater. stripcaged ========== @@ -2663,6 +2811,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. @@ -2677,6 +2837,11 @@ undump-buildings ================ Undesignates building base materials for dumping. +unsuspend +========= +Unsuspend construction jobs, on a one-off basis. See ``autounsuspend`` for regular use. +Equivalent to ``resume all``. + view-item-info ============== A script to extend the item or unit viewscreen with additional information 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-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); + } + +} 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/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/dfhack.init-example b/dfhack.init-example index daed81041..cc4d67775 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 ############################## @@ -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 @@ -201,8 +204,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 \ @@ -222,6 +225,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/library/CMakeLists.txt b/library/CMakeLists.txt index 845f8dd07..e653555f9 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) @@ -298,6 +297,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}) @@ -307,7 +309,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) @@ -374,6 +376,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/library/Core.cpp b/library/Core.cpp index 11af466ce..c5834875e 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" @@ -385,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) @@ -426,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 ""; } @@ -484,6 +526,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_; @@ -498,8 +577,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()) { @@ -515,21 +596,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++) @@ -547,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()); @@ -563,97 +648,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" ) - { - 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" ) + else if (builtin == "load" || builtin == "unload" || builtin == "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()) { @@ -672,11 +718,11 @@ 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) { - std::string lua = findScript(this->p->getPath(), part + ".lua"); + std::string lua = findScript(part + ".lua"); if (lua.size()) { res = enableLuaScript(con, part, enable); @@ -690,14 +736,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()); } } @@ -705,9 +751,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( @@ -719,7 +765,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") @@ -730,10 +776,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++) { @@ -748,27 +802,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++) @@ -794,17 +849,85 @@ 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"); + + plug_mgr->refresh(); + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - const Plugin * plug = (plug_mgr->operator[](i)); - if(!plug->size()) + const Plugin * plug = it->second; + if (!plug) + continue; + if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) 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, + plug->getName().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(parts[0] + ".lua").size()) + { + con << " is a Lua script." << std::endl; + } + else if (findScript(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")) { @@ -852,12 +975,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(); @@ -867,11 +990,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) @@ -882,7 +1005,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) { @@ -895,7 +1018,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()) { @@ -904,7 +1027,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()) { @@ -913,7 +1036,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) { @@ -1017,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); @@ -1190,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) @@ -1314,6 +1438,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); @@ -1337,7 +1463,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; @@ -1755,6 +1881,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); @@ -1854,7 +2042,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'; } @@ -1900,7 +2088,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); @@ -2012,9 +2200,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; @@ -2042,6 +2236,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)) @@ -2093,11 +2304,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; 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..698b4ba5a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -38,6 +38,8 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "DataFuncs.h" +#include "DFHackVersion.h" +#include "PluginManager.h" #include "modules/World.h" #include "modules/Gui.h" @@ -1396,6 +1398,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 +1412,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 } }; @@ -2201,7 +2210,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 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 }, @@ -2632,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/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..fa9445a81 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 @@ -48,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 * **************************************/ 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 diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0d6442699..306ad67f4 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,24 @@ 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()); + DFLibrary * plug = OpenPlugin(path.c_str()); if(!plug) { - con.printerr("Can't load plugin %s\n", filename.c_str()); RefAutolock lock(access); - state = PS_BROKEN; - 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_BROKEN + #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED #define plugin_check_symbol(sym) \ if (!LookupPlugin(plug, sym)) \ { \ @@ -226,14 +273,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 +305,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 +342,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 +352,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 @@ -326,7 +376,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(); @@ -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,155 @@ 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)) + { + 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(filez[i],searchstr)) + 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; +} + +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); + if (!(*this)[name] && !addPlugin(name)) + return false; + Plugin *p = (*this)[name]; + if (!p) + { + Core::printerr("Plugin failed to register: %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,50 +972,71 @@ 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! 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()); + 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; } - belongs[name] = p; + command_map[name] = p; } if (p->plugin_eval_ruby) ruby = p; cmdlist_mutex->unlock(); } -// FIXME: doesn't check name collisions! void PluginManager::unregisterCommands( Plugin * p ) { cmdlist_mutex->lock(); 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) +{ + MUTEX_GUARD(plugin_mutex); + 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/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() 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); diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 5cc2bbb67..9c5e7a874 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,9 +1,15 @@ -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) +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/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/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 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); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 680262bad..aba6aaa0f 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,44 @@ 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); + // 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(); + 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 +304,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 +315,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 +347,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/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 diff --git a/library/xml b/library/xml index 5c4d627d7..2a49a0761 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5c4d627d7306b8425493f8ebbfe66c0f347f6871 +Subproject commit 2a49a0761e4413603d9a72542077755cea2ad56d 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) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f5a82e916..4b37095f1 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) @@ -112,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) @@ -120,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) @@ -133,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) @@ -143,7 +146,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) @@ -159,6 +162,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/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) 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.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 +} 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/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/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 +#include +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "uicommon.h" +#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" + +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, vector & parameters); + +struct conf_wrapper; +static std::map confirmations; + +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) \ + { \ + 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; \ + } \ + } \ + return true; \ +} +goods_all_selected_func(trader); +goods_all_selected_func(broker); +#undef goods_all_selected_func + +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 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); + 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; + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y2, ": Cancel"); + 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); + 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 string get_title() { return "Confirm"; } + virtual string get_message() = 0; + virtual UIColor get_color() { return COLOR_YELLOW; } +protected: + cstate state; + 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 string get_id() { return "trade"; } + virtual string get_title() { return "Confirm trade"; } + 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); + +class trade_cancel_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) + { + 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"; } + 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 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?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); + +class trade_offer_confirmation : public confirmation { +public: + 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) + { + 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); + +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, 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) \ + CHOOK(note_delete_confirmation) \ + CHOOK(route_delete_confirmation) + +DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) +{ +#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 + commands.push_back(PluginCommand( + "confirm", + "Confirmation dialogs", + df_confirm, + false, //allow non-interactive use + + " confirmation enable|disable option|all ...\n" + " confirmation help|status\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + if (is_enabled != enable) + { + 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) +{ + 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 = confirmations.begin(); it != confirmations.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, 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") + state = true; + else if (*it == "disable") + state = false; + else if (*it == "all") + { + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + { + it->second->apply(state); + } + } + else + enable_conf(out, *it, state); + } + return CR_OK; +} 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/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 -#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/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 798d4e36d..216664a93 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include "DataDefs.h" @@ -43,8 +44,6 @@ #include "df/descriptor_shape.h" #include "df/descriptor_color.h" -#include "jsonxx.h" - using std::deque; DFHACK_PLUGIN("dwarfmonitor"); @@ -156,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() @@ -165,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 @@ -1976,6 +1976,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector +#include #include #include @@ -61,37 +62,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; @@ -116,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; /* @@ -160,47 +130,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: @@ -647,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), 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)); @@ -656,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() + ": "); @@ -677,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(); @@ -687,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; } @@ -714,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); @@ -738,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()) { @@ -770,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); } @@ -781,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); } @@ -790,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); } @@ -798,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); } @@ -814,17 +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 NanoEmbark); - 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", @@ -855,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_FAILURE; + } + } + return CR_OK; +} + DFhackCExport command_result plugin_onupdate (color_ostream &out) { static int8_t mask = 0; @@ -874,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; @@ -915,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; } } diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp new file mode 100644 index 000000000..4675b5cfb --- /dev/null +++ b/plugins/fix-unit-occupancy.cpp @@ -0,0 +1,238 @@ +#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; + + unsigned count = fix_unit_occupancy(out, opts); + if (!count) + out << "No occupancy issues found." << endl; + + return CR_OK; +} 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..0576ca4e4 --- /dev/null +++ b/plugins/listcolumn.h @@ -0,0 +1,481 @@ +#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, "_"); + } + } + + 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; + display_list.clear(); + + search_string = toLower(search_string); + vector search_tokens; + tokenizeSearch(&search_tokens, search_string); + + for (size_t i = 0; i < list.size(); i++) + { + ListEntry *entry = &list[i]; + + if (showEntry(entry, search_tokens)) + { + 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(); + } + + 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::STANDARDSCROLL_UP)) + { + changeHighlight(-1); + } + else if (input->count(interface_key::STANDARDSCROLL_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 >= 0 && validSearchInput((unsigned char)charcode)) + { + // 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/lua/luasocket.lua b/plugins/lua/luasocket.lua new file mode 100644 index 000000000..da0f0fe38 --- /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 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/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 diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index f675793df..d6d2cb222 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; @@ -997,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) @@ -1012,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!"); 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/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 5597e7fdb..c0d7104a7 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; @@ -169,6 +177,12 @@ message UnitDefinition optional int32 pos_x = 3; 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; + optional bool is_soldier = 11; } message UnitList @@ -231,3 +245,94 @@ 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; + optional int32 map_x = 15; + optional int32 map_y = 16; +} + +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 6629e7893..857c70b07 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,7 +1,4 @@ - -//define which version of DF this is being built for. -#define DF_VER_040 -//#define DF_VER_034 +#define DF_VERSION 40024 // some headers required for a plugin. Nothing special, just the basics. #include "Core.h" @@ -27,8 +24,10 @@ #include "df/builtin_mats.h" #include "df/map_block_column.h" #include "df/plant.h" +#if DF_VERSION > 40001 #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" @@ -39,9 +38,16 @@ #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/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" @@ -51,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" @@ -67,7 +74,11 @@ using namespace RemoteFortressReader; using namespace std; DFHACK_PLUGIN("RemoteFortressReader"); +#if DF_VERSION < 40024 +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 @@ -84,6 +95,9 @@ 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); +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); @@ -132,6 +146,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("ResetMapHashes", ResetMapHashes); svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetBuildingDefList", GetBuildingDefList); + svc->addFunction("GetWorldMap", GetWorldMap); + svc->addFunction("GetRegionMaps", GetRegionMaps); + svc->addFunction("GetCreatureRaws", GetCreatureRaws); return svc; } @@ -163,6 +180,25 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes) return sum2 << 8 | sum1; } +void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) +{ + if (!df::global::enabler) + return; + + auto enabler = df::global::enabler; + + 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) @@ -236,6 +272,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m case df::enums::tiletype_material::RIVER: return RemoteFortressReader::RIVER; break; +#if DF_VERSION > 40001 case df::enums::tiletype_material::ROOT: return RemoteFortressReader::ROOT; break; @@ -248,6 +285,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 +333,11 @@ RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special spec case df::enums::tiletype_special::TRACK: return RemoteFortressReader::TRACK; break; +#if DF_VERSION > 40001 case df::enums::tiletype_special::SMOOTH_DEAD: return RemoteFortressReader::SMOOTH_DEAD; break; +#endif default: return RemoteFortressReader::NO_SPECIAL; break; @@ -351,20 +391,25 @@ RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) case df::enums::tiletype_shape::BROOK_TOP: return RemoteFortressReader::BROOK_TOP; break; +#if DF_VERSION > 40001 case df::enums::tiletype_shape::BRANCH: return RemoteFortressReader::BRANCH; break; -#ifdef DF_VER_034 +#endif +#if DF_VERSION < 40001 case df::enums::tiletype_shape::TREE: - return RemoteFortressReader::TREE; + return RemoteFortressReader::TREE_SHAPE; break; #endif +#if DF_VERSION > 40001 + 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 +667,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); +#if DF_VERSION > 40001 for (int g = 0; g < pp->growths.size(); g++) { df::plant_growth* growth = pp->growths[g]; @@ -636,6 +682,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i out_growth->mutable_mat_pair()->set_mat_index(i); } } +#endif } return CR_OK; } @@ -741,7 +788,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in int max_x = in->max_x(); int max_y = in->max_y(); //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = in->max_z()-1; zz >= in->min_z(); zz--) + for (int zz = in->max_z() - 1; zz >= in->min_z(); zz--) { // (di, dj) is a vector - direction in which we move right now int di = 1; @@ -860,6 +907,9 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in int max_y = in->max_y() / 3; int max_z = in->max_z(); +#if DF_VERSION < 40001 + //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 +944,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in out_plant->set_pos_z(plant->pos.z); } } +#endif return CR_OK; } @@ -908,6 +959,13 @@ 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); + 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); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); } return CR_OK; } @@ -931,6 +989,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); @@ -1158,5 +1218,225 @@ static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessa break; } } + 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; +} + +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::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], + &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++) + { + //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: + 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; +} + +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 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/stockflow.cpp b/plugins/stockflow.cpp index 34dfc1bd7..15a013dea 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); @@ -346,7 +343,7 @@ static command_result stockflow_cmd(color_ostream &out, 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) { @@ -420,7 +417,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::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; @@ -509,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 ); } @@ -521,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 ); } diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index b6bf68d05..85fedb9f2 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1,4 +1,5 @@ #include "uicommon.h" +#include "listcolumn.h" #include @@ -598,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 { @@ -661,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) @@ -698,7 +810,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(); @@ -801,6 +913,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); @@ -897,49 +1015,49 @@ 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); + OutputHotkeyString(x, y, "Toggle Grouping", interface_key::CHANGETAB, 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; 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); - 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, "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); - 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; - 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"; } @@ -1307,7 +1425,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 +1437,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 +1557,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; } 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; +} 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); diff --git a/plugins/tweak/tweaks/farm-plot-select.h b/plugins/tweak/tweaks/farm-plot-select.h index 063180c75..696c7ca2e 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; @@ -53,9 +49,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 +76,10 @@ struct farm_select_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); - if (!getFarmPlot()) + 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, diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 56519c260..7182ee2f2 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 @@ -116,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) { @@ -146,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(); @@ -206,6 +214,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 +392,7 @@ protected: y2 = sp->room.y + sp->room.height; } -private: +protected: int x1, x2, y1, y2, z; }; @@ -414,475 +436,7 @@ public: DFHack::World::DeletePersistentData(config); } -private: +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 = *input->rbegin(); - 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(); - } - -private: - 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/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) { diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor new file mode 160000 index 000000000..ceed207e3 --- /dev/null +++ b/scripts/3rdparty/lethosor @@ -0,0 +1 @@ +Subproject commit ceed207e38220e21067a91b8d6f7b9680a476f69 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() diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb index 2c474e993..9613288ba 100644 --- a/scripts/autounsuspend.rb +++ b/scripts/autounsuspend.rb @@ -1,3 +1,5 @@ +# un-suspend construction 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/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() 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/gui/advfort.lua b/scripts/gui/advfort.lua index 1c8713d02..08fe29e0a 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,24 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.012 + 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 + - add a deadlock fix + *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 + - 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 - fix for some jobs not finding correct building. *0.011 @@ -21,6 +37,16 @@ - 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 + bugs list: + - items blocking construction stuck the game + - burning charcoal crashed game + - gem thingies probably broken + - custom reactions semibroken + - gathering plants still broken + --]==] --keybinding, change to your hearts content. Only the key part. @@ -47,6 +73,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 @@ -97,14 +124,17 @@ for k,v in ipairs({...}) do --setting parsing if v=="-c" or v=="--cheat" then settings.build_by_items=true settings.df_assign=false + 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 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 @@ -139,6 +169,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 @@ -152,14 +188,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 @@ -285,7 +320,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 @@ -460,7 +496,7 @@ 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 @@ -499,7 +535,6 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id) last_building.custom=args.custom if chooseBuildingWidthHeightDir(args) then - return end --if settings.build_by_items then @@ -530,7 +565,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 @@ -558,7 +592,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 @@ -580,7 +614,7 @@ function isSuitableItem(job_item,item) print(v) end --]] - + return false,"matinfo" end -- some bonus checks: @@ -595,8 +629,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" @@ -719,39 +753,30 @@ 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) @@ -773,21 +798,32 @@ function AssignJobItems(args) end end end - print("before block") + + return item_suitability,item_counts +end +function AssignJobItems(args) + 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 @@ -803,8 +839,6 @@ function AssignJobItems(args) return true end - - end CheckAndFinishBuilding=function (args,bld) @@ -877,7 +911,125 @@ 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}) +-- 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 = {}, + 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 +--[[ 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 + 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}}, @@ -895,7 +1047,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}}, @@ -904,7 +1056,7 @@ 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 @@ -922,7 +1074,23 @@ function usetool:getModeName() else return actions[(mode or 0)+1][1] or " " end + +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.safe then + site_label:itemById("site").text="" + else + site_label:itemById("site").text="" + end + end end function usetool:init(args) @@ -934,19 +1102,17 @@ function usetool:init(args) } }, - 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}, - visible=false, text={ {id="text1", text="Site:"},{id="site", text="name"} } @@ -956,6 +1122,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 }, @@ -1007,40 +1174,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 --Turn + 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 @@ -1051,7 +1215,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, @@ -1063,16 +1226,15 @@ 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] 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 @@ -1106,7 +1268,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}) @@ -1122,7 +1284,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 @@ -1159,17 +1321,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,16 +1343,16 @@ 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, 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 @@ -1205,7 +1367,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 @@ -1298,6 +1460,11 @@ 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 @@ -1333,6 +1500,13 @@ function usetool:setupFields() ui.site_id=site.id end end +function usetool:siteCheck() + 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" +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] @@ -1340,24 +1514,40 @@ 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 break 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 - + if not failed then local ok,msg if type(cur_mode[2])=="function" then @@ -1365,9 +1555,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 @@ -1381,25 +1571,27 @@ function usetool:fieldInput(keys) end end end - + end + function usetool:onInput(keys) - local adv=df.global.world.units.active[0] + 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") @@ -1417,14 +1609,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() @@ -1432,7 +1617,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 diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua index 39a10367c..b233dcf66 100644 --- a/scripts/gui/advfort_items.lua +++ b/scripts/gui/advfort_items.lua @@ -12,8 +12,10 @@ jobitemEditor.ATTRS{ allow_remove=false, allow_any_item=false, job=DEFAULT_NIL, + job_items=DEFAULT_NIL, items=DEFAULT_NIL, on_okay=DEFAULT_NIL, + autofill=true, } function update_slot_text(slot) local items="" @@ -29,7 +31,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{ @@ -74,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() @@ -104,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 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 diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 344dd7e07..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)) @@ -228,6 +243,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) @@ -310,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) @@ -379,21 +396,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()) diff --git a/scripts/points.lua b/scripts/points.lua new file mode 100644 index 000000000..0bedd2074 --- /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(...) diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb index 690ce6f03..bd0c3ad78 100644 --- a/scripts/unsuspend.rb +++ b/scripts/unsuspend.rb @@ -1,3 +1,6 @@ +# un-suspend construction jobs, one time only +# same as "resume all" + joblist = df.world.job_list.next count = 0 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 '.')