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_TESTS:BOOL=ON \
-DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SKELETON:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \
-DBUILD_SUPPORTED:BOOL=1 \ -DBUILD_SUPPORTED:BOOL=1 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \

@ -22,7 +22,7 @@ Plugins
DFHack plugins are written in C++ and located in the ``plugins`` folder. DFHack plugins are written in C++ and located in the ``plugins`` folder.
Currently, documentation on how to write plugins is somewhat sparse. There are 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. 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 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 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 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, 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 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 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). 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 dumper
====== ======

@ -154,11 +154,20 @@ function numberList(arg, arg_name, list_length)
return strings return strings
end end
-- throws if val is not a nonnegative integer; otherwise returns val function positiveInt(arg, arg_name)
local function check_nonnegative_int(val, 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 if not val or val < 0 or val ~= math.floor(val) then
arg_error(arg_name, arg_error(arg_name,
'expected non-negative integer; got "%s"', tostring(val)) 'expected non-negative integer; got "%s"', tostring(arg))
end end
return val return val
end end
@ -177,9 +186,9 @@ function coords(arg, arg_name, skip_validation)
return cursor return cursor
end end
local numbers = numberList(arg, arg_name, 3) local numbers = numberList(arg, arg_name, 3)
local pos = xyz2pos(check_nonnegative_int(numbers[1]), local pos = xyz2pos(nonnegativeInt(numbers[1]),
check_nonnegative_int(numbers[2]), nonnegativeInt(numbers[2]),
check_nonnegative_int(numbers[3])) nonnegativeInt(numbers[3]))
if not skip_validation and not dfhack.maps.isValidTilePos(pos) then if not skip_validation and not dfhack.maps.isValidTilePos(pos) then
arg_error(arg_name, arg_error(arg_name,
'specified coordinates not on current map: "%s"', arg) '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 # 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) option(BUILD_SKELETON "Build the skeleton plugin." OFF)
if(BUILD_SKELETON) if(BUILD_SKELETON)
add_subdirectory(skeleton) dfhack_plugin(skeleton examples/skeleton.cpp)
endif() endif()
macro(subdirlist result subdir) 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) 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