Merge branch 'develop' into modding-guide
commit
29d4d530b5
@ -0,0 +1,145 @@
|
||||
{
|
||||
"manipulator": 75,
|
||||
"autolabor": 59,
|
||||
"reveal": 51,
|
||||
"help": 50,
|
||||
"ls": 50,
|
||||
"die": 50,
|
||||
"tags": 50,
|
||||
"embark-assistant": 42,
|
||||
"prospect": 37,
|
||||
"autodump": 36,
|
||||
"clean": 35,
|
||||
"gui/workflow": 28,
|
||||
"workflow": 28,
|
||||
"exportlegends": 26,
|
||||
"gui/autobutcher": 25,
|
||||
"autobutcher": 25,
|
||||
"digv": 24,
|
||||
"fastdwarf": 22,
|
||||
"autonestbox": 20,
|
||||
"showmood": 19,
|
||||
"gui/liquids": 18,
|
||||
"liquids": 18,
|
||||
"search": 18,
|
||||
"gui/quickfort": 15,
|
||||
"quickfort": 15,
|
||||
"createitem": 14,
|
||||
"stocks": 14,
|
||||
"autofarm": 12,
|
||||
"autochop": 12,
|
||||
"tiletypes": 12,
|
||||
"exterminate": 12,
|
||||
"buildingplan": 12,
|
||||
"quicksave": 11,
|
||||
"gui/gm-editor": 11,
|
||||
"cleanowned": 10,
|
||||
"gui/autogems": 9,
|
||||
"autogems": 9,
|
||||
"stonesense": 9,
|
||||
"gui/stockpiles": 8,
|
||||
"stockpiles": 8,
|
||||
"changevein": 8,
|
||||
"gui/teleport": 7,
|
||||
"teleport": 7,
|
||||
"seedwatch": 6,
|
||||
"automelt": 6,
|
||||
"embark-tools": 6,
|
||||
"cursecheck": 5,
|
||||
"open-legends": 5,
|
||||
"ban-cooking": 5,
|
||||
"burial": 5,
|
||||
"automaterial": 5,
|
||||
"remove-stress": 5,
|
||||
"gui/blueprint": 5,
|
||||
"blueprint": 5,
|
||||
"tailor": 4,
|
||||
"startdwarf": 4,
|
||||
"3dveins": 4,
|
||||
"digcircle": 4,
|
||||
"nestboxes": 3,
|
||||
"deathcause": 3,
|
||||
"list-agreements": 3,
|
||||
"gui/room-list": 3,
|
||||
"points": 3,
|
||||
"region-pops": 3,
|
||||
"gui/advfort": 3,
|
||||
"unsuspend": 3,
|
||||
"locate-ore": 3,
|
||||
"changelayer": 3,
|
||||
"source": 3,
|
||||
"gui/gm-unit": 3,
|
||||
"combine-drinks": 3,
|
||||
"combine-plants": 3,
|
||||
"deteriorate": 3,
|
||||
"warn-starving": 3,
|
||||
"gaydar": 2,
|
||||
"gui/dfstatus": 2,
|
||||
"gui/rename": 2,
|
||||
"rename": 2,
|
||||
"fix-ster": 2,
|
||||
"job-material": 2,
|
||||
"stockflow": 2,
|
||||
"drain-aquifer": 2,
|
||||
"full-heal": 2,
|
||||
"spawnunit": 2,
|
||||
"flashstep": 2,
|
||||
"gui/family-affairs": 2,
|
||||
"caravan": 2,
|
||||
"mousequery": 2,
|
||||
"tweak": 2,
|
||||
"confirm": 2,
|
||||
"autoclothing": 1,
|
||||
"autounsuspend": 1,
|
||||
"prioritize": 1,
|
||||
"dwarfmonitor": 1,
|
||||
"show-unit-syndromes": 1,
|
||||
"troubleshoot-item": 1,
|
||||
"gui/mechanisms": 1,
|
||||
"gui/pathable": 1,
|
||||
"hotkeys": 1,
|
||||
"infiniteSky": 1,
|
||||
"force": 1,
|
||||
"hermit": 1,
|
||||
"strangemood": 1,
|
||||
"weather": 1,
|
||||
"add-recipe": 1,
|
||||
"autotrade": 1,
|
||||
"zone": 1,
|
||||
"autonick": 1,
|
||||
"stripcaged": 1,
|
||||
"unforbid": 1,
|
||||
"workorder": 1,
|
||||
"gui/mod-manager": 1,
|
||||
"spotclean": 1,
|
||||
"plant": 1,
|
||||
"regrass": 1,
|
||||
"dig-now": 1,
|
||||
"build-now": 1,
|
||||
"clear-webs": 1,
|
||||
"gui/siege-engine": 1,
|
||||
"assign-skills": 1,
|
||||
"brainwash": 1,
|
||||
"elevate-mental": 1,
|
||||
"elevate-physical": 1,
|
||||
"launch": 1,
|
||||
"linger": 1,
|
||||
"make-legendary": 1,
|
||||
"rejuvenate": 1,
|
||||
"resurrect-adv": 1,
|
||||
"questport": 1,
|
||||
"getplants": 1,
|
||||
"gui/stamper": 1,
|
||||
"tweak": 1,
|
||||
"fixveins": 1,
|
||||
"deramp": 1,
|
||||
"fix/dead-units": 1,
|
||||
"fix/fat-dwarves": 1,
|
||||
"fix/loyaltycascade": 1,
|
||||
"fix/retrieve-units": 1,
|
||||
"tweak": 1,
|
||||
"sort-items": 1,
|
||||
"gui/color-schemes": 1,
|
||||
"color-schemes": 1,
|
||||
"season-palette": 1
|
||||
}
|
Can't render this file because it has a wrong number of fields in line 55.
|
@ -0,0 +1,8 @@
|
||||
# Default DFHack commands to run on program init
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/dfhack.init
|
||||
|
||||
script hack/init/dfhack.keybindings.init
|
||||
script hack/init/dfhack.tools.init
|
@ -0,0 +1,131 @@
|
||||
# Default DFHack tool configuration
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/dfhack.init
|
||||
|
||||
############################
|
||||
# UI and game logic tweaks #
|
||||
############################
|
||||
|
||||
# stabilize the cursor of dwarfmode when switching menus
|
||||
tweak stable-cursor
|
||||
|
||||
# stop stacked liquid/bar/thread/cloth items from lasting forever
|
||||
# if used in reactions that use only a fraction of the dimension.
|
||||
# might be fixed by DF
|
||||
# tweak fix-dimensions
|
||||
|
||||
# make reactions requiring containers usable in advmode - the issue is
|
||||
# that the screen asks for those reagents to be selected directly
|
||||
tweak advmode-contained
|
||||
|
||||
# support Shift-Enter in Trade and Move Goods to Depot screens for faster
|
||||
# selection; it selects the current item or stack and scrolls down one line
|
||||
tweak fast-trade
|
||||
|
||||
# stop the right list in military->positions from resetting to top all the time
|
||||
tweak military-stable-assign
|
||||
# in same list, color units already assigned to squads in brown & green
|
||||
tweak military-color-assigned
|
||||
|
||||
# make crafted cloth items wear out with time like in old versions (bug 6003)
|
||||
tweak craft-age-wear
|
||||
|
||||
# stop adamantine clothing from wearing out (bug 6481)
|
||||
#tweak adamantine-cloth-wear
|
||||
|
||||
# Add "Select all" and "Deselect all" options to farm plot menus
|
||||
tweak farm-plot-select
|
||||
|
||||
# Add Shift-Left/Right controls to import agreement screen
|
||||
tweak import-priority-category
|
||||
|
||||
# Fixes a crash in the work order contition material list (bug 9905).
|
||||
tweak condition-material
|
||||
|
||||
# Adds an option to clear currently-bound hotkeys
|
||||
tweak hotkey-clear
|
||||
|
||||
# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving
|
||||
tweak embark-profile-name
|
||||
|
||||
# Reduce performance impact of temperature changes
|
||||
tweak fast-heat 100
|
||||
|
||||
# Misc. UI tweaks
|
||||
tweak block-labors # Prevents labors that can't be used from being toggled
|
||||
tweak burrow-name-cancel
|
||||
tweak cage-butcher
|
||||
tweak civ-view-agreement
|
||||
tweak do-job-now
|
||||
tweak eggs-fertile
|
||||
tweak fps-min
|
||||
tweak hide-priority
|
||||
tweak kitchen-prefs-all
|
||||
tweak kitchen-prefs-empty
|
||||
tweak max-wheelbarrow
|
||||
tweak partial-items
|
||||
tweak shift-8-scroll
|
||||
tweak stone-status-all
|
||||
tweak title-start-rename
|
||||
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
|
||||
|
||||
# Search tool in various screens (by falconne)
|
||||
enable search
|
||||
|
||||
# Improved build material selection interface (by falconne)
|
||||
enable automaterial
|
||||
|
||||
# Other interface improvement tools
|
||||
enable \
|
||||
confirm \
|
||||
dwarfmonitor \
|
||||
mousequery \
|
||||
autogems \
|
||||
autodump \
|
||||
automelt \
|
||||
autotrade \
|
||||
buildingplan \
|
||||
resume \
|
||||
trackstop \
|
||||
zone \
|
||||
stocks \
|
||||
autochop \
|
||||
stockpiles
|
||||
#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command.
|
||||
# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console.
|
||||
# You cannot extend a commented line.
|
||||
# You can comment out the extension of a line.
|
||||
|
||||
# enable mouse controls and sand indicator in embark screen
|
||||
embark-tools enable sticky sand mouse
|
||||
|
||||
# enable option to enter embark assistant
|
||||
enable embark-assistant
|
||||
|
||||
###########
|
||||
# Scripts #
|
||||
###########
|
||||
|
||||
# write extra information to the gamelog
|
||||
modtools/extra-gamelog enable
|
||||
|
||||
# extended status screen (bedrooms page)
|
||||
enable gui/extended-status
|
||||
|
||||
# add information to item viewscreens
|
||||
view-item-info enable
|
||||
|
||||
# a replacement for the "load game" screen
|
||||
gui/load-screen enable
|
@ -0,0 +1,5 @@
|
||||
# Default DFHack commands to run when a world is loaded
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/onLoad.init
|
@ -0,0 +1,6 @@
|
||||
# Default DFHack commands to run when a map is loaded, either in
|
||||
# adventure or fort mode.
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/onMapLoad.init
|
@ -0,0 +1,5 @@
|
||||
# Default DFHack commands to run when a map is unloaded
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/onMapUnload.init
|
@ -0,0 +1,5 @@
|
||||
# Default DFHack commands to run when a world is unloaded
|
||||
|
||||
# Please do not edit this file directly. It will be overwritten with new
|
||||
# defaults when you update DFHack. Instead, add your configuration to
|
||||
# dfhack-config/init/onUnload.init
|
@ -1,4 +1,4 @@
|
||||
NAME Chef
|
||||
NAME library/Chef
|
||||
BUTCHER
|
||||
TANNER
|
||||
COOK
|
@ -1,4 +1,4 @@
|
||||
NAME Craftsdwarf
|
||||
NAME library/Craftsdwarf
|
||||
WOOD_CRAFT
|
||||
STONE_CRAFT
|
||||
BONE_CARVE
|
@ -1,4 +1,4 @@
|
||||
NAME Doctor
|
||||
NAME library/Doctor
|
||||
ANIMALCARE
|
||||
DIAGNOSE
|
||||
SURGERY
|
@ -1,4 +1,4 @@
|
||||
NAME Farmer
|
||||
NAME library/Farmer
|
||||
PLANT
|
||||
MILLER
|
||||
BREWER
|
@ -1,4 +1,4 @@
|
||||
NAME Fisherdwarf
|
||||
NAME library/Fisherdwarf
|
||||
FISH
|
||||
CLEAN_FISH
|
||||
DISSECT_FISH
|
@ -1,4 +1,4 @@
|
||||
NAME Hauler
|
||||
NAME library/Hauler
|
||||
FEED_WATER_CIVILIANS
|
||||
SIEGEOPERATE
|
||||
MECHANIC
|
@ -1,4 +1,4 @@
|
||||
NAME Laborer
|
||||
NAME library/Laborer
|
||||
SOAP_MAKER
|
||||
BURN_WOOD
|
||||
POTASH_MAKING
|
@ -1,4 +1,4 @@
|
||||
NAME Marksdwarf
|
||||
NAME library/Marksdwarf
|
||||
MECHANIC
|
||||
HAUL_STONE
|
||||
HAUL_WOOD
|
@ -1,4 +1,4 @@
|
||||
NAME Mason
|
||||
NAME library/Mason
|
||||
MASON
|
||||
CUT_GEM
|
||||
ENCRUST_GEM
|
@ -1,4 +1,4 @@
|
||||
NAME Meleedwarf
|
||||
NAME library/Meleedwarf
|
||||
RECOVER_WOUNDED
|
||||
MECHANIC
|
||||
HAUL_STONE
|
@ -1,4 +1,4 @@
|
||||
NAME Migrant
|
||||
NAME library/Migrant
|
||||
FEED_WATER_CIVILIANS
|
||||
SIEGEOPERATE
|
||||
MECHANIC
|
@ -1,4 +1,4 @@
|
||||
NAME Miner
|
||||
NAME library/Miner
|
||||
MINE
|
||||
DETAIL
|
||||
RECOVER_WOUNDED
|
@ -1,4 +1,4 @@
|
||||
NAME Outdoorsdwarf
|
||||
NAME library/Outdoorsdwarf
|
||||
CARPENTER
|
||||
BOWYER
|
||||
CUTWOOD
|
@ -1,4 +1,4 @@
|
||||
NAME Smith
|
||||
NAME library/Smith
|
||||
FORGE_WEAPON
|
||||
FORGE_ARMOR
|
||||
FORGE_FURNITURE
|
@ -1,4 +1,4 @@
|
||||
NAME StartManager
|
||||
NAME library/StartManager
|
||||
CUTWOOD
|
||||
ANIMALCARE
|
||||
DIAGNOSE
|
@ -1,4 +1,4 @@
|
||||
NAME Tailor
|
||||
NAME library/Tailor
|
||||
DYER
|
||||
LEATHER
|
||||
WEAVER
|
@ -0,0 +1,7 @@
|
||||
# Load DFHack defaults.
|
||||
#
|
||||
# If you delete this file, it will reappear when you restart DFHack.
|
||||
# Instead, please comment out the following line if you do not want DFHack to
|
||||
# load its default configuration.
|
||||
|
||||
script hack/init/dfhack.default.init
|
@ -0,0 +1,7 @@
|
||||
# Load DFHack defaults.
|
||||
#
|
||||
# If you delete this file, it will reappear when you restart DFHack.
|
||||
# Instead, please comment out the following line if you do not want DFHack to
|
||||
# load its default configuration.
|
||||
|
||||
script hack/init/onLoad.default.init
|
@ -0,0 +1,7 @@
|
||||
# Load DFHack defaults.
|
||||
#
|
||||
# If you delete this file, it will reappear when you restart DFHack.
|
||||
# Instead, please comment out the following line if you do not want DFHack to
|
||||
# load its default configuration.
|
||||
|
||||
script hack/init/onMapLoad.default.init
|
@ -0,0 +1,7 @@
|
||||
# Load DFHack defaults.
|
||||
#
|
||||
# If you delete this file, it will reappear when you restart DFHack.
|
||||
# Instead, please comment out the following line if you do not want DFHack to
|
||||
# load its default configuration.
|
||||
|
||||
script hack/init/onMapUnload.default.init
|
@ -0,0 +1,7 @@
|
||||
# Load DFHack defaults.
|
||||
#
|
||||
# If you delete this file, it will reappear when you restart DFHack.
|
||||
# Instead, please comment out the following line if you do not want DFHack to
|
||||
# load its default configuration.
|
||||
|
||||
script hack/init/onUnload.default.init
|
@ -0,0 +1,5 @@
|
||||
# This file runs when DFHack is initialized, when Dwarf Fortress is first
|
||||
# started, before any world or save data is loaded.
|
||||
#
|
||||
# You can extend or override DFHack's default configuration by adding commands
|
||||
# to this file.
|
@ -0,0 +1,6 @@
|
||||
# This file runs when a world is loaded. This happens when you open a save file
|
||||
# in fort, adventure, or legends mode. If a fort is being loaded, this file runs
|
||||
# before any onMapLoad.init files.
|
||||
#
|
||||
# You can extend or override DFHack's default configuration by adding commands
|
||||
# to this file.
|
@ -0,0 +1,5 @@
|
||||
# This file runs when a map is loaded in adventure or fort mode, after any
|
||||
# onLoad.init files (which run earlier, when the world is loaded).
|
||||
#
|
||||
# You can extend or override DFHack's default configuration by adding commands
|
||||
# to this file.
|
@ -0,0 +1,5 @@
|
||||
# This file runs when a fortress map is unloaded, before any onUnload.init files
|
||||
# (which run later, when the world is unloaded).
|
||||
#
|
||||
# You can extend or override DFHack's default configuration by adding commands
|
||||
# to this file.
|
@ -0,0 +1,4 @@
|
||||
# This file runs when a world is unloaded.
|
||||
#
|
||||
# You can extend or override DFHack's default configuration by adding commands
|
||||
# to this file.
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "Core.h"
|
||||
#include "Hooks.h"
|
||||
#include <iostream>
|
||||
|
||||
// hook - called before rendering
|
||||
DFhackCExport int egg_init(void)
|
||||
{
|
||||
// reroute stderr
|
||||
freopen("stderr.log", "w", stderr);
|
||||
// we don't reroute stdout until we figure out if this should be done at all
|
||||
// See: Console-linux.cpp
|
||||
fprintf(stderr,"dfhack: hooking successful\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// hook - called before rendering
|
||||
DFhackCExport int egg_shutdown(void)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
return c.Shutdown();
|
||||
}
|
||||
|
||||
// hook - called for each game tick (or more often)
|
||||
DFhackCExport int egg_tick(void)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
return c.Update();
|
||||
}
|
||||
// hook - called before rendering
|
||||
DFhackCExport int egg_prerender(void)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
return c.TileUpdate();
|
||||
}
|
||||
|
||||
// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise
|
||||
DFhackCExport int egg_sdl_event(SDL::Event* event)
|
||||
{
|
||||
// if the event is valid, intercept
|
||||
if( event != 0 )
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
return c.DFH_SDL_Event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// return this if you want to kill the event.
|
||||
const int curses_error = -1;
|
||||
// hook - ncurses event, -1 signifies error.
|
||||
DFhackCExport int egg_curses_event(int orig_return)
|
||||
{
|
||||
/*
|
||||
if(orig_return != -1)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
int out;
|
||||
return c.ncurses_wgetch(orig_return,);
|
||||
}*/
|
||||
return orig_return;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef CL_MOD_ENGRAVINGS
|
||||
#define CL_MOD_ENGRAVINGS
|
||||
/*
|
||||
* DF engravings
|
||||
*/
|
||||
#include "Export.h"
|
||||
#include "DataDefs.h"
|
||||
#include "df/engraving.h"
|
||||
/**
|
||||
* \defgroup grp_engraving Engraving module parts
|
||||
* @ingroup grp_modules
|
||||
*/
|
||||
namespace DFHack
|
||||
{
|
||||
namespace Engravings
|
||||
{
|
||||
// "Simplified" copy of engraving
|
||||
struct t_engraving {
|
||||
int32_t artist;
|
||||
int32_t masterpiece_event;
|
||||
int32_t skill_rating;
|
||||
df::coord pos;
|
||||
df::engraving_flags flags;
|
||||
int8_t tile;
|
||||
int32_t art_id;
|
||||
int16_t art_subid;
|
||||
df::item_quality quality;
|
||||
// Pointer to original object, in case you want to modify it
|
||||
df::engraving *origin;
|
||||
};
|
||||
|
||||
DFHACK_EXPORT bool isValid();
|
||||
DFHACK_EXPORT uint32_t getCount();
|
||||
DFHACK_EXPORT bool copyEngraving (const int32_t index, t_engraving &out);
|
||||
DFHACK_EXPORT df::engraving * getEngraving (const int32_t index);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef CL_MOD_NOTES
|
||||
#define CL_MOD_NOTES
|
||||
/**
|
||||
* \defgroup grp_notes In game notes (and routes)
|
||||
* @ingroup grp_notes
|
||||
*/
|
||||
#include "Export.h"
|
||||
#include "Module.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace DFHack
|
||||
{
|
||||
#endif
|
||||
/**
|
||||
* Game's structure for a note.
|
||||
* \ingroup grp_notes
|
||||
*/
|
||||
struct t_note
|
||||
{
|
||||
// First note created has id 0, second has id 1, etc. Not affected
|
||||
// by lower id notes being deleted.
|
||||
uint32_t id; // 0
|
||||
uint8_t symbol; // 4
|
||||
uint8_t unk1; // alignment padding?
|
||||
uint16_t foreground; // 6
|
||||
uint16_t background; // 8
|
||||
uint16_t unk2; // alignment padding?
|
||||
|
||||
std::string name; // C
|
||||
std::string text; // 10
|
||||
|
||||
uint16_t x; // 14
|
||||
uint16_t y; // 16
|
||||
uint16_t z; // 18
|
||||
|
||||
// Is there more?
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
/**
|
||||
* The notes module - allows reading DF in-game notes
|
||||
* \ingroup grp_modules
|
||||
* \ingroup grp_notes
|
||||
*/
|
||||
class DFHACK_EXPORT Notes : public Module
|
||||
{
|
||||
public:
|
||||
Notes();
|
||||
~Notes(){};
|
||||
bool Finish()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::vector<t_note*>* notes;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
@ -1,270 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Export.h"
|
||||
#include <cstddef>
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
namespace Windows
|
||||
{
|
||||
/*
|
||||
* DF window stuffs
|
||||
*/
|
||||
enum df_color
|
||||
{
|
||||
black,
|
||||
blue,
|
||||
green,
|
||||
cyan,
|
||||
red,
|
||||
magenta,
|
||||
brown,
|
||||
lgray,
|
||||
dgray,
|
||||
lblue,
|
||||
lgreen,
|
||||
lcyan,
|
||||
lred,
|
||||
lmagenta,
|
||||
yellow,
|
||||
white
|
||||
// maybe add transparency?
|
||||
};
|
||||
|
||||
// The tile format DF uses internally
|
||||
struct df_screentile
|
||||
{
|
||||
uint8_t symbol;
|
||||
uint8_t foreground; ///< df_color
|
||||
uint8_t background; ///< df_color
|
||||
uint8_t bright;
|
||||
};
|
||||
|
||||
|
||||
// our silly painter things and window things follow.
|
||||
class df_window;
|
||||
struct df_tilebuf
|
||||
{
|
||||
df_screentile * data;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
};
|
||||
|
||||
DFHACK_EXPORT df_screentile *getScreenBuffer();
|
||||
|
||||
class DFHACK_EXPORT painter
|
||||
{
|
||||
friend class df_window;
|
||||
public:
|
||||
df_screentile* get(unsigned int x, unsigned int y)
|
||||
{
|
||||
if(x >= width || y >= height)
|
||||
return 0;
|
||||
return &buffer[x*height + y];
|
||||
};
|
||||
bool set(unsigned int x, unsigned int y, df_screentile tile )
|
||||
{
|
||||
if(x >= width || y >= height)
|
||||
return false;
|
||||
buffer[x*height + y] = tile;
|
||||
return true;
|
||||
}
|
||||
df_color foreground (df_color change = (df_color) -1)
|
||||
{
|
||||
if(change != -1)
|
||||
current_foreground = change;
|
||||
return current_foreground;
|
||||
}
|
||||
df_color background (df_color change = (df_color) -1)
|
||||
{
|
||||
if(change != -1)
|
||||
current_background = change;
|
||||
return current_background;
|
||||
}
|
||||
void bright (bool change)
|
||||
{
|
||||
current_bright = change;
|
||||
}
|
||||
bool bright ()
|
||||
{
|
||||
return current_bright;
|
||||
}
|
||||
void printStr(std::string & str, bool wrap = false)
|
||||
{
|
||||
for ( auto iter = str.begin(); iter != str.end(); iter++)
|
||||
{
|
||||
auto elem = *iter;
|
||||
if(cursor_y >= (int)height)
|
||||
break;
|
||||
if(wrap)
|
||||
{
|
||||
if(cursor_x >= (int)width)
|
||||
cursor_x = wrap_column;
|
||||
}
|
||||
df_screentile & tile = buffer[cursor_x * height + cursor_y];
|
||||
tile.symbol = elem;
|
||||
tile.foreground = current_foreground;
|
||||
tile.background = current_background;
|
||||
tile.bright = current_bright;
|
||||
cursor_x++;
|
||||
}
|
||||
}
|
||||
void set_wrap (int new_column)
|
||||
{
|
||||
wrap_column = new_column;
|
||||
}
|
||||
void gotoxy(unsigned int x, unsigned int y)
|
||||
{
|
||||
cursor_x = x;
|
||||
cursor_y = y;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
cursor_x = 0;
|
||||
cursor_y = 0;
|
||||
current_background = black;
|
||||
current_foreground = white;
|
||||
current_bright = false;
|
||||
wrap_column = 0;
|
||||
}
|
||||
private:
|
||||
painter (df_window * orig, df_screentile * buf, unsigned int width, unsigned int height)
|
||||
{
|
||||
origin = orig;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->buffer = buf;
|
||||
reset();
|
||||
}
|
||||
df_window* origin;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
df_screentile* buffer;
|
||||
// current paint cursor position
|
||||
int cursor_x;
|
||||
int cursor_y;
|
||||
int wrap_column;
|
||||
// current foreground color
|
||||
df_color current_foreground;
|
||||
// current background color
|
||||
df_color current_background;
|
||||
// make bright?
|
||||
bool current_bright;
|
||||
};
|
||||
|
||||
class DFHACK_EXPORT df_window
|
||||
{
|
||||
friend class painter;
|
||||
public:
|
||||
df_window(int x, int y, unsigned int width, unsigned int height);
|
||||
virtual ~df_window();
|
||||
virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) = 0;
|
||||
virtual void paint () = 0;
|
||||
virtual painter * lock();
|
||||
bool unlock (painter * painter);
|
||||
virtual bool addChild(df_window *);
|
||||
virtual df_tilebuf getBuffer() = 0;
|
||||
public:
|
||||
df_screentile* buffer;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
protected:
|
||||
df_window * parent;
|
||||
std::vector <df_window *> children;
|
||||
int left;
|
||||
int top;
|
||||
// FIXME: FAKE
|
||||
bool locked;
|
||||
painter * current_painter;
|
||||
};
|
||||
|
||||
class DFHACK_EXPORT top_level_window : public df_window
|
||||
{
|
||||
public:
|
||||
top_level_window();
|
||||
virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_);
|
||||
virtual void paint ();
|
||||
virtual painter * lock();
|
||||
virtual df_tilebuf getBuffer();
|
||||
};
|
||||
class DFHACK_EXPORT buffered_window : public df_window
|
||||
{
|
||||
public:
|
||||
buffered_window(int x, int y, unsigned int width, unsigned int height):df_window(x,y,width, height)
|
||||
{
|
||||
buffer = new df_screentile[width*height];
|
||||
};
|
||||
virtual ~buffered_window()
|
||||
{
|
||||
delete buffer;
|
||||
}
|
||||
virtual void blit_to_parent ()
|
||||
{
|
||||
df_tilebuf par = parent->getBuffer();
|
||||
for(unsigned xi = 0; xi < width; xi++)
|
||||
{
|
||||
for(unsigned yi = 0; yi < height; yi++)
|
||||
{
|
||||
unsigned parx = left + xi;
|
||||
unsigned pary = top + yi;
|
||||
if(pary >= par.height) continue;
|
||||
if(parx >= par.width) continue;
|
||||
par.data[parx * par.height + pary] = buffer[xi * height + yi];
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual df_tilebuf getBuffer()
|
||||
{
|
||||
df_tilebuf buf;
|
||||
buf.data = buffer;
|
||||
buf.width = width;
|
||||
buf.height = height;
|
||||
return buf;
|
||||
};
|
||||
};
|
||||
class DFHACK_EXPORT dfhack_dummy : public buffered_window
|
||||
{
|
||||
public:
|
||||
dfhack_dummy(int x, int y):buffered_window(x,y,6,1){};
|
||||
virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_)
|
||||
{
|
||||
top = top_;
|
||||
left = left_;
|
||||
return true;
|
||||
}
|
||||
virtual void paint ()
|
||||
{
|
||||
painter * p = lock();
|
||||
p->bright(true);
|
||||
p->background(black);
|
||||
p->foreground(white);
|
||||
std::string dfhack = "DFHack";
|
||||
p->printStr(dfhack);
|
||||
blit_to_parent();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,735 @@
|
||||
-- The help text database and query interface.
|
||||
--
|
||||
-- Help text is read from the rendered text in hack/docs/docs/. If no rendered
|
||||
-- text exists, it is read from the script sources (for scripts) or the string
|
||||
-- passed to the PluginCommand initializer (for plugins).
|
||||
--
|
||||
-- There should be one help file for each plugin that contains a summary for the
|
||||
-- plugin itself and help for all the commands that plugin provides (if any).
|
||||
-- Each script should also have one documentation file.
|
||||
--
|
||||
-- The database is lazy-loaded when an API method is called. It rechecks its
|
||||
-- help sources for updates if an API method has not been called in the last
|
||||
-- 60 seconds.
|
||||
|
||||
local _ENV = mkmodule('helpdb')
|
||||
|
||||
local MAX_STALE_MS = 60000
|
||||
|
||||
-- paths
|
||||
local RENDERED_PATH = 'hack/docs/docs/tools/'
|
||||
local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt'
|
||||
|
||||
-- used when reading help text embedded in script sources
|
||||
local SCRIPT_DOC_BEGIN = '[====['
|
||||
local SCRIPT_DOC_END = ']====]'
|
||||
local SCRIPT_DOC_BEGIN_RUBY = '=begin'
|
||||
local SCRIPT_DOC_END_RUBY = '=end'
|
||||
|
||||
-- enums
|
||||
local ENTRY_TYPES = {
|
||||
BUILTIN='builtin',
|
||||
PLUGIN='plugin',
|
||||
COMMAND='command'
|
||||
}
|
||||
|
||||
local HELP_SOURCES = {
|
||||
RENDERED='rendered', -- from the installed, rendered help text
|
||||
PLUGIN='plugin', -- from the plugin source code
|
||||
SCRIPT='script', -- from the script source code
|
||||
STUB='stub', -- from a generated stub
|
||||
}
|
||||
|
||||
-- builtin command names, with aliases mapped to their canonical form
|
||||
local BUILTINS = {
|
||||
['?']='help',
|
||||
alias=true,
|
||||
clear='cls',
|
||||
cls=true,
|
||||
['devel/dump-rpc']=true,
|
||||
die=true,
|
||||
dir='ls',
|
||||
disable=true,
|
||||
enable=true,
|
||||
fpause=true,
|
||||
help=true,
|
||||
hide=true,
|
||||
keybinding=true,
|
||||
['kill-lua']=true,
|
||||
['load']=true,
|
||||
ls=true,
|
||||
man='help',
|
||||
plug=true,
|
||||
reload=true,
|
||||
script=true,
|
||||
['sc-script']=true,
|
||||
show=true,
|
||||
tags=true,
|
||||
['type']=true,
|
||||
unload=true,
|
||||
}
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- data structures
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- help text database, keys are a subset of the entry database
|
||||
-- entry name -> {
|
||||
-- help_source (element of HELP_SOURCES),
|
||||
-- short_help (string),
|
||||
-- long_help (string),
|
||||
-- tags (set),
|
||||
-- source_timestamp (mtime, 0 for non-files),
|
||||
-- source_path (string, nil for non-files)
|
||||
-- }
|
||||
local textdb = {}
|
||||
|
||||
-- entry database, points to text in textdb
|
||||
-- entry name -> {
|
||||
-- entry_types (set of ENTRY_TYPES),
|
||||
-- short_help (string, if not nil then overrides short_help in text_entry),
|
||||
-- text_entry (string)
|
||||
-- }
|
||||
--
|
||||
-- entry_types is a set because plugin commands can also be the plugin names.
|
||||
local entrydb = {}
|
||||
|
||||
|
||||
-- tag name -> list of entry names
|
||||
-- Tags defined in the TAG_DEFINITIONS file that have no associated db entries
|
||||
-- will have an empty list.
|
||||
local tag_index = {}
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- data ingestion
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
local function get_rendered_path(entry_name)
|
||||
return RENDERED_PATH .. entry_name .. '.txt'
|
||||
end
|
||||
|
||||
local function has_rendered_help(entry_name)
|
||||
return dfhack.filesystem.mtime(get_rendered_path(entry_name)) ~= -1
|
||||
end
|
||||
|
||||
local DEFAULT_HELP_TEMPLATE = [[
|
||||
%s
|
||||
%s
|
||||
|
||||
No help available.
|
||||
]]
|
||||
local function make_default_entry(entry_name, help_source, kwargs)
|
||||
local default_long_help = DEFAULT_HELP_TEMPLATE:format(
|
||||
entry_name, ('*'):rep(#entry_name))
|
||||
return {
|
||||
help_source=help_source,
|
||||
short_help='No help available.',
|
||||
long_help=default_long_help,
|
||||
tags={},
|
||||
source_timestamp=kwargs.source_timestamp or 0,
|
||||
source_path=kwargs.source_path}
|
||||
end
|
||||
|
||||
-- updates the short_text, the long_text, and the tags in the given entry based
|
||||
-- on the text returned from the iterator.
|
||||
-- if defined, opts can have the following fields:
|
||||
-- begin_marker (string that marks the beginning of the help text; all text
|
||||
-- before this marker is ignored)
|
||||
-- end_marker (string that marks the end of the help text; text will stop
|
||||
-- being parsed after this marker is seen)
|
||||
-- no_header (don't try to find the entity name at the top of the help text)
|
||||
-- first_line_is_short_help (if set, then read the short help text from the
|
||||
-- first commented line of the script instead of
|
||||
-- using the first sentence of the long help text.
|
||||
-- value is the comment character.)
|
||||
local function update_entry(entry, iterator, opts)
|
||||
opts = opts or {}
|
||||
local lines, tags = {}, ''
|
||||
local first_line_is_short_help = opts.first_line_is_short_help
|
||||
local begin_marker_found,header_found = not opts.begin_marker,opts.no_header
|
||||
local tags_found, short_help_found = false, opts.skip_short_help
|
||||
local in_tags, in_short_help = false, false
|
||||
for line in iterator do
|
||||
if not short_help_found and first_line_is_short_help then
|
||||
line = line:trim()
|
||||
local _,_,text = line:find('^'..first_line_is_short_help..'%s*(.*)')
|
||||
if not text then
|
||||
-- if no first-line short help found, fall back to getting the
|
||||
-- first sentence of the help text.
|
||||
first_line_is_short_help = false
|
||||
else
|
||||
if not text:endswith('.') then
|
||||
text = text .. '.'
|
||||
end
|
||||
entry.short_help = text
|
||||
short_help_found = true
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
if not begin_marker_found then
|
||||
local _, endpos = line:find(opts.begin_marker, 1, true)
|
||||
if endpos == #line then
|
||||
begin_marker_found = true
|
||||
end
|
||||
goto continue
|
||||
end
|
||||
if opts.end_marker then
|
||||
local _, endpos = line:find(opts.end_marker, 1, true)
|
||||
if endpos == #line then
|
||||
break
|
||||
end
|
||||
end
|
||||
if not header_found and line:find('%w') then
|
||||
header_found = true
|
||||
elseif in_tags then
|
||||
if #line == 0 then
|
||||
in_tags = false
|
||||
else
|
||||
tags = tags .. line
|
||||
end
|
||||
elseif not tags_found and line:find('^[*]*Tags:[*]*') then
|
||||
_,_,tags = line:trim():find('[*]*Tags:[*]* *(.*)')
|
||||
in_tags, tags_found = true, true
|
||||
elseif not short_help_found and
|
||||
line:find('^%w') then
|
||||
if in_short_help then
|
||||
entry.short_help = entry.short_help .. ' ' .. line
|
||||
else
|
||||
entry.short_help = line
|
||||
end
|
||||
local sentence_end = entry.short_help:find('.', 1, true)
|
||||
if sentence_end then
|
||||
entry.short_help = entry.short_help:sub(1, sentence_end)
|
||||
short_help_found = true
|
||||
else
|
||||
in_short_help = true
|
||||
end
|
||||
end
|
||||
table.insert(lines, line)
|
||||
::continue::
|
||||
end
|
||||
entry.tags = {}
|
||||
for _,tag in ipairs(tags:split('[ ,|]+')) do
|
||||
if #tag > 0 and tag_index[tag] then
|
||||
entry.tags[tag] = true
|
||||
end
|
||||
end
|
||||
if #lines > 0 then
|
||||
entry.long_help = table.concat(lines, '\n')
|
||||
end
|
||||
end
|
||||
|
||||
-- create db entry based on parsing sphinx-rendered help text
|
||||
local function make_rendered_entry(old_entry, entry_name, kwargs)
|
||||
local source_path = get_rendered_path(entry_name)
|
||||
local source_timestamp = dfhack.filesystem.mtime(source_path)
|
||||
if old_entry and old_entry.help_source == HELP_SOURCES.RENDERED and
|
||||
old_entry.source_timestamp >= source_timestamp then
|
||||
-- we already have the latest info
|
||||
return old_entry
|
||||
end
|
||||
kwargs.source_path, kwargs.source_timestamp = source_path, source_timestamp
|
||||
local entry = make_default_entry(entry_name, HELP_SOURCES.RENDERED, kwargs)
|
||||
local ok, lines = pcall(io.lines, source_path)
|
||||
if not ok then
|
||||
return entry
|
||||
end
|
||||
update_entry(entry, lines)
|
||||
return entry
|
||||
end
|
||||
|
||||
-- create db entry based on the help text in the plugin source (used by
|
||||
-- out-of-tree plugins)
|
||||
local function make_plugin_entry(old_entry, entry_name, kwargs)
|
||||
if old_entry and old_entry.source == HELP_SOURCES.PLUGIN then
|
||||
-- we can't tell when a plugin is reloaded, so we can either choose to
|
||||
-- always refresh or never refresh. let's go with never for now for
|
||||
-- performance.
|
||||
return old_entry
|
||||
end
|
||||
local entry = make_default_entry(entry_name, HELP_SOURCES.PLUGIN, kwargs)
|
||||
local long_help = dfhack.internal.getCommandHelp(entry_name)
|
||||
if long_help and #long_help:trim() > 0 then
|
||||
update_entry(entry, long_help:trim():gmatch('[^\n]*'), {no_header=true})
|
||||
end
|
||||
return entry
|
||||
end
|
||||
|
||||
-- create db entry based on the help text in the script source (used by
|
||||
-- out-of-tree scripts)
|
||||
local function make_script_entry(old_entry, entry_name, kwargs)
|
||||
local source_path = kwargs.source_path
|
||||
local source_timestamp = dfhack.filesystem.mtime(source_path)
|
||||
if old_entry and old_entry.source == HELP_SOURCES.SCRIPT and
|
||||
old_entry.source_path == source_path and
|
||||
old_entry.source_timestamp >= source_timestamp then
|
||||
-- we already have the latest info
|
||||
return old_entry
|
||||
end
|
||||
kwargs.source_timestamp, kwargs.entry_type = source_timestamp
|
||||
local entry = make_default_entry(entry_name, HELP_SOURCES.SCRIPT, kwargs)
|
||||
local ok, lines = pcall(io.lines, source_path)
|
||||
if not ok then
|
||||
return entry
|
||||
end
|
||||
local is_rb = source_path:endswith('.rb')
|
||||
update_entry(entry, lines,
|
||||
{begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN),
|
||||
end_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_END),
|
||||
first_line_is_short_help=(is_rb and '#' or '%-%-')})
|
||||
return entry
|
||||
end
|
||||
|
||||
-- updates the dbs (and associated tag index) with a new entry if the entry_name
|
||||
-- doesn't already exist in the dbs.
|
||||
local function update_db(old_db, entry_name, text_entry, help_source, kwargs)
|
||||
if entrydb[entry_name] then
|
||||
-- already in db (e.g. from a higher-priority script dir); skip
|
||||
return
|
||||
end
|
||||
entrydb[entry_name] = {
|
||||
entry_types=kwargs.entry_types,
|
||||
short_help=kwargs.short_help,
|
||||
text_entry=text_entry
|
||||
}
|
||||
if entry_name ~= text_entry then
|
||||
return
|
||||
end
|
||||
|
||||
local text_entry, old_entry = nil, old_db[entry_name]
|
||||
if help_source == HELP_SOURCES.RENDERED then
|
||||
text_entry = make_rendered_entry(old_entry, entry_name, kwargs)
|
||||
elseif help_source == HELP_SOURCES.PLUGIN then
|
||||
text_entry = make_plugin_entry(old_entry, entry_name, kwargs)
|
||||
elseif help_source == HELP_SOURCES.SCRIPT then
|
||||
text_entry = make_script_entry(old_entry, entry_name, kwargs)
|
||||
elseif help_source == HELP_SOURCES.STUB then
|
||||
text_entry = make_default_entry(entry_name, HELP_SOURCES.STUB, kwargs)
|
||||
else
|
||||
error('unhandled help source: ' .. help_source)
|
||||
end
|
||||
textdb[entry_name] = text_entry
|
||||
end
|
||||
|
||||
-- add the builtin commands to the db
|
||||
local function scan_builtins(old_db)
|
||||
local entry_types = {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true}
|
||||
for builtin,canonical in pairs(BUILTINS) do
|
||||
if canonical == true then canonical = builtin end
|
||||
update_db(old_db, builtin, canonical,
|
||||
has_rendered_help(canonical) and
|
||||
HELP_SOURCES.RENDERED or HELP_SOURCES.STUB,
|
||||
{entry_types=entry_types})
|
||||
end
|
||||
end
|
||||
|
||||
-- scan for enableable plugins and plugin-provided commands and add their help
|
||||
-- to the db
|
||||
local function scan_plugins(old_db)
|
||||
local plugin_names = dfhack.internal.listPlugins()
|
||||
for _,plugin in ipairs(plugin_names) do
|
||||
local commands = dfhack.internal.listCommands(plugin)
|
||||
local includes_plugin = false
|
||||
for _,command in ipairs(commands) do
|
||||
local kwargs = {entry_types={[ENTRY_TYPES.COMMAND]=true}}
|
||||
if command == plugin then
|
||||
kwargs.entry_types[ENTRY_TYPES.PLUGIN]=true
|
||||
includes_plugin = true
|
||||
end
|
||||
kwargs.short_help = dfhack.internal.getCommandDescription(command)
|
||||
update_db(old_db, command, plugin,
|
||||
has_rendered_help(plugin) and
|
||||
HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN,
|
||||
kwargs)
|
||||
end
|
||||
if not includes_plugin then
|
||||
update_db(old_db, plugin, plugin,
|
||||
has_rendered_help(plugin) and
|
||||
HELP_SOURCES.RENDERED or HELP_SOURCES.STUB,
|
||||
{entry_types={[ENTRY_TYPES.PLUGIN]=true}})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- scan for scripts and add their help to the db
|
||||
local function scan_scripts(old_db)
|
||||
local entry_types = {[ENTRY_TYPES.COMMAND]=true}
|
||||
for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do
|
||||
local files = dfhack.filesystem.listdir_recursive(
|
||||
script_path, nil, false)
|
||||
if not files then goto skip_path end
|
||||
for _,f in ipairs(files) do
|
||||
if f.isdir or
|
||||
(not f.path:endswith('.lua') and not f.path:endswith('.rb')) or
|
||||
f.path:startswith('test/') or
|
||||
f.path:startswith('internal/') then
|
||||
goto continue
|
||||
end
|
||||
local dot_index = f.path:find('%.[^.]*$')
|
||||
local entry_name = f.path:sub(1, dot_index - 1)
|
||||
local source_path = script_path .. '/' .. f.path
|
||||
update_db(old_db, entry_name, entry_name,
|
||||
has_rendered_help(entry_name) and
|
||||
HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT,
|
||||
{entry_types=entry_types, source_path=source_path})
|
||||
::continue::
|
||||
end
|
||||
::skip_path::
|
||||
end
|
||||
end
|
||||
|
||||
-- read tags and descriptions from the TAG_DEFINITIONS file and add them all
|
||||
-- to tag_index, initizlizing each entry with an empty list.
|
||||
local function initialize_tags()
|
||||
local tag, desc, in_desc = nil, nil, false
|
||||
local ok, lines = pcall(io.lines, TAG_DEFINITIONS)
|
||||
if not ok then return end
|
||||
for line in lines do
|
||||
if in_desc then
|
||||
line = line:trim()
|
||||
if #line == 0 then
|
||||
in_desc = false
|
||||
goto continue
|
||||
end
|
||||
desc = desc .. ' ' .. line
|
||||
tag_index[tag].description = desc
|
||||
else
|
||||
_,_,tag,desc = line:find('^%* (%w+): (.+)')
|
||||
if not tag then goto continue end
|
||||
tag_index[tag] = {description=desc}
|
||||
in_desc = true
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
local function index_tags()
|
||||
for entry_name,entry in pairs(entrydb) do
|
||||
for tag in pairs(textdb[entry.text_entry].tags) do
|
||||
-- ignore unknown tags
|
||||
if tag_index[tag] then
|
||||
table.insert(tag_index[tag], entry_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ensures the db is up to date by scanning all help sources. does not do
|
||||
-- anything if it has already been run within the last MAX_STALE_MS milliseconds
|
||||
local last_refresh_ms = 0
|
||||
local function ensure_db()
|
||||
local now_ms = dfhack.getTickCount()
|
||||
if now_ms - last_refresh_ms <= MAX_STALE_MS then return end
|
||||
last_refresh_ms = now_ms
|
||||
|
||||
local old_db = textdb
|
||||
textdb, entrydb, tag_index = {}, {}, {}
|
||||
|
||||
initialize_tags()
|
||||
scan_builtins(old_db)
|
||||
scan_plugins(old_db)
|
||||
scan_scripts(old_db)
|
||||
index_tags()
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- get API
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- converts strings into single-element lists containing that string
|
||||
local function normalize_string_list(l)
|
||||
if not l or #l == 0 then return nil end
|
||||
if type(l) == 'string' then
|
||||
return {l}
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
local function has_keys(str, dict)
|
||||
if not str or #str == 0 then
|
||||
return false
|
||||
end
|
||||
for _,s in ipairs(normalize_string_list(str)) do
|
||||
if not dict[s] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- returns whether the given string (or list of strings) is an entry (are all
|
||||
-- entries) in the db
|
||||
function is_entry(str)
|
||||
ensure_db()
|
||||
return has_keys(str, entrydb)
|
||||
end
|
||||
|
||||
local function get_db_property(entry_name, property)
|
||||
ensure_db()
|
||||
if not entrydb[entry_name] then
|
||||
error(('helpdb entry not found: "%s"'):format(entry_name))
|
||||
end
|
||||
return entrydb[entry_name][property] or
|
||||
textdb[entrydb[entry_name].text_entry][property]
|
||||
end
|
||||
|
||||
function get_entry_types(entry)
|
||||
return get_db_property(entry, 'entry_types')
|
||||
end
|
||||
|
||||
-- returns the ~54 char summary blurb associated with the entry
|
||||
function get_entry_short_help(entry)
|
||||
return get_db_property(entry, 'short_help')
|
||||
end
|
||||
|
||||
-- returns the full help documentation associated with the entry
|
||||
function get_entry_long_help(entry)
|
||||
return get_db_property(entry, 'long_help')
|
||||
end
|
||||
|
||||
-- returns the set of tags associated with the entry
|
||||
function get_entry_tags(entry)
|
||||
return get_db_property(entry, 'tags')
|
||||
end
|
||||
|
||||
-- returns whether the given string (or list of strings) matches a tag name
|
||||
function is_tag(str)
|
||||
ensure_db()
|
||||
return has_keys(str, tag_index)
|
||||
end
|
||||
|
||||
local function set_to_sorted_list(set)
|
||||
local list = {}
|
||||
for item in pairs(set) do
|
||||
table.insert(list, item)
|
||||
end
|
||||
table.sort(list)
|
||||
return list
|
||||
end
|
||||
|
||||
-- returns the defined tags in alphabetical order
|
||||
function get_tags()
|
||||
ensure_db()
|
||||
return set_to_sorted_list(tag_index)
|
||||
end
|
||||
|
||||
function get_tag_data(tag)
|
||||
ensure_db()
|
||||
if not tag_index[tag] then
|
||||
error(('helpdb tag not found: "%s"'):format(tag))
|
||||
end
|
||||
return tag_index[tag]
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- search API
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- returns a list of path elements in reverse order
|
||||
local function chunk_for_sorting(str)
|
||||
local parts = str:split('/')
|
||||
local chunks = {}
|
||||
for i=1,#parts do
|
||||
chunks[#parts - i + 1] = parts[i]
|
||||
end
|
||||
return chunks
|
||||
end
|
||||
|
||||
-- sorts by last path component, then by parent path components.
|
||||
-- something comes before nothing.
|
||||
-- e.g. gui/autofarm comes immediately before autofarm
|
||||
function sort_by_basename(a, b)
|
||||
local a = chunk_for_sorting(a)
|
||||
local b = chunk_for_sorting(b)
|
||||
local i = 1
|
||||
while a[i] do
|
||||
if not b[i] then
|
||||
return true
|
||||
end
|
||||
if a[i] ~= b[i] then
|
||||
return a[i] < b[i]
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function matches(entry_name, filter)
|
||||
if filter.tag then
|
||||
local matched = false
|
||||
local tags = get_db_property(entry_name, 'tags')
|
||||
for _,tag in ipairs(filter.tag) do
|
||||
if tags[tag] then
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then
|
||||
return false
|
||||
end
|
||||
end
|
||||
if filter.entry_type then
|
||||
local matched = false
|
||||
local etypes = get_db_property(entry_name, 'entry_types')
|
||||
for _,etype in ipairs(filter.entry_type) do
|
||||
if etypes[etype] then
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then
|
||||
return false
|
||||
end
|
||||
end
|
||||
if filter.str then
|
||||
local matched = false
|
||||
for _,str in ipairs(filter.str) do
|
||||
if entry_name:find(str, 1, true) then
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- normalizes the lists in the filter and returns nil if no filter elements are
|
||||
-- populated
|
||||
local function normalize_filter(f)
|
||||
if not f then return nil end
|
||||
local filter = {}
|
||||
filter.str = normalize_string_list(f.str)
|
||||
filter.tag = normalize_string_list(f.tag)
|
||||
filter.entry_type = normalize_string_list(f.entry_type)
|
||||
if not filter.str and not filter.tag and not filter.entry_type then
|
||||
return nil
|
||||
end
|
||||
return filter
|
||||
end
|
||||
|
||||
-- returns a list of entry names, alphabetized by their last path component,
|
||||
-- with populated path components coming before null path components (e.g.
|
||||
-- autobutcher will immediately follow gui/autobutcher).
|
||||
-- the optional include and exclude filter params are maps with the following
|
||||
-- elements:
|
||||
-- str - if a string, filters by the given substring. if a table of strings,
|
||||
-- includes entry names that match any of the given substrings.
|
||||
-- tag - if a string, filters by the given tag name. if a table of strings,
|
||||
-- includes entries that match any of the given tags.
|
||||
-- entry_type - if a string, matches entries of the given type. if a table of
|
||||
-- strings, includes entries that match any of the given types. valid
|
||||
-- types are: "builtin", "plugin", "command". note that many plugin
|
||||
-- commands have the same name as the plugin, so those entries will
|
||||
-- match both "plugin" and "command" types.
|
||||
function search_entries(include, exclude)
|
||||
ensure_db()
|
||||
include = normalize_filter(include)
|
||||
exclude = normalize_filter(exclude)
|
||||
local entries = {}
|
||||
for entry in pairs(entrydb) do
|
||||
if (not include or matches(entry, include)) and
|
||||
(not exclude or not matches(entry, exclude)) then
|
||||
table.insert(entries, entry)
|
||||
end
|
||||
end
|
||||
table.sort(entries, sort_by_basename)
|
||||
return entries
|
||||
end
|
||||
|
||||
-- returns a list of all commands. used by Core's autocomplete functionality.
|
||||
function get_commands()
|
||||
local include = {entry_type=ENTRY_TYPES.COMMAND}
|
||||
return search_entries(include)
|
||||
end
|
||||
|
||||
function is_builtin(command)
|
||||
return is_entry(command) and get_entry_types(command)[ENTRY_TYPES.BUILTIN]
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- print API (outputs to console)
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
-- implements the 'help' builtin command
|
||||
function help(entry)
|
||||
ensure_db()
|
||||
if not entrydb[entry] then
|
||||
dfhack.printerr(('No help entry found for "%s"'):format(entry))
|
||||
return
|
||||
end
|
||||
print(get_entry_long_help(entry))
|
||||
end
|
||||
|
||||
-- prints col1text (width 21), a one space gap, and col2 (width 58)
|
||||
-- if col1text is longer than 21 characters, col2text is printed starting on the
|
||||
-- next line. if col2text is longer than 58 characters, it is wrapped. col2text
|
||||
-- lines on lines below the col1text output are indented by one space further
|
||||
-- than the col2text on the first line.
|
||||
local COL1WIDTH, COL2WIDTH = 20, 58
|
||||
local function print_columns(col1text, col2text)
|
||||
col2text = col2text:wrap(COL2WIDTH)
|
||||
local wrapped_col2 = {}
|
||||
for line in col2text:gmatch('[^'..NEWLINE..']*') do
|
||||
table.insert(wrapped_col2, line)
|
||||
end
|
||||
local col2_start_line = 1
|
||||
if #col1text > COL1WIDTH then
|
||||
print(col1text)
|
||||
else
|
||||
print(('%-'..COL1WIDTH..'s %s'):format(col1text, wrapped_col2[1]))
|
||||
col2_start_line = 2
|
||||
end
|
||||
for i=col2_start_line,#wrapped_col2 do
|
||||
print(('%'..COL1WIDTH..'s %s'):format(' ', wrapped_col2[i]))
|
||||
end
|
||||
end
|
||||
|
||||
-- implements the 'tags' builtin command
|
||||
function tags()
|
||||
local tags = get_tags()
|
||||
for _,tag in ipairs(tags) do
|
||||
print_columns(tag, get_tag_data(tag).description)
|
||||
end
|
||||
end
|
||||
|
||||
-- prints the requested entries to the console. include and exclude filters are
|
||||
-- defined as in search_entries() above.
|
||||
local function list_entries(skip_tags, include, exclude)
|
||||
local entries = search_entries(include, exclude)
|
||||
for _,entry in ipairs(entries) do
|
||||
print_columns(entry, get_entry_short_help(entry))
|
||||
if not skip_tags then
|
||||
local tags = set_to_sorted_list(get_entry_tags(entry))
|
||||
if #tags > 0 then
|
||||
print((' tags: %s'):format(table.concat(tags, ', ')))
|
||||
end
|
||||
end
|
||||
end
|
||||
if #entries == 0 then
|
||||
print('No matches.')
|
||||
end
|
||||
end
|
||||
|
||||
-- wraps the list_entries() API to provide a more convenient interface for Core
|
||||
-- to implement the 'ls' builtin command.
|
||||
-- filter_str - if a tag name, will filter by that tag. otherwise, will filter
|
||||
-- as a substring
|
||||
-- skip_tags - whether to skip printing tag info
|
||||
-- show_dev_commands - if true, will include scripts in the modtools/ and
|
||||
-- devel/ directories. otherwise those scripts will be
|
||||
-- excluded
|
||||
function ls(filter_str, skip_tags, show_dev_commands)
|
||||
local include = {entry_type={ENTRY_TYPES.COMMAND}}
|
||||
if is_tag(filter_str) then
|
||||
include.tag = filter_str
|
||||
else
|
||||
include.str = filter_str
|
||||
end
|
||||
list_entries(skip_tags, include,
|
||||
show_dev_commands and {} or {tag='dev'})
|
||||
end
|
||||
|
||||
return _ENV
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
|
||||
#include "Internal.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
using namespace std;
|
||||
|
||||
#include "VersionInfo.h"
|
||||
#include "MemAccess.h"
|
||||
#include "Types.h"
|
||||
#include "Core.h"
|
||||
|
||||
#include "modules/Engravings.h"
|
||||
#include "df/world.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using df::global::world;
|
||||
|
||||
bool Engravings::isValid()
|
||||
{
|
||||
return (world != NULL);
|
||||
}
|
||||
|
||||
uint32_t Engravings::getCount()
|
||||
{
|
||||
return world->engravings.size();
|
||||
}
|
||||
|
||||
df::engraving * Engravings::getEngraving(int index)
|
||||
{
|
||||
if (uint32_t(index) >= getCount())
|
||||
return NULL;
|
||||
return world->engravings[index];
|
||||
}
|
||||
|
||||
bool Engravings::copyEngraving(const int32_t index, t_engraving &out)
|
||||
{
|
||||
if (uint32_t(index) >= getCount())
|
||||
return false;
|
||||
|
||||
out.origin = world->engravings[index];
|
||||
|
||||
out.artist = out.origin->artist;
|
||||
out.masterpiece_event = out.origin->masterpiece_event;
|
||||
out.skill_rating = out.origin->skill_rating;
|
||||
out.pos = out.origin->pos;
|
||||
out.flags = out.origin->flags;
|
||||
out.tile = out.origin->tile;
|
||||
out.art_id = out.origin->art_id;
|
||||
out.art_subid = out.origin->art_subid;
|
||||
out.quality = out.origin->quality;
|
||||
return true;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
www.sourceforge.net/projects/dfhack
|
||||
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#include "Internal.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
using namespace std;
|
||||
|
||||
#include "VersionInfo.h"
|
||||
#include "Types.h"
|
||||
#include "Error.h"
|
||||
#include "MemAccess.h"
|
||||
#include "MiscUtils.h"
|
||||
#include "ModuleFactory.h"
|
||||
#include "Core.h"
|
||||
#include "modules/Notes.h"
|
||||
#include <DataDefs.h>
|
||||
#include "df/ui.h"
|
||||
using namespace DFHack;
|
||||
|
||||
std::unique_ptr<Module> DFHack::createNotes()
|
||||
{
|
||||
return dts::make_unique<Notes>();
|
||||
}
|
||||
|
||||
// FIXME: not even a wrapper now
|
||||
Notes::Notes()
|
||||
{
|
||||
notes = (std::vector<t_note*>*) &df::global::ui->waypoints.points;
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#include "Export.h"
|
||||
#include "Module.h"
|
||||
#include "BitArray.h"
|
||||
#include <string>
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/init.h"
|
||||
#include "df/ui.h"
|
||||
#include <df/graphic.h>
|
||||
#include "modules/Windows.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using df::global::gps;
|
||||
|
||||
Windows::df_screentile *Windows::getScreenBuffer()
|
||||
{
|
||||
if (!gps) return NULL;
|
||||
return (df_screentile *) gps->screen;
|
||||
}
|
||||
|
||||
Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height)
|
||||
:buffer(0), width(width), height(height), parent(0), left(x), top(y), current_painter(NULL)
|
||||
{
|
||||
buffer = 0;
|
||||
};
|
||||
Windows::df_window::~df_window()
|
||||
{
|
||||
for(auto iter = children.begin();iter != children.end();iter++)
|
||||
{
|
||||
delete *iter;
|
||||
}
|
||||
children.clear();
|
||||
};
|
||||
Windows::painter * Windows::df_window::lock()
|
||||
{
|
||||
locked = true;
|
||||
current_painter = new Windows::painter(this,buffer,width, height);
|
||||
return current_painter;
|
||||
};
|
||||
|
||||
bool Windows::df_window::addChild( df_window * child)
|
||||
{
|
||||
children.push_back(child);
|
||||
child->parent = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Windows::df_window::unlock (painter * painter)
|
||||
{
|
||||
if(current_painter == painter)
|
||||
{
|
||||
delete current_painter;
|
||||
current_painter = 0;
|
||||
locked = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Windows::top_level_window::top_level_window() : df_window(0,0,gps ? gps->dimx : 80,gps ? gps->dimy : 25)
|
||||
{
|
||||
buffer = 0;
|
||||
}
|
||||
|
||||
bool Windows::top_level_window::move (int left_, int top_, unsigned int width_, unsigned int height_)
|
||||
{
|
||||
width = width_;
|
||||
height = height_;
|
||||
// what if we are painting already? Is that possible?
|
||||
return true;
|
||||
};
|
||||
|
||||
Windows::painter * Windows::top_level_window::lock()
|
||||
{
|
||||
buffer = getScreenBuffer();
|
||||
return df_window::lock();
|
||||
}
|
||||
|
||||
void Windows::top_level_window::paint ()
|
||||
{
|
||||
for(auto iter = children.begin();iter != children.end();iter++)
|
||||
{
|
||||
(*iter)->paint();
|
||||
}
|
||||
};
|
||||
|
||||
Windows::df_tilebuf Windows::top_level_window::getBuffer()
|
||||
{
|
||||
df_tilebuf buf;
|
||||
buf.data = getScreenBuffer();
|
||||
buf.height = df::global::gps->dimy;
|
||||
buf.width = df::global::gps->dimx;
|
||||
return buf;
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit ef5c180ff220e53ec6b51e01ca3594a5780b8b4b
|
||||
Subproject commit dc118c5e90aea6181a290e4bbf40e7f2974fb053
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,430 @@
|
||||
// - full automation of handling mini-pastures over nestboxes:
|
||||
// go through all pens, check if they are empty and placed over a nestbox
|
||||
// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture
|
||||
// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds
|
||||
// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used)
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "df/building_cagest.h"
|
||||
#include "df/building_civzonest.h"
|
||||
#include "df/building_nest_boxst.h"
|
||||
#include "df/general_ref_building_civzone_assignedst.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include "Debug.h"
|
||||
#include "LuaTools.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/Units.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("autonestbox");
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
static const string autonestbox_help =
|
||||
"Assigns unpastured female egg-layers to nestbox zones.\n"
|
||||
"Requires that you create pen/pasture zones above nestboxes.\n"
|
||||
"If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n"
|
||||
"Only 1 unit will be assigned per pen, regardless of the size.\n"
|
||||
"The age of the units is currently not checked, most birds grow up quite fast.\n"
|
||||
"Usage:\n"
|
||||
"\n"
|
||||
"enable autonestbox\n"
|
||||
" Start checking for unpastured egg-layers and assigning them to nestbox zones.\n"
|
||||
"autonestbox\n"
|
||||
" Print current status."
|
||||
"autonestbox now\n"
|
||||
" Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n"
|
||||
"autonestbox ticks <ticks>\n"
|
||||
" Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n"
|
||||
" The default is 6000 (about 8 days)\n";
|
||||
|
||||
namespace DFHack {
|
||||
// for configuration-related logging
|
||||
DBG_DECLARE(autonestbox, status, DebugCategory::LINFO);
|
||||
// for logging during the periodic scan
|
||||
DBG_DECLARE(autonestbox, 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 bool did_complain = false; // avoids message spam
|
||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||
|
||||
static command_result df_autonestbox(color_ostream &out, vector<string> ¶meters);
|
||||
static void autonestbox_cycle(color_ostream &out);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand(
|
||||
plugin_name,
|
||||
"Auto-assign egg-laying female pets to nestbox zones.",
|
||||
df_autonestbox,
|
||||
false,
|
||||
autonestbox_help.c_str()));
|
||||
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_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");
|
||||
did_complain = 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))
|
||||
autonestbox_cycle(out);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// configuration interface
|
||||
//
|
||||
|
||||
struct autonestbox_options {
|
||||
// whether to display help
|
||||
bool help = false;
|
||||
|
||||
// whether to run a cycle right now
|
||||
bool now = false;
|
||||
|
||||
// how many ticks to wait between automatic cycles, -1 means unset
|
||||
int32_t ticks = -1;
|
||||
|
||||
static struct_identity _identity;
|
||||
};
|
||||
static const struct_field_info autonestbox_options_fields[] = {
|
||||
{ struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits<bool>::identity, 0, 0 },
|
||||
{ struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits<bool>::identity, 0, 0 },
|
||||
{ struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits<int32_t>::identity, 0, 0 },
|
||||
{ struct_field_info::END }
|
||||
};
|
||||
struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn<autonestbox_options>, NULL, "autonestbox_options", NULL, autonestbox_options_fields);
|
||||
|
||||
static bool get_options(color_ostream &out,
|
||||
autonestbox_options &opts,
|
||||
const vector<string> ¶meters)
|
||||
{
|
||||
auto L = Lua::Core::State;
|
||||
Lua::StackUnwinder top(L);
|
||||
|
||||
if (!lua_checkstack(L, parameters.size() + 2) ||
|
||||
!Lua::PushModulePublic(
|
||||
out, L, "plugins.autonestbox", "parse_commandline")) {
|
||||
out.printerr("Failed to load autonestbox Lua code\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Lua::Push(L, &opts);
|
||||
for (const string ¶m : parameters)
|
||||
Lua::Push(L, param);
|
||||
|
||||
if (!Lua::SafeCall(out, L, parameters.size() + 1, 0))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static command_result df_autonestbox(color_ostream &out, vector<string> ¶meters) {
|
||||
CoreSuspender suspend;
|
||||
|
||||
if (!Core::getInstance().isWorldLoaded()) {
|
||||
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
autonestbox_options opts;
|
||||
if (!get_options(out, opts, parameters) || opts.help)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
if (opts.ticks > -1) {
|
||||
set_config_val(CONFIG_CYCLE_TICKS, opts.ticks);
|
||||
INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks);
|
||||
}
|
||||
else if (opts.now) {
|
||||
autonestbox_cycle(out);
|
||||
}
|
||||
else {
|
||||
out << "autonestbox is " << (is_enabled ? "" : "not ") << "running" << endl;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// cycle logic
|
||||
//
|
||||
|
||||
static bool isEmptyPasture(df::building *building) {
|
||||
if (!Buildings::isPenPasture(building))
|
||||
return false;
|
||||
df::building_civzonest *civ = (df::building_civzonest *)building;
|
||||
return (civ->assigned_units.size() == 0);
|
||||
}
|
||||
|
||||
static bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) {
|
||||
for (auto building : world->buildings.all) {
|
||||
if (building->getType() == df::building_type::NestBox
|
||||
&& building->x1 == x
|
||||
&& building->y1 == y
|
||||
&& building->z == z) {
|
||||
df::building_nest_boxst *nestbox = (df::building_nest_boxst *)building;
|
||||
if (nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static df::building* findFreeNestboxZone() {
|
||||
for (auto building : world->buildings.all) {
|
||||
if (isEmptyPasture(building) &&
|
||||
Buildings::isActive(building) &&
|
||||
isFreeNestboxAtPos(building->x1, building->y1, building->z)) {
|
||||
return building;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool isInBuiltCage(df::unit *unit) {
|
||||
for (auto building : world->buildings.all) {
|
||||
if (building->getType() == df::building_type::Cage) {
|
||||
df::building_cagest* cage = (df::building_cagest *)building;
|
||||
for (auto unitid : cage->assigned_units) {
|
||||
if (unitid == unit->id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if assigned to pen, pit, (built) cage or chain
|
||||
// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence)
|
||||
// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead
|
||||
// removing them from cages on stockpiles is no problem even without clearing the ref
|
||||
// and usually it will be desired behavior to do so.
|
||||
static bool isAssigned(df::unit *unit) {
|
||||
for (auto ref : unit->general_refs) {
|
||||
auto rtype = ref->getType();
|
||||
if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED
|
||||
|| rtype == df::general_ref_type::BUILDING_CAGED
|
||||
|| rtype == df::general_ref_type::BUILDING_CHAIN
|
||||
|| (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isFreeEgglayer(df::unit *unit)
|
||||
{
|
||||
return Units::isActive(unit) && !Units::isUndead(unit)
|
||||
&& Units::isFemale(unit)
|
||||
&& Units::isTame(unit)
|
||||
&& Units::isOwnCiv(unit)
|
||||
&& Units::isEggLayer(unit)
|
||||
&& !isAssigned(unit)
|
||||
&& !Units::isGrazer(unit) // exclude grazing birds because they're messy
|
||||
&& !Units::isMerchant(unit) // don't steal merchant mounts
|
||||
&& !Units::isForest(unit); // don't steal birds from traders, they hate that
|
||||
}
|
||||
|
||||
static df::unit * findFreeEgglayer() {
|
||||
for (auto unit : world->units.all) {
|
||||
if (isFreeEgglayer(unit))
|
||||
return unit;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static df::general_ref_building_civzone_assignedst * createCivzoneRef() {
|
||||
static bool vt_initialized = false;
|
||||
|
||||
// after having run successfully for the first time it's safe to simply create the object
|
||||
if (vt_initialized) {
|
||||
return (df::general_ref_building_civzone_assignedst *)
|
||||
df::general_ref_building_civzone_assignedst::_identity.instantiate();
|
||||
}
|
||||
|
||||
// being called for the first time, need to initialize the vtable
|
||||
for (auto creature : world->units.all) {
|
||||
for (auto ref : creature->general_refs) {
|
||||
if (ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) {
|
||||
if (strict_virtual_cast<df::general_ref_building_civzone_assignedst>(ref)) {
|
||||
vt_initialized = true;
|
||||
// !! calling new() doesn't work, need _identity.instantiate() instead !!
|
||||
return (df::general_ref_building_civzone_assignedst *)
|
||||
df::general_ref_building_civzone_assignedst::_identity.instantiate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *building) {
|
||||
// try to get a fresh civzone ref
|
||||
df::general_ref_building_civzone_assignedst *ref = createCivzoneRef();
|
||||
if (!ref) {
|
||||
ERR(cycle,out).print("Could not find a clonable activity zone reference!"
|
||||
" You need to manually pen/pasture/pit at least one creature"
|
||||
" before autonestbox can function.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ref->building_id = building->id;
|
||||
unit->general_refs.push_back(ref);
|
||||
|
||||
df::building_civzonest *civz = (df::building_civzonest *)building;
|
||||
civz->assigned_units.push_back(unit->id);
|
||||
|
||||
INFO(cycle,out).print("Unit %d (%s) assigned to nestbox zone %d (%s)\n",
|
||||
unit->id, Units::getRaceName(unit).c_str(),
|
||||
building->id, building->name.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t countFreeEgglayers() {
|
||||
size_t count = 0;
|
||||
for (auto unit : world->units.all) {
|
||||
if (isFreeEgglayer(unit))
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static size_t assign_nestboxes(color_ostream &out) {
|
||||
size_t processed = 0;
|
||||
df::building *free_building = NULL;
|
||||
df::unit *free_unit = NULL;
|
||||
do {
|
||||
free_building = findFreeNestboxZone();
|
||||
free_unit = findFreeEgglayer();
|
||||
if (free_building && free_unit) {
|
||||
if (!assignUnitToZone(out, free_unit, free_building)) {
|
||||
DEBUG(cycle,out).print("Failed to assign unit to building.\n");
|
||||
return processed;
|
||||
}
|
||||
DEBUG(cycle,out).print("assigned unit %d to zone %d\n",
|
||||
free_unit->id, free_building->id);
|
||||
++processed;
|
||||
}
|
||||
} while (free_unit && free_building);
|
||||
|
||||
if (free_unit && !free_building) {
|
||||
static size_t old_count = 0;
|
||||
size_t freeEgglayers = countFreeEgglayers();
|
||||
// avoid spamming the same message
|
||||
if (old_count != freeEgglayers)
|
||||
did_complain = false;
|
||||
old_count = freeEgglayers;
|
||||
if (!did_complain) {
|
||||
stringstream ss;
|
||||
ss << freeEgglayers;
|
||||
string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more.";
|
||||
Gui::showAnnouncement(announce, 6, true);
|
||||
out << announce << endl;
|
||||
did_complain = true;
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
static void autonestbox_cycle(color_ostream &out) {
|
||||
// mark that we have recently run
|
||||
cycle_timestamp = world->frame_counter;
|
||||
|
||||
DEBUG(cycle,out).print("running autonestbox cycle\n");
|
||||
|
||||
size_t processed = assign_nestboxes(out);
|
||||
if (processed > 0) {
|
||||
stringstream ss;
|
||||
ss << processed << " nestboxes were assigned.";
|
||||
string announce = ss.str();
|
||||
DEBUG(cycle,out).print("%s\n", announce.c_str());
|
||||
Gui::showAnnouncement(announce, 2, false);
|
||||
out << announce << endl;
|
||||
// can complain again
|
||||
// (might lead to spamming the same message twice, but catches the case
|
||||
// where for example 2 new egglayers hatched right after 2 zones were created and assigned)
|
||||
did_complain = false;
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <modules/Notes.h>
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using namespace DFHack;
|
||||
|
||||
command_result df_notes (color_ostream &out, vector <string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("notes");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand("dumpnotes",
|
||||
"Dumps in-game notes",
|
||||
df_notes));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result df_notes (color_ostream &con, vector <string> & parameters)
|
||||
{
|
||||
CoreSuspender suspend;
|
||||
|
||||
DFHack::Notes * note_mod = Core::getInstance().getNotes();
|
||||
std::vector<t_note*>* note_list = note_mod->notes;
|
||||
|
||||
if (note_list == NULL)
|
||||
{
|
||||
con.printerr("Notes are not supported under this version of DF.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
if (note_list->empty())
|
||||
{
|
||||
con << "There are no notes." << std::endl;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
for (size_t i = 0; i < note_list->size(); i++)
|
||||
{
|
||||
t_note* note = (*note_list)[i];
|
||||
|
||||
con.print("Note %p at: %d/%d/%d\n",note, note->x, note->y, note->z);
|
||||
con.print("Note id: %d\n", note->id);
|
||||
con.print("Note symbol: '%c'\n", note->symbol);
|
||||
|
||||
if (note->name.length() > 0)
|
||||
con << "Note name: " << (note->name) << std::endl;
|
||||
if (note->text.length() > 0)
|
||||
con << "Note text: " << (note->text) << std::endl;
|
||||
|
||||
if (note->unk1 != 0)
|
||||
con.print("unk1: %x\n", note->unk1);
|
||||
if (note->unk2 != 0)
|
||||
con.print("unk2: %x\n", note->unk2);
|
||||
|
||||
con << std::endl;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
@ -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> ¶meters);
|
||||
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> ¶meters) {
|
||||
// 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> ¶meters);
|
||||
|
||||
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> ¶meters) {
|
||||
// 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> ¶meters);
|
||||
|
||||
// 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> ¶meters) {
|
||||
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;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
local _ENV = mkmodule('plugins.autobutcher')
|
||||
|
||||
local argparse = require('argparse')
|
||||
|
||||
local function is_int(val)
|
||||
return val and val == math.floor(val)
|
||||
end
|
||||
|
||||
local function is_positive_int(val)
|
||||
return is_int(val) and val > 0
|
||||
end
|
||||
|
||||
local function check_nonnegative_int(str)
|
||||
local val = tonumber(str)
|
||||
if is_positive_int(val) or val == 0 then return val end
|
||||
qerror('expecting a non-negative integer, but got: '..tostring(str))
|
||||
end
|
||||
|
||||
local function process_args(opts, args)
|
||||
if args[1] == 'help' then
|
||||
opts.help = true
|
||||
return
|
||||
end
|
||||
|
||||
return argparse.processArgsGetopt(args, {
|
||||
{'h', 'help', handler=function() opts.help = true end},
|
||||
})
|
||||
end
|
||||
|
||||
local function process_races(opts, races, start_idx)
|
||||
if #races < start_idx then
|
||||
qerror('missing list of races (or "all" or "new" keywords)')
|
||||
end
|
||||
for i=start_idx,#races do
|
||||
local race = races[i]
|
||||
if race == 'all' then
|
||||
opts.races_all = true
|
||||
elseif race == 'new' then
|
||||
opts.races_new = true
|
||||
else
|
||||
local str = df.new('string')
|
||||
str.value = race
|
||||
opts.races:insert('#', str)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function parse_commandline(opts, ...)
|
||||
local positionals = process_args(opts, {...})
|
||||
|
||||
local command = positionals[1]
|
||||
if command then opts.command = command end
|
||||
|
||||
if opts.help or not command or command == 'now' or
|
||||
command == 'autowatch' or command == 'noautowatch' or
|
||||
command == 'list' or command == 'list_export' then
|
||||
return
|
||||
end
|
||||
|
||||
if command == 'watch' or command == 'unwatch' or command == 'forget' then
|
||||
process_races(opts, positionals, 2)
|
||||
elseif command == 'target' then
|
||||
opts.fk = check_nonnegative_int(positionals[2])
|
||||
opts.mk = check_nonnegative_int(positionals[3])
|
||||
opts.fa = check_nonnegative_int(positionals[4])
|
||||
opts.ma = check_nonnegative_int(positionals[5])
|
||||
process_races(opts, positionals, 6)
|
||||
elseif command == 'ticks' then
|
||||
local ticks = tonumber(positionals[2])
|
||||
if not is_positive_int(arg) then
|
||||
qerror('number of ticks must be a positive integer: ' .. ticks)
|
||||
else
|
||||
opts.ticks = ticks
|
||||
end
|
||||
else
|
||||
qerror(('unrecognized command: "%s"'):format(command))
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue