1- // This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
1+ // This is an example plugin that just documents and implements all the plugin
2+ // callbacks and features. You can compile it, load it, run it, and see the
3+ // debug messages get printed to the console.
4+ //
5+ // See the other example plugins in this directory for plugins that are
6+ // configured for specific use cases (but don't come with as many comments as
7+ // this one does).
8+
9+ #include < string>
10+ #include < vector>
11+
12+ #include " df/world.h"
213
3- // some headers required for a plugin. Nothing special, just the basics.
414#include " Core.h"
5- #include < Console.h>
6- #include < Export.h>
7- #include < PluginManager.h>
8- #include < modules/EventManager.h>
9- // If you need to save data per-world:
10- // #include "modules/Persistence.h"
15+ #include " Debug.h"
16+ #include " LuaTools.h"
17+ #include " PluginManager.h"
1118
12- // DF data structure definition headers
13- #include " DataDefs.h"
14- // #include "df/world.h"
19+ #include " modules/Persistence.h"
20+ #include " modules/World.h"
1521
16- // our own, empty header.
17- # include " skeleton.h "
22+ using std::string;
23+ using std::vector;
1824
1925using namespace DFHack ;
20- using namespace df ::enums;
2126
22- // Expose the plugin name to the DFHack core, as well as metadata like the DFHack version.
23- // The name string provided must correspond to the filename -
27+ // Expose the plugin name to the DFHack core, as well as metadata like the
28+ // DFHack version that this plugin was compiled with. This macro provides a
29+ // variable for the plugin name as const char * plugin_name.
30+ // The name provided must correspond to the filename --
2431// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case
2532DFHACK_PLUGIN (" skeleton" );
2633
27- // The identifier declared with this macro (ie. enabled) can be specified by the user
28- // and subsequently used to manage the plugin's operations.
29- // This will also be tracked by `plug`; when true the plugin will be shown as enabled.
30- DFHACK_PLUGIN_IS_ENABLED (enabled);
34+ // The identifier declared with this macro (i.e. is_enabled) is used to track
35+ // whether the plugin is in an "enabled" state. If you don't need enablement
36+ // for your plugin, you don't need this line. This variable will also be read
37+ // by the `plug` builtin command; when true the plugin will be shown as enabled.
38+ DFHACK_PLUGIN_IS_ENABLED (is_enabled);
3139
3240// Any globals a plugin requires (e.g. world) should be listed here.
3341// For example, this line expands to "using df::global::world" and prevents the
34- // plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml):
35- //
42+ // plugin from being loaded if df::global::world is null (i.e. missing from
43+ // symbols.xml).
3644REQUIRE_GLOBAL (world);
3745
38- // You may want some compile time debugging options
39- // one easy system just requires you to cache the color_ostream &out into a global debug variable
40- // #define P_DEBUG 1
41- // uint16_t maxTickFreq = 1200; //maybe you want to use some events
46+ // logging levels can be dynamically controlled with the `debugfilter` command.
47+ namespace DFHack {
48+ // for configuration-related logging
49+ DBG_DECLARE (skeleton, status, DebugCategory::LINFO );
50+ // for logging during the periodic scan
51+ DBG_DECLARE (skeleton, cycle, DebugCategory::LINFO );
52+ }
4253
43- command_result command_callback1 (color_ostream &out, std:: vector<std:: string> ¶meters);
54+ command_result command_callback1 (color_ostream &out, vector<string> ¶meters);
4455
56+ // run when the plugin is loaded
4557DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands) {
46- commands.push_back (PluginCommand (" skeleton" ,
47- " ~54 character description of plugin" , // to use one line in the ``[DFHack]# ls`` output
48- command_callback1,
49- false ,
50- " example usage"
51- " skeleton <option> <args>\n "
52- " explanation of plugin/command\n "
53- " \n "
54- " skeleton\n "
55- " what happens when using the command\n "
56- " \n "
57- " skeleton option1\n "
58- " what happens when using the command with option1\n "
59- " \n " ));
58+ // For in-tree plugins, don't use the "usage" parameter of PluginCommand.
59+ // Instead, add an .rst file with the same name as the plugin to the
60+ // docs/plugins/ directory.
61+ commands.push_back (PluginCommand (
62+ " skeleton" ,
63+ " Short (~54 character) description of command." , // to use one line in the ``[DFHack]# ls`` output
64+ command_callback1));
6065 return CR_OK ;
6166}
6267
68+ // run when the plugin is unloaded
6369DFhackCExport command_result plugin_shutdown (color_ostream &out) {
6470 // You *MUST* kill all threads you created before this returns.
6571 // If everything fails, just return CR_FAILURE. Your plugin will be
@@ -68,28 +74,20 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) {
6874
6975}
7076
77+ // run when the `enable` or `disable` command is run with this plugin name as
78+ // an argument
7179DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) {
72- namespace EM = EventManager;
73- if (enable && !enabled) {
74- // using namespace EM::EventType;
75- // EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
76- // EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
77- // out.print("plugin enabled!\n");
78- } else if (!enable && enabled) {
79- EM::unregisterAll (plugin_self);
80- // out.print("plugin disabled!\n");
81- }
82- enabled = enable;
80+ // you have to maintain the state of the is_enabled variable yourself. it
81+ // doesn't happen automatically.
82+ is_enabled = enable;
8383 return CR_OK ;
8484}
8585
86-
87- /* OPTIONAL *
8886// Called to notify the plugin about important state changes.
8987// Invoked with DF suspended, and always before the matching plugin_onupdate.
9088// More event codes may be added in the future.
9189DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event event) {
92- if (enabled ) {
90+ if (is_enabled ) {
9391 switch (event) {
9492 case SC_UNKNOWN :
9593 break ;
@@ -116,9 +114,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
116114 return CR_OK ;
117115}
118116
119- // Whatever you put here will be done in each game step. Don't abuse it.
120- DFhackCExport command_result plugin_onupdate ( color_ostream &out )
121- {
117+ // Whatever you put here will be done in each game frame refresh. Don't abuse it.
118+ DFhackCExport command_result plugin_onupdate (color_ostream &out) {
122119 // whetever. You don't need to suspend DF execution here.
123120 return CR_OK ;
124121}
@@ -128,44 +125,94 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
128125// and plugin_load_data is called whenever a new world is loaded. If the plugin
129126// is loaded or unloaded while a world is active, plugin_save_data or
130127// plugin_load_data will be called immediately.
131- DFhackCExport command_result plugin_save_data (color_ostream &out)
132- {
128+ DFhackCExport command_result plugin_save_data (color_ostream &out) {
133129 // Call functions in the Persistence module here.
134130 return CR_OK ;
135131}
136132
137- DFhackCExport command_result plugin_load_data (color_ostream &out)
138- {
133+ DFhackCExport command_result plugin_load_data (color_ostream &out) {
139134 // Call functions in the Persistence module here.
140135 return CR_OK ;
141136}
142- * OPTIONAL */
143-
144-
145- // A command! It sits around and looks pretty. And it's nice and friendly.
146- command_result command_callback1 (color_ostream &out, std::vector<std::string> ¶meters) {
147- // It's nice to print a help message you get invalid options
148- // from the user instead of just acting strange.
149- // This can be achieved by adding the extended help string to the
150- // PluginCommand registration as show above, and then returning
151- // CR_WRONG_USAGE from the function. The same string will also
152- // be used by 'help your-command'.
153- if (!parameters.empty ()) {
154- return CR_WRONG_USAGE ; // or maybe you want it to do something else
137+
138+ // define the structure that will represent the possible commandline options
139+ struct command_options {
140+ // whether to display help
141+ bool help = false ;
142+
143+ // whether to run a cycle right now
144+ bool now = false ;
145+
146+ // how many ticks to wait between cycles when enabled, -1 means unset
147+ int32_t ticks = -1 ;
148+
149+ // example params of different types
150+ df::coord start;
151+ string format;
152+ vector<string*> list; // note this must be a vector of pointers, not objects
153+
154+ static struct_identity _identity;
155+ };
156+ static const struct_field_info command_options_fields[] = {
157+ { struct_field_info::PRIMITIVE , " help" , offsetof (command_options, help), &df::identity_traits<bool >::identity, 0 , 0 },
158+ { struct_field_info::PRIMITIVE , " now" , offsetof (command_options, now), &df::identity_traits<bool >::identity, 0 , 0 },
159+ { struct_field_info::PRIMITIVE , " ticks" , offsetof (command_options, ticks), &df::identity_traits<int32_t >::identity, 0 , 0 },
160+ { struct_field_info::SUBSTRUCT , " start" , offsetof (command_options, start), &df::coord::_identity, 0 , 0 },
161+ { struct_field_info::PRIMITIVE , " format" , offsetof (command_options, format), df::identity_traits<string>::get (), 0 , 0 },
162+ { struct_field_info::STL_VECTOR_PTR , " list" , offsetof (command_options, list), df::identity_traits<string>::get (), 0 , 0 },
163+ { struct_field_info::END }
164+ };
165+ struct_identity command_options::_identity (sizeof (command_options), &df::allocator_fn<command_options>, NULL, "command_options", NULL, command_options_fields);
166+
167+ // load the lua module associated with the plugin and parse the commandline
168+ // in lua (which has better facilities than C++ for string parsing). You should
169+ // create a file named after your plugin in the plugins/lua directory. This
170+ // example expects you to define a global function in that file named
171+ // "parse_commandline" that takes the options struct defined above along with
172+ // the commandline parameters. It should parse the parameters and set data in
173+ // the options structure. See plugins/lua/skeleton.lua for an example.
174+ static bool get_options (color_ostream &out,
175+ command_options &opts,
176+ const vector<string> ¶meters)
177+ {
178+ auto L = Lua::Core::State;
179+ Lua::StackUnwinder top (L);
180+
181+ if (!lua_checkstack (L, parameters.size () + 2 ) ||
182+ !Lua::PushModulePublic (
183+ out, L, (" plugins." + string (plugin_name)).c_str (),
184+ " parse_commandline" )) {
185+ out.printerr (" Failed to load %s Lua code\n " , plugin_name);
186+ return false ;
155187 }
156- // Commands are called from threads other than the DF one.
157- // Suspend this thread until DF has time for us.
158- // **If you use CoreSuspender** it'll automatically resume DF when
159- // execution leaves the current scope.
188+
189+ Lua::Push (L, &opts);
190+ for (const string ¶m : parameters)
191+ Lua::Push (L, param);
192+
193+ if (!Lua::SafeCall (out, L, parameters.size () + 1 , 0 ))
194+ return false ;
195+
196+ return true ;
197+ }
198+
199+ // This is the callback we registered in plugin_init. Note that while plugin
200+ // callbacks are called with the core suspended, command callbacks are called
201+ // from a different thread and need to explicity suspend the core if they
202+ // interact with Lua or DF game state (most commands do at least one of these).
203+ static command_result command_callback1 (color_ostream &out, vector<string> ¶meters) {
204+ // I'll say it again: always suspend the core in command callbacks unless
205+ // all your data is local.
160206 CoreSuspender suspend;
161- // Actually do something here. Yay.
162207
163- // process parameters
164- if (parameters.size () == 1 && parameters[0 ] == " option1" ) {
165- // stuff
166- } else {
167- return CR_FAILURE ;
168- }
169- // Give control back to DF.
208+ // Return CR_WRONG_USAGE to print out your help text. The help text is
209+ // sourced from the associated rst file in docs/plugins/. The same help will
210+ // also be returned by 'help your-command'.
211+ command_options opts;
212+ if (!get_options (out, opts, parameters) || opts.help )
213+ return CR_WRONG_USAGE ;
214+
215+ // TODO: do something according to the flags set in the options struct
216+
170217 return CR_OK ;
171218}
0 commit comments