Merge pull request #2257 from myk002/myk_class3example

Extend documentation in skeleton plugin, add example plugin structures for class 1, 2, and 3 plugins
develop
Myk 2022-08-15 16:18:39 -07:00 committed by GitHub
commit eef0824cef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 518 additions and 278 deletions

@ -86,6 +86,7 @@ jobs:
-DBUILD_TESTS:BOOL=ON \
-DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SKELETON:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SUPPORTED:BOOL=1 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \

@ -22,7 +22,7 @@ Plugins
DFHack plugins are written in C++ and located in the ``plugins`` folder.
Currently, documentation on how to write plugins is somewhat sparse. There are
templates that you can use to get started in the ``plugins/skeleton``
templates that you can use to get started in the ``plugins/examples``
folder, and the source code of existing plugins can also be helpful.
If you want to compile a plugin that you have just added, you will need to add a
@ -35,7 +35,7 @@ other commands).
Plugins can also register handlers to run on every tick, and can interface with
the built-in `enable` and `disable` commands. For the full plugin API, see the
skeleton plugins or ``PluginManager.cpp``.
example ``skeleton`` plugin or ``PluginManager.cpp``.
Installed plugins live in the ``hack/plugins`` folder of a DFHack installation,
and the `load` family of commands can be used to load a recompiled plugin

@ -3029,6 +3029,18 @@ parameters.
function also verifies that the coordinates are valid for the current map and
throws if they are not (unless ``skip_validation`` is set to true).
* ``argparse.positiveInt(arg, arg_name)``
Throws if ``tonumber(arg)`` is not a positive integer; otherwise returns
``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error
messages more useful.
* ``argparse.nonnegativeInt(arg, arg_name)``
Throws if ``tonumber(arg)`` is not a non-negative integer; otherwise returns
``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error
messages more useful.
dumper
======

@ -154,11 +154,20 @@ function numberList(arg, arg_name, list_length)
return strings
end
-- throws if val is not a nonnegative integer; otherwise returns val
local function check_nonnegative_int(val, arg_name)
function positiveInt(arg, arg_name)
local val = tonumber(arg)
if not val or val <= 0 or val ~= math.floor(val) then
arg_error(arg_name,
'expected positive integer; got "%s"', tostring(arg))
end
return val
end
function nonnegativeInt(arg, arg_name)
local val = tonumber(arg)
if not val or val < 0 or val ~= math.floor(val) then
arg_error(arg_name,
'expected non-negative integer; got "%s"', tostring(val))
'expected non-negative integer; got "%s"', tostring(arg))
end
return val
end
@ -177,9 +186,9 @@ function coords(arg, arg_name, skip_validation)
return cursor
end
local numbers = numberList(arg, arg_name, 3)
local pos = xyz2pos(check_nonnegative_int(numbers[1]),
check_nonnegative_int(numbers[2]),
check_nonnegative_int(numbers[3]))
local pos = xyz2pos(nonnegativeInt(numbers[1]),
nonnegativeInt(numbers[2]),
nonnegativeInt(numbers[3]))
if not skip_validation and not dfhack.maps.isValidTilePos(pos) then
arg_error(arg_name,
'specified coordinates not on current map: "%s"', arg)

@ -188,7 +188,7 @@ endif()
# this is the skeleton plugin. If you want to make your own, make a copy and then change it
option(BUILD_SKELETON "Build the skeleton plugin." OFF)
if(BUILD_SKELETON)
add_subdirectory(skeleton)
dfhack_plugin(skeleton examples/skeleton.cpp)
endif()
macro(subdirlist result subdir)

@ -0,0 +1,166 @@
// This template is appropriate for plugins that periodically check game state
// and make some sort of automated change. These types of plugins typically
// provide a command that can be used to configure the plugin behavior and
// require a world to be loaded before they can function. This kind of plugin
// should persist its state in the savegame and auto-re-enable itself when a
// savegame that had this plugin enabled is loaded.
#include <string>
#include <vector>
#include "df/world.h"
#include "Core.h"
#include "Debug.h"
#include "PluginManager.h"
#include "modules/Persistence.h"
#include "modules/World.h"
using std::string;
using std::vector;
using namespace DFHack;
DFHACK_PLUGIN("persistent_per_save_example");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command.
namespace DFHack {
// for configuration-related logging
DBG_DECLARE(persistent_per_save_example, status, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO);
}
static const string CONFIG_KEY = string(plugin_name) + "/config";
static PersistentDataItem config;
enum ConfigValues {
CONFIG_IS_ENABLED = 0,
CONFIG_CYCLE_TICKS = 1,
};
static int get_config_val(int index) {
if (!config.isValid())
return -1;
return config.ival(index);
}
static bool get_config_bool(int index) {
return get_config_val(index) == 1;
}
static void set_config_val(int index, int value) {
if (config.isValid())
config.ival(index) = value;
}
static void set_config_bool(int index, bool value) {
set_config_val(index, value ? 1 : 0);
}
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static command_result do_command(color_ostream &out, vector<string> &parameters);
static void do_cycle(color_ostream &out);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(status,out).print("initializing %s\n", plugin_name);
// provide a configuration interface for the plugin
commands.push_back(PluginCommand(
plugin_name,
"Short (~54 character) description of command.",
do_command));
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
if (enable != is_enabled) {
is_enabled = enable;
DEBUG(status,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
} else {
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled",
is_enabled ? "enabled" : "disabled");
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
DEBUG(status,out).print("shutting down %s\n", plugin_name);
return CR_OK;
}
DFhackCExport command_result plugin_load_data (color_ostream &out) {
config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) {
DEBUG(status,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
set_config_val(CONFIG_CYCLE_TICKS, 6000);
}
// we have to copy our enabled flag into the global plugin variable, but
// all the other state we can directly read/modify from the persistent
// data structure.
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
DEBUG(status,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
if (is_enabled) {
DEBUG(status,out).print("world unloaded; disabling %s\n",
plugin_name);
is_enabled = false;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS))
do_cycle(out);
return CR_OK;
}
static command_result do_command(color_ostream &out, vector<string> &parameters) {
// be sure to suspend the core if any DF state is read or modified
CoreSuspender suspend;
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
// TODO: configuration logic
// simple commandline parsing can be done in C++, but there are lua libraries
// that can easily handle more complex commandlines. see the blueprint plugin
// for an example.
return CR_OK;
}
/////////////////////////////////////////////////////
// cycle logic
//
static void do_cycle(color_ostream &out) {
// mark that we have recently run
cycle_timestamp = world->frame_counter;
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
// TODO: logic that runs every get_config_val(CONFIG_CYCLE_TICKS) ticks
}

@ -0,0 +1,41 @@
// This template is appropriate for plugins that simply provide one or more
// commands, but don't need to be "enabled" to function.
#include <string>
#include <vector>
#include "Debug.h"
#include "PluginManager.h"
using std::string;
using std::vector;
using namespace DFHack;
DFHACK_PLUGIN("simple_command_example");
namespace DFHack {
DBG_DECLARE(simple_command_example, log);
}
static command_result do_command(color_ostream &out, vector<string> &parameters);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(log,out).print("initializing %s\n", plugin_name);
commands.push_back(PluginCommand(
plugin_name,
"Short (~54 character) description of command.",
do_command));
return CR_OK;
}
static command_result do_command(color_ostream &out, vector<string> &parameters) {
// be sure to suspend the core if any DF state is read or modified
CoreSuspender suspend;
// TODO: command logic
return CR_OK;
}

@ -0,0 +1,193 @@
// This is an example plugin that documents and implements all the plugin
// callbacks and features. You can include it in the regular build by setting
// the BUILD_SKELETON option in CMake to ON. Play with loading and unloading
// the plugin in various game states (e.g. with and without a world loaded),
// and see the debug messages get printed to the console.
//
// See the other example plugins in this directory for plugins that are
// configured for specific use cases (but don't come with as many comments as
// this one does).
#include <string>
#include <vector>
#include "df/world.h"
#include "Core.h"
#include "Debug.h"
#include "PluginManager.h"
#include "modules/Persistence.h"
#include "modules/World.h"
using std::string;
using std::vector;
using namespace DFHack;
// Expose the plugin name to the DFHack core, as well as metadata like the
// DFHack version that this plugin was compiled with. This macro provides a
// variable for the plugin name as const char * plugin_name.
// The name provided must correspond to the filename --
// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
DFHACK_PLUGIN("skeleton");
// The identifier declared with this macro (i.e. is_enabled) is used to track
// whether the plugin is in an "enabled" state. If you don't need enablement
// for your plugin, you don't need this line. This variable will also be read
// by the `plug` builtin command; when true the plugin will be shown as enabled.
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
// Any globals a plugin requires (e.g. world) should be listed here.
// For example, this line expands to "using df::global::world" and prevents the
// plugin from being loaded if df::global::world is null (i.e. missing from
// symbols.xml).
REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command.
// Actual plugins will likely want to set the default level to LINFO or LWARNING
// instead of the LDEBUG used here.
namespace DFHack {
// for configuration-related logging
DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG);
// for plugin_onupdate logging
DBG_DECLARE(skeleton, onupdate, DebugCategory::LDEBUG);
// for command-related logging
DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG);
}
static command_result command_callback1(color_ostream &out, vector<string> &parameters);
// run when the plugin is loaded
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
DEBUG(status,out).print("initializing %s\n", plugin_name);
// For in-tree plugins, don't use the "usage" parameter of PluginCommand.
// Instead, add an .rst file with the same name as the plugin to the
// docs/plugins/ directory.
commands.push_back(PluginCommand(
"skeleton",
"Short (~54 character) description of command.", // to use one line in the ``[DFHack]# ls`` output
command_callback1));
return CR_OK;
}
// run when the plugin is unloaded
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
DEBUG(status,out).print("shutting down %s\n", plugin_name);
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}
// run when the `enable` or `disable` command is run with this plugin name as
// an argument
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DEBUG(status,out).print("%s from the API\n", enable ? "enabled" : "disabled");
// you have to maintain the state of the is_enabled variable yourself. it
// doesn't happen automatically.
is_enabled = enable;
return CR_OK;
}
// Called to notify the plugin about important state changes.
// Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future.
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
switch (event) {
case SC_UNKNOWN:
DEBUG(status,out).print("game state changed: SC_UNKNOWN\n");
break;
case SC_WORLD_LOADED:
DEBUG(status,out).print("game state changed: SC_WORLD_LOADED\n");
break;
case SC_WORLD_UNLOADED:
DEBUG(status,out).print("game state changed: SC_WORLD_UNLOADED\n");
break;
case SC_MAP_LOADED:
DEBUG(status,out).print("game state changed: SC_MAP_LOADED\n");
break;
case SC_MAP_UNLOADED:
DEBUG(status,out).print("game state changed: SC_MAP_UNLOADED\n");
break;
case SC_VIEWSCREEN_CHANGED:
DEBUG(status,out).print("game state changed: SC_VIEWSCREEN_CHANGED\n");
break;
case SC_CORE_INITIALIZED:
DEBUG(status,out).print("game state changed: SC_CORE_INITIALIZED\n");
break;
case SC_BEGIN_UNLOAD:
DEBUG(status,out).print("game state changed: SC_BEGIN_UNLOAD\n");
break;
case SC_PAUSED:
DEBUG(status,out).print("game state changed: SC_PAUSED\n");
break;
case SC_UNPAUSED:
DEBUG(status,out).print("game state changed: SC_UNPAUSED\n");
break;
}
return CR_OK;
}
// Whatever you put here will be done in each game frame refresh. Don't abuse it.
// Note that if the plugin implements the enabled API, this function is only called
// if the plugin is enabled.
DFhackCExport command_result plugin_onupdate (color_ostream &out) {
DEBUG(onupdate,out).print(
"onupdate called (run 'debugfilter set info skeleton onupdate' to stop"
" seeing these messages)\n");
return CR_OK;
}
// If you need to save or load world-specific data, define these functions.
// plugin_save_data is called when the game might be about to save the world,
// and plugin_load_data is called whenever a new world is loaded. If the plugin
// is loaded or unloaded while a world is active, plugin_save_data or
// plugin_load_data will be called immediately.
DFhackCExport command_result plugin_save_data (color_ostream &out) {
DEBUG(status,out).print("save or unload is imminent; time to persist state\n");
// Call functions in the Persistence module here. If your PersistantDataItem
// objects are already up to date, then they will get persisted with the
// save automatically and there is nothing extra you need to do here.
return CR_OK;
}
DFhackCExport command_result plugin_load_data (color_ostream &out) {
DEBUG(status,out).print("world is loading; time to load persisted state\n");
// Call functions in the Persistence module here. See
// persistent_per_save_example.cpp for an example.
return CR_OK;
}
// This is the callback we registered in plugin_init. Note that while plugin
// callbacks are called with the core suspended, command callbacks are called
// from a different thread and need to explicity suspend the core if they
// interact with Lua or DF game state (most commands do at least one of these).
static command_result command_callback1(color_ostream &out, vector<string> &parameters) {
DEBUG(command,out).print("%s command called with %zu parameters\n",
plugin_name, parameters.size());
// I'll say it again: always suspend the core in command callbacks unless
// all your data is local.
CoreSuspender suspend;
// Return CR_WRONG_USAGE to print out your help text. The help text is
// sourced from the associated rst file in docs/plugins/. The same help will
// also be returned by 'help your-command'.
// simple commandline parsing can be done in C++, but there are lua libraries
// that can easily handle more complex commandlines. see the blueprint plugin
// for an example.
// TODO: do something according to the flags set in the options struct
return CR_OK;
}

@ -0,0 +1,57 @@
// This template is appropriate for plugins that can be enabled to make some
// specific persistent change to the game, but don't need a world to be loaded
// before they are enabled. These types of plugins typically register some sort
// of hook on enable and clear the hook on disable. They are generally enabled
// from dfhack.init and do not need to persist and reload their enabled state.
#include <string>
#include <vector>
#include "df/viewscreen_titlest.h"
#include "Debug.h"
#include "PluginManager.h"
#include "VTableInterpose.h"
using std::string;
using std::vector;
using namespace DFHack;
DFHACK_PLUGIN("ui_addition_example");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
namespace DFHack {
DBG_DECLARE(ui_addition_example, log);
}
// example of hooking a screen so the plugin code will run whenever the screen
// is visible
struct title_version_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
// TODO: injected render logic here
}
};
IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render);
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
DEBUG(log,out).print("shutting down %s\n", plugin_name);
INTERPOSE_HOOK(title_version_hook, render).remove();
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) {
if (enable != is_enabled) {
DEBUG(log,out).print("%s %s\n", plugin_name,
is_enabled ? "enabled" : "disabled");
if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}

@ -1,36 +0,0 @@
project(skeleton)
# A list of source files
set(PROJECT_SRCS
skeleton.cpp
)
# A list of headers
set(PROJECT_HDRS
skeleton.h
)
set_source_files_properties(${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
# mash them together (headers are marked as headers and nothing will try to compile them)
list(APPEND PROJECT_SRCS ${PROJECT_HDRS})
# option to use a thread for no particular reason
option(SKELETON_THREAD "Use threads in the skeleton plugin." ON)
if(UNIX)
if(APPLE)
set(PROJECT_LIBS
# add any extra mac libraries here
${PROJECT_LIBS}
)
else()
set(PROJECT_LIBS
# add any extra linux libraries here
${PROJECT_LIBS}
)
endif()
else()
set(PROJECT_LIBS
# add any extra windows libraries here
${PROJECT_LIBS}
)
endif()
# this makes sure all the stuff is put in proper places and linked to dfhack
dfhack_plugin(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -1,171 +0,0 @@
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
// some headers required for a plugin. Nothing special, just the basics.
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/EventManager.h>
// If you need to save data per-world:
//#include "modules/Persistence.h"
// DF data structure definition headers
#include "DataDefs.h"
//#include "df/world.h"
// our own, empty header.
#include "skeleton.h"
using namespace DFHack;
using namespace df::enums;
// Expose the plugin name to the DFHack core, as well as metadata like the DFHack version.
// The name string provided must correspond to the filename -
// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
DFHACK_PLUGIN("skeleton");
// The identifier declared with this macro (ie. enabled) can be specified by the user
// and subsequently used to manage the plugin's operations.
// This will also be tracked by `plug`; when true the plugin will be shown as enabled.
DFHACK_PLUGIN_IS_ENABLED(enabled);
// Any globals a plugin requires (e.g. world) should be listed here.
// For example, this line expands to "using df::global::world" and prevents the
// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml):
//
REQUIRE_GLOBAL(world);
// You may want some compile time debugging options
// one easy system just requires you to cache the color_ostream &out into a global debug variable
//#define P_DEBUG 1
//uint16_t maxTickFreq = 1200; //maybe you want to use some events
command_result command_callback1(color_ostream &out, std::vector<std::string> &parameters);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("skeleton",
"~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output
command_callback1,
false,
"example usage"
" skeleton <option> <args>\n"
" explanation of plugin/command\n"
"\n"
" skeleton\n"
" what happens when using the command\n"
"\n"
" skeleton option1\n"
" what happens when using the command with option1\n"
"\n"));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
namespace EM = EventManager;
if (enable && !enabled) {
//using namespace EM::EventType;
//EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
//EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
//out.print("plugin enabled!\n");
} else if (!enable && enabled) {
EM::unregisterAll(plugin_self);
//out.print("plugin disabled!\n");
}
enabled = enable;
return CR_OK;
}
/* OPTIONAL *
// Called to notify the plugin about important state changes.
// Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future.
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (enabled) {
switch (event) {
case SC_UNKNOWN:
break;
case SC_WORLD_LOADED:
break;
case SC_WORLD_UNLOADED:
break;
case SC_MAP_LOADED:
break;
case SC_MAP_UNLOADED:
break;
case SC_VIEWSCREEN_CHANGED:
break;
case SC_CORE_INITIALIZED:
break;
case SC_BEGIN_UNLOAD:
break;
case SC_PAUSED:
break;
case SC_UNPAUSED:
break;
}
}
return CR_OK;
}
// Whatever you put here will be done in each game step. Don't abuse it.
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// whetever. You don't need to suspend DF execution here.
return CR_OK;
}
// If you need to save or load world-specific data, define these functions.
// plugin_save_data is called when the game might be about to save the world,
// and plugin_load_data is called whenever a new world is loaded. If the plugin
// is loaded or unloaded while a world is active, plugin_save_data or
// plugin_load_data will be called immediately.
DFhackCExport command_result plugin_save_data (color_ostream &out)
{
// Call functions in the Persistence module here.
return CR_OK;
}
DFhackCExport command_result plugin_load_data (color_ostream &out)
{
// Call functions in the Persistence module here.
return CR_OK;
}
* OPTIONAL */
// A command! It sits around and looks pretty. And it's nice and friendly.
command_result command_callback1(color_ostream &out, std::vector<std::string> &parameters) {
// It's nice to print a help message you get invalid options
// from the user instead of just acting strange.
// This can be achieved by adding the extended help string to the
// PluginCommand registration as show above, and then returning
// CR_WRONG_USAGE from the function. The same string will also
// be used by 'help your-command'.
if (!parameters.empty()) {
return CR_WRONG_USAGE; //or maybe you want it to do something else
}
// Commands are called from threads other than the DF one.
// Suspend this thread until DF has time for us.
// **If you use CoreSuspender** it'll automatically resume DF when
// execution leaves the current scope.
CoreSuspender suspend;
// Actually do something here. Yay.
// process parameters
if (parameters.size() == 1 && parameters[0] == "option1") {
// stuff
} else {
return CR_FAILURE;
}
// Give control back to DF.
return CR_OK;
}

@ -1,61 +0,0 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/EventManager.h>
//#include "df/world.h"
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("skeleton2");
DFHACK_PLUGIN_IS_ENABLED(enabled);
//REQUIRE_GLOBAL(world);
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) {
commands.push_back(PluginCommand("skeleton2",
"~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output
skeleton2,
false,
"example usage"
" skeleton2 <option> <args>\n"
" explanation of plugin/command\n"
"\n"
" skeleton2\n"
" what happens when using the command\n"
"\n"
" skeleton2 option1\n"
" what happens when using the command with option1\n"
"\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) {
namespace EM = EventManager;
if (enable && !enabled) {
//using namespace EM::EventType;
//EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
//EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
//out.print("plugin enabled!\n");
} else if (!enable && enabled) {
EM::unregisterAll(plugin_self);
//out.print("plugin disabled!\n");
}
enabled = enable;
return CR_OK;
}
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters) {
if (!parameters.empty())
return CR_WRONG_USAGE;
CoreSuspender suspend;
out.print("blah");
return CR_OK;
}

@ -265,3 +265,33 @@ function test.coords()
end)
end)
end
function test.positiveInt()
expect.eq(5, argparse.positiveInt(5))
expect.eq(5, argparse.positiveInt('5'))
expect.eq(5, argparse.positiveInt('5.0'))
expect.eq(1, argparse.positiveInt('1'))
expect.error_match('expected positive integer',
function() argparse.positiveInt('0') end)
expect.error_match('expected positive integer',
function() argparse.positiveInt('5.01') end)
expect.error_match('expected positive integer',
function() argparse.positiveInt(-1) end)
end
function test.nonnegativeInt()
expect.eq(5, argparse.nonnegativeInt(5))
expect.eq(5, argparse.nonnegativeInt('5'))
expect.eq(5, argparse.nonnegativeInt('5.0'))
expect.eq(1, argparse.nonnegativeInt('1'))
expect.eq(0, argparse.nonnegativeInt('0'))
expect.eq(0, argparse.nonnegativeInt('-0'))
expect.error_match('expected non%-negative integer',
function() argparse.nonnegativeInt('-0.01') end)
expect.error_match('expected non%-negative integer',
function() argparse.nonnegativeInt(-5) end)
expect.error_match('expected non%-negative integer',
function() argparse.nonnegativeInt(-1) end)
end