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
|
BUTCHER
|
||||||
TANNER
|
TANNER
|
||||||
COOK
|
COOK
|
@ -1,4 +1,4 @@
|
|||||||
NAME Craftsdwarf
|
NAME library/Craftsdwarf
|
||||||
WOOD_CRAFT
|
WOOD_CRAFT
|
||||||
STONE_CRAFT
|
STONE_CRAFT
|
||||||
BONE_CARVE
|
BONE_CARVE
|
@ -1,4 +1,4 @@
|
|||||||
NAME Doctor
|
NAME library/Doctor
|
||||||
ANIMALCARE
|
ANIMALCARE
|
||||||
DIAGNOSE
|
DIAGNOSE
|
||||||
SURGERY
|
SURGERY
|
@ -1,4 +1,4 @@
|
|||||||
NAME Farmer
|
NAME library/Farmer
|
||||||
PLANT
|
PLANT
|
||||||
MILLER
|
MILLER
|
||||||
BREWER
|
BREWER
|
@ -1,4 +1,4 @@
|
|||||||
NAME Fisherdwarf
|
NAME library/Fisherdwarf
|
||||||
FISH
|
FISH
|
||||||
CLEAN_FISH
|
CLEAN_FISH
|
||||||
DISSECT_FISH
|
DISSECT_FISH
|
@ -1,4 +1,4 @@
|
|||||||
NAME Hauler
|
NAME library/Hauler
|
||||||
FEED_WATER_CIVILIANS
|
FEED_WATER_CIVILIANS
|
||||||
SIEGEOPERATE
|
SIEGEOPERATE
|
||||||
MECHANIC
|
MECHANIC
|
@ -1,4 +1,4 @@
|
|||||||
NAME Laborer
|
NAME library/Laborer
|
||||||
SOAP_MAKER
|
SOAP_MAKER
|
||||||
BURN_WOOD
|
BURN_WOOD
|
||||||
POTASH_MAKING
|
POTASH_MAKING
|
@ -1,4 +1,4 @@
|
|||||||
NAME Marksdwarf
|
NAME library/Marksdwarf
|
||||||
MECHANIC
|
MECHANIC
|
||||||
HAUL_STONE
|
HAUL_STONE
|
||||||
HAUL_WOOD
|
HAUL_WOOD
|
@ -1,4 +1,4 @@
|
|||||||
NAME Mason
|
NAME library/Mason
|
||||||
MASON
|
MASON
|
||||||
CUT_GEM
|
CUT_GEM
|
||||||
ENCRUST_GEM
|
ENCRUST_GEM
|
@ -1,4 +1,4 @@
|
|||||||
NAME Meleedwarf
|
NAME library/Meleedwarf
|
||||||
RECOVER_WOUNDED
|
RECOVER_WOUNDED
|
||||||
MECHANIC
|
MECHANIC
|
||||||
HAUL_STONE
|
HAUL_STONE
|
@ -1,4 +1,4 @@
|
|||||||
NAME Migrant
|
NAME library/Migrant
|
||||||
FEED_WATER_CIVILIANS
|
FEED_WATER_CIVILIANS
|
||||||
SIEGEOPERATE
|
SIEGEOPERATE
|
||||||
MECHANIC
|
MECHANIC
|
@ -1,4 +1,4 @@
|
|||||||
NAME Miner
|
NAME library/Miner
|
||||||
MINE
|
MINE
|
||||||
DETAIL
|
DETAIL
|
||||||
RECOVER_WOUNDED
|
RECOVER_WOUNDED
|
@ -1,4 +1,4 @@
|
|||||||
NAME Outdoorsdwarf
|
NAME library/Outdoorsdwarf
|
||||||
CARPENTER
|
CARPENTER
|
||||||
BOWYER
|
BOWYER
|
||||||
CUTWOOD
|
CUTWOOD
|
@ -1,4 +1,4 @@
|
|||||||
NAME Smith
|
NAME library/Smith
|
||||||
FORGE_WEAPON
|
FORGE_WEAPON
|
||||||
FORGE_ARMOR
|
FORGE_ARMOR
|
||||||
FORGE_FURNITURE
|
FORGE_FURNITURE
|
@ -1,4 +1,4 @@
|
|||||||
NAME StartManager
|
NAME library/StartManager
|
||||||
CUTWOOD
|
CUTWOOD
|
||||||
ANIMALCARE
|
ANIMALCARE
|
||||||
DIAGNOSE
|
DIAGNOSE
|
@ -1,4 +1,4 @@
|
|||||||
NAME Tailor
|
NAME library/Tailor
|
||||||
DYER
|
DYER
|
||||||
LEATHER
|
LEATHER
|
||||||
WEAVER
|
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