From e2eae9bdb97bcf1840b5fe8d63fc0f987e86a717 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 22 Jun 2015 08:01:54 -0400 Subject: [PATCH 01/95] Fixed silly, minor changelog typo --- .gitignore | 34 +++++++++++++++++----------------- changelog.txt | 2 +- readme.md | 26 +++++++++++++------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 0d7ff45..771bfac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,17 @@ -*.DS_Store -*.pyc -*.xcf -*test.py - -# Manager logs go here -logs/* - -# Outputted raws go here (for my personal testing and stuff anyway) -output/* -backup/* - -# Older DF versions go here (for my help figuring version compatibility) -refs/* - -# Stuff that's just not meant to be committed right now -build/* +*.DS_Store +*.pyc +*.xcf +*test.py + +# Manager logs go here +logs/* + +# Outputted raws go here (for my personal testing and stuff anyway) +output/* +backup/* + +# Older DF versions go here (for my help figuring version compatibility) +refs/* + +# Stuff that's just not meant to be committed right now +build/* diff --git a/changelog.txt b/changelog.txt index d3c3e8a..f4568eb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,7 +4,7 @@ Fixed pineapple.boneflux reaction by being less bad at string formatting Fixed pineapple.utils.addreaction not adding an OBJECT:REACTION token to created files Fixed new grasses in pineapple.cavegrass not being recognized by DF Fixed a small issue with logging in pineapple.cavegrass -Fixed non-raws file from input directory not being written to output +Fixed non-raws files from input directory not being written to output Fixed pydwarf.response casting to string always returning a string starting with "SUCCESS" even when unsuccessful Added a lot more documentation Added a shitty WIP tool for outputting docstrings as more readable html diff --git a/readme.md b/readme.md index 0ef20b7..63eb5fa 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,13 @@ -# PyDwarf - -PyDwarf is licensed via the exceptionally permissive [zlib/libpng license](https://github.com/pineapplemachine/PyDwarf/blob/master/license.txt). It's written for [Python 2.7](https://www.python.org/download/releases/2.7.8/) and is intended as a mod tool for the wonderful game [Dwarf Fortress](http://www.bay12games.com/dwarves/). - -I have an enormous love for Dwarf Fortress and for its mods. In the years I've been playing and following this game, I think that there's always been a gap where a really powerful raws mod manager could be. This is the niche PyDwarf is intended to fill: I want to give mod authors the power to query and modify Dwarf Fortress's raws files with an elegance hitherto unseen, and I want to give users the ability to apply these mods with a minimum of effort. - -PyDwarf is easy to configure! Open `config.json` with your favorite text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. - -If you're interested in writing your own mods for PyDwarf or in understanding its more advanced features, take a look at [the tutorial](https://github.com/pineapplemachine/PyDwarf/blob/master/tutorial.md)! - -You can see a list of your installed mods by running `python manager.py --list` and you can view some documentation for a particular script by running, for example, `python manager.py --meta "pineapple.flybears"`. - -![Image of a flying female bear](https://github.com/pineapplemachine/PyDwarf/blob/master/images/logo_transparent.png?raw=true) +# PyDwarf + +PyDwarf is licensed via the exceptionally permissive [zlib/libpng license](https://github.com/pineapplemachine/PyDwarf/blob/master/license.txt). It's written for [Python 2.7](https://www.python.org/download/releases/2.7.8/) and is intended as a mod tool for the wonderful game [Dwarf Fortress](http://www.bay12games.com/dwarves/). + +I have an enormous love for Dwarf Fortress and for its mods. In the years I've been playing and following this game, I think that there's always been a gap where a really powerful raws mod manager could be. This is the niche PyDwarf is intended to fill: I want to give mod authors the power to query and modify Dwarf Fortress's raws files with an elegance hitherto unseen, and I want to give users the ability to apply these mods with a minimum of effort. + +PyDwarf is easy to configure! Open `config.json` with your favorite text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. + +If you're interested in writing your own mods for PyDwarf or in understanding its more advanced features, take a look at [the tutorial](https://github.com/pineapplemachine/PyDwarf/blob/master/tutorial.md)! + +You can see a list of your installed mods by running `python manager.py --list` and you can view some documentation for a particular script by running, for example, `python manager.py --meta "pineapple.flybears"`. + +![Image of a flying female bear](https://github.com/pineapplemachine/PyDwarf/blob/master/images/logo_transparent.png?raw=true) From 7d80e6ce25ec6c919456e9ec46e965d37ee74c70 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 22 Jun 2015 14:57:08 -0400 Subject: [PATCH 02/95] Lots of revisions and additions - Added dfhack class to raws package. Largely untested so far, may not behave as advertised. - Updated tutorial to reflect the above, and made a few general edits as well - Renamed raws.queryable_obj to raws.queryableobj - Moved a few small things out of the manager script and into pydwarf.session - Moved config.py out of its lonely, dedicated package and into the pydwarf package - And, of course, more documentation --- config.json | 2 + config/__init__.py | 3 -- config/config.py | 86 ------------------------------ config_override.py | 3 ++ manager.py | 24 ++++----- pydwarf/__init__.py | 15 ++++++ pydwarf/config.py | 126 ++++++++++++++++++++++++++++++++++++++++++++ pydwarf/findfile.py | 18 +++++++ pydwarf/log.py | 3 ++ pydwarf/urist.py | 19 ++++--- pydwarf/version.py | 24 +++------ raws/__init__.py | 26 ++++++++- raws/copytree.py | 15 ++++++ raws/dfhack.py | 64 ++++++++++++++++++++++ raws/dir.py | 22 ++------ raws/file.py | 4 +- raws/queryable.py | 6 +-- tutorial.md | 16 +++--- 18 files changed, 320 insertions(+), 156 deletions(-) delete mode 100644 config/__init__.py delete mode 100644 config/config.py create mode 100644 pydwarf/config.py create mode 100644 pydwarf/findfile.py create mode 100644 pydwarf/log.py create mode 100644 raws/copytree.py create mode 100644 raws/dfhack.py diff --git a/config.json b/config.json index 233a1e9..9d82d39 100644 --- a/config.json +++ b/config.json @@ -7,6 +7,8 @@ "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawbak/", "version": "auto", + "dfhackdir": "auto", + "dfhackver": "auto", "scripts": [ { diff --git a/config/__init__.py b/config/__init__.py deleted file mode 100644 index cbe96ec..0000000 --- a/config/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from config import * - -__version__ = '1.0.0' diff --git a/config/config.py b/config/config.py deleted file mode 100644 index d500be2..0000000 --- a/config/config.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import sys - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -import logging -import json -import importlib -from datetime import datetime -import pydwarf - - - -# Used in some file paths and such -datetimeformat = '%Y.%m.%d.%H.%M.%S' -timestamp = datetime.now().strftime(datetimeformat) - - - -class config: - def __init__(self, version=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): - self.version = version # Dwarf Fortress version, for handling script compatibility metadata - self.input = input # Raws are loaded from this input directory - self.output = output # Raws are written to this output directory - self.backup = backup # Raws are backed up to this directory before any changes are made - self.scripts = scripts # These scripts are run in the order that they appear - self.packages = packages # These packages are imported (probably because they contain PyDwarf scripts) - self.verbose = verbose # Log DEBUG messages to stdout if True, otherwise only INFO and above - self.log = log # Log file goes here - - def json(self, path, *args, **kwargs): - with open(path, 'rb') as jsonfile: return self.apply(json.load(jsonfile), *args, **kwargs) - - def apply(self, data, applynone=False): - if data: - for key, value in data.iteritems(): - if applynone or value is not None: self.__dict__[key] = value - return self - - def __str__(self): - return str(self.__dict__) - def __repr__(self): - return self.__str__() - - def setup(self): - # Set up the pydwarf logger - self.setuplogger() - # Handle version == 'auto' - self.setupversion() - # Import packages - self.setuppackages() - - def setuplogger(self): - # Set up the logger (And you should do it first thing!) - pydwarf.log.setLevel(logging.DEBUG) - # Handler for console output - stdouthandler = logging.StreamHandler(sys.stdout) - stdouthandler.setLevel(logging.DEBUG if self.verbose else logging.INFO) - stdouthandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(message)s', datetimeformat)) - pydwarf.log.addHandler(stdouthandler) - # Handler for log file output - if self.log: - logdir = os.path.dirname(self.log) - if not os.path.exists(logdir): os.makedirs(logdir) - logfilehandler = logging.FileHandler(self.log) - logfilehandler.setLevel(logging.DEBUG) - logfilehandler.setFormatter(logging.Formatter('%(asctime)s: %(filename)s[%(lineno)s]: %(levelname)s: %(message)s', datetimeformat)) - pydwarf.log.addHandler(logfilehandler) - - def setuppackages(self): - self.importedpackages = [importlib.import_module(package) for package in self.packages] - - def setupversion(self): - # Handle automatic version detection - if self.version == 'auto': - pydwarf.log.info('Attempting to automatically detect Dwarf Fortress version.') - self.version = pydwarf.detectversion(paths=(self.input, self.output), log=pydwarf.log) - if self.version == None: - pydwarf.log.info('Unable to detect Dwarf Fortress version.') - else: - pydwarf.log.info('Detected Dwarf Fortress version %s.' % self.version) - elif self.version is None: - pydwarf.log.warning('No Dwarf Fortress version was specified. Scripts will be run regardless of their indicated compatibility.') - else: - pydwarf.log.info('Managing Dwarf Fortress version %s.' % self.version) - pydwarf.urist.session.dfversion = self.version diff --git a/config_override.py b/config_override.py index b118b19..4f89c84 100644 --- a/config_override.py +++ b/config_override.py @@ -14,6 +14,8 @@ 'output': 'output', 'backup': 'backup', 'version': 'auto', + 'dfhackdir': 'auto', + 'dfhackver': 'auto', 'scripts': [ { 'name': 'pineapple.deerappear', @@ -36,3 +38,4 @@ else: export = {} + diff --git a/manager.py b/manager.py index 259a451..b940ec9 100644 --- a/manager.py +++ b/manager.py @@ -5,7 +5,6 @@ import importlib import pydwarf import raws -from config import config @@ -19,7 +18,7 @@ def getconf(args=None): # Load initial config from json file - conf = config() + conf = pydwarf.config() if os.path.isfile(jsonconfigpath): conf.json(jsonconfigpath) # Default name of configuration override package @@ -52,11 +51,8 @@ def getconf(args=None): if overrideexception: pydwarf.log.error('Failed to apply configuration from %s package.\n%s' % (overridename, overrideexception)) - # Setup version (Handle 'auto') - conf.setupversion() - - # Import packages - conf.setuppackages() + # Handle things like automatic version detection, package importing + conf.setup() # All done! return conf @@ -70,8 +66,10 @@ def __main__(args=None): # Report versions pydwarf.log.info('Running PyDwarf manager version %s.' % __version__) - pydwarf.log.debug('With PyDwarf version %s.' % pydwarf.__version__) + pydwarf.log.debug('With pydwarf version %s.' % pydwarf.__version__) pydwarf.log.debug('With raws version %s.' % raws.__version__) + pydwarf.log.debug('With Dwarf Fortress version %s.' % conf.version) + pydwarf.log.debug('With DFHack version %s.' % conf.dfhackver) # Handle flags that completely change behavior if args.list: @@ -92,18 +90,18 @@ def __main__(args=None): try: raws.copytree(conf.input, conf.backup) except: - pydwarf.log.error('Failed to create backup.') + pydwarf.log.exception('Failed to create backup.') exit(1) else: pydwarf.log.warning('Proceeding without backing up raws.') # Read input raws - pydwarf.log.info('Reading raws from input directory %s.' % conf.input) - pydwarf.urist.session.dfraws = raws.dir(path=conf.input, log=pydwarf.log) + pydwarf.log.info('Configuring raws with input directory %s.' % conf.input) + pydwarf.urist.session.configure(raws, conf) # Run each script pydwarf.log.info('Running scripts.') - pydwarf.urist.session.handleall(conf.scripts) + pydwarf.urist.session.handleall() # Get the output directory, remove old raws if present outputdir = conf.output if conf.output else conf.input @@ -135,6 +133,8 @@ def parseargs(): parser.add_argument('-p', '--packages', help='import packages containing PyDwarf scripts', nargs='+', type=str) parser.add_argument('-c', '--config', help='run with json config file if the extension is json, otherwise treat as a Python package, import, and override settings using export dict', type=str) parser.add_argument('-v', '--verbose', help='set stdout logging level to DEBUG', action='store_true') + parser.add_argument('-hdir', '--dfhackdir', help='indicate DFHack directory', type=str) + parser.add_argument('-hver', '--dfhackver', help='indicate DFHack version', type=str) parser.add_argument('--log', help='output log file to path', type=str) parser.add_argument('--list', help='list available scripts', action='store_true') parser.add_argument('--jscripts', help='specify scripts given a json array', type=str) diff --git a/pydwarf/__init__.py b/pydwarf/__init__.py index d47658b..9400ccb 100644 --- a/pydwarf/__init__.py +++ b/pydwarf/__init__.py @@ -1,5 +1,20 @@ +''' + +The pydwarf package acts as a layer of abstraction over the raws package, providing functionality for mod management and application. + +pydwarf.log: A shared logging object. +pydwarf.version: Utilities pertinent to handling Dwarf Fortress versions. +pydwarf.response: PyDwarf expects plugins to return pydwarf.response objects. +pydwarf.urist: A combined decorator and global repository for PyDwarf plugins. +pydwarf.session: Ideally for use by a mod manager, abstracts the handling and execution of PyDwarf plugins. +pydwarf.config: Provides an object to simplify config loading and application. + +''' + +from log import * from version import * from response import * from urist import * +from config import * __version__ = '1.0.1' diff --git a/pydwarf/config.py b/pydwarf/config.py new file mode 100644 index 0000000..c588dd8 --- /dev/null +++ b/pydwarf/config.py @@ -0,0 +1,126 @@ +import sys +import os +import logging +import json +import importlib +from datetime import datetime + +from log import log +from version import detectversion +from urist import urist, session +from findfile import findfile + + + +# Used in some file paths and such +datetimeformat = '%Y.%m.%d.%H.%M.%S' +timestamp = datetime.now().strftime(datetimeformat) + + + +class config: + def __init__(self, version=None, dfhackdir=None, dfhackver=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): + self.version = version # Dwarf Fortress version, for handling script compatibility metadata + self.dfhackdir = dfhackdir # DFHack directory, located within the pertinent DF directory + self.dfhackver = dfhackver # DFHack version + self.input = input # Raws are loaded from this input directory + self.output = output # Raws are written to this output directory + self.backup = backup # Raws are backed up to this directory before any changes are made + self.scripts = scripts # These scripts are run in the order that they appear + self.packages = packages # These packages are imported (probably because they contain PyDwarf scripts) + self.verbose = verbose # Log DEBUG messages to stdout if True, otherwise only INFO and above + self.log = log # Log file goes here + + def json(self, path, *args, **kwargs): + with open(path, 'rb') as jsonfile: return self.apply(json.load(jsonfile), *args, **kwargs) + + def apply(self, data, applynone=False): + if data: + for key, value in data.iteritems(): + if applynone or value is not None: self.__dict__[key] = value + return self + + def __str__(self): + return str(self.__dict__) + def __repr__(self): + return self.__str__() + + def setup(self, logger=False): + # Set up the pydwarf logger + if logger: self.setuplogger() + # Handle version == 'auto' + self.setupversion() + # Handle dfhackdir == 'auto' + self.setupdfhack() + # Import packages + self.setuppackages() + + def setuplogger(self): + # Set up the logger (And it should be done first thing!) + log.setLevel(logging.DEBUG) + # Handler for console output + stdouthandler = logging.StreamHandler(sys.stdout) + stdouthandler.setLevel(logging.DEBUG if self.verbose else logging.INFO) + stdouthandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(message)s', datetimeformat)) + log.addHandler(stdouthandler) + # Handler for log file output + if self.log: + logdir = os.path.dirname(self.log) + if not os.path.exists(logdir): os.makedirs(logdir) + logfilehandler = logging.FileHandler(self.log) + logfilehandler.setLevel(logging.DEBUG) + logfilehandler.setFormatter(logging.Formatter('%(asctime)s: %(filename)s[%(lineno)s]: %(levelname)s: %(message)s', datetimeformat)) + log.addHandler(logfilehandler) + + def setuppackages(self): + self.importedpackages = [importlib.import_module(package) for package in self.packages] + + def setupversion(self): + # Handle automatic version detection + if self.version == 'auto': + log.debug('Attempting to automatically detect Dwarf Fortress version.') + self.version = detectversion(paths=(self.input, self.output)) + if self.version is None: + log.error('Unable to detect Dwarf Fortress version.') + else: + log.debug('Detected Dwarf Fortress version %s.' % self.version) + elif self.version is None: + log.warning('No Dwarf Fortress version was specified. Scripts will be run regardless of their indicated compatibility.') + else: + log.info('Managing Dwarf Fortress version %s.' % self.version) + urist.session.dfversion = self.version + + def setupdfhack(self): + self.setupdfhackdir() + self.setupdfhackver() + + def setupdfhackdir(self): + if self.dfhackdir == 'auto': + log.debug('Attempting to automatically detect DFHack directory.') + self.dfhackdir = findfile(name='hack', paths=(self.input, self.output)) + if self.dfhackdir is None: + log.error('Unable to detect DFHack directory.') + else: + log.debug('Detected DFHack directory at %s.' % self.dfhackdir) + elif self.dfhackdir is None: + log.warning('No DFHack directory was specified. Scripts which attempt to access or modify DFHack data will fail.') + else: + log.info('Managing DFHack directory %s.' % self.dfhackdir) + + def setupdfhackver(self): + if self.dfhackver == 'auto': + if self.dfhackdir is not None: + log.debug('Attempting to automatically detect DFHack version.') + newspath = os.path.join(self.dfhackdir, 'NEWS') + if os.path.isfile(newspath): + with open(newspath, 'rb') as news: self.dfhackver = news.readline().strip() + if self.dfhackver is None: + log.error('Unable to detect DFHack version.') + else: + log.debug('Detected DFHack version %s.' % self.dfhackver) + else: + log.error('Failed to automatically detect DFHack version because the DFHack directory has not been located.') + elif self.dfhackver is None: + log.warning('No DFHack version was specified.') + else: + log.info('Managing DFHack version %s.' % self.dfhackver) diff --git a/pydwarf/findfile.py b/pydwarf/findfile.py new file mode 100644 index 0000000..d7b7055 --- /dev/null +++ b/pydwarf/findfile.py @@ -0,0 +1,18 @@ +import os +from log import log + +def findfile(name, paths, recursion=6): + log.debug('Looking for file %s.' % name) + for path in paths: + if path is not None: + currentpath = path + for i in xrange(0, recursion): + if os.path.isdir(currentpath): + log.debug('Checking path %s for file %s.' % (currentpath, name)) + if name in os.listdir(currentpath): + return os.path.join(currentpath, name) + else: + currentpath = os.path.dirname(currentpath) + else: + break + return None diff --git a/pydwarf/log.py b/pydwarf/log.py new file mode 100644 index 0000000..77c2a81 --- /dev/null +++ b/pydwarf/log.py @@ -0,0 +1,3 @@ +# Super simple: just make a default, shared logger object +import logging +log = logging.getLogger() diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 98bb937..c481b5a 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -1,16 +1,12 @@ -import logging import textwrap import version as versionutils - - - -# Make a default logger object -log = logging.getLogger() +from log import log class session: - def __init__(self, dfraws=None, dfversion=None): + def __init__(self, conf=None, dfraws=None, dfversion=None): + self.conf = conf self.dfraws = dfraws self.dfversion = dfversion self.successes = [] @@ -22,6 +18,12 @@ def successful(self, info): def failed(self, info): return self.inlist(info, self.failures) + def configure(self, raws, conf): + self.conf = conf + self.dfraws = raws.dir(path=conf.input, log=log) + self.dfraws.hack = raws.dfhack(path=conf.dfhackdir, version=conf.dfhackver) + self.dfversion = conf.version + def inlist(self, info, flist): funcs = self.funcs(info) if funcs: @@ -82,7 +84,8 @@ def handle(self, info): else: log.error('Found no scripts matching %s.' % info) - def handleall(self, infos): + def handleall(self, infos=None): + if infos is None and self.conf is not None: infos = self.conf.scripts if infos and len(infos): for info in infos: self.handle(info) else: diff --git a/pydwarf/version.py b/pydwarf/version.py index aa2b592..bc1a608 100644 --- a/pydwarf/version.py +++ b/pydwarf/version.py @@ -1,6 +1,9 @@ import os import re +from log import log +from findfile import findfile + # Can be expected to match all past and future 0.40.* releases. (Time of writing is 21 May 15, the most recent version is 0.40.24.) df_0_40 = '(0\.40\.\d{2,}[abcdefg]?)' # Matches all DF 0.34.* releases @@ -42,25 +45,12 @@ def compatible(compatibility, version): else: return any([re.match(item, version) for item in compatibility]) -def detectversion(paths, recursion=8, log=None): +def detectversion(*args, **kwargs): # Given a list of directories that may be inside a DF directory, e.g. raws input or output, look for release notes.txt and get the version from that - if log: log.debug('Attempting to detect Dwarf Fortress version given paths %s.' % [str(p) for p in paths]) - for path in paths: - if path is not None: - currentpath = path - for i in xrange(0, recursion): - if os.path.isdir(currentpath): - if log: log.debug('Checking path %s for release notes.' % currentpath) - if 'release notes.txt' in os.listdir(currentpath): - version = versionfromreleasenotes(os.path.join(currentpath, 'release notes.txt'), log) - if version: return version - else: - currentpath = os.path.dirname(currentpath) - else: - break - return None + path = findfile(name='release notes.txt', *args, **kwargs) + return versionfromreleasenotes(path) -def versionfromreleasenotes(path, log=None): +def versionfromreleasenotes(path): with open(path, 'rb') as releasenotes: for line in releasenotes.readlines(): if line.startswith('Release notes for'): return line.split()[3] diff --git a/raws/__init__.py b/raws/__init__.py index c44fc92..baf1a5f 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -1,11 +1,35 @@ +''' + +The raws package provides querying and modification functionality for Dwarf Fortress raws. + +raws.dir: An entire directory of raws files, stored as a dictionary of files. +raws.dfhack: Possessed by dir objects as a hack attribute, the class exposes methods for interacting with DFHack files. +raws.file: A single raws file, stored as a linked list. +raws.token: A single token within a raws file, for example [CREATURE:DWARF] or [INORGANIC:IRON]. +raws.tokenlist: Extends Python's inbuilt list class with additional, specialized functionality. +raws.queryable: Many raws classes extend this class, which provides token querying functionality. +raws.queryableobj: An extension of the queryable class which adds methods optimized for finding object tokens, like the [CREATURE:DWARF] token which is underneath [OBJECT:CREATURE] in the creature_standard file. +raws.tokenfilter: Used by queryable objects' query method to find tokens meeting specific conditions. +raws.boolfilter: Can be used in place of a tokenfilter for operations like Filter A OR Filter B. +raws.color: Contains a convenience class and objects for dealing with colors in the DF raws. +raws.copytree: A general utility method for copying an entire directory and its contents from one location to another. + +raws.filter: A convenience alias for raws.tokenfilter. +raws.parse: A convenience alias for raws.token.parse, which accepts an input string and parses it into a tokenlist. +raws.parseone: A convenience alias for raws.token.parseone, which acts like raws.token.parse but expects a single token instead of a list of them. + +''' + from filters import rawstokenfilter as tokenfilter from filters import rawsboolfilter as boolfilter from queryable import rawsqueryable as queryable +from queryable import rawsqueryableobj as queryableobj from queryable import rawstokenlist as tokenlist from token import rawstoken as token from file import rawsfile as file from dir import rawsdir as dir -from dir import copytree +from dfhack import dfhack +from copytree import copytree import color filter = tokenfilter diff --git a/raws/copytree.py b/raws/copytree.py new file mode 100644 index 0000000..159f6b7 --- /dev/null +++ b/raws/copytree.py @@ -0,0 +1,15 @@ +import os +import shutil + +# credit belongs to http://stackoverflow.com/a/13814557/3478907 +def copytree(src, dst, symlinks=False, ignore=None): + if not os.path.exists(dst): + os.makedirs(dst) + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isdir(s): + copytree(s, d, symlinks, ignore) + else: + if not os.path.exists(d) or os.stat(src).st_mtime - os.stat(dst).st_mtime > 1: + shutil.copy2(s, d) diff --git a/raws/dfhack.py b/raws/dfhack.py new file mode 100644 index 0000000..3630444 --- /dev/null +++ b/raws/dfhack.py @@ -0,0 +1,64 @@ +# Add some basic functionality for working with DFHack + +import os +import shutil + + + +class dfhack: + nodir = 'Can\'t add file, DFHack directory hasn\'t been specified.' + + def __init__(self, path=None, version=None): + '''Constructor for dfhack object.''' + self.path = path + self.version = version + + def open(path, *args, **kwargs): + '''Open a file within the DFHack directory. Acts as a shortcut for open('dfhack/path', mode).''' + + if not self.path: + log.error(dfhack.nodir) + return None + else: + return open(os.path.join(self.path, path), *args, **kwargs) + + def exists(path): + return self.path and os.path.exists(os.path.join(self.path, path)) + def isfile(path): + return self.path and os.path.isfile(os.path.join(self.path, path)) + def isdir(path): + return self.path and os.path.isdir(os.path.join(self.path, path)) + + def add(path, dest, overwrite=False): + '''Copies an existing file into the DFHack directory.''' + + if not os.path.exists(path): + raise ValueError('File %s does not exist.' % path) + elif not self.path: + raise ValueError(dfhack.nodir) + else: + destpath = os.path.join(self.path, dest) + destexists = os.path.exists(destpath) + if destexists and not overwrite: + raise ValueError('File %s already exists.' % destpath) + else: + shutil.copy2(path, destpath) + return True + return False + + def remove(path): + '''Removes a file from the DFHack directory.''' + + if not self.path: + raise ValueError(dfhack.nodir) + else: + path = os.path.join(self.path, path) + if os.path.isfile(path): + os.remove(path) + return True + elif os.path.isdir(path): + shutil.rmtree(path) + return True + else: + raise ValueError('Failed to remove file or directory %s because the path is invalid.' % path) + return False diff --git a/raws/dir.py b/raws/dir.py index ef81bf3..8856388 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -1,11 +1,11 @@ import os -import shutil -from queryable import rawsqueryable_obj, rawstokenlist +from copytree import copytree +from queryable import rawsqueryableobj, rawstokenlist from file import rawsfile -class rawsdir(rawsqueryable_obj): +class rawsdir(rawsqueryableobj): '''Represents as a whole all the raws contained within a directory.''' def __init__(self, *args, **kwargs): @@ -13,6 +13,7 @@ def __init__(self, *args, **kwargs): self.files = {} self.otherfiles = [] + self.hack = None if len(args) or len(kwargs): self.read(*args, **kwargs) def getfile(self, filename, create=None): @@ -132,18 +133,3 @@ def getobjheaders(self, type): if root is not None and root.value == 'OBJECT' and root.nargs() == 1 and root.args[0] in match_types: results.append(root) return results - - - -# credit belongs to http://stackoverflow.com/a/13814557/3478907 -def copytree(src, dst, symlinks=False, ignore=None): - if not os.path.exists(dst): - os.makedirs(dst) - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.isdir(s): - copytree(s, d, symlinks, ignore) - else: - if not os.path.exists(d) or os.stat(src).st_mtime - os.stat(dst).st_mtime > 1: - shutil.copy2(s, d) diff --git a/raws/file.py b/raws/file.py index d0b6f4c..8102f6f 100644 --- a/raws/file.py +++ b/raws/file.py @@ -1,7 +1,7 @@ -from queryable import rawsqueryable_obj, rawstokenlist +from queryable import rawsqueryableobj, rawstokenlist from token import rawstoken -class rawsfile(rawsqueryable_obj): +class rawsfile(rawsqueryableobj): '''Represents a single file within a raws directory.''' def __init__(self, header=None, data=None, path=None, tokens=None, rfile=None, dir=None): diff --git a/raws/queryable.py b/raws/queryable.py index 8cc2f9f..203a66f 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -399,7 +399,7 @@ def argsprops(self): -class rawsqueryable_obj(rawsqueryable): +class rawsqueryableobj(rawsqueryable): def __init__(self): self.files = None @@ -442,7 +442,7 @@ def getobj(self, pretty=None, type=None, exact_id=None): [WEAPON:ITEM_WEAPON_AXE_BATTLE] ''' - type, exact_id = rawsqueryable_obj.objpretty(pretty, type, exact_id) + type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) for objecttoken in self.getobjheaders(type): obj = objecttoken.get(exact_value=type, exact_args=(exact_id,)) if obj: return obj @@ -472,7 +472,7 @@ def allobj(self, pretty=None, type=None, exact_id=None, re_id=None, id_in=None): ''' if re_id and id_in: raise ValueError - type, exact_id = rawsqueryable_obj.objpretty(pretty, type, exact_id) + type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) results = rawstokenlist() for objecttoken in self.getobjheaders(type): for result in objecttoken.all( diff --git a/tutorial.md b/tutorial.md index 846d595..eed67eb 100644 --- a/tutorial.md +++ b/tutorial.md @@ -35,6 +35,8 @@ For convenient reference, this is an example config.json file: "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawbak/", "version": "auto", + "dfhackdir": "auto", + "dfhackver": "auto", "scripts": [ {"name": "pineapple.deerappear", "args": {"tile": "'d'", "color": [6, 0, 1]}}, @@ -57,6 +59,8 @@ Here is the purpose of each of those attributes: * `output`: Which directory to output the raws to after the scripts have run and modified them. If set to `null` (in json) or `None` (in Python) then the output directory will be the same as the input. * `backup`: Before anything else is done, if it's not `null` or `None`, the input raws will be copied and saved to this directory. * `version`: Specifies the Dwarf Fortress version. For example, `"version": "0.40.24"`. If set to `auto` then PyDwarf will attempt to detect the Dwarf Fortress version automatically. This should succeed as long as either the `input` or `output` directory is somewhere inside Dwarf Fortress's directory. +* `dfhackdir`: The location of a hack/ directory, if any, within the Dwarf Fortress directory +* `dfhackver`: The version of DFHack contained within a hack/ directory, if any, within the Dwarf Fortress directory * `scripts`: Lists the scripts that should be run. * `packages`: Lists the Python packages that should be imported. In essence, it specifies for PyDwarf that it should look for scripts inside the `scripts` package, in this case a directory containing an `__init__.py` file. This is an advanced feature and the typical user won't need to worry about this. @@ -68,10 +72,12 @@ Once PyDwarf has been configured, applying the mods specified in its configurati PyDwarf's configuration can also be passed as command line arguments when running manager.py. These are the arguments it accepts, all of which supersede any identically-named options set in `config.json` or `config.py` when specified. -* `-i` or `--input`: Specifies input directory. -* `-o` or `--output`: Specifies output directory. -* `-b` or `--backup`: Specifies backup directory. +* `-i` or `--input`: Specifies raws input directory. +* `-o` or `--output`: Specifies raws output directory. +* `-b` or `--backup`: Specifies raws backup directory. * `-ver` or `--version`: Specifies Dwarf Fortress version. +* `-hdir` or `--dfhackdir`: Specifies DFHack directory. +* `-hver` or `--dfhackver`: Specifies DFHack version. * `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) * `-p` or `--packages`: The list of Python packages to import. * `-c` or `--config`: Imports configuration from the json file given by the path. @@ -79,11 +85,9 @@ PyDwarf's configuration can also be passed as command line arguments when runnin * `--log`: Specifies the log file path. * `--list`: Lists registered scripts in alphabetical order. * `--meta`: When given names of scripts as arguments, shows each script's metadata in a readable format. When given no arguments, metadata for all registered scripts is displayed. -* `--jscripts`: Alternative to `--scripts` which accepts a json array just like the `scripts` attribute in `config.json`. +* `--jscripts`: More complicated alternative to `--scripts` which accepts a json array just like the `scripts` attribute in `config.json`. * `-h` or `--help`: Shows a summary of each argument's purpose. -The arguments include `-i` or `--input`, `-o` or `--output`, `-b` or `--backup`, `-ver` or `--version`, which behave identically to their `config.json` or `config.py` counterparts. Additional arguments are `-s` or `--scripts`, which is a list of scripts just the same except with support only for strings (not for dicts), and `-c` or `--config`, which can be used to specify a json file to read as configuration. - ![Example gif of running PyDwarf from the command line](http://www.pineapplemachine.com/pydwarf/terminal_example.gif) From b2b2feb0430379702903ed08930db44e11702da6 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 22 Jun 2015 15:05:38 -0400 Subject: [PATCH 03/95] Fixed --config argument documentation in tutorial.md --- tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial.md b/tutorial.md index eed67eb..0018f0a 100644 --- a/tutorial.md +++ b/tutorial.md @@ -80,7 +80,7 @@ PyDwarf's configuration can also be passed as command line arguments when runnin * `-hver` or `--dfhackver`: Specifies DFHack version. * `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) * `-p` or `--packages`: The list of Python packages to import. -* `-c` or `--config`: Imports configuration from the json file given by the path. +* `-c` or `--config`: Imports configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. Se the config_override.py file in the root directory for an example. * `-v` or `--verbose`: Sets the logging level for standard output to `DEBUG`. (By default, fully verbose logs are written to the `logs/` directory regardless of this flag.) * `--log`: Specifies the log file path. * `--list`: Lists registered scripts in alphabetical order. From a325e462aa6b732cbd49ab54eab7b6ccb1a35f30 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 22 Jun 2015 15:06:44 -0400 Subject: [PATCH 04/95] Wow I'm good at making typos --- tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial.md b/tutorial.md index 0018f0a..dbab449 100644 --- a/tutorial.md +++ b/tutorial.md @@ -80,7 +80,7 @@ PyDwarf's configuration can also be passed as command line arguments when runnin * `-hver` or `--dfhackver`: Specifies DFHack version. * `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) * `-p` or `--packages`: The list of Python packages to import. -* `-c` or `--config`: Imports configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. Se the config_override.py file in the root directory for an example. +* `-c` or `--config`: Reads configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. See `config_override.py` for an example. * `-v` or `--verbose`: Sets the logging level for standard output to `DEBUG`. (By default, fully verbose logs are written to the `logs/` directory regardless of this flag.) * `--log`: Specifies the log file path. * `--list`: Lists registered scripts in alphabetical order. From 3e084af72367340239e8177a11f93f6697e065a1 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 23 Jun 2015 16:27:37 -0400 Subject: [PATCH 05/95] Added some helpful new methods and op overloads to pydwarf.config --- pydwarf/config.py | 51 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/pydwarf/config.py b/pydwarf/config.py index c588dd8..24979b3 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -31,6 +31,32 @@ def __init__(self, version=None, dfhackdir=None, dfhackver=None, input=None, out self.verbose = verbose # Log DEBUG messages to stdout if True, otherwise only INFO and above self.log = log # Log file goes here + def __str__(self): + return str(self.__dict__) + def __repr__(self): + return self.__str__() + + def __getitem__(self, attr): + return self.__dict__[attr] + def __setitem__(self, attr, value): + self.__dict__[attr] = value + + def __iter__(self): + return iter(self.__dict__) + def iteritems(self): + return self.__dict__.iteritems() + + def __add__(self, other): + return config.concat(self, other) + def __radd__(self, other): + return config.concat(other, self) + def __iadd__(self, item): + self.apply(item) + return self + + def __and__(self, other): + return config.intersect(self, other) + def json(self, path, *args, **kwargs): with open(path, 'rb') as jsonfile: return self.apply(json.load(jsonfile), *args, **kwargs) @@ -40,10 +66,27 @@ def apply(self, data, applynone=False): if applynone or value is not None: self.__dict__[key] = value return self - def __str__(self): - return str(self.__dict__) - def __repr__(self): - return self.__str__() + def copy(self): + copy = config() + for key, value in self: copy[key] = value + return copy + + @staticmethod + def concat(*configs): + result = config() + for conf in configs: result.apply(conf) + return result + + @staticmethod + def intersect(*configs): + result = config() + first = configs[0] + for attr, value in first.iteritems(): + for conf in configs: + if (conf is not first) and (attr not in conf or conf[attr] != value): break + else: + result[attr] = value + return result def setup(self, logger=False): # Set up the pydwarf logger From 7137d8b51d5635f39f5f3f02d4439279207ce5e4 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 23 Jun 2015 16:38:42 -0400 Subject: [PATCH 06/95] Improvements to file system handling Warning that something somewhere is bugged and so as of this commit things don't really work, but I'd like to get this stuff committed anyway so here goes Also some other stuff. A summary of changes: - raws.dir now remembers what path it was read from when specified in the constructor. - raws.file has some additional handling for relative file paths, this being especially relevant for the planned change of initializing raws.dir from the raw directory instead of the raw/objects directory. - Moved a lot of the handling of non-raws files out of raws.dir and into a class similar to raws.file, called raws.otherfile (it's a very creative name, I know) - A lot of places, both as arguments and internally, now use variable names like "name" and/or "file" in place of "filename", "file" in place of "rfile". This is meant to make argument names easier to understand at a glance. - Partially revised some file-related methods of raws.dir like addfile and setfile, and began renaming these methods to things like add and set, respectively. - raws.dir tokens iterator now accepts arguments, which it passes on to the raws.file iterators it goes through. - Added more operator overloads to raws.file, including support for with ... as syntax. - Added support for with ... as syntax to raws.dir, too. - Renamed raws.file attribute header to name. - Improved raws.file read/write methods, now they can optionally write to a directory instead of just to a file stream. - Added some additional coolness to raws.queryable.__getitem__, namely handling for ellipses and for tuples. --- manager.py | 88 +++++++++++----------- pydwarf/urist.py | 2 +- raws/dir.py | 168 +++++++++++++++++++++++------------------- raws/file.py | 183 +++++++++++++++++++++++++++++++++------------- raws/queryable.py | 34 ++++++++- 5 files changed, 301 insertions(+), 174 deletions(-) diff --git a/manager.py b/manager.py index b940ec9..390019e 100644 --- a/manager.py +++ b/manager.py @@ -16,49 +16,6 @@ -def getconf(args=None): - # Load initial config from json file - conf = pydwarf.config() - if os.path.isfile(jsonconfigpath): conf.json(jsonconfigpath) - - # Default name of configuration override package - overridename = 'config_override' - - # Override settings from command line arguments, first check for --config argument - if args.config: - if args.config.endswith('.json'): - conf.json(args.config) - else: - overridename = args.config - - # Apply settings in override package - overrideexception = None - if overridename and (os.path.isfile(overridename + '.py') or os.path.isfile(os.path.join(overridename, '__init__.py'))): - try: - package = importlib.import_module(overridename) - conf.apply(package.export) - except Exception, e: - overrideexception = e - - # Apply other command line arguments - conf.apply(args.__dict__) - - # Setup logger - conf.setuplogger() - - # If there was an exception when reading the overridename package, report it now - # Don't report it earlier because the logger wasn't set up yet - if overrideexception: - pydwarf.log.error('Failed to apply configuration from %s package.\n%s' % (overridename, overrideexception)) - - # Handle things like automatic version detection, package importing - conf.setup() - - # All done! - return conf - - - # Actually run the program def __main__(args=None): conf = getconf(args) @@ -116,13 +73,56 @@ def __main__(args=None): # Write the output pydwarf.log.info('Writing changes to raws to %s.' % outputdir) - pydwarf.urist.session.dfraws.write(outputdir, pydwarf.log) + pydwarf.urist.session.dfraws.write(outputdir) # All done! pydwarf.log.info('All done!') +def getconf(args=None): + # Load initial config from json file + conf = pydwarf.config() + if os.path.isfile(jsonconfigpath): conf.json(jsonconfigpath) + + # Default name of configuration override package + overridename = 'config_override' + + # Override settings from command line arguments, first check for --config argument + if args.config: + if args.config.endswith('.json'): + conf.json(args.config) + else: + overridename = args.config + + # Apply settings in override package + overrideexception = None + if overridename and (os.path.isfile(overridename + '.py') or os.path.isfile(os.path.join(overridename, '__init__.py'))): + try: + package = importlib.import_module(overridename) + conf.apply(package.export) + except Exception, e: + overrideexception = e + + # Apply other command line arguments + conf.apply(args.__dict__) + + # Setup logger + conf.setuplogger() + + # If there was an exception when reading the overridename package, report it now + # Don't report it earlier because the logger wasn't set up yet + if overrideexception: + pydwarf.log.error('Failed to apply configuration from %s package.\n%s' % (overridename, overrideexception)) + + # Handle things like automatic version detection, package importing + conf.setup() + + # All done! + return conf + + + def parseargs(): parser = argparse.ArgumentParser() parser.add_argument('-ver', '--version', help='indicate Dwarf Fortress version', type=str) diff --git a/pydwarf/urist.py b/pydwarf/urist.py index c481b5a..073a80c 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -20,7 +20,7 @@ def failed(self, info): def configure(self, raws, conf): self.conf = conf - self.dfraws = raws.dir(path=conf.input, log=log) + self.dfraws = raws.dir(path=conf.input, version=conf.version, log=log) self.dfraws.hack = raws.dfhack(path=conf.dfhackdir, version=conf.dfhackver) self.dfversion = conf.version diff --git a/raws/dir.py b/raws/dir.py index 8856388..6001cb4 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -1,107 +1,123 @@ import os from copytree import copytree from queryable import rawsqueryableobj, rawstokenlist -from file import rawsfile +from file import rawsfile, rawsotherfile class rawsdir(rawsqueryableobj): '''Represents as a whole all the raws contained within a directory.''' - def __init__(self, *args, **kwargs): + def __init__(self, path=None, dest=None, version=None, log=None, *args, **kwargs): '''Constructor for rawsdir object.''' - self.files = {} self.otherfiles = [] + self.path = path + self.dest = dest + self.version = version + self.log = log self.hack = None - if len(args) or len(kwargs): self.read(*args, **kwargs) + if len(args) or len(kwargs): self.read(path=path, *args, **kwargs) + + def __enter__(self): + return self + def __exit__(self, type, value, traceback): + if traceback is None and self.path is not None: self.write(path=self.path) + + def __getitem__(self, name): + return self.get(name) + def __setitem__(self, name, value): + return self.set(name, value) + + def __contains__(self, item): + if isinstance(item, basestring): + return item in self.files + else: + return item in self.files.itervalues() - def getfile(self, filename, create=None): + def getfile(self, name, create=None): '''Gets the file with a given name. If no file by that name is found, None is returned instead. If creature is set to something other than None, the behavior when no file by some name exists is altered: A new file is created and associated with that name, and then its add method is called using the value for create as its argument.''' - rfile = self.files.get(filename) - if create is not None and rfile is None: - rfile = self.addfile(filename) - rfile.add(create) - return rfile + file = self.files.get(name) + if create is not None and file is None: + file = self.addfile(name) + file.add(create) + return file - def addfile(self, filename=None, rfile=None, path=None): - if path is not None: - return self.addpath(path) + def add(self, file=None, path=None, replace=False, **kwargs): + if file is None: + if path is None: raise ValueError + file = rawsfile(path=path, dir=self, **kwargs) + if isinstance(file, basestring): + file = rawsfile(name=file, path=path, dir=self, **kwargs) else: - if rfile and not filename: filename = rfile.header - if filename in self.files: raise KeyError - if not rfile: rfile = rawsfile(header=filename) - self.files[filename] = rfile - rfile.dir = self - return rfile - - def setfile(self, filename=None, rfile=None): - if rfile and not filename: filename = rfile.header - rfile.dir = self - self.files[filename] = rfile - - def removefile(self, filename=None, rfile=None): - if not rfile.dir == self: raise ValueError - if rfile and not filename: filename = rfile.header - rfile.dir = None - del self.files[filename] - - def addpath(self, path): - with open(path, 'rb') as rfilestream: - rfile = rawsfile(path=path, rfile=rfilestream, dir=self) - if rfile.header in self.files: raise ValueError - self.files[rfile.header] = rfile - return rfile - - def __getitem__(self, name): return self.getfile(name) - def __setitem__(self, name, value): return self.setfile(name, value) - def __contains__(self, name): return name in self.files - - def read(self, path, log=None): + if file.dir is self: raise ValueError + if file.dir is not None: raise ValueError + file.dir = self + if (not replace) and file in self.files: raise KeyError('Dir already contains a file by the name %s.' % file) + self.files[file] = file + return file + + def remove(self, file=None): + if file not in self.files: raise KeyError + self.files[file].dir = None + del self.files[file] + + def addfile(self, filename=None, rfile=None, path=None): + '''Deprecated: As of v1.0.2. Use the add method instead.''' + raise ValueError + return self.add(file=filename if filename is not None else rfile, path=path) + + def removefile(self, name=None, file=None): + '''Deprecated: As of v1.0.2. Use the remove method instead.''' + raise ValueError + return self.remove(file if file is not None else name) + + def read(self, path=None): '''Reads raws from all text files in the specified directory.''' - for filename in os.listdir(path): - filepath = os.path.join(path, filename) - if filename.endswith('.txt') and os.path.isfile(filepath): - if log: log.debug('Reading raws file %s.' % filepath) - with open(filepath, 'rb') as rfile: - filenamekey = os.path.splitext(os.path.basename(filename))[0] - self.files[filenamekey] = rawsfile(path=filepath, rfile=rfile, dir=self) - else: - if log: log.debug('Found non-raws file %s.' % filepath) - self.otherfiles.append((path, filename)) - - def write(self, path, log=None): + + if path is None: + if self.path is None: raise ValueError + path = self.path + paths = (path,) if isinstance(path, basestring) else path + + for path in paths: + for filename in os.listdir(path): + filepath = os.path.join(path, filename) + if filename.endswith('.txt') and os.path.isfile(filepath): + if self.log: self.log.debug('Reading raws file %s.' % filepath) + with open(filepath, 'rb') as file: + filenamekey = os.path.splitext(os.path.basename(filename))[0] + self.files[filenamekey] = rawsfile(path=filepath, file=file, dir=self) + else: + if self.log: self.log.debug('Found non-raws file %s.' % filepath) + self.otherfiles.append(rawsotherfile(filepath, path)) + + def write(self, path=None): '''Writes raws to the specified directory.''' + if path is None: + if self.path is None and self.dest is None: raise ValueError + path = self.dest if self.dest else self.path + # Write raws files - for filename in self.files: - filepath = os.path.join(path, filename) - if not filepath.endswith('.txt'): filepath += '.txt' - with open(filepath, 'wb') as rfile: - if log: log.debug('Writing raws file %s.' % filepath) - self.files[filename].write(rfile) + if self.log: self.log.debug('Writing %d raws files to %s.' % (len(self.files), path)) + for file in self.files.itervalues(): + file.write(path) + # Copy other files - for filepath, filename in self.otherfiles: - if filepath != path: - originalpath = os.path.join(filepath, filename) - writepath = os.path.join(path, filename) - if log: log.debug('Writing non-raws file %s.' % writepath) - if os.path.isfile(originalpath): - shutil.copy2(originalpath, writepath) - elif os.path.isdir(originalpath): - copytree(originalpath, writepath) - elif log: - log.error('Failed to write non-raws file %s: it\'s neither a file nor a directory.' % writepath) + if self.log: self.log.debug('Copying %d other files to %s.' % (len(self.otherfiles), path)) + for file in self.otherfiles: + file.write(path) - def tokens(self): + def tokens(self, *args, **kwargs): '''Iterate through all tokens.''' for filename in self.files: - for token in self.files[filename].tokens(): + for token in self.files[filename].tokens(*args, **kwargs): yield token def getobjheaders(self, type): @@ -128,8 +144,8 @@ def getobjheaders(self, type): match_types = self.getobjheadername(type) results = rawstokenlist() - for rfile in self.files.itervalues(): - root = rfile.root() + for file in self.files.itervalues(): + root = file.root() if root is not None and root.value == 'OBJECT' and root.nargs() == 1 and root.args[0] in match_types: results.append(root) return results diff --git a/raws/file.py b/raws/file.py index 8102f6f..1c0d88c 100644 --- a/raws/file.py +++ b/raws/file.py @@ -1,35 +1,125 @@ +import os +import shutil + +from copytree import copytree from queryable import rawsqueryableobj, rawstokenlist from token import rawstoken -class rawsfile(rawsqueryableobj): + + +class rawsbasefile(object): + def __init__(self): + self.path = None + self.loc = None + self.name = None + self.ext = None + + def __str__(self): + return os.path.join(self.loc, ('.'.join((self.name, self.ext)) if self.ext else self.name)) if self.loc else self.name + def __repr__(self): + return str(self) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + return str(self) == str(other) + def __ne__(self, other): + return str(self) != str(other) + + def __gt__(self, other): + return str(self) > str(other) + def __ge__(self, other): + return str(self) >= str(other) + def __lt__(self, other): + return str(self) < str(other) + def __le__(self, other): + return str(self) <= str(other) + + def setpath(self, path, root=None): + self.path = path + self.rootpath = root + self.loc = os.path.relpath(path, root) if root and path else None + self.name, self.ext = (os.path.splitext(os.path.basename(path)) if os.path.isfile(path) else (os.path.basename(path), None)) if path else (None, None) + + def dest(self, path, makedir=False): + '''Internal: Given a root directory that this file would be written to, get the full path of where this file belongs.''' + dir = os.path.join(path, self.loc) if self.loc else path + dest = os.path.join(dir, '.'.join((self.name, self.ext)) if self.ext else self.name) + if makedir and not os.path.isdir(dir): os.makedirs(dir) + return dest + + + +class rawsotherfile(rawsbasefile): + def __init__(self, path, root=None): + self.setpath(path, root) + + def write(self, path): + dest = self.dest(path, makedir=True) + if path != dest: + if os.path.isfile(self.path): + shutil.copy2(path, dest) + elif os.path.isdir(self.path): + copytree(originalpath, writepath) + else: + raise ValueError + + + +class rawsfile(rawsbasefile, rawsqueryableobj): '''Represents a single file within a raws directory.''' - def __init__(self, header=None, data=None, path=None, tokens=None, rfile=None, dir=None): + def __init__(self, name=None, data=None, path=None, tokens=None, file=None, dir=None, root=None): '''Constructs a new raws file object. - header: The header string to appear at the top of the file. Also used to determine filename. + name: The name string to appear at the top of the file. Also used to determine filename. data: A string to be parsed into token data. path: A path to the file from which this object is being parsed, if any exists. tokens: An iterable of tokens from which to construct the object; these tokens will be its initial contents. - rfile: A file-like object from which to automatically read the header and data attributes. + file: A file-like object from which to automatically read the name and data attributes. dir: Which raws.dir object this file belongs to. + root: Root directory for raws files. If left as None, dir.path will be used instead. ''' - if rfile: - self.read(rfile) - if header is not None: self.header = header + + self.dir = dir + self.setpath(path, dir.path if (dir.path and not root) else root) + + self.roottoken = None + self.tailtoken = None + + if file: + self.read(file) + if name is not None: self.name = name if data is not None: self.data = data else: - self.header = header + self.name = name self.data = data - self.path = path - self.roottoken = None - self.tailtoken = None - self.dir = dir + if self.data is not None: tokens = rawstoken.parse(self.data, implicit_braces=False, file=self) self.settokens(tokens, setfile=False) elif tokens is not None: self.settokens(tokens, setfile=True) + + def __enter__(self): + return self + def __exit__(self): + if self.path: self.write(self.path) + + def __eq__(self, other): + return self.equals(other) + def __ne__(self, other): + return not self.equals(other) + + def __len__(self): + return self.length() + + def __nonzero__(self): + return True + + def __repr__(self): + return '%s\n%s' %(self.name, ''.join([repr(o) for o in self.tokens()])) def index(self, index): itrtoken = self.root() if index >= 0 else self.tail() @@ -41,35 +131,33 @@ def index(self, index): def getpath(self): return self.path - def setpath(self, path): - self.path = path - def getheader(self): - '''Get the file header. + def getname(self): + '''Get the file name. Example usage: >>> dwarf = df.getobj('CREATURE:DWARF') >>> creature_standard = dwarf.file - >>> print creature_standard.getheader() + >>> print creature_standard.getname() creature_standard >>> creature_standard.setheader('example_header') - >>> print creature_standard.getheader() + >>> print creature_standard.getname() example_header ''' - return self.header - def setheader(self, header): - '''Set the file header. + return self.name + def setname(self, name): + '''Set the file name. Example usage: >>> dwarf = df.getobj('CREATURE:DWARF') >>> creature_standard = dwarf.file - >>> print creature_standard.getheader() + >>> print creature_standard.getname() creature_standard >>> creature_standard.setheader('example_header') - >>> print creature_standard.getheader() + >>> print creature_standard.getname() example_header ''' - self.header = header + self.name = name def settokens(self, tokens, setfile=True): '''Internal: Utility method for setting the root and tail tokens given an iterable.''' @@ -101,26 +189,13 @@ def copy(self): >>> print item_food == food_copy False ''' - rfile = rawsfile(header=self.header, path=self.path, dir=self.dir) - rfile.settokens(rawstoken.copy(self.tokens())) - return rfile + file = rawsfile(name=self.name, path=self.path, dir=self.dir) + file.settokens(rawstoken.copy(self.tokens())) + return file def equals(self, other): return rawstoken.tokensequal(self.tokens(), other.tokens()) - def __eq__(self, other): - return self.equals(other) - def __ne__(self, other): - return not self.equals(other) - - def __len__(self): - return self.length() - - def __str__(self): - return self.header - def __repr__(self): - return '%s\n%s' %(self.header, ''.join([repr(o) for o in self.tokens()])) - def root(self): '''Gets the first token in the file. @@ -160,12 +235,23 @@ def tokens(self, reverse=False, **kwargs): for token in generator: yield token - def read(self, rfile): - '''Internal: Given a file-like object, reads header and data from it.''' - self.header, self.data = rfile.readline().strip(), rfile.read() - def write(self, rfile): - '''Internal: Given a file-like object, writes the file's contents to that file.''' - rfile.write(self.__repr__()) + def read(self, file): + '''Given a path or file-like object, reads name and data.''' + if isinstance(file, basestring): + self.path = file + self.ext = os.path.splitext(file)[1] + with open(file, 'rb') as src: + self.name, self.data = src.readline().strip(), src.read() + else: + self.name, self.data = file.readline().strip(), file.read() + + def write(self, file): + '''Given a path to a directory or a file-like object, writes the file's contents to that file.''' + if isinstance(file, basestring): + with open(self.dest(file, makedir=True), 'wb') as dest: + dest.write(repr(self)) + else: + file.write(repr(self)) def add(self, auto=None, pretty=None, token=None, tokens=None, **kwargs): '''Adds tokens to the end of a file. @@ -183,7 +269,7 @@ def add(self, auto=None, pretty=None, token=None, tokens=None, **kwargs): [ITEM_FOOD:ITEM_FOOD_ROAST] [NAME:roast] [LEVEL:4] - >>> tokens = item_food.add('\nhi! [THIS][IS][AN][EXAMPLE]') + >>> tokens = item_food.add('hi! [THIS][IS][AN][EXAMPLE]') >>> print tokens hi! [THIS][IS][AN][EXAMPLE] >>> print item_food.list() @@ -196,8 +282,7 @@ def add(self, auto=None, pretty=None, token=None, tokens=None, **kwargs): [LEVEL:3] [ITEM_FOOD:ITEM_FOOD_ROAST] [NAME:roast] - [LEVEL:4] - hi! [THIS][IS][AN][EXAMPLE] + [LEVEL:4]hi! [THIS][IS][AN][EXAMPLE] ''' tail = self.tail() if tail: diff --git a/raws/queryable.py b/raws/queryable.py index 203a66f..fd0a9fe 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -13,11 +13,11 @@ class rawsqueryable(object): its limit or the tokens have run out.''' quick_query_args_docstring = ''' - %s pretty: Convenience argument which acts as a substitute for directly assigning a filter's exact_value and exact_args arguments. Some methods also accept an until_pretty argument which acts as a substitute for until_exact_value and until_exact_args. + %s **kwargs: If no tokeniter is specified, then arguments which correspond to named arguments of the object's tokens method will be passed to that method. All other arguments will be passed to the appropriate filters, @@ -38,15 +38,41 @@ def __contains__(self, item): return item in self.tokens() def __getitem__(self, item): - if isinstance(item, basestring): + '''Overrides object[...] behavior. Accepts a number of different types for the item argument, each resulting in different behavior. + + object[...] + Returns the same as object.list(). + object[str] + Returns the same as object.get(str). + object[int] + Returns the same as object.index(int). + object[slice] + Returns the same as object.slice(slice). + object[iterable] + Returns a flattened list containing object[member] in order for each member of iterable. + object[anything else] + Raises an exception. + ''' + if item is Ellipsis: + return self.list() + elif isinstance(item, basestring): return self.get(pretty=item) elif isinstance(item, int): return self.index(item) elif isinstance(item, slice): return self.slice(item) + elif hasattr(item, '__iter__') or hasattr(item, '__getitem__'): + return self.getitems(items) else: raise ValueError - + + def getitems(self, items): + result = [] + for item in items: + ext = self.__getitem__(item) + (result.extend if isinstance(ext, list) else result.append)(ext) + return result + def slice(self, slice): return rawstokenlist(self.islice(slice)) @@ -63,9 +89,9 @@ def query(self, filters, tokeniter=None, **kwargs): '''Executes a query on some iterable containing tokens. filters: A dict or other iterable containing rawstokenfilter-like objects. + %s **kwargs: If tokeniter is not given, then the object's token method will be called with these arguments and used instead. - %s ''' % rawsqueryable.query_tokeniter_docstring if tokeniter is None: tokeniter = self.tokens(**kwargs) From e89f3200b0dd5441202dcd4f51da27c70898e57c Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 23 Jun 2015 17:07:26 -0400 Subject: [PATCH 07/95] Fixed some of the broken things from the previous commit --- manager.py | 13 ++++++------- raws/dfhack.py | 2 +- raws/dir.py | 4 ++-- raws/file.py | 15 +++++++++------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/manager.py b/manager.py index 390019e..e929bdd 100644 --- a/manager.py +++ b/manager.py @@ -1,5 +1,6 @@ import re import os +import shutil import json import argparse import importlib @@ -63,13 +64,11 @@ def __main__(args=None): # Get the output directory, remove old raws if present outputdir = conf.output if conf.output else conf.input if os.path.exists(outputdir): - pydwarf.log.info('Removing obsolete raws from %s.' % outputdir) - for removefile in [os.path.join(outputdir, f) for f in os.listdir(outputdir)]: - pydwarf.log.debug('Removing file %s.' % removefile) - if removefile.endswith('.txt'): os.remove(removefile) - else: - pydwarf.log.info('Creating raws output directory %s.' % outputdir) - os.makedirs(outputdir) + pydwarf.log.info('Removing obsolete output directory %s.' % outputdir) + shutil.rmtree(outputdir) + + pydwarf.log.info('Creating raws output directory %s.' % outputdir) + os.makedirs(outputdir) # Write the output pydwarf.log.info('Writing changes to raws to %s.' % outputdir) diff --git a/raws/dfhack.py b/raws/dfhack.py index 3630444..3fed467 100644 --- a/raws/dfhack.py +++ b/raws/dfhack.py @@ -11,7 +11,7 @@ class dfhack: def __init__(self, path=None, version=None): '''Constructor for dfhack object.''' self.path = path - self.version = version + self.version = version # TODO: automatic detection in pydwarf.config isn't reliable (why am I putting it here? who the fuck knows) def open(path, *args, **kwargs): '''Open a file within the DFHack directory. Acts as a shortcut for open('dfhack/path', mode).''' diff --git a/raws/dir.py b/raws/dir.py index 6001cb4..5edf6c8 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -17,7 +17,7 @@ def __init__(self, path=None, dest=None, version=None, log=None, *args, **kwargs self.version = version self.log = log self.hack = None - if len(args) or len(kwargs): self.read(path=path, *args, **kwargs) + if path: self.read(path=path, *args, **kwargs) def __enter__(self): return self @@ -44,7 +44,7 @@ def getfile(self, name, create=None): file = self.files.get(name) if create is not None and file is None: - file = self.addfile(name) + file = self.add(name) file.add(create) return file diff --git a/raws/file.py b/raws/file.py index 1c0d88c..e643bc7 100644 --- a/raws/file.py +++ b/raws/file.py @@ -39,7 +39,7 @@ def __le__(self, other): def setpath(self, path, root=None): self.path = path self.rootpath = root - self.loc = os.path.relpath(path, root) if root and path else None + self.loc = os.path.relpath(path, root) if (root and path and os.path.abspath(path).startswith(os.path.abspath(root))) else None self.name, self.ext = (os.path.splitext(os.path.basename(path)) if os.path.isfile(path) else (os.path.basename(path), None)) if path else (None, None) def dest(self, path, makedir=False): @@ -61,7 +61,7 @@ def write(self, path): if os.path.isfile(self.path): shutil.copy2(path, dest) elif os.path.isdir(self.path): - copytree(originalpath, writepath) + copytree(path, dest) else: raise ValueError @@ -83,7 +83,7 @@ def __init__(self, name=None, data=None, path=None, tokens=None, file=None, dir= ''' self.dir = dir - self.setpath(path, dir.path if (dir.path and not root) else root) + self.setpath(path, dir.path if (dir and dir.path and not root) else root) self.roottoken = None self.tailtoken = None @@ -189,9 +189,12 @@ def copy(self): >>> print item_food == food_copy False ''' - file = rawsfile(name=self.name, path=self.path, dir=self.dir) - file.settokens(rawstoken.copy(self.tokens())) - return file + copy = rawsfile(name=self.name) + copy.path = self.path + copy.loc = self.loc + copy.ext = self.ext + copy.settokens(rawstoken.copy(self.tokens())) + return copy def equals(self, other): return rawstoken.tokensequal(self.tokens(), other.tokens()) From 069071034833c1f34d539f9ba96d63c989ba4618 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 23 Jun 2015 17:08:12 -0400 Subject: [PATCH 08/95] Updated a bunch of scripts to comply with changes made to raws.dir --- scripts/pineapple/pydwarf.bauxitetoaluminum.py | 6 ++++-- scripts/pineapple/pydwarf.cavegrass.py | 9 ++++++--- scripts/pineapple/pydwarf.diff.py | 2 +- scripts/pineapple/pydwarf.greensteel.py | 2 +- scripts/putnam/pydwarf.microreduce.py | 2 +- scripts/shukaro/shukaroutils.py | 2 +- scripts/stal/pydwarf.armourypack.py | 2 +- scripts/umiman/pydwarf.smallthings.py | 13 ++++++++++--- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/scripts/pineapple/pydwarf.bauxitetoaluminum.py b/scripts/pineapple/pydwarf.bauxitetoaluminum.py index 66fbb30..5901ed9 100644 --- a/scripts/pineapple/pydwarf.bauxitetoaluminum.py +++ b/scripts/pineapple/pydwarf.bauxitetoaluminum.py @@ -34,11 +34,13 @@ }, compatibility = (pydwarf.df_0_3x, pydwarf.df_0_40) ) -def bauxtoalum(df, aluminum_value=0.75, entities=default_entities, add_to_file=default_file): +def bauxitetoaluminum(df, aluminum_value=0.75, entities=default_entities, add_to_file=default_file): # Affect value of aluminum pydwarf.log.debug('Multiplying value of aluminum by %f.' % aluminum_value) try: - matvaluetoken = df.getobj('INORGANIC:ALUMINUM').getprop('MATERIAL_VALUE') + aluminum = df.getobj('INORGANIC:ALUMINUM') + if aluminum is None: return pydwarf.failure('Couldn\'t find aluminum token to affect its value.') + matvaluetoken = aluminum.getprop('MATERIAL_VALUE') matvaluetoken.args[0] = str( float(matvaluetoken.args[0]) * aluminum_value ) except: pydwarf.log.exception('Failed to affect value of aluminum.') diff --git a/scripts/pineapple/pydwarf.cavegrass.py b/scripts/pineapple/pydwarf.cavegrass.py index d1782a2..d8e02e4 100644 --- a/scripts/pineapple/pydwarf.cavegrass.py +++ b/scripts/pineapple/pydwarf.cavegrass.py @@ -120,9 +120,12 @@ def cavegrass(df, grasses=default_grasses, add_file='plant_grasses_cavegrass_pin # Add the new file for new grasses grassfile = None if add_file: - if add_file in df.files: return pydwarf.failure('File %s already exists.' % add_file) - grassfile = df.addfile(filename=add_file) - grassfile.add('OBJECT:PLANT') + try: + grassfile = df.add(add_file) + grassfile.add('OBJECT:PLANT') + except: + pydwarf.log.exception('Failed to add file %s.' % add_file) + return pydwarf.failure('Failed to add file %s.' % add_file) # Handle each grass failures = 0 diff --git a/scripts/pineapple/pydwarf.diff.py b/scripts/pineapple/pydwarf.diff.py index d768ff6..0d1ac88 100644 --- a/scripts/pineapple/pydwarf.diff.py +++ b/scripts/pineapple/pydwarf.diff.py @@ -87,7 +87,7 @@ def diff(dfraws, paths): # File doesn't exist yet, don't bother with a diff else: pydwarf.log.debug('File didn\'t exist yet, adding...') - dfraws.addfile(rfile=newfile) + dfraws.add(newfile) for fileheader, fileops in operations.iteritems(): # Do some handling for potentially conflicting replacements diff --git a/scripts/pineapple/pydwarf.greensteel.py b/scripts/pineapple/pydwarf.greensteel.py index c33d612..5b0aa14 100644 --- a/scripts/pineapple/pydwarf.greensteel.py +++ b/scripts/pineapple/pydwarf.greensteel.py @@ -23,7 +23,7 @@ def greensteel(df, entities=default_entities): # Add greensteel raws try: - df.read(path=greendir, log=pydwarf.log) + df.read(path=greendir) return pydwarf.urist.getfn('pineapple.utils.addtoentity')( df, entities = entities, diff --git a/scripts/putnam/pydwarf.microreduce.py b/scripts/putnam/pydwarf.microreduce.py index accc2b3..6c4d09e 100644 --- a/scripts/putnam/pydwarf.microreduce.py +++ b/scripts/putnam/pydwarf.microreduce.py @@ -15,7 +15,7 @@ def microreduce(dfraws): # Add files genericpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Microreduce', '%s.txt') for filename in ('building_macro_fantastic', 'item_macro_fantastic', 'reaction_macro_fantastic'): - rfile = dfraws.addfile(path=genericpath % filename) + rfile = dfraws.add(path=genericpath % filename) # Add PERMITTED_BUILDING and PERMITTED_REACTION tokens to ENTITY:MOUNTAIN for building in rfile.all(re_value='BUILDING.*', args_count=1): mountain.add(raws.token(value='PERMITTED_BUILDING', args=[building.args[0]])) diff --git a/scripts/shukaro/shukaroutils.py b/scripts/shukaro/shukaroutils.py index 2994c1a..e39c61b 100644 --- a/scripts/shukaro/shukaroutils.py +++ b/scripts/shukaro/shukaroutils.py @@ -34,7 +34,7 @@ def addraws(pydwarf, dfraws, rawsdir, entities, extratokens=None): # Add new files for filename, rfile in shukaroraws.files.iteritems(): - if filename not in dfraws: dfraws.addfile(rfile=rfile) + if filename not in dfraws: dfraws.add(rfile.copy()) # All done! return pydwarf.success() diff --git a/scripts/stal/pydwarf.armourypack.py b/scripts/stal/pydwarf.armourypack.py index 5439ff1..cc9be7d 100644 --- a/scripts/stal/pydwarf.armourypack.py +++ b/scripts/stal/pydwarf.armourypack.py @@ -88,7 +88,7 @@ def additemstoraws(dfraws, armouryraws): # Add new item elif armourytokens: pydwarf.log.debug('Adding new item %s...' % armouryitem) - if filename not in dfraws.files: dfraws.addfile(filename) + if filename not in dfraws.files: dfraws.add(filename) armouryitem.prefix = '\n\n' # Makes outputted raws a bit neater dfraws[filename].add(token=armouryitem) dfraws[filename].add(tokens=raws.token.copy(armourytokens)) diff --git a/scripts/umiman/pydwarf.smallthings.py b/scripts/umiman/pydwarf.smallthings.py index 72df1ff..85e703c 100644 --- a/scripts/umiman/pydwarf.smallthings.py +++ b/scripts/umiman/pydwarf.smallthings.py @@ -73,7 +73,10 @@ def prefstring(dfraws): pydwarf.log.debug('Added %d prefstrings to %s.' % (len(prefs), dfcreature)) # All done! - return pydwarf.success('Added prefstrings to %d creatures.' % (len(smallcreatures) - failedcreatures)) + if (len(smallcreatures) - failedcreatures): + return pydwarf.success('Added prefstrings to %d creatures.' % (len(smallcreatures) - failedcreatures)) + else: + return pydwarf.failure('Added prefstrings to no creatures.') @@ -94,6 +97,8 @@ def prefstring(dfraws): compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) ) def engraving(dfraws): + if 'descriptor_shape_umiman' in dfraws: return pydwarf.failure('File descriptor_shape_umiman already exists.') + # Get the smallthings ModBase raws, which is where this data will be coming from smallraws = getsmallraws() if not smallraws: return pydwarf.failure('Failed to read smallthings raws.') @@ -103,12 +108,14 @@ def engraving(dfraws): dfshapesdict = dfraws.objdict('SHAPE') # Add a new file for the new shapes - dfshapesfile = dfraws.addfile(filename='descriptor_shape_umiman') + dfshapesfile = dfraws.add('descriptor_shape_umiman') dfshapesfile.add('OBJECT:DESCRIPTOR_SHAPE') shapesadded = 0 # Add each shape - for smallshape in smallraws['descriptor_shape_standard'].all(exact_value='SHAPE'): + smallshapes = smallraws['descriptor_shape_standard'] + if smallshapes is None: return pydwarf.failure('Failed to find smallthings raws file named descriptor_shape_standard.') + for smallshape in smallshapes.all(exact_value='SHAPE'): if smallshape.args[0] not in dfshapesdict: # Verify that the shape isn't already in the raws pydwarf.log.debug('Adding shape %s...' % smallshape) From 93808481f1c24f9680afc12952f1a52f740fb3d9 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 07:25:34 -0400 Subject: [PATCH 09/95] Fixed most bugs related to changes to file handling --- raws/dir.py | 95 ++++++++++++++++++++++++-------------------- raws/file.py | 109 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 129 insertions(+), 75 deletions(-) diff --git a/raws/dir.py b/raws/dir.py index 5edf6c8..291dcb5 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -1,7 +1,7 @@ import os from copytree import copytree -from queryable import rawsqueryableobj, rawstokenlist -from file import rawsfile, rawsotherfile +from queryable import rawsqueryable, rawsqueryableobj, rawstokenlist +from file import rawsbasefile, rawsotherfile, rawsfile @@ -11,7 +11,7 @@ class rawsdir(rawsqueryableobj): def __init__(self, path=None, dest=None, version=None, log=None, *args, **kwargs): '''Constructor for rawsdir object.''' self.files = {} - self.otherfiles = [] + self.filenames = {} self.path = path self.dest = dest self.version = version @@ -19,6 +19,9 @@ def __init__(self, path=None, dest=None, version=None, log=None, *args, **kwargs self.hack = None if path: self.read(path=path, *args, **kwargs) + def __str__(self): + return '\n'.join(['%s %s' % (file.kind, str(file)) for file in self.files.itervalues()]) + def __enter__(self): return self def __exit__(self, type, value, traceback): @@ -30,10 +33,7 @@ def __setitem__(self, name, value): return self.set(name, value) def __contains__(self, item): - if isinstance(item, basestring): - return item in self.files - else: - return item in self.files.itervalues() + return str(item) in self.files def getfile(self, name, create=None): '''Gets the file with a given name. If no file by that name is found, @@ -43,59 +43,75 @@ def getfile(self, name, create=None): method is called using the value for create as its argument.''' file = self.files.get(name) + if file is None: + file = self.filenames.get(name) + if file is not None: + if len(file) == 1: + file = file[0] + else: + raise ValueError('Failed to retrieve file from dir because the name found no exact matches, and because multiple files were found with that name.') + if create is not None and file is None: file = self.add(name) file.add(create) return file - def add(self, file=None, path=None, replace=False, **kwargs): + def add(self, file=None, path=None, loc=None, replace=False, **kwargs): if file is None: if path is None: raise ValueError file = rawsfile(path=path, dir=self, **kwargs) if isinstance(file, basestring): - file = rawsfile(name=file, path=path, dir=self, **kwargs) + splitloc, name = os.path.split(file) + loc = os.path.join(loc, splitloc) if loc else splitloc + file = rawsfile(name=name, loc=loc, path=path, dir=self, **kwargs) else: - if file.dir is self: raise ValueError - if file.dir is not None: raise ValueError + if file.dir is self: raise ValueError('Failed to add file %s because it already belongs to this dir.' % file) + if file.dir is not None: raise ValueError('Failed to add file %s because it already belongs to another dir. You probably meant to remove the file first or to add a copy.' % file) file.dir = self if (not replace) and file in self.files: raise KeyError('Dir already contains a file by the name %s.' % file) - self.files[file] = file + self.files[str(file)] = file return file + def addfilekey(self, file): + '''Internal: Add a raws file to this dir's files and filenames dicts.''' + self.files[str(file)] = file + if file.name not in self.filenames: self.filenames[file.name] = [] + self.filenames[file.name].append(file) + def remove(self, file=None): if file not in self.files: raise KeyError - self.files[file].dir = None - del self.files[file] + self.files[str(file)].dir = None + del self.files[str(file)] def addfile(self, filename=None, rfile=None, path=None): '''Deprecated: As of v1.0.2. Use the add method instead.''' - raise ValueError return self.add(file=filename if filename is not None else rfile, path=path) - def removefile(self, name=None, file=None): '''Deprecated: As of v1.0.2. Use the remove method instead.''' - raise ValueError return self.remove(file if file is not None else name) - + def read(self, path=None): '''Reads raws from all text files in the specified directory.''' if path is None: - if self.path is None: raise ValueError + if self.path is None: raise ValueError('Couldn\'t read dir because no path was specified.') path = self.path paths = (path,) if isinstance(path, basestring) else path for path in paths: - for filename in os.listdir(path): - filepath = os.path.join(path, filename) - if filename.endswith('.txt') and os.path.isfile(filepath): - if self.log: self.log.debug('Reading raws file %s.' % filepath) - with open(filepath, 'rb') as file: - filenamekey = os.path.splitext(os.path.basename(filename))[0] - self.files[filenamekey] = rawsfile(path=filepath, file=file, dir=self) - else: - if self.log: self.log.debug('Found non-raws file %s.' % filepath) - self.otherfiles.append(rawsotherfile(filepath, path)) + for root, dirs, files in os.walk(path): + addeddirs = [] + # Add files + for name in files: + filepath = os.path.join(root, name) + file = rawsbasefile.factory(filepath, root=path, dir=self) + self.files[str(file)] = file + addeddirs.append(os.path.abspath(os.path.dirname(filepath))) + # Add empty directories + for dir in dirs: + dir = os.path.abspath(dir) + if not any([added.startswith(dir) for added in addeddirs]): + file = rawsotherfile(path=dir, root=path, dir=self) def write(self, path=None): '''Writes raws to the specified directory.''' @@ -104,21 +120,15 @@ def write(self, path=None): if self.path is None and self.dest is None: raise ValueError path = self.dest if self.dest else self.path - # Write raws files - if self.log: self.log.debug('Writing %d raws files to %s.' % (len(self.files), path)) + if self.log: self.log.debug('Writing %d files to %s.' % (len(self.files), path)) for file in self.files.itervalues(): file.write(path) - - # Copy other files - if self.log: self.log.debug('Copying %d other files to %s.' % (len(self.otherfiles), path)) - for file in self.otherfiles: - file.write(path) def tokens(self, *args, **kwargs): '''Iterate through all tokens.''' - for filename in self.files: - for token in self.files[filename].tokens(*args, **kwargs): - yield token + for file in self.files.itervalues(): + if isinstance(file, rawsqueryable): + for token in file.tokens(*args, **kwargs): yield token def getobjheaders(self, type): '''Gets OBJECT:X tokens where X is type. Is also prepared for special cases @@ -145,7 +155,8 @@ def getobjheaders(self, type): match_types = self.getobjheadername(type) results = rawstokenlist() for file in self.files.itervalues(): - root = file.root() - if root is not None and root.value == 'OBJECT' and root.nargs() == 1 and root.args[0] in match_types: - results.append(root) + if isinstance(file, rawsqueryable): + root = file.root() + if root is not None and root.value == 'OBJECT' and root.nargs() == 1 and root.args[0] in match_types: + results.append(root) return results diff --git a/raws/file.py b/raws/file.py index e643bc7..d9b04f6 100644 --- a/raws/file.py +++ b/raws/file.py @@ -1,4 +1,5 @@ import os +import re import shutil from copytree import copytree @@ -9,13 +10,27 @@ class rawsbasefile(object): def __init__(self): + self.dir = None self.path = None + self.rootpath = None self.loc = None self.name = None self.ext = None + self.kind = None + + @staticmethod + def factory(path, **kwargs): + if path.endswith('.txt'): + with open(path, 'rb') as txt: + if txt.readline().strip() == os.path.splitext(os.path.basename(path))[0]: + txt.seek(0) + return rawsfile(path=path, file=txt, **kwargs) + return rawsotherfile(path=path, **kwargs) def __str__(self): - return os.path.join(self.loc, ('.'.join((self.name, self.ext)) if self.ext else self.name)) if self.loc else self.name + name = ''.join((self.name, self.ext)) if self.ext else self.name + path = os.path.join(self.loc, name) if self.loc else name + return path def __repr__(self): return str(self) @@ -36,32 +51,70 @@ def __lt__(self, other): def __le__(self, other): return str(self) <= str(other) - def setpath(self, path, root=None): + def setpath(self, path, root=None, loc=None, name=None, ext=None): self.path = path self.rootpath = root - self.loc = os.path.relpath(path, root) if (root and path and os.path.abspath(path).startswith(os.path.abspath(root))) else None self.name, self.ext = (os.path.splitext(os.path.basename(path)) if os.path.isfile(path) else (os.path.basename(path), None)) if path else (None, None) + if root and path and (not os.path.samefile(root, os.path.dirname(path))) and os.path.abspath(path).startswith(os.path.abspath(root)): + self.loc = os.path.dirname(os.path.relpath(path, root)) + else: + self.loc = None + if loc: self.loc = loc + if name: self.name = name + if ext: self.ext = ext def dest(self, path, makedir=False): '''Internal: Given a root directory that this file would be written to, get the full path of where this file belongs.''' - dir = os.path.join(path, self.loc) if self.loc else path - dest = os.path.join(dir, '.'.join((self.name, self.ext)) if self.ext else self.name) + dest = os.path.join(path, str(self)) + dir = os.path.dirname(dest) if makedir and not os.path.isdir(dir): os.makedirs(dir) return dest + + def remove(self): + '''Remove this file from the raws.dir object to which it belongs. + + Example usage: + >>> dwarf = df.getobj('CREATURE:DWARF') + >>> print dwarf + [CREATURE:DWARF] + >>> print dwarf.file + creature_standard + >>> dwarf.file.remove() + >>> print df.getobj('CREATURE:DWARF') + None + >>> print df.getfile('creature_standard') + None + ''' + if self.dir is not None: + self.dir.remove(self) + else: + raise ValueError('Failed to remove file because it doesn\'t belong to any dir.') class rawsotherfile(rawsbasefile): - def __init__(self, path, root=None): + def __init__(self, path, dir=None, root=None): + self.dir = dir self.setpath(path, root) + self.kind = self.ext[1:] if self.ext else 'dir' + + def copy(self): + copy = rawsotherfile() + copy.path = self.path + copy.dir = self.dir + copy.rootpath = self.rootpath + copy.name = self.name + copy.ext = self.ext + copy.loc = self.loc + return copy def write(self, path): dest = self.dest(path, makedir=True) - if path != dest: + if self.path != dest: if os.path.isfile(self.path): - shutil.copy2(path, dest) + shutil.copy2(self.path, dest) elif os.path.isdir(self.path): - copytree(path, dest) + copytree(self.path, dest) else: raise ValueError @@ -70,7 +123,7 @@ def write(self, path): class rawsfile(rawsbasefile, rawsqueryableobj): '''Represents a single file within a raws directory.''' - def __init__(self, name=None, data=None, path=None, tokens=None, file=None, dir=None, root=None): + def __init__(self, name=None, file=None, path=None, root=None, data=None, tokens=None, dir=None, **kwargs): '''Constructs a new raws file object. name: The name string to appear at the top of the file. Also used to determine filename. @@ -79,11 +132,10 @@ def __init__(self, name=None, data=None, path=None, tokens=None, file=None, dir= tokens: An iterable of tokens from which to construct the object; these tokens will be its initial contents. file: A file-like object from which to automatically read the name and data attributes. dir: Which raws.dir object this file belongs to. - root: Root directory for raws files. If left as None, dir.path will be used instead. ''' self.dir = dir - self.setpath(path, dir.path if (dir and dir.path and not root) else root) + self.setpath(path=path, root=dir.path if (dir and dir.path and not root) else root, **kwargs) self.roottoken = None self.tailtoken = None @@ -102,6 +154,9 @@ def __init__(self, name=None, data=None, path=None, tokens=None, file=None, dir= elif tokens is not None: self.settokens(tokens, setfile=True) + if (not self.path) and (not self.ext): self.ext = '.txt' + self.kind = 'raw' + def __enter__(self): return self def __exit__(self): @@ -119,6 +174,9 @@ def __nonzero__(self): return True def __repr__(self): + return self.content() + + def content(self): return '%s\n%s' %(self.name, ''.join([repr(o) for o in self.tokens()])) def index(self, index): @@ -189,10 +247,12 @@ def copy(self): >>> print item_food == food_copy False ''' - copy = rawsfile(name=self.name) + copy = rawsfile() copy.path = self.path - copy.loc = self.loc + copy.rootpath = self.rootpath + copy.name = self.name copy.ext = self.ext + copy.loc = self.loc copy.settokens(rawstoken.copy(self.tokens())) return copy @@ -252,9 +312,9 @@ def write(self, file): '''Given a path to a directory or a file-like object, writes the file's contents to that file.''' if isinstance(file, basestring): with open(self.dest(file, makedir=True), 'wb') as dest: - dest.write(repr(self)) + dest.write(self.content()) else: - file.write(repr(self)) + file.write(self.content()) def add(self, auto=None, pretty=None, token=None, tokens=None, **kwargs): '''Adds tokens to the end of a file. @@ -303,23 +363,6 @@ def add(self, auto=None, pretty=None, token=None, tokens=None, **kwargs): elif tokens is not None: self.settokens(tokens) return tokens - - def remove(self): - '''Remove this file from the raws.dir object to which it belongs. - - Example usage: - >>> dwarf = df.getobj('CREATURE:DWARF') - >>> print dwarf - [CREATURE:DWARF] - >>> print dwarf.file - creature_standard - >>> dwarf.file.remove() - >>> print df.getobj('CREATURE:DWARF') - None - >>> print df.getfile('creature_standard') - None - ''' - self.dir.removefile(rfile=self) def length(self): '''Get the number of tokens in this file. From c2fc02312579b8af87ebabb7fad5c66b9d9d3425 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 10:10:59 -0400 Subject: [PATCH 10/95] More descriptive exceptions --- manager.py | 2 +- raws/dir.py | 5 +++-- raws/file.py | 2 +- raws/queryable.py | 9 ++++----- raws/token.py | 28 ++++++++++++++-------------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/manager.py b/manager.py index e929bdd..bdac95f 100644 --- a/manager.py +++ b/manager.py @@ -44,7 +44,7 @@ def __main__(args=None): # Make backup if conf.backup is not None: - pydwarf.log.info('Backing up raws to %s.' % conf.backup) + pydwarf.log.info('Backing up raws to directory %s.' % conf.backup) try: raws.copytree(conf.input, conf.backup) except: diff --git a/raws/dir.py b/raws/dir.py index 291dcb5..6c7e56d 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -1,4 +1,5 @@ import os + from copytree import copytree from queryable import rawsqueryable, rawsqueryableobj, rawstokenlist from file import rawsbasefile, rawsotherfile, rawsfile @@ -94,7 +95,7 @@ def read(self, path=None): '''Reads raws from all text files in the specified directory.''' if path is None: - if self.path is None: raise ValueError('Couldn\'t read dir because no path was specified.') + if self.path is None: raise ValueError('Failed to read dir because no path was specified.') path = self.path paths = (path,) if isinstance(path, basestring) else path @@ -117,7 +118,7 @@ def write(self, path=None): '''Writes raws to the specified directory.''' if path is None: - if self.path is None and self.dest is None: raise ValueError + if self.path is None and self.dest is None: raise ValueError('Failed to write dir because no path was specified.') path = self.dest if self.dest else self.path if self.log: self.log.debug('Writing %d files to %s.' % (len(self.files), path)) diff --git a/raws/file.py b/raws/file.py index d9b04f6..041a89c 100644 --- a/raws/file.py +++ b/raws/file.py @@ -116,7 +116,7 @@ def write(self, path): elif os.path.isdir(self.path): copytree(self.path, dest) else: - raise ValueError + raise ValueError('Failed to write file because its path %s refers to neither a file nor a directory.' % self.path) diff --git a/raws/queryable.py b/raws/queryable.py index fd0a9fe..a2702dd 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -1,6 +1,7 @@ # vim:fileencoding=UTF-8 import inspect + from filters import * @@ -64,7 +65,7 @@ def __getitem__(self, item): elif hasattr(item, '__iter__') or hasattr(item, '__getitem__'): return self.getitems(items) else: - raise ValueError + raise ValueError('Failed to get item because the argument was of an unrecognized type.') def getitems(self, items): result = [] @@ -497,7 +498,6 @@ def allobj(self, pretty=None, type=None, exact_id=None, re_id=None, id_in=None): [CREATURE:BEAR_SLOTH] ''' - if re_id and id_in: raise ValueError type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) results = rawstokenlist() for objecttoken in self.getobjheaders(type): @@ -539,7 +539,7 @@ def objpretty(pretty, type, id): if pretty is not None: if ':' in pretty: parts = pretty.split(':') - if len(parts) != 2: raise ValueError + if len(parts) != 2: raise ValueError('Failed to parse argument because there were too many or too few colons, there ought to be be just one.') return parts[0], parts[1] elif type is None: return pretty, id @@ -553,8 +553,7 @@ def objpretty(pretty, type, id): class rawstokenlist(list, rawsqueryable): '''Extends builtin list with token querying functionality.''' - def tokens(self, range=None, include_self=False, reverse=False): - if include_self: raise ValueError + def tokens(self, range=None, reverse=False): for i in xrange(self.__len__()-1, -1, -1) if reverse else xrange(0, self.__len__()): if range is not None and range <= count: break yield self.__getitem__(i) diff --git a/raws/token.py b/raws/token.py index 86a0a1b..62d887c 100644 --- a/raws/token.py +++ b/raws/token.py @@ -1,4 +1,5 @@ import itertools + from queryable import rawsqueryable, rawstokenlist from filters import rawstokenfilter @@ -54,7 +55,7 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr ''' % rawstoken.auto_arg_docstring pretty, token, tokens = rawstoken.auto(auto, pretty, token, None) - if tokens is not None: raise ValueError + if tokens is not None: raise ValueError('Failed to initialize token object because the given string %s contained more than one token.' % pretty) if pretty is not None: token = rawstoken.parseone(pretty, implicit_braces=True) if token is not None: @@ -221,8 +222,7 @@ def __add__(self, other): tokens.extend(other) return tokens else: - raise ValueError - + raise ValueError('Failed to perform concatenation because the type of the other operand was unrecognized.') def __radd__(self, other): '''Internal: Same as __add__ except reversed.''' if isinstance(other, rawstoken): @@ -236,7 +236,7 @@ def __radd__(self, other): tokens.append(self) return tokens else: - raise ValueError + raise ValueError('Failed to perform concatenation because the type of the other operand was unrecognized.') def __mul__(self, value): '''Concatenates copies of this token the number of times specified. @@ -278,7 +278,7 @@ def sanitizeargstring(value): if valuestr in rawstoken.argument_replacements: valuestr = rawstoken.argument_replacements[valuestr] else: - if any([char in valuestr for char in rawstoken.illegal_internal_chars]): raise ValueError('Illegal character in argument: %s.' % valuestr) + if any([char in valuestr for char in rawstoken.illegal_internal_chars]): raise ValueError('Illegal character in argument %s.' % valuestr) return valuestr def index(self, index): @@ -409,7 +409,7 @@ def setvalue(self, value): [JUST KIDDING:a:b:c] ''' valuestr = str(value) - if any([char in valuestr for char in rawstoken.illegal_internal_chars]): raise ValueError + if any([char in valuestr for char in rawstoken.illegal_internal_chars]): raise ValueError('Failed to set token value to %s because the string contains illegal characters.' % valuestr) self.value = value def getprefix(self): @@ -439,7 +439,7 @@ def setprefix(self, value): hello [EXAMPLE] ''' valuestr = str(value) - if any([char in valuestr for char in rawstoken.illegal_external_chars]): raise ValueError + if any([char in valuestr for char in rawstoken.illegal_external_chars]): raise ValueError('Failed to set token prefix to %s because the string contains illegal characters.' % valuestr) self.prefix = value def getsuffix(self): @@ -469,7 +469,7 @@ def setsuffix(self, value): [EXAMPLE] world ''' valuestr = str(value) - if any([char in valuestr for char in rawstoken.illegal_external_chars]): raise ValueError + if any([char in valuestr for char in rawstoken.illegal_external_chars]): raise ValueError('Failed to set token suffix to %s because the string contains illegal characters.' % valuestr) self.suffix = value def arg(self): @@ -491,7 +491,7 @@ def arg(self): if len(self.args) == 1: return self.args[0] else: - raise ValueError + raise ValueError('Failed to retrieve token argument because it doesn\'t have exactly one.') def equals(self, other): '''Returns True if two tokens have identical values and arguments, False otherwise. @@ -582,7 +582,7 @@ def copy(auto=None, token=None, tokens=None): prevtoken = newtoken return copied else: - raise ValueError + raise ValueError('Failed to copy token or tokens because no object was specified.') def tokens(self, range=None, include_self=False, reverse=False, until_token=None, step=None): '''Iterate through successive tokens starting with this one. @@ -678,7 +678,7 @@ def add(self, auto=None, pretty=None, token=None, tokens=None, reverse=False): elif tokens is not None: return self.addall(tokens, reverse) else: - raise ValueError + raise ValueError('Failed to add token or tokens because no object was specified.') def addprop(self, auto=None, **kwargs): '''When this token is an object token like CREATURE:X or INORGANIC:X, a @@ -724,7 +724,7 @@ def firstandlast(tokens, setfile=None): try: if setfile is not None: raise ValueError return tokens[0], tokens[-1] - except: + except Exception as e: first, last = None, None for token in tokens: if first is None: first = token @@ -836,7 +836,7 @@ def parse(data, implicit_braces=True, **kwargs): tokens.append(token) return tokens else: - raise ValueError + raise ValueError('Failed to parse data string because it had no braces and because implicit_braces was set to False.') else: while pos < len(data): token = None @@ -879,5 +879,5 @@ def parseone(*args, **kwargs): There was more than one token! ''' tokens = rawstoken.parse(*args, **kwargs) - if len(tokens) != 1: raise ValueError + if len(tokens) != 1: raise ValueError('Failed to parse one token because the data string contained %d tokens.' % len(tokens)) return tokens[0] From 114b0d01af1061e3791b04403ac1de70597bb97c Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 10:53:10 -0400 Subject: [PATCH 11/95] Futher improvements to how files are stored in dir objects --- raws/dir.py | 58 +++++++++++++++++++++++++++++++++------------------- raws/file.py | 2 +- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/raws/dir.py b/raws/dir.py index 6c7e56d..c691eff 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -29,9 +29,19 @@ def __exit__(self, type, value, traceback): if traceback is None and self.path is not None: self.write(path=self.path) def __getitem__(self, name): - return self.get(name) - def __setitem__(self, name, value): - return self.set(name, value) + return self.getfile(name) + + def __setitem__(self, name, content): + if isinstance(content, rawsbasefile): + if content.dir: content = content.copy() + content.setpath(name) + self.add(file=content, replace=True) + elif isinstance(content, rawsqueryable): + self.add(file=name, replace=True, tokens=content.tokens()) + elif isinstance(content, basestring): + self.add(file=name, replace=True, data=content) + else: + self.add(file=name, tokens=content) def __contains__(self, item): return str(item) in self.files @@ -59,28 +69,33 @@ def getfile(self, name, create=None): def add(self, file=None, path=None, loc=None, replace=False, **kwargs): if file is None: - if path is None: raise ValueError + if path is None: raise ValueError('Failed to add file to dir because neither a name, path, nor file object was specified.') file = rawsfile(path=path, dir=self, **kwargs) if isinstance(file, basestring): - splitloc, name = os.path.split(file) - loc = os.path.join(loc, splitloc) if loc else splitloc - file = rawsfile(name=name, loc=loc, path=path, dir=self, **kwargs) + if path: + rawsfile(name=file, path=path, dir=self, **kwargs) + else: + splitloc, name = os.path.split(file) + name, ext = os.path.splitext(name) + loc = os.path.join(loc, splitloc) if loc else splitloc + file = rawsfile(name=name, ext=ext, loc=loc, path=path, dir=self, **kwargs) else: - if file.dir is self: raise ValueError('Failed to add file %s because it already belongs to this dir.' % file) - if file.dir is not None: raise ValueError('Failed to add file %s because it already belongs to another dir. You probably meant to remove the file first or to add a copy.' % file) + if file.dir is not self and file.dir is not None: + raise ValueError('Failed to add file %s to dir because it already belongs to another dir. You probably meant to remove the file first or to add a copy.' % file) file.dir = self - if (not replace) and file in self.files: raise KeyError('Dir already contains a file by the name %s.' % file) - self.files[str(file)] = file - return file - - def addfilekey(self, file): - '''Internal: Add a raws file to this dir's files and filenames dicts.''' + if str(file) in self.files: + if replace: + self.remove(file) + else: + raise KeyError('Failed to add file %s to dir because it already contains a file by the same name.' % file) self.files[str(file)] = file if file.name not in self.filenames: self.filenames[file.name] = [] self.filenames[file.name].append(file) + return file def remove(self, file=None): - if file not in self.files: raise KeyError + if isinstance(file, basestring): file = self.getfile(file) + if (file not in self.files) or (file.dir is not self): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) self.files[str(file)].dir = None del self.files[str(file)] @@ -101,18 +116,19 @@ def read(self, path=None): for path in paths: for root, dirs, files in os.walk(path): - addeddirs = [] + addeddirs = {} # Add files for name in files: filepath = os.path.join(root, name) file = rawsbasefile.factory(filepath, root=path, dir=self) - self.files[str(file)] = file - addeddirs.append(os.path.abspath(os.path.dirname(filepath))) + addeddirs[os.path.abspath(os.path.dirname(filepath))] = True + self.add(file) # Add empty directories for dir in dirs: - dir = os.path.abspath(dir) - if not any([added.startswith(dir) for added in addeddirs]): + dir = os.path.abspath(os.path.join(path, dir)) + if not any([added.startswith(dir) for added in addeddirs.iterkeys()]): file = rawsotherfile(path=dir, root=path, dir=self) + self.add(file) def write(self, path=None): '''Writes raws to the specified directory.''' diff --git a/raws/file.py b/raws/file.py index 041a89c..7a618c7 100644 --- a/raws/file.py +++ b/raws/file.py @@ -62,6 +62,7 @@ def setpath(self, path, root=None, loc=None, name=None, ext=None): if loc: self.loc = loc if name: self.name = name if ext: self.ext = ext + self.kind = self.ext[1:] if self.ext else 'dir' def dest(self, path, makedir=False): '''Internal: Given a root directory that this file would be written to, get the full path of where this file belongs.''' @@ -96,7 +97,6 @@ class rawsotherfile(rawsbasefile): def __init__(self, path, dir=None, root=None): self.dir = dir self.setpath(path, root) - self.kind = self.ext[1:] if self.ext else 'dir' def copy(self): copy = rawsotherfile() From 77b72a3da519761c6712bdee5e175d57406cb981 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 10:54:45 -0400 Subject: [PATCH 12/95] Improved handling of pydwarf sessions - Moved some smaller functionality out of the manager and into the session class - The urist class no longer stores a global session object --- manager.py | 22 +++++++--------------- pydwarf/config.py | 1 - pydwarf/urist.py | 40 +++++++++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/manager.py b/manager.py index bdac95f..ff9a0bc 100644 --- a/manager.py +++ b/manager.py @@ -53,26 +53,18 @@ def __main__(args=None): else: pydwarf.log.warning('Proceeding without backing up raws.') - # Read input raws - pydwarf.log.info('Configuring raws with input directory %s.' % conf.input) - pydwarf.urist.session.configure(raws, conf) + # Create a new session + pydwarf.log.info('Configuring session using raws input directory %s.' % conf.input) + session = pydwarf.session(raws, conf) # Run each script pydwarf.log.info('Running scripts.') - pydwarf.urist.session.handleall() - - # Get the output directory, remove old raws if present - outputdir = conf.output if conf.output else conf.input - if os.path.exists(outputdir): - pydwarf.log.info('Removing obsolete output directory %s.' % outputdir) - shutil.rmtree(outputdir) - - pydwarf.log.info('Creating raws output directory %s.' % outputdir) - os.makedirs(outputdir) + session.handleall() # Write the output - pydwarf.log.info('Writing changes to raws to %s.' % outputdir) - pydwarf.urist.session.dfraws.write(outputdir) + outputdir = conf.output if conf.output else conf.input + pydwarf.log.info('Writing new raws to directory %s.' % outputdir) + session.write(outputdir) # All done! pydwarf.log.info('All done!') diff --git a/pydwarf/config.py b/pydwarf/config.py index 24979b3..791124a 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -131,7 +131,6 @@ def setupversion(self): log.warning('No Dwarf Fortress version was specified. Scripts will be run regardless of their indicated compatibility.') else: log.info('Managing Dwarf Fortress version %s.' % self.version) - urist.session.dfversion = self.version def setupdfhack(self): self.setupdfhackdir() diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 073a80c..1f24c61 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -1,22 +1,21 @@ +import os +import shutil import textwrap + import version as versionutils from log import log class session: - def __init__(self, conf=None, dfraws=None, dfversion=None): - self.conf = conf - self.dfraws = dfraws - self.dfversion = dfversion + def __init__(self, raws=None, conf=None): + self.dfraws = None + self.dfversion = None + self.conf = None self.successes = [] self.failures = [] self.noresponse = [] - - def successful(self, info): - return self.inlist(info, self.successes) - def failed(self, info): - return self.inlist(info, self.failures) + if raws is not None and conf is not None: self.configure(raws, conf) def configure(self, raws, conf): self.conf = conf @@ -24,6 +23,12 @@ def configure(self, raws, conf): self.dfraws.hack = raws.dfhack(path=conf.dfhackdir, version=conf.dfhackver) self.dfversion = conf.version + def successful(self, info): + return self.inlist(info, self.successes) + def failed(self, info): + return self.inlist(info, self.failures) + + def inlist(self, info, flist): funcs = self.funcs(info) if funcs: @@ -90,6 +95,10 @@ def handleall(self, infos=None): for info in infos: self.handle(info) else: log.error('No scripts to run.') + + def write(self, path=None, *args, **kwargs): + if os.path.exists(path): shutil.rmtree(path) + self.dfraws.write(path=path, *args, **kwargs) @@ -125,8 +134,6 @@ class urist: # Track registered functions registered = {} - # Track data about which scripts have run successfully, etc. - session = session() # Decorator handling def __init__(self, **kwargs): @@ -226,7 +233,7 @@ def get(name, version=None, match=None, session=None): return urist.cullcandidates( version = version, match = match, - session = session if session is not None else urist.session, + session = session, candidates = urist.getregistered(*urist.splitname(name)) ) @@ -281,9 +288,12 @@ def cullcandidates_compatibility(version, candidates): @staticmethod def cullcandidates_dependency(session, candidates): - newcand, culled = [], [] - for cand in candidates: (newcand if cand.depsatisfied(session) else culled).append(cand) - return newcand, culled + if session: + newcand, culled = [], [] + for cand in candidates: (newcand if cand.depsatisfied(session) else culled).append(cand) + return newcand, culled + else: + return candidates, [] @staticmethod def cullcandidates_duplicates(candidates): From ea5fc5ce15eaac46f1d5e1fcc9380c79ba031e13 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 12:40:29 -0400 Subject: [PATCH 13/95] Improvements to raws.token class - Added an __iadd__ overload which maps to the existing addarg method - Added setargs method - Added input sanitization to constructor --- raws/token.py | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/raws/token.py b/raws/token.py index 62d887c..19cbbb8 100644 --- a/raws/token.py +++ b/raws/token.py @@ -54,8 +54,18 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr False ''' % rawstoken.auto_arg_docstring + self.prev = prev # previous token sequentially + self.next = next # next token sequentially + + self.value = None + self.args = None + self.prefix = None + self.suffix = None + self.file = None + pretty, token, tokens = rawstoken.auto(auto, pretty, token, None) - if tokens is not None: raise ValueError('Failed to initialize token object because the given string %s contained more than one token.' % pretty) + if tokens is not None: raise ValueError('Failed to initialize token object because the given argument was not a string or a single token.' % pretty) + if pretty is not None: token = rawstoken.parseone(pretty, implicit_braces=True) if token is not None: @@ -63,16 +73,13 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr args = list(token.args) if token.args else [] prefix = token.prefix suffix = token.suffix - file = token.file - # tokens look like this: [value:arg1:arg2:...:argn] - self.prev = prev # previous token sequentially - self.next = next # next token sequentially - self.value = value # value for the token - self.args = args # arguments for the token - self.prefix = prefix # non-token text between the preceding token and this one - self.suffix = suffix # between this token and the next/eof (should typically apply to eof) - self.file = file # parent rawsfile object - if not self.args: self.args = [] + + if value: self.setvalue(value) # value for the token + if args: self.setargs(args) # arguments for the token + if prefix: self.setprefix(prefix) # non-token text between the preceding token and this one + if suffix: self.setsuffix(suffix) # between this token and the next/eof (should typically apply to eof) + + if self.args is None: self.args = [] def __hash__(self): # Not that this class is immutable, just means you'll need to be careful about when you're using token hashes return hash('%s:%s' % (self.value, self.argsstr()) if self.nargs() else self.value) @@ -257,6 +264,9 @@ def __len__(self): return self.nargs() def __contains__(self, value): return self.containsarg(value) + + def __iadd__(self, value): + self.addarg(value) def __nonzero__(self): return True @@ -359,6 +369,14 @@ def setarg(self, index, value=None): [EXAMPLE:hi!:b:500]''' if value is None and index is not None: value = index; index = 0 self.args[index] = rawstoken.sanitizeargstring(value) + + def setargs(self, args=None): + self.args = [] + if args: + for arg in args: self.addarg(arg) + + def clearargs(self): + self.args = [] def addarg(self, value): '''Appends an argument to the end of the argument list. From 0f39bc9140100293fc96a9e1c851380fed38320d Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 13:00:24 -0400 Subject: [PATCH 14/95] Resolved cyclic import between raws.token and raws.tokenfilter Did so by removing the token.match method entirely. Also moved a bit of the tokenfilter constructor into a separate method while I was at it --- raws/filters.py | 16 ++++++++-------- raws/token.py | 24 ------------------------ 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/raws/filters.py b/raws/filters.py index 5545eaf..2b6422d 100644 --- a/raws/filters.py +++ b/raws/filters.py @@ -1,12 +1,7 @@ import re import copy - - -# Hackish solution to cyclic import -def rawstoken(): - if 'token' not in globals(): import token - return token.rawstoken +from token import rawstoken @@ -118,13 +113,16 @@ def __init__(self, True. ''' self.pretty = pretty + if pretty: - token = rawstoken().parseone(pretty) + token = rawstoken.parseone(pretty) exact_value = token.value if token.nargs(): exact_args = token.args + if match_token: exact_value = match_token.value exact_args = match_token.args + self.exact_token = exact_token self.exact_value = exact_value self.except_value = except_value @@ -148,7 +146,9 @@ def __init__(self, self.limit = limit self.limit_terminates = limit_terminates - # Anchor regular expressions + self.anchor() + + def anchor(self): if self.re_value: self.re_value += '$' if self.re_prefix: self.re_prefix += '$' if self.re_suffix: self.re_suffix += '$' diff --git a/raws/token.py b/raws/token.py index 19cbbb8..b1ec253 100644 --- a/raws/token.py +++ b/raws/token.py @@ -1,7 +1,6 @@ import itertools from queryable import rawsqueryable, rawstokenlist -from filters import rawstokenfilter class rawstoken(rawsqueryable): @@ -641,29 +640,6 @@ def tokens(self, range=None, include_self=False, reverse=False, until_token=None if (step is None) or (count % step == 0): yield itertoken itertoken = itertoken.prev if reverse else itertoken.next count += 1 - - def match(self, filter=None, **kwargs): - '''Returns True if this method matches some filter, false otherwise. - - filter: The filter to check, for example a raws.tokenfilter object. - **kwargs: Passed to the taws.tokenfilter constructor to nab a filter - to use if no filter was otherwise specified. - - Example usage: - >>> filter = raws.tokenfilter(exact_value='EXAMPLE') - >>> token_a = raws.token('HELLO:THERE') - >>> token_b = raws.token('EXAMPLE') - >>> token_c = raws.token('EXAMPLE:NUMBER:TWO') - >>> print token_a.match(filter) - False - >>> print token_b.match(filter) - True - >>> print token_c.match(filter) - True - >>> print token_a.match(exact_value='HELLO') - True - ''' - return (filter if filter else rawstokenfilter(**kwargs)).match(self) def add(self, auto=None, pretty=None, token=None, tokens=None, reverse=False): '''Adds a token or tokens nearby this one. If reverse is False the token From 1d1e7de657a13fa437b6a5bc9e8d9de6ca6cf1db Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 13:00:47 -0400 Subject: [PATCH 15/95] Made print statements in stal helper script look like functions --- scripts/stal/armouryentities.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/stal/armouryentities.py b/scripts/stal/armouryentities.py index 4818dd1..a1f2f08 100644 --- a/scripts/stal/armouryentities.py +++ b/scripts/stal/armouryentities.py @@ -6,14 +6,14 @@ import json import raws -print 'And so it begins.' +print('And so it begins.') entities = raws.dir(path='StalsArmouryPackv1_8a_4024')['entity_default'] edict = {} for entity in entities.all(exact_value='ENTITY'): - print 'Entity: %s' % entity + print('Entity: %s' % entity) itemtypes = ('AMMO', 'DIGGER', 'TOOL', 'WEAPON', 'ARMOR', 'PANTS', 'GLOVES', 'SHOES', 'HELM', 'SHIELD') edict[entity.args[0]] = {} entitydict = edict[entity.args[0]] @@ -27,8 +27,8 @@ if item.value not in entitydict: entitydict[item.value] = [] entitydict[item.value].append(item.args[0]) -print edict +print(edict) with open('armouryentities.json', 'wb') as efile: json.dump(edict, efile) -print 'All done!' +print('All done!') From fe42615f8a3eeba51392942e2c9e92bb1baa79bb Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 20:09:15 -0400 Subject: [PATCH 16/95] Fixed typo in pineapple.nograzers --- scripts/pineapple/pydwarf.nograzers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pineapple/pydwarf.nograzers.py b/scripts/pineapple/pydwarf.nograzers.py index 52c2fa3..1678fd1 100644 --- a/scripts/pineapple/pydwarf.nograzers.py +++ b/scripts/pineapple/pydwarf.nograzers.py @@ -15,5 +15,5 @@ def nograzers(df): if len(grazers) or len(standardgrazers): return pydwarf.success('Removed %d GRAZER and %d STANDARD_GRAZER tokens.' % (len(grazers), len(standardgrazers))) else: - return pydwarf.failure('I found no grazer tokens to replace.') + return pydwarf.failure('I found no grazer tokens to remove.') From a0e1919b4d73cad457b249682db8b489fa74d7e3 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 20:09:56 -0400 Subject: [PATCH 17/95] Put cyclic import back since removing it actually broke stuff How the fuck do I deal with this code structure goddamn --- raws/filters.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/raws/filters.py b/raws/filters.py index 2b6422d..9ecfde9 100644 --- a/raws/filters.py +++ b/raws/filters.py @@ -1,7 +1,12 @@ import re import copy -from token import rawstoken + + +# Hackish solution to cyclic import (the struggle is real) +def rawstoken(): + if 'token' not in globals(): import token + return token.rawstoken @@ -115,7 +120,7 @@ def __init__(self, self.pretty = pretty if pretty: - token = rawstoken.parseone(pretty) + token = rawstoken().parseone(pretty) exact_value = token.value if token.nargs(): exact_args = token.args From 63de66f5dc883137427773a5b1660badee2e214d Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 20:10:27 -0400 Subject: [PATCH 18/95] Updated putname.materialsplus to use new file-adding syntax --- scripts/putnam/pydwarf.materialsplus.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/putnam/pydwarf.materialsplus.py b/scripts/putnam/pydwarf.materialsplus.py index b7ada0b..d181bcc 100644 --- a/scripts/putnam/pydwarf.materialsplus.py +++ b/scripts/putnam/pydwarf.materialsplus.py @@ -71,9 +71,11 @@ def materialsplus(dfraws): rfile = dfraws.getfile(destname) if rfile: pydwarf.log.debug('Appending data to file %s from %s...' % (destname, path)) - with open(path, 'rb') as matplusfile: rfile.add(pretty=matplusfile) + with open(path, 'rb') as matplusfile: + rfile.add(pretty=matplusfile) else: - with open(path, 'rb') as matplusfile: rfile = dfraws.addfile(rfile=raws.file(header=destname, rfile=matplusfile)) + with open(path, 'rb') as matplusfile: + rfile = dfraws.add(raws.file(name=destname, file=matplusfile)) pydwarf.log.debug('Adding data to new file %s.' % destname) addedreactions += rfile.all(exact_value='REACTION', args_count=1) From 8effc19fd425263907616730bb3253f5ca5498d2 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 25 Jun 2015 20:11:03 -0400 Subject: [PATCH 19/95] Trivial update to tutorial.md --- tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tutorial.md b/tutorial.md index dbab449..acae9bb 100644 --- a/tutorial.md +++ b/tutorial.md @@ -137,7 +137,6 @@ Type "help", "copyright", "credits" or "license" for more information. >>> print description [DESCRIPTION:A medium-sized creature undeserving of life.] >>> df.write(path='raw/objects') - >>> quit() client-170:PyDwarf pineapple$ grep 'CREATURE:ELF' raw/objects/creature_standard.txt -A 4 [CREATURE:ELF] From 45081aa038bb6e3a9c87f45c3927ed025debc5f3 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:20:09 -0400 Subject: [PATCH 20/95] Big updates to the way dirs and files are handled - Now an entire DF directory is given as input and then specific paths that PyDwarf should be worried about can be specified. (This obsoletes the raws.dfhack code and soon it should be removed.) - As a consequence of the above file objects now remember things like where they were located relative to the root, file extension, things of that nature - There are now three different kinds of file objects which coexist in the files dict of a dir object: a raw file, the current, a ref file which simply points to a path and copies the file over upon writing the dir, and a bin file which has a string representing its contents intended for files that aren't raws but might still need to be accessed by mods - Vastly improved the code which handles adding a new file to a raws.dir object - Added a clean method to raws.dir for removing old files but not the entire DF directory (which it was annoyingly doing for a short time before I fixed this) - Updated pydwarf.config and pydwarf.session to cope with the changes to file handling - Rewrote the backup code and moved it into a method of pydwarf.session - Moved the pydwarf.findfile utility function into a pydwarf.helpers module and added pydwarf.rel also, which will make finding paths relative to pydwarf scripts much less annoying - Removed dfhackdir from config objects because the file changes have rendered it obsolete (Mostly, anyway. The code still needs some cleaning up.) --- config.json | 9 +- config_override.py | 2 +- manager.py | 16 +- pydwarf/__init__.py | 1 + pydwarf/config.py | 87 +++++------ pydwarf/{findfile.py => helpers.py} | 7 + pydwarf/urist.py | 34 ++-- pydwarf/version.py | 10 +- raws/__init__.py | 5 +- raws/dir.py | 232 +++++++++++++++++++++------- raws/file.py | 101 ++++++++++-- 11 files changed, 371 insertions(+), 133 deletions(-) rename pydwarf/{findfile.py => helpers.py} (83%) diff --git a/config.json b/config.json index 9d82d39..adb03d0 100644 --- a/config.json +++ b/config.json @@ -2,12 +2,13 @@ "comment": "This is an example file! Before running the manager you will need to change the provided paths to better suit your particular setup.", - "input": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawvanilla/objects", - "output": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/raw/objects", - "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawbak/", + "input": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/", + "output": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/pydwarf_output", + "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/pydwarf_backup", + + "paths": "auto", "version": "auto", - "dfhackdir": "auto", "dfhackver": "auto", "scripts": [ diff --git a/config_override.py b/config_override.py index 4f89c84..8ccb74d 100644 --- a/config_override.py +++ b/config_override.py @@ -13,8 +13,8 @@ 'input': os.path.join(dfdir, 'vanillaraw/objects'), 'output': 'output', 'backup': 'backup', + 'paths': 'auto', 'version': 'auto', - 'dfhackdir': 'auto', 'dfhackver': 'auto', 'scripts': [ { diff --git a/manager.py b/manager.py index ff9a0bc..e6f93a0 100644 --- a/manager.py +++ b/manager.py @@ -42,21 +42,17 @@ def __main__(args=None): pydwarf.log.error('Specified raws directory %s does not exist.' % conf.input) exit(1) + # Create a new session + pydwarf.log.info('Configuring session using raws input directory %s.' % conf.input) + session = pydwarf.session(raws, conf) + # Make backup if conf.backup is not None: pydwarf.log.info('Backing up raws to directory %s.' % conf.backup) - try: - raws.copytree(conf.input, conf.backup) - except: - pydwarf.log.exception('Failed to create backup.') - exit(1) + session.backup() else: pydwarf.log.warning('Proceeding without backing up raws.') - # Create a new session - pydwarf.log.info('Configuring session using raws input directory %s.' % conf.input) - session = pydwarf.session(raws, conf) - # Run each script pydwarf.log.info('Running scripts.') session.handleall() @@ -120,11 +116,11 @@ def parseargs(): parser.add_argument('-i', '--input', help='raws input directory', type=str) parser.add_argument('-o', '--output', help='raws output directory', type=str) parser.add_argument('-b', '--backup', help='raws backup directory', type=str) + parser.add_argument('-t', '--paths', help='which paths relative to input to store in memory and allow access to', nargs='+', type=str) parser.add_argument('-s', '--scripts', help='run scripts by name or namespace', nargs='+', type=str) parser.add_argument('-p', '--packages', help='import packages containing PyDwarf scripts', nargs='+', type=str) parser.add_argument('-c', '--config', help='run with json config file if the extension is json, otherwise treat as a Python package, import, and override settings using export dict', type=str) parser.add_argument('-v', '--verbose', help='set stdout logging level to DEBUG', action='store_true') - parser.add_argument('-hdir', '--dfhackdir', help='indicate DFHack directory', type=str) parser.add_argument('-hver', '--dfhackver', help='indicate DFHack version', type=str) parser.add_argument('--log', help='output log file to path', type=str) parser.add_argument('--list', help='list available scripts', action='store_true') diff --git a/pydwarf/__init__.py b/pydwarf/__init__.py index 9400ccb..0d0f5c4 100644 --- a/pydwarf/__init__.py +++ b/pydwarf/__init__.py @@ -16,5 +16,6 @@ from response import * from urist import * from config import * +from helpers import * __version__ = '1.0.1' diff --git a/pydwarf/config.py b/pydwarf/config.py index 791124a..d0409ef 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -8,7 +8,7 @@ from log import log from version import detectversion from urist import urist, session -from findfile import findfile +from helpers import findfile @@ -19,17 +19,17 @@ class config: - def __init__(self, version=None, dfhackdir=None, dfhackver=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): - self.version = version # Dwarf Fortress version, for handling script compatibility metadata - self.dfhackdir = dfhackdir # DFHack directory, located within the pertinent DF directory - self.dfhackver = dfhackver # DFHack version - self.input = input # Raws are loaded from this input directory - self.output = output # Raws are written to this output directory - self.backup = backup # Raws are backed up to this directory before any changes are made - self.scripts = scripts # These scripts are run in the order that they appear - self.packages = packages # These packages are imported (probably because they contain PyDwarf scripts) - self.verbose = verbose # Log DEBUG messages to stdout if True, otherwise only INFO and above - self.log = log # Log file goes here + def __init__(self, version=None, paths=None, dfhackver=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): + self.version = version # Dwarf Fortress version, for handling script compatibility metadata + self.dfhackver = dfhackver # DFHack version + self.input = input # Raws are loaded from this input directory + self.output = output # Raws are written to this output directory + self.backup = backup # Raws are backed up to this directory before any changes are made + self.paths = paths # Files are only handled in these paths, relative to input + self.scripts = scripts # These scripts are run in the order that they appear + self.packages = packages # These packages are imported (probably because they contain PyDwarf scripts) + self.verbose = verbose # Log DEBUG messages to stdout if True, otherwise only INFO and above + self.log = log # Log file goes here def __str__(self): return str(self.__dict__) @@ -91,10 +91,12 @@ def intersect(*configs): def setup(self, logger=False): # Set up the pydwarf logger if logger: self.setuplogger() + # Handle paths == 'auto' or ['auto'] + self.setuppaths() # Handle version == 'auto' self.setupversion() - # Handle dfhackdir == 'auto' - self.setupdfhack() + # Handle dfhackver == 'auto' + self.setupdfhackver() # Import packages self.setuppackages() @@ -118,6 +120,17 @@ def setuplogger(self): def setuppackages(self): self.importedpackages = [importlib.import_module(package) for package in self.packages] + def setuppaths(self): + if self.paths == 'auto' or self.paths == ['auto'] or self.paths == ('auto',): + self.paths = [ + 'gamelog.txt', 'errorlog.txt', + 'stderr.log', 'stdout.log', + 'raw/objects', 'raw/graphics', + 'data/init', + 'dfhack.init', 'dfhack.init-example', 'dfhack.history', + 'hack/lua', 'hack/plugins', 'hack/raw', 'hack/ruby', 'hack/scripts', + ] + def setupversion(self): # Handle automatic version detection if self.version == 'auto': @@ -131,38 +144,26 @@ def setupversion(self): log.warning('No Dwarf Fortress version was specified. Scripts will be run regardless of their indicated compatibility.') else: log.info('Managing Dwarf Fortress version %s.' % self.version) - - def setupdfhack(self): - self.setupdfhackdir() - self.setupdfhackver() - - def setupdfhackdir(self): - if self.dfhackdir == 'auto': - log.debug('Attempting to automatically detect DFHack directory.') - self.dfhackdir = findfile(name='hack', paths=(self.input, self.output)) - if self.dfhackdir is None: - log.error('Unable to detect DFHack directory.') - else: - log.debug('Detected DFHack directory at %s.' % self.dfhackdir) - elif self.dfhackdir is None: - log.warning('No DFHack directory was specified. Scripts which attempt to access or modify DFHack data will fail.') - else: - log.info('Managing DFHack directory %s.' % self.dfhackdir) def setupdfhackver(self): if self.dfhackver == 'auto': - if self.dfhackdir is not None: - log.debug('Attempting to automatically detect DFHack version.') - newspath = os.path.join(self.dfhackdir, 'NEWS') - if os.path.isfile(newspath): - with open(newspath, 'rb') as news: self.dfhackver = news.readline().strip() - if self.dfhackver is None: - log.error('Unable to detect DFHack version.') - else: - log.debug('Detected DFHack version %s.' % self.dfhackver) + log.debug('Attempting to automatically detect DFHack version.') + + dfhackdir = findfile(name='hack', paths=(self.input, self.output)) + if dfhackdir is None: + log.error('Unable to detect DFHack directory.') + return else: - log.error('Failed to automatically detect DFHack version because the DFHack directory has not been located.') + log.debug('Detected DFHack directory at %s.' % dfhackdir) + + newspath = os.path.join(dfhackdir, 'NEWS') + if os.path.isfile(newspath): + with open(newspath, 'rb') as news: self.dfhackver = news.readline().strip() + + if self.dfhackver is None: + log.error('Unable to detect DFHack version.') + else: + log.debug('Detected DFHack version %s.' % self.dfhackver) + elif self.dfhackver is None: log.warning('No DFHack version was specified.') - else: - log.info('Managing DFHack version %s.' % self.dfhackver) diff --git a/pydwarf/findfile.py b/pydwarf/helpers.py similarity index 83% rename from pydwarf/findfile.py rename to pydwarf/helpers.py index d7b7055..1b3afb5 100644 --- a/pydwarf/findfile.py +++ b/pydwarf/helpers.py @@ -1,6 +1,13 @@ import os from log import log + + +def rel(base, *parts): + return os.path.join(os.path.dirname(base) if os.path.isfile(base) else base, *parts) + + + def findfile(name, paths, recursion=6): log.debug('Looking for file %s.' % name) for path in paths: diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 1f24c61..4ca057f 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -11,23 +11,25 @@ class session: def __init__(self, raws=None, conf=None): self.dfraws = None self.dfversion = None + self.hackversion = None self.conf = None + self.raws = None self.successes = [] self.failures = [] self.noresponse = [] if raws is not None and conf is not None: self.configure(raws, conf) def configure(self, raws, conf): + self.raws = raws self.conf = conf - self.dfraws = raws.dir(path=conf.input, version=conf.version, log=log) - self.dfraws.hack = raws.dfhack(path=conf.dfhackdir, version=conf.dfhackver) + self.dfraws = raws.dir(root=conf.input, dest=conf.output, paths=conf.paths, version=conf.version, log=log) self.dfversion = conf.version - + self.hackversion = conf.dfhackver + def successful(self, info): return self.inlist(info, self.successes) def failed(self, info): return self.inlist(info, self.failures) - def inlist(self, info, flist): funcs = self.funcs(info) @@ -45,11 +47,11 @@ def eval(self, func, args=None): name = uristinstance.getname() else: name = func.__name__ + # Actually execute the script log.info('Running script %s%s.' % (name, ('with args %s' % args) if args else '')) try: - # Call the function - response = func(self.dfraws, **args) if args else func(self.dfraws) + response = func(self.dfraws, **args) if args else func(self.dfraws) # Call the function if response: # Handle success/failure response log.info(str(response)) @@ -57,9 +59,11 @@ def eval(self, func, args=None): else: log.error('Received no response from script %s.' % name) self.noresponse.append(uristinstance if uristinstance else func) + except Exception: log.exception('Unhandled exception while running script %s.' % name) return False + else: log.info('Finished running script %s.' % name) return True @@ -96,9 +100,21 @@ def handleall(self, infos=None): else: log.error('No scripts to run.') - def write(self, path=None, *args, **kwargs): - if os.path.exists(path): shutil.rmtree(path) - self.dfraws.write(path=path, *args, **kwargs) + def write(self, dest=None, *args, **kwargs): + self.dfraws.clean(dest=dest) + self.dfraws.write(dest=dest, *args, **kwargs) + + def backup(self, dest=None): + if dest is None: dest = self.conf.backup + if dest: + for path in self.conf.paths: + srcpath = os.path.join(self.conf.input, path) + destpath = os.path.join(dest, path) + if not os.path.isdir(os.path.dirname(destpath)): os.makedirs(os.path.dirname(destpath)) + if os.path.isfile(srcpath): + shutil.copy2(srcpath, destpath) + else: + self.raws.copytree(srcpath, destpath) diff --git a/pydwarf/version.py b/pydwarf/version.py index bc1a608..f0e9fde 100644 --- a/pydwarf/version.py +++ b/pydwarf/version.py @@ -2,7 +2,9 @@ import re from log import log -from findfile import findfile +from helpers import findfile + + # Can be expected to match all past and future 0.40.* releases. (Time of writing is 21 May 15, the most recent version is 0.40.24.) df_0_40 = '(0\.40\.\d{2,}[abcdefg]?)' @@ -25,6 +27,8 @@ # Matches all DF 0.27, 0.23, 0.22, and 0.21 releases df_0_2x = '|'.join((df_0_21, df_0_22, df_0_23, df_0_27, df_0_28)) + + # Generates a regex which should properly match from, until, and each version in-between. # For example: pydwarf_range('0.40.14', '0.40.24') def df_revision_range(prettymin=None, prettymax=None, major=None, minor=None, minrevision=None, maxrevision=None): @@ -38,6 +42,8 @@ def df_revision_range(prettymin=None, prettymax=None, major=None, minor=None, mi maxrevision = parts[2] if len(parts) > 2 else '0' return '%s\.%s\.(%s)' % (major, minor, '|'.join([str(r) for r in range(int(minrevision), int(maxrevision)+1)])) + + # Given a version and a compatibility regex, determine compatibility def compatible(compatibility, version): if isinstance(compatibility, basestring): @@ -45,6 +51,8 @@ def compatible(compatibility, version): else: return any([re.match(item, version) for item in compatibility]) + + def detectversion(*args, **kwargs): # Given a list of directories that may be inside a DF directory, e.g. raws input or output, look for release notes.txt and get the version from that path = findfile(name='release notes.txt', *args, **kwargs) diff --git a/raws/__init__.py b/raws/__init__.py index baf1a5f..a8b9c38 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -26,7 +26,10 @@ from queryable import rawsqueryableobj as queryableobj from queryable import rawstokenlist as tokenlist from token import rawstoken as token -from file import rawsfile as file +from file import rawsbasefile as basefile +from file import rawsreffile as reffile +from file import rawsbinfile as binfile +from file import rawsfile as file # TODO: rename to "rawfile" from dir import rawsdir as dir from dfhack import dfhack from copytree import copytree diff --git a/raws/dir.py b/raws/dir.py index c691eff..a93cf92 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -1,24 +1,25 @@ import os +import shutil from copytree import copytree from queryable import rawsqueryable, rawsqueryableobj, rawstokenlist -from file import rawsbasefile, rawsotherfile, rawsfile +from file import rawsbasefile, rawsfile, rawsbinfile, rawsreffile class rawsdir(rawsqueryableobj): '''Represents as a whole all the raws contained within a directory.''' - def __init__(self, path=None, dest=None, version=None, log=None, *args, **kwargs): + def __init__(self, root=None, dest=None, paths=None, version=None, log=None, **kwargs): '''Constructor for rawsdir object.''' self.files = {} self.filenames = {} - self.path = path - self.dest = dest + self.root = root # Root input directory + self.dest = dest # Root output directory + self.paths = paths # Only worry about these file paths in input/output directories self.version = version self.log = log - self.hack = None - if path: self.read(path=path, *args, **kwargs) + if root: self.read(**kwargs) def __str__(self): return '\n'.join(['%s %s' % (file.kind, str(file)) for file in self.files.itervalues()]) @@ -44,9 +45,9 @@ def __setitem__(self, name, content): self.add(file=name, tokens=content) def __contains__(self, item): - return str(item) in self.files + return str(item) in self.files or str(item) in self.filenames - def getfile(self, name, create=None): + def getfile(self, name, create=None, conflicts=False): '''Gets the file with a given name. If no file by that name is found, None is returned instead. If creature is set to something other than None, the behavior when no file by some name exists is altered: A new @@ -60,42 +61,131 @@ def getfile(self, name, create=None): if len(file) == 1: file = file[0] else: - raise ValueError('Failed to retrieve file from dir because the name found no exact matches, and because multiple files were found with that name.') + if not conflicts: raise ValueError('Failed to retrieve file from dir because the name found no exact matches, and because multiple files were found with that name.') + return file if create is not None and file is None: file = self.add(name) file.add(create) return file - def add(self, file=None, path=None, loc=None, replace=False, **kwargs): - if file is None: - if path is None: raise ValueError('Failed to add file to dir because neither a name, path, nor file object was specified.') - file = rawsfile(path=path, dir=self, **kwargs) - if isinstance(file, basestring): - if path: - rawsfile(name=file, path=path, dir=self, **kwargs) + def add(self, auto=None, **kwargs): + if auto is not None: + return self.addbyauto(auto, **kwargs) + elif 'file' in kwargs: + return self.addbyfile(**kwargs) + elif 'dir' in kwargs: + return self.addbydir(**kwargs) + elif 'tokens' in kwargs: + return self.addbytokens(**kwargs) + elif 'content' in kwargs: + return self.addbybincontent(**kwargs) + elif 'name' in kwargs: + return self.addbyname(**kwargs) + elif 'path' in kwargs: + path = kwargs['path'] + if os.path.isfile(path): + return self.addbyfilepath(**kwargs) else: - splitloc, name = os.path.split(file) - name, ext = os.path.splitext(name) - loc = os.path.join(loc, splitloc) if loc else splitloc - file = rawsfile(name=name, ext=ext, loc=loc, path=path, dir=self, **kwargs) + return self.addbydirpath(**kwargs) + elif 'filepath' in kwargs: + kwargs['path'] = kwargs['filepath'] + del kwargs['filepath'] + return self.addbyfilepath(**kwargs) + elif 'dirpath' in kwargs: + kwargs['path'] = kwargs['dirpath'] + del kwargs['dirpath'] + return self.addbydirpath(**kwargs) else: - if file.dir is not self and file.dir is not None: - raise ValueError('Failed to add file %s to dir because it already belongs to another dir. You probably meant to remove the file first or to add a copy.' % file) - file.dir = self - if str(file) in self.files: - if replace: - self.remove(file) + raise ValueError('Failed to add file because no recognized arguments were specificed.') + + def addbyauto(self, auto, **kwargs): + if isinstance(auto, rawsbasefile): + return self.addbyfile(auto, **kwargs) + elif isinstance(auto, basestring): + if os.path.isfile(auto): + return self.addbyfilepath(auto, **kwargs) + elif os.path.isdir(auto): + return self.addbydirpath(auto, **kwargs) else: - raise KeyError('Failed to add file %s to dir because it already contains a file by the same name.' % file) + return self.addbyname(auto, **kwargs) + elif isinstance(auto, rawsdir): + return self.addbydir(auto, **kwargs) + + def addbyfile(self, file, **kwargs): + self.addfiletodicts(file, **kwargs) + return file + def addbyname(self, name, ext=None, loc=None, kind=None, **kwargs): + file = self.filebyname(name=name, ext=ext, loc=loc, kind=kind) + self.addfiletodicts(file, **kwargs) + return file + def addbyfilepath(self, path, root=None, loc=None, kind=None, **kwargs): + file = self.filebyfilepath(path=path, root=root, loc=loc, kind=kind) + self.addfiletodicts(file, **kwargs) + return file + def addbydirpath(self, path, root=None, loc=None, kind=None, **kwargs): + files = self.filesbydirpath(path=path, root=root, loc=loc, kind=kind) + self.addfilestodicts(files, **kwargs) + return files + def addbydir(self, dir, loc=None, **kwargs): + files = self.filesbydir(dir=dir, loc=loc) + self.addfilestodicts(files, **kwargs) + return files + def addbytokens(self, name, tokens, **kwargs): + file = self.addbyname(name, **kwargs) + file.add(tokens) + return file + def addbybincontent(self, name, content, **kwargs): + file = self.addbyname(name, kind=rawsbinfile, **kwargs) + file.content = content + return file + + def filebyname(self, name, ext=None, loc=None, kind=None): + if kind is None: kind = rawsfile + splitloc, name = os.path.split(name) + if not ext: name, ext = os.path.splitext(name) + loc = os.path.join(loc, splitloc) if loc else splitloc + return kind(name=name, ext=ext, loc=loc, dir=self) + def filebyfilepath(self, path, root=None, loc=None, kind=None): + if kind is None: kind = rawsfile + return kind(path=path, loc=loc, dir=self) + def filesbydirpath(self, path, root=None, loc=None, kind=None): + for walkroot, walkdirs, walkfiles in os.walk(path): + return ((kind if kind else rawsbasefile.factory)(path=os.path.join(walkroot, walkfile), root=root, loc=loc, dir=self) for walkfile in walkfiles) + def filesbydir(self, dir, loc=None): + for dirfile in dir.files.iteritems(): + newfile = dirfile.copy() + newfile.dir = self + newfile.reloc(loc) + yield newfile + + def addtodicts(self, file, replace=False): + if isinstance(file, rawsbasefile): + self.addfiletodicts(file) + else: + self.addfilestodicts(file) + def addfilestodicts(self, files, replace=False): + for file in files: self.addfiletodicts(file, replace) + def addfiletodicts(self, file, replace=False): + '''Internal: Used to add a file to files and filenames dictionaries.''' + + if file.dir is not self and file.dir is not None: + raise ValueError('Failed to add file %s to dir because it already belongs to another dir. You probably meant to remove the file first or to add a copy.' % file) + + if str(file) in self.files: + if not replace: raise KeyError('Failed to add file %s to dir because it already contains a file by the same name.' % file) + self.remove(file) + + file.dir = self + self.files[str(file)] = file + if file.name not in self.filenames: self.filenames[file.name] = [] self.filenames[file.name].append(file) - return file def remove(self, file=None): if isinstance(file, basestring): file = self.getfile(file) - if (file not in self.files) or (file.dir is not self): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) + if (file not in self.files) or (file.dir is not self) or (file is None): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) self.files[str(file)].dir = None del self.files[str(file)] @@ -106,40 +196,72 @@ def removefile(self, name=None, file=None): '''Deprecated: As of v1.0.2. Use the remove method instead.''' return self.remove(file if file is not None else name) - def read(self, path=None): + def read(self, root=None, paths=None): '''Reads raws from all text files in the specified directory.''' - if path is None: - if self.path is None: raise ValueError('Failed to read dir because no path was specified.') - path = self.path - paths = (path,) if isinstance(path, basestring) else path + if root is None: + if self.root is None: raise ValueError('Failed to read dir because no root directory was specified.') + root = self.root + + if paths is None: paths = self.paths + if paths is not None: + paths = (paths,) if isinstance(paths, basestring) else paths + else: + paths = os.listdir(root) + + addeddirs = {} for path in paths: - for root, dirs, files in os.walk(path): - addeddirs = {} - # Add files - for name in files: - filepath = os.path.join(root, name) - file = rawsbasefile.factory(filepath, root=path, dir=self) - addeddirs[os.path.abspath(os.path.dirname(filepath))] = True - self.add(file) - # Add empty directories - for dir in dirs: - dir = os.path.abspath(os.path.join(path, dir)) - if not any([added.startswith(dir) for added in addeddirs.iterkeys()]): - file = rawsotherfile(path=dir, root=path, dir=self) + path = os.path.join(root, path) + + if os.path.isdir(path): + for walkroot, walkdirs, walkfiles in os.walk(path): + + # Add files + for name in walkfiles: + filepath = os.path.join(walkroot, name) + file = rawsbasefile.factory(filepath, root=root, dir=self) + addeddirs[os.path.abspath(os.path.dirname(filepath)).replace('\\', '/')] = True self.add(file) + + # Add empty directories + for dir in walkdirs: + dir = os.path.abspath(os.path.join(walkroot, dir)).replace('\\', '/') + if not any([added.startswith(dir) for added in addeddirs.iterkeys()]): + file = rawsbasefile.factory(path=dir, root=root, dir=self) + self.add(file) + + elif os.path.isfile(path): + file = rawsbasefile.factory(path, root=root, dir=self) + addeddirs[os.path.abspath(os.path.dirname(path))] = True + self.add(file) + + else: + raise ValueError('Failed to read dir because a bad path %s was provided.' % path) - def write(self, path=None): + def write(self, dest=None): '''Writes raws to the specified directory.''' - - if path is None: - if self.path is None and self.dest is None: raise ValueError('Failed to write dir because no path was specified.') - path = self.dest if self.dest else self.path - - if self.log: self.log.debug('Writing %d files to %s.' % (len(self.files), path)) + dest = self.getdestforfileop(dest) + if self.log: self.log.debug('Writing %d files to %s.' % (len(self.files), dest)) for file in self.files.itervalues(): - file.write(path) + file.write(dest) + + def clean(self, dest=None): + dest = self.getdestforfileop(dest) + if self.log: self.log.debug('Cleaning files in %s.' % dest) + for path in self.paths: + path = os.path.join(dest, path) + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + shutil.rmtree(path) + + def getdestforfileop(self, dest, exception=True): + '''Internal''' + if dest is None: + dest = self.dest if self.dest else self.root + if exception and dest is None: raise ValueError('Failed to write dir because no destination path was specified.') + return dest def tokens(self, *args, **kwargs): '''Iterate through all tokens.''' diff --git a/raws/file.py b/raws/file.py index 7a618c7..f8c6cc5 100644 --- a/raws/file.py +++ b/raws/file.py @@ -25,12 +25,14 @@ def factory(path, **kwargs): if txt.readline().strip() == os.path.splitext(os.path.basename(path))[0]: txt.seek(0) return rawsfile(path=path, file=txt, **kwargs) - return rawsotherfile(path=path, **kwargs) - + if os.path.basename(path) in ('dfhack.init', 'dfhack.init-example'): + return rawsbinfile(path=path, **kwargs) + return rawsreffile(path=path, **kwargs) + def __str__(self): name = ''.join((self.name, self.ext)) if self.ext else self.name path = os.path.join(self.loc, name) if self.loc else name - return path + return path.replace('\\', '/') def __repr__(self): return str(self) @@ -52,10 +54,13 @@ def __le__(self, other): return str(self) <= str(other) def setpath(self, path, root=None, loc=None, name=None, ext=None): + if self.dir and self.dir.root and (not root): root = self.dir.root + path = os.path.abspath(path) if path else None + root = os.path.abspath(root) if root else None self.path = path self.rootpath = root self.name, self.ext = (os.path.splitext(os.path.basename(path)) if os.path.isfile(path) else (os.path.basename(path), None)) if path else (None, None) - if root and path and (not os.path.samefile(root, os.path.dirname(path))) and os.path.abspath(path).startswith(os.path.abspath(root)): + if root and path and root != path and path.startswith(root): self.loc = os.path.dirname(os.path.relpath(path, root)) else: self.loc = None @@ -64,6 +69,12 @@ def setpath(self, path, root=None, loc=None, name=None, ext=None): if ext: self.ext = ext self.kind = self.ext[1:] if self.ext else 'dir' + def reloc(self, loc): + if loc and self.loc: + self.loc = os.path.join(loc, self.loc) + elif loc: + self.loc = loc + def dest(self, path, makedir=False): '''Internal: Given a root directory that this file would be written to, get the full path of where this file belongs.''' dest = os.path.join(path, str(self)) @@ -93,10 +104,10 @@ def remove(self): -class rawsotherfile(rawsbasefile): - def __init__(self, path, dir=None, root=None): +class rawsreffile(rawsbasefile): + def __init__(self, path=None, dir=None, root=None, **kwargs): self.dir = dir - self.setpath(path, root) + self.setpath(path, root, **kwargs) def copy(self): copy = rawsotherfile() @@ -108,6 +119,19 @@ def copy(self): copy.loc = self.loc return copy + def ref(self): + return self + def bin(self): + self.kind = 'bin' + self.__class__ = rawsbinfile + self.read() + return self + def raw(self): + self.kind = 'raw' + self.__class__ = rawsfile + self.read() + return self + def write(self, path): dest = self.dest(path, makedir=True) if self.path != dest: @@ -120,6 +144,54 @@ def write(self, path): +class rawsbinfile(rawsreffile): + def __init__(self, content=None, path=None, dir=None, **kwargs): + self.dir = None + self.setpath(path, **kwargs) + self.dir = dir + self.content = content + if self.content is None and self.path is not None and os.path.isfile(self.path): self.read(self.path) + + def read(self, path=None): + with open(path if path else self.path, 'rb') as binfile: self.content = binfile.read() + + def ref(self): + raise ValueError('Failed to cast binary file to reference file because it is an invalid conversion.') + def bin(self): + return self + def raw(self): + self.kind = 'raw' + self.__class__ = rawsfile + self.settokens(rawstoken.parse(self.content)) + return self + + def copy(self): + copy = rawsbinfile() + copy.path = self.path + copy.dir = self.dir + copy.rootpath = self.rootpath + copy.name = self.name + copy.ext = self.ext + copy.loc = self.loc + copy.content = self.content + return copy + + def __repr__(self): + return str(self.content) + + def __len__(self): + return len(self.content) + + def write(self, path): + dest = self.dest(path, makedir=True) + with open(dest, 'wb') as file: + file.write(self.content) + + def add(self, content): + self.content += content + + + class rawsfile(rawsbasefile, rawsqueryableobj): '''Represents a single file within a raws directory.''' @@ -135,7 +207,7 @@ def __init__(self, name=None, file=None, path=None, root=None, data=None, tokens ''' self.dir = dir - self.setpath(path=path, root=dir.path if (dir and dir.path and not root) else root, **kwargs) + self.setpath(path=path, root=root, **kwargs) self.roottoken = None self.tailtoken = None @@ -178,6 +250,16 @@ def __repr__(self): def content(self): return '%s\n%s' %(self.name, ''.join([repr(o) for o in self.tokens()])) + + def ref(self): + raise ValueError('Failed to cast binary file to reference file because it is an invalid conversion.') + def bin(self): + self.kind = 'bin' + self.content = self.content() + self.__class__ = rawsbinfile + return self + def raw(self): + return self def index(self, index): itrtoken = self.root() if index >= 0 else self.tail() @@ -298,8 +380,9 @@ def tokens(self, reverse=False, **kwargs): for token in generator: yield token - def read(self, file): + def read(self, file=None): '''Given a path or file-like object, reads name and data.''' + if file is None: file = self.path if isinstance(file, basestring): self.path = file self.ext = os.path.splitext(file)[1] From cfa74339f7a4a7b534d5d965a283db28ca3009cc Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:21:42 -0400 Subject: [PATCH 21/95] Updated umiman.smallthings scripts Also cleaned up the file structure in there, it was kind of a mess --- .../{smallthingsreadmes => misc}/Engraving_README.txt | 0 .../dfmod_umiman_smallthings.txt | 0 scripts/umiman/pydwarf.smallthings.py | 6 +++--- .../smallthings}/creature_amphibians.txt | 0 .../smallthings}/creature_annelids.txt | 0 .../{smallthingsmod => raw/smallthings}/creature_birds.txt | 0 .../smallthings}/creature_domestic.txt | 0 .../smallthings}/creature_fanciful.txt | 0 .../smallthings}/creature_insects.txt | 0 .../smallthings}/creature_large_mountain.txt | 0 .../smallthings}/creature_large_ocean.txt | 0 .../smallthings}/creature_large_riverlake.txt | 0 .../smallthings}/creature_large_temperate.txt | 0 .../smallthings}/creature_large_tropical.txt | 0 .../smallthings}/creature_large_tundra.txt | 0 .../{smallthingsmod => raw/smallthings}/creature_other.txt | 0 .../smallthings}/creature_reptiles.txt | 0 .../smallthings}/creature_savage_tropical.txt | 0 .../smallthings}/creature_small_mammals.txt | 0 .../smallthings}/creature_small_ocean.txt | 0 .../smallthings}/creature_small_riverlake.txt | 0 .../smallthings}/creature_standard.txt | 0 .../smallthings}/creature_subterranean.txt | 0 .../smallthings}/descriptor_shape_standard.txt | 0 24 files changed, 3 insertions(+), 3 deletions(-) rename scripts/umiman/{smallthingsreadmes => misc}/Engraving_README.txt (100%) rename scripts/umiman/{smallthingsreadmes => misc}/dfmod_umiman_smallthings.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_amphibians.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_annelids.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_birds.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_domestic.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_fanciful.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_insects.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_mountain.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_ocean.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_riverlake.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_temperate.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_tropical.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_large_tundra.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_other.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_reptiles.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_savage_tropical.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_small_mammals.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_small_ocean.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_small_riverlake.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_standard.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/creature_subterranean.txt (100%) rename scripts/umiman/{smallthingsmod => raw/smallthings}/descriptor_shape_standard.txt (100%) diff --git a/scripts/umiman/smallthingsreadmes/Engraving_README.txt b/scripts/umiman/misc/Engraving_README.txt similarity index 100% rename from scripts/umiman/smallthingsreadmes/Engraving_README.txt rename to scripts/umiman/misc/Engraving_README.txt diff --git a/scripts/umiman/smallthingsreadmes/dfmod_umiman_smallthings.txt b/scripts/umiman/misc/dfmod_umiman_smallthings.txt similarity index 100% rename from scripts/umiman/smallthingsreadmes/dfmod_umiman_smallthings.txt rename to scripts/umiman/misc/dfmod_umiman_smallthings.txt diff --git a/scripts/umiman/pydwarf.smallthings.py b/scripts/umiman/pydwarf.smallthings.py index 85e703c..1c06932 100644 --- a/scripts/umiman/pydwarf.smallthings.py +++ b/scripts/umiman/pydwarf.smallthings.py @@ -2,14 +2,14 @@ import pydwarf import raws -smalldir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'smallthingsmod') +smalldir = pydwarf.rel(__file__, 'raw/smallthings') # A bit of esoteric code which makes smallraws only be read once def getsmallraws(): if 'smallraws' not in globals(): - globals()['smallraws'] = raws.dir(path=smalldir, log=pydwarf.log) + globals()['smallraws'] = raws.dir(root=smalldir, log=pydwarf.log) return smallraws @@ -108,7 +108,7 @@ def engraving(dfraws): dfshapesdict = dfraws.objdict('SHAPE') # Add a new file for the new shapes - dfshapesfile = dfraws.add('descriptor_shape_umiman') + dfshapesfile = dfraws.add('raw/objects/descriptor_shape_umiman.txt') dfshapesfile.add('OBJECT:DESCRIPTOR_SHAPE') shapesadded = 0 diff --git a/scripts/umiman/smallthingsmod/creature_amphibians.txt b/scripts/umiman/raw/smallthings/creature_amphibians.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_amphibians.txt rename to scripts/umiman/raw/smallthings/creature_amphibians.txt diff --git a/scripts/umiman/smallthingsmod/creature_annelids.txt b/scripts/umiman/raw/smallthings/creature_annelids.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_annelids.txt rename to scripts/umiman/raw/smallthings/creature_annelids.txt diff --git a/scripts/umiman/smallthingsmod/creature_birds.txt b/scripts/umiman/raw/smallthings/creature_birds.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_birds.txt rename to scripts/umiman/raw/smallthings/creature_birds.txt diff --git a/scripts/umiman/smallthingsmod/creature_domestic.txt b/scripts/umiman/raw/smallthings/creature_domestic.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_domestic.txt rename to scripts/umiman/raw/smallthings/creature_domestic.txt diff --git a/scripts/umiman/smallthingsmod/creature_fanciful.txt b/scripts/umiman/raw/smallthings/creature_fanciful.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_fanciful.txt rename to scripts/umiman/raw/smallthings/creature_fanciful.txt diff --git a/scripts/umiman/smallthingsmod/creature_insects.txt b/scripts/umiman/raw/smallthings/creature_insects.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_insects.txt rename to scripts/umiman/raw/smallthings/creature_insects.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_mountain.txt b/scripts/umiman/raw/smallthings/creature_large_mountain.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_mountain.txt rename to scripts/umiman/raw/smallthings/creature_large_mountain.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_ocean.txt b/scripts/umiman/raw/smallthings/creature_large_ocean.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_ocean.txt rename to scripts/umiman/raw/smallthings/creature_large_ocean.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_riverlake.txt b/scripts/umiman/raw/smallthings/creature_large_riverlake.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_riverlake.txt rename to scripts/umiman/raw/smallthings/creature_large_riverlake.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_temperate.txt b/scripts/umiman/raw/smallthings/creature_large_temperate.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_temperate.txt rename to scripts/umiman/raw/smallthings/creature_large_temperate.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_tropical.txt b/scripts/umiman/raw/smallthings/creature_large_tropical.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_tropical.txt rename to scripts/umiman/raw/smallthings/creature_large_tropical.txt diff --git a/scripts/umiman/smallthingsmod/creature_large_tundra.txt b/scripts/umiman/raw/smallthings/creature_large_tundra.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_large_tundra.txt rename to scripts/umiman/raw/smallthings/creature_large_tundra.txt diff --git a/scripts/umiman/smallthingsmod/creature_other.txt b/scripts/umiman/raw/smallthings/creature_other.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_other.txt rename to scripts/umiman/raw/smallthings/creature_other.txt diff --git a/scripts/umiman/smallthingsmod/creature_reptiles.txt b/scripts/umiman/raw/smallthings/creature_reptiles.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_reptiles.txt rename to scripts/umiman/raw/smallthings/creature_reptiles.txt diff --git a/scripts/umiman/smallthingsmod/creature_savage_tropical.txt b/scripts/umiman/raw/smallthings/creature_savage_tropical.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_savage_tropical.txt rename to scripts/umiman/raw/smallthings/creature_savage_tropical.txt diff --git a/scripts/umiman/smallthingsmod/creature_small_mammals.txt b/scripts/umiman/raw/smallthings/creature_small_mammals.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_small_mammals.txt rename to scripts/umiman/raw/smallthings/creature_small_mammals.txt diff --git a/scripts/umiman/smallthingsmod/creature_small_ocean.txt b/scripts/umiman/raw/smallthings/creature_small_ocean.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_small_ocean.txt rename to scripts/umiman/raw/smallthings/creature_small_ocean.txt diff --git a/scripts/umiman/smallthingsmod/creature_small_riverlake.txt b/scripts/umiman/raw/smallthings/creature_small_riverlake.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_small_riverlake.txt rename to scripts/umiman/raw/smallthings/creature_small_riverlake.txt diff --git a/scripts/umiman/smallthingsmod/creature_standard.txt b/scripts/umiman/raw/smallthings/creature_standard.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_standard.txt rename to scripts/umiman/raw/smallthings/creature_standard.txt diff --git a/scripts/umiman/smallthingsmod/creature_subterranean.txt b/scripts/umiman/raw/smallthings/creature_subterranean.txt similarity index 100% rename from scripts/umiman/smallthingsmod/creature_subterranean.txt rename to scripts/umiman/raw/smallthings/creature_subterranean.txt diff --git a/scripts/umiman/smallthingsmod/descriptor_shape_standard.txt b/scripts/umiman/raw/smallthings/descriptor_shape_standard.txt similarity index 100% rename from scripts/umiman/smallthingsmod/descriptor_shape_standard.txt rename to scripts/umiman/raw/smallthings/descriptor_shape_standard.txt From 8434fa6a7cd699a74971cb9d330b1d261cadaf1f Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:22:29 -0400 Subject: [PATCH 22/95] Updated various pineapple.* scripts to conform to the new raws stuff --- .../pineapple/pydwarf.bauxitetoaluminum.py | 2 +- scripts/pineapple/pydwarf.boneflux.py | 2 +- scripts/pineapple/pydwarf.castanvil.py | 2 +- scripts/pineapple/pydwarf.greensteel.py | 17 +++++--- scripts/pineapple/pydwarf.nograzers.py | 1 - scripts/pineapple/pydwarf.utils.py | 43 +++++++++++++++++++ scripts/pineapple/pydwarf.woodmechanisms.py | 2 +- .../inorganic_green_steel_pineapple.txt | 0 .../reaction_green_steel_pineapple.txt | 0 9 files changed, 58 insertions(+), 11 deletions(-) rename scripts/pineapple/{raws => raw}/greensteel/inorganic_green_steel_pineapple.txt (100%) rename scripts/pineapple/{raws => raw}/greensteel/reaction_green_steel_pineapple.txt (100%) diff --git a/scripts/pineapple/pydwarf.bauxitetoaluminum.py b/scripts/pineapple/pydwarf.bauxitetoaluminum.py index 5901ed9..876d276 100644 --- a/scripts/pineapple/pydwarf.bauxitetoaluminum.py +++ b/scripts/pineapple/pydwarf.bauxitetoaluminum.py @@ -13,7 +13,7 @@ default_entities = ['MOUNTAIN'] -default_file = 'reaction_smelter_bauxtoalum_pineapple' +default_file = 'raw/objects/reaction_smelter_bauxtoalum_pineapple.txt' diff --git a/scripts/pineapple/pydwarf.boneflux.py b/scripts/pineapple/pydwarf.boneflux.py index 1785222..e9296f5 100644 --- a/scripts/pineapple/pydwarf.boneflux.py +++ b/scripts/pineapple/pydwarf.boneflux.py @@ -21,7 +21,7 @@ default_entities = ['MOUNTAIN', 'PLAINS'] -default_file = 'reaction_kiln_boneflux_pineapple' +default_file = 'raw/objects/reaction_kiln_boneflux_pineapple.txt' diff --git a/scripts/pineapple/pydwarf.castanvil.py b/scripts/pineapple/pydwarf.castanvil.py index cbd1ce8..2f5a681 100644 --- a/scripts/pineapple/pydwarf.castanvil.py +++ b/scripts/pineapple/pydwarf.castanvil.py @@ -15,7 +15,7 @@ default_anvil_cost = 5 # Cost in iron bars -default_file = 'reaction_smelter_castanvil_pineapple' +default_file = 'raw/objects/reaction_smelter_castanvil_pineapple.txt' diff --git a/scripts/pineapple/pydwarf.greensteel.py b/scripts/pineapple/pydwarf.greensteel.py index 5b0aa14..9c0ad9b 100644 --- a/scripts/pineapple/pydwarf.greensteel.py +++ b/scripts/pineapple/pydwarf.greensteel.py @@ -1,12 +1,20 @@ import os import pydwarf -greendir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'raws/greensteel') + + +greendir = pydwarf.rel(__file__, 'raw/greensteel') +added_reactions = ( + 'GREEN_STEEL_MAKING_ADAMANT_PINEAPPLE', + 'GREEN_STEEL_MAKING_ADMANTINE_PINEAPPLE', +) + default_entities = ['MOUNTAIN'] + @pydwarf.urist( name = 'pineapple.greensteel', version = '1.0.0', @@ -23,14 +31,11 @@ def greensteel(df, entities=default_entities): # Add greensteel raws try: - df.read(path=greendir) + df.add(path=greendir, loc='raw/objects') return pydwarf.urist.getfn('pineapple.utils.addtoentity')( df, entities = entities, - permitted_reaction = ( - 'GREEN_STEEL_MAKING_ADAMANT_PINEAPPLE', - 'GREEN_STEEL_MAKING_ADMANTINE_PINEAPPLE' - ) + permitted_reaction = added_reactions ) except: pydwarf.log.exception('Failed to add greensteel raws.') diff --git a/scripts/pineapple/pydwarf.nograzers.py b/scripts/pineapple/pydwarf.nograzers.py index 1678fd1..276547f 100644 --- a/scripts/pineapple/pydwarf.nograzers.py +++ b/scripts/pineapple/pydwarf.nograzers.py @@ -16,4 +16,3 @@ def nograzers(df): return pydwarf.success('Removed %d GRAZER and %d STANDARD_GRAZER tokens.' % (len(grazers), len(standardgrazers))) else: return pydwarf.failure('I found no grazer tokens to remove.') - diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index fd46678..71f45ac 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -105,3 +105,46 @@ def objecttokens(df, object_type, token, add_to=None, remove_from=None): return pydwarf.success('Added %d %s tokens and removed %d from object type %s.' % (added, token, removed, object_type)) else: return pydwarf.failure('Didn\'t add or remove any %s tokens.' % token) + + + +@pydwarf.urist( + name = 'pineapple.utils.addhack', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Utility script for adding a new DFHack script.''', + arguments = { + 'name': 'The file name of the script to add.', + 'auto_run': '''If set to True, a line will be added to dfhack.init containing only + the name of the added script. If set to None, no such line will be added. If set + to an arbitrary string, that string will be added as a new line at the end of + dfhack.init.''', + '**kwargs': '''Other named arguments will be passed on to the dir.add method used to + create the file object corresponding to the added script.''' + }, + compatibility = '.*' +) +def addhack(df, name, auto_run, **kwargs): + pydwarf.log.debug('Adding new file %s.' % name) + file = df.add(name=name, **kwargs) + + if auto_run: + if auto_run is True: auto_run = '\n%s' % file.name + pydwarf.log.debug('Appending line %s to the end of dfhack.init.' % auto_run) + + if 'dfhack.init' not in df: + if 'dfhack.init-example' in df: + pydwarf.log.info('Copying dfhack.init-example to new file dfhack.init before adding new content to the file.') + init = df['dfhack.init-example'].copy().bin() + init.name = 'dfhack.init' + df.add(file=init) + else: + return pydwarf.failure('Failed to locate dfhack.init or dfhack.init-example.') + else: + init = df['dfhack.init'].bin() + + init.add('\n%s # Added by PyDwarf\n' % auto_run) + return pydwarf.success('Added new file %s and appended line %s to dfhack.init.' % (name, auto_run)) + + else: + return pydwarf.success('Added new file %s.' % name) diff --git a/scripts/pineapple/pydwarf.woodmechanisms.py b/scripts/pineapple/pydwarf.woodmechanisms.py index 7a33cfb..5264758 100644 --- a/scripts/pineapple/pydwarf.woodmechanisms.py +++ b/scripts/pineapple/pydwarf.woodmechanisms.py @@ -14,7 +14,7 @@ default_entities = ['MOUNTAIN', 'PLAINS'] -default_file = 'reaction_woodmechanisms_pineapple' +default_file = 'raw/objects/reaction_woodmechanisms_pineapple.txt' diff --git a/scripts/pineapple/raws/greensteel/inorganic_green_steel_pineapple.txt b/scripts/pineapple/raw/greensteel/inorganic_green_steel_pineapple.txt similarity index 100% rename from scripts/pineapple/raws/greensteel/inorganic_green_steel_pineapple.txt rename to scripts/pineapple/raw/greensteel/inorganic_green_steel_pineapple.txt diff --git a/scripts/pineapple/raws/greensteel/reaction_green_steel_pineapple.txt b/scripts/pineapple/raw/greensteel/reaction_green_steel_pineapple.txt similarity index 100% rename from scripts/pineapple/raws/greensteel/reaction_green_steel_pineapple.txt rename to scripts/pineapple/raw/greensteel/reaction_green_steel_pineapple.txt From 970891b54b9cbf4551ac0d1625bf680833df81e2 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:23:07 -0400 Subject: [PATCH 23/95] Moved a lot of sanitization worries out of raws.token and into a new raws.tokenargs class --- raws/token.py | 61 +++++++++++++++++------------------------- raws/tokenargs.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 raws/tokenargs.py diff --git a/raws/token.py b/raws/token.py index b1ec253..c7941bc 100644 --- a/raws/token.py +++ b/raws/token.py @@ -1,5 +1,6 @@ import itertools +from tokenargs import tokenargs from queryable import rawsqueryable, rawstokenlist class rawstoken(rawsqueryable): @@ -10,19 +11,11 @@ class rawstoken(rawsqueryable): detected automatically. If a rawstoken is specified it will be treated as a token argument. If a string, pretty. If anything else, tokens.''' - '''Don't allow these characters in values or arguments.''' - illegal_internal_chars = '[]:' + illegal_internal_chars = tokenargs.illegal # TODO: make this better '''Don't allow these characters in a token's prefix or suffix.''' illegal_external_chars = '[' - '''Automatically replace some illegal strings when passed to setarg with their legal equivalents.''' - argument_replacements = { - "':'": '58', - "'['": '91', - "']'": '93' - } - def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, prefix=None, suffix=None, prev=None, next=None, file=None): '''Constructs a token object. @@ -69,7 +62,7 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr token = rawstoken.parseone(pretty, implicit_braces=True) if token is not None: value = token.value - args = list(token.args) if token.args else [] + args = tokenargs(token.args) if token.args else tokenargs() prefix = token.prefix suffix = token.suffix @@ -78,7 +71,7 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr if prefix: self.setprefix(prefix) # non-token text between the preceding token and this one if suffix: self.setsuffix(suffix) # between this token and the next/eof (should typically apply to eof) - if self.args is None: self.args = [] + if self.args is None: self.args = tokenargs() def __hash__(self): # Not that this class is immutable, just means you'll need to be careful about when you're using token hashes return hash('%s:%s' % (self.value, self.argsstr()) if self.nargs() else self.value) @@ -274,21 +267,15 @@ def __nonzero__(self): def auto(auto, pretty, token, tokens): '''Internal: Convenience function for handling method arguments''' if auto is not None: - if isinstance(auto, basestring): pretty = auto - elif isinstance(auto, rawstoken): token = auto - elif isinstance(auto, rawsqueryable): tokens = auto.tokens() - else: tokens = auto + if isinstance(auto, basestring): + pretty = auto + elif isinstance(auto, rawstoken): + token = auto + elif isinstance(auto, rawsqueryable): + tokens = auto.tokens() + else: + tokens = auto return pretty, token, tokens - - @staticmethod - def sanitizeargstring(value): - '''Internal: Utility method for sanitizing a string intended to be evaluated as an arugment for a token.''' - valuestr = str(value) - if valuestr in rawstoken.argument_replacements: - valuestr = rawstoken.argument_replacements[valuestr] - else: - if any([char in valuestr for char in rawstoken.illegal_internal_chars]): raise ValueError('Illegal character in argument %s.' % valuestr) - return valuestr def index(self, index): itrtoken = self @@ -367,15 +354,16 @@ def setarg(self, index, value=None): >>> print token [EXAMPLE:hi!:b:500]''' if value is None and index is not None: value = index; index = 0 - self.args[index] = rawstoken.sanitizeargstring(value) + self.args[index] = value def setargs(self, args=None): - self.args = [] - if args: - for arg in args: self.addarg(arg) + if self.args is None: + self.args = tokenargs(args) + else: + self.args[:] = args def clearargs(self): - self.args = [] + self.args.clear() def addarg(self, value): '''Appends an argument to the end of the argument list. @@ -390,10 +378,13 @@ def addarg(self, value): >>> print token [EXAMPLE:hi!] ''' - self.args.append(rawstoken.sanitizeargstring(value)) + self.args.append(value) + + def addargs(self, values): + self.args.extend(values) def containsarg(self, value): - return rawstoken.sanitizeargstring(value) in self.args + return value in self.args def argsstr(self): '''Return arguments joined by ':'. @@ -403,7 +394,7 @@ def argsstr(self): >>> print token.argsstr() a:b:c ''' - return ':'.join([str(a) for a in self.args]) + return str(self.args) def getvalue(self): '''Get the token's value. @@ -530,12 +521,10 @@ def equals(self, other): >>> print token_c is token_a False ''' - return( other is not None and self.value == other.value and - self.nargs() == other.nargs() and - all([str(self.args[i]) == str(other.args[i]) for i in xrange(0, self.nargs())]) + self.args == other.args ) @staticmethod diff --git a/raws/tokenargs.py b/raws/tokenargs.py new file mode 100644 index 0000000..b8325b7 --- /dev/null +++ b/raws/tokenargs.py @@ -0,0 +1,68 @@ +class tokenargs(list): + + # Disallow these characters inside token arguments + illegal = '[]:' + + # Replace simple inputs with illegal characters with their legal equivalents + replace = { + "':'": '58', + "'['": '91', + "']'": '93' + } + + @staticmethod + def sanitize(value): + '''Internal: Utility method for sanitizing a string intended to be evaluated as an arugment for a token.''' + valuestr = str(value) + if valuestr in tokenargs.replace: + valuestr = tokenargs.replace[valuestr] + else: + if any([char in valuestr for char in tokenargs.illegal]): raise ValueError('Illegal character in argument %s.' % valuestr) + return valuestr + + def append(self, item): + list.append(self, tokenargs.sanitize(item)) + + def extend(self, items): + list.extend(self, (tokenargs.sanitize(item) for item in items)) + + def insert(self, index, item): + list.insert(self, index, tokenargs.sanitize(item)) + + def clear(self): + del self[:] + + def reset(self, items): + self[:] = (tokenargs.sanitize(item) for item in items) + + def __str__(self): + return ':'.join(self) + + def __repr__(self): + return str(self) + + def __add__(self, items): + return list.__add__(self, (tokenargs.sanitize(item) for item in items)) + + def __contains__(self, item): + try: + san = tokenargs.sanitize(item) + except: + return False + else: + return list.__contains__(self, san) + + def __iadd__(self, item): + list.__iadd__(self, tokenargs.sanitize(item)) + + def __init__(self, items=None): + if items: + list.__init__(self, (tokenargs.sanitize(item) for item in items)) + else: + list.__init__(self) + + def __setslice__(self, start, stop, items): + list.__setslice__(self, start, stop, (tokenargs.sanitize(item) for item in items)) + + def __setitem__(self, index, item): + list.__setitem__(self, index, tokenargs.sanitize(item)) From 70b194b7ef96f82d02c7d59a3cd6a4be0338570a Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:23:53 -0400 Subject: [PATCH 24/95] In raws.queryable, replaced import * with import rawstokenfilter --- raws/queryable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raws/queryable.py b/raws/queryable.py index a2702dd..190644c 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -2,7 +2,7 @@ import inspect -from filters import * +from filters import rawstokenfilter From 821048df2df98e7fd191316fcbc20e48900fd131 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 26 Jun 2015 14:26:13 -0400 Subject: [PATCH 25/95] Removed raws.dfhack altogether Replaced by new functionality of raws.dir --- raws/__init__.py | 1 - raws/dfhack.py | 64 ------------------------------------------------ 2 files changed, 65 deletions(-) delete mode 100644 raws/dfhack.py diff --git a/raws/__init__.py b/raws/__init__.py index a8b9c38..0a5a41d 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -31,7 +31,6 @@ from file import rawsbinfile as binfile from file import rawsfile as file # TODO: rename to "rawfile" from dir import rawsdir as dir -from dfhack import dfhack from copytree import copytree import color diff --git a/raws/dfhack.py b/raws/dfhack.py deleted file mode 100644 index 3fed467..0000000 --- a/raws/dfhack.py +++ /dev/null @@ -1,64 +0,0 @@ -# Add some basic functionality for working with DFHack - -import os -import shutil - - - -class dfhack: - nodir = 'Can\'t add file, DFHack directory hasn\'t been specified.' - - def __init__(self, path=None, version=None): - '''Constructor for dfhack object.''' - self.path = path - self.version = version # TODO: automatic detection in pydwarf.config isn't reliable (why am I putting it here? who the fuck knows) - - def open(path, *args, **kwargs): - '''Open a file within the DFHack directory. Acts as a shortcut for open('dfhack/path', mode).''' - - if not self.path: - log.error(dfhack.nodir) - return None - else: - return open(os.path.join(self.path, path), *args, **kwargs) - - def exists(path): - return self.path and os.path.exists(os.path.join(self.path, path)) - def isfile(path): - return self.path and os.path.isfile(os.path.join(self.path, path)) - def isdir(path): - return self.path and os.path.isdir(os.path.join(self.path, path)) - - def add(path, dest, overwrite=False): - '''Copies an existing file into the DFHack directory.''' - - if not os.path.exists(path): - raise ValueError('File %s does not exist.' % path) - elif not self.path: - raise ValueError(dfhack.nodir) - else: - destpath = os.path.join(self.path, dest) - destexists = os.path.exists(destpath) - if destexists and not overwrite: - raise ValueError('File %s already exists.' % destpath) - else: - shutil.copy2(path, destpath) - return True - return False - - def remove(path): - '''Removes a file from the DFHack directory.''' - - if not self.path: - raise ValueError(dfhack.nodir) - else: - path = os.path.join(self.path, path) - if os.path.isfile(path): - os.remove(path) - return True - elif os.path.isdir(path): - shutil.rmtree(path) - return True - else: - raise ValueError('Failed to remove file or directory %s because the path is invalid.' % path) - return False From 10b01a38fa1547152904b1c2881f24dfb17412da Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sat, 27 Jun 2015 07:36:00 -0400 Subject: [PATCH 26/95] Added script pkdawson.vegan This is PyDwarf's first mod that does stuff with DFHack! Sweet --- scripts/pkdawson/pydwarf.vegan.py | 107 ++++++++++++++++++++++++++++++ scripts/pkdawson/readme.md | 30 +++++++++ 2 files changed, 137 insertions(+) create mode 100644 scripts/pkdawson/pydwarf.vegan.py create mode 100644 scripts/pkdawson/readme.md diff --git a/scripts/pkdawson/pydwarf.vegan.py b/scripts/pkdawson/pydwarf.vegan.py new file mode 100644 index 0000000..a210056 --- /dev/null +++ b/scripts/pkdawson/pydwarf.vegan.py @@ -0,0 +1,107 @@ +import os +import pydwarf + + + +vegan_reactions = { + 'CLOTH_QUIVER_PKDAWSON': ''' + [NAME:weave cloth quiver] + [BUILDING:CRAFTSMAN:NONE] + [REAGENT:mat:1:CLOTH:NONE:NONE:NONE][DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + [PRODUCT:100:1:QUIVER:NONE:GET_MATERIAL_FROM_REAGENT:mat:NONE] + [SKILL:CLOTHESMAKING] + ''', + 'CLOTH_BACKPACK_PKDAWSON': ''' + [NAME:weave cloth backpack] + [BUILDING:CRAFTSMAN:NONE] + [REAGENT:mat:1:CLOTH:NONE:NONE:NONE][DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + [PRODUCT:100:1:BACKPACK:NONE:GET_MATERIAL_FROM_REAGENT:mat:NONE] + [SKILL:CLOTHESMAKING] + ''', +} + +default_entities = ['MOUNTAIN', 'PLAINS'] + +default_file = 'raw/objects/reaction_vegan_pkdawson.txt' + +default_lua_file = 'hack/scripts/autolabor-vegan-pkdawson.lua' + +lua_content = ''' +default_labors = { + %(labors)s +} +for i, labor in ipairs(nonvegan_labors) do + dfhack.run_command(string.format("autolabor %(format)s 0 0", labor)) +end +''' + +default_labors = [ + 'BUTCHER', + 'TRAPPER', + 'DISSECT_VERMIN', + 'LEATHER', + 'TANNER', + 'MAKE_CHEESE', + 'MILK', + 'FISH', + 'CLEAN_FISH', + 'DISSECT_FISH', + 'HUNT', + 'BONE_CARVE', + 'SHEARER', + 'BEEKEEPING', + 'WAX_WORKING', + 'GELD', +] + +def format_lua_content(content, labors): + return content % {'labors': '\n '.join('"%s"' % labor for labor in labors), 'format': '%s'} + + + +@pydwarf.urist( + name = 'pkdawson.vegan', + version = '1.0.0', + author = ('Patrick Dawson', 'Sophie Kirschner'), + description = '''Adds reactions to the craftdwarf's workshop for making quivers and + backpacks from cloth, which normally require leather. Also adds a dfhack script + which disables non-vegan labors using autolabor.''', + arguments = { + 'labors': '''These labors will be disabled using a DFHack script. If set to None + then no DFHack script will be written.''', + 'lua_file': '''The DFHack script will be added to this path, relative to the hack/ + directory. If set to None then no DFHack script will be written.''', + 'auto_run': '''If set to True, and if the DFHack script is added, then a line + will be added to the end of dfhack.init which runs this script on startup. + If set to False then the script will wait to be run manually.''' + 'entities': 'Adds the reaction to these entities. Defaults to MOUNTAIN and PLAINS.', + 'add_to_file': 'Adds the reaction to this file.' + }, + compatibility = pydwarf.df_0_40 +) +def vegan(df, labors=default_labors, lua_file=default_lua_file, auto_run=True, entities=default_entities, add_to_file=default_file): + # Add the reactions + addreaction = pydwarf.urist.getfn('pineapple.utils.addreaction') + for reactionid, reactioncontent in vegan_reactions.iteritems(): + pydwarf.log.debug('Adding reaction %s.' % reactionid) + response = addreaction( + df, + id = reactionid, + tokens = reactioncontent, + add_to_file = add_to_file, + permit_entities = entities + ) + if not response.success: return response + + # Add the dfhack script + if labors and lua_file: + pydwarf.log.debug('Adding DFHack script %s.' % lua_file) + pydwarf.urist.getfn('pineapple.utils.addhack')( + df, + name = lua_file, + content = format_lua_content(lua_content, labors), + auto_run = auto_run + ) + + # All done + return pydwarf.success() diff --git a/scripts/pkdawson/readme.md b/scripts/pkdawson/readme.md new file mode 100644 index 0000000..c34e2fe --- /dev/null +++ b/scripts/pkdawson/readme.md @@ -0,0 +1,30 @@ +df-vegan +======== + +Created by Patrick Dawson. (https://github.com/pkdawson) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +A few tweaks are required to make vegan dwarves completely practical in Dwarf Fortress. This mod adds cloth quivers and backpacks, which can be made at a Craftsdwarf's Workshop, using the Clothesmaking skill. + +I'd also recommend turning off artifacts (strange moods), which often demand bone or other animal products. + +To do this challenge properly, you should never use any kind of animal product. That includes no wool, no silk, and no beekeeping. Keeping pets is fine, but training or using war dogs (or other war animals) would be unethical. + +Here's a list of labors that are disabled by default, using a DFHack script: + +- Hunting +- Trapping +- Small Animal Dissection +- Butchery +- Tanning +- Cheese Making +- Milking +- Shearing +- Beekeeping +- Gelding +- Fishing +- Fish Cleaning +- Fish Dissection +- Leatherworking +- Bone Carving +- Wax Working From 2b8d91b54386fd8cb3a9e9ab6e3a3d4812ca6a69 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sat, 27 Jun 2015 07:37:17 -0400 Subject: [PATCH 27/95] Readme edit for pkdawson.vegan Markdown is hard --- scripts/pkdawson/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pkdawson/readme.md b/scripts/pkdawson/readme.md index c34e2fe..b00d989 100644 --- a/scripts/pkdawson/readme.md +++ b/scripts/pkdawson/readme.md @@ -1,7 +1,7 @@ df-vegan ======== -Created by Patrick Dawson. (https://github.com/pkdawson) +Created by Patrick Dawson. (https://github.com/pkdawson) Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) A few tweaks are required to make vegan dwarves completely practical in Dwarf Fortress. This mod adds cloth quivers and backpacks, which can be made at a Craftsdwarf's Workshop, using the Clothesmaking skill. From 7894882893d75d0d6ea2b1cb767cbe3a4935d7ed Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sat, 27 Jun 2015 07:39:38 -0400 Subject: [PATCH 28/95] Removed unused import --- scripts/pkdawson/pydwarf.vegan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/pkdawson/pydwarf.vegan.py b/scripts/pkdawson/pydwarf.vegan.py index a210056..da8df99 100644 --- a/scripts/pkdawson/pydwarf.vegan.py +++ b/scripts/pkdawson/pydwarf.vegan.py @@ -1,4 +1,3 @@ -import os import pydwarf From 56cc8a407ea61c0e35e6cea4fc436d14a93ac963 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sat, 27 Jun 2015 10:14:18 -0400 Subject: [PATCH 29/95] Improved urist metadata for pkdawson.vegan --- scripts/pkdawson/pydwarf.vegan.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/pkdawson/pydwarf.vegan.py b/scripts/pkdawson/pydwarf.vegan.py index da8df99..62ef9d2 100644 --- a/scripts/pkdawson/pydwarf.vegan.py +++ b/scripts/pkdawson/pydwarf.vegan.py @@ -63,13 +63,14 @@ def format_lua_content(content, labors): version = '1.0.0', author = ('Patrick Dawson', 'Sophie Kirschner'), description = '''Adds reactions to the craftdwarf's workshop for making quivers and - backpacks from cloth, which normally require leather. Also adds a dfhack script + backpacks from cloth, which normally require leather. Also adds a DFHack script which disables non-vegan labors using autolabor.''', arguments = { 'labors': '''These labors will be disabled using a DFHack script. If set to None - then no DFHack script will be written.''', - 'lua_file': '''The DFHack script will be added to this path, relative to the hack/ - directory. If set to None then no DFHack script will be written.''', + then no DFHack script will be written. The default labors are %s. + ''' % ', '.join(default_labors), + 'lua_file': '''The DFHack script will be added to this path, relative to DF's + root directory. If set to None then no DFHack script will be written.''', 'auto_run': '''If set to True, and if the DFHack script is added, then a line will be added to the end of dfhack.init which runs this script on startup. If set to False then the script will wait to be run manually.''' From 1cd97974a4a53cc51021962e48f626da2be721c9 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Wed, 1 Jul 2015 06:58:30 -0400 Subject: [PATCH 30/95] Added a few additional default paths for config --- pydwarf/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydwarf/config.py b/pydwarf/config.py index d0409ef..aac9128 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -126,7 +126,7 @@ def setuppaths(self): 'gamelog.txt', 'errorlog.txt', 'stderr.log', 'stdout.log', 'raw/objects', 'raw/graphics', - 'data/init', + 'data/art', 'data/init', 'data/speech', 'dfhack.init', 'dfhack.init-example', 'dfhack.history', 'hack/lua', 'hack/plugins', 'hack/raw', 'hack/ruby', 'hack/scripts', ] From cc05cb1e9dcc16b08bd141819680badcabd01e12 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 06:17:27 -0400 Subject: [PATCH 31/95] Added module raws.objects Code that needs to know what objects correspond to what headers should refer to this. For example, BUILDING_WORKSHOP:SOMETHING should correspond to OBJECT:BUILDING. --- raws/__init__.py | 1 + raws/objects.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 raws/objects.py diff --git a/raws/__init__.py b/raws/__init__.py index 0a5a41d..809208f 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -31,6 +31,7 @@ from file import rawsbinfile as binfile from file import rawsfile as file # TODO: rename to "rawfile" from dir import rawsdir as dir +from objects import objects, headers, objectdict, headerdict, headerforobject, objectsforheader from copytree import copytree import color diff --git a/raws/objects.py b/raws/objects.py new file mode 100644 index 0000000..dc30014 --- /dev/null +++ b/raws/objects.py @@ -0,0 +1,128 @@ +def getoption(version, *options): + '''Internal: Used by objects functions to get an option corresponding to some Dwarf Fortress version.''' + if version: + if isinstance(version, basestring): # For strings + pass + elif 'config' in version.__dict__ and 'version' in version.config.__dict__: # For dirs + version = rawsdir.config.version + elif 'version' in version.__dict__: # For configs + version = version.version + else: + version = str(version) + if version and version.startswith('0.2'): + return options[1] + return options[0] + +def objectsdict(headersdict): + '''Internal: Used to build a dict mapping objects to their headers from a dict mapping headers to their possible objects.''' + objdict = {} + for header, objects in headersdict.iteritems(): + for obj in objects: objdict[obj] = header + return objdict + + + +headers_base = { + 'BODY': [ + 'BODY', + ], + 'CREATURE': [ + 'CREATURE', + ], + 'ENTITY': [ + 'ENTITY', + ], + 'GRAPHICS': [ + 'TILE_PAGE', 'CREATURE_GRAPHICS', + ], + 'ITEM': [ + 'ITEM_WEAPON', 'ITEM_TOY', 'ITEM_TOOL', 'ITEM_INSTRUMENT', + 'ITEM_TRAPCOMP', 'ITEM_ARMOR', 'ITEM_AMMO', 'ITEM_SIEGEAMMO', + 'ITEM_GLOVES', 'ITEM_SHOES', 'ITEM_SHIELD', 'ITEM_HELM', + 'ITEM_PANTS', 'ITEM_FOOD', + ], + 'LANGUAGE': [ + 'WORD', 'SYMBOL', 'TRANSLATION', + ], + 'REACTION': [ + 'REACTION', + ], + 'TISSUE_TEMPLATE': [ + 'TISSUE_TEMPLATE', + ], +} + +headers_23 = { + 'DESCRIPTOR': [ + 'COLOR', 'SHAPE' + ], + 'MATGLOSS': [ + 'MATGLOSS_GEM', 'MATGLOSS_PLANT', 'MATGLOSS_STONE', 'MATGLOSS_WOOD' + ], +} +headers_23.update(headers_base) + +headers_31 = { + 'BODY_DETAIL_PLAN': [ + 'BODY_DETAIL_PLAN' + ], + 'BUILDING': [ + 'BUILDING_WORKSHOP', 'BUILDING_FURNACE' + ], + 'CREATURE_VARIATION': [ + 'CREATURE_VARIATION' + ], + 'DESCRIPTOR_COLOR': [ + 'COLOR' + ], + 'DESCRIPTOR_PATTERN': [ + 'COLOR_PATTERN' + ], + 'DESCRIPTOR_SHAPE': [ + 'SHAPE' + ], + 'INORGANIC': [ + 'INORGANIC' + ], + 'INTERACTION': [ + 'INTERACTION' + ], + 'MATERIAL_TEMPLATE': [ + 'MATERIAL_TEMPLATE' + ], + 'PLANT': [ + 'PLANT' + ], +} +headers_31.update(headers_base) + +objects_23 = objectsdict(headers_23) +objects_31 = objectsdict(headers_31) + + + +def headers(version=None): + '''Get a list of valid object types as given by the OBJECT:TYPE tokens which appear + at the beginning of raws files.''' + return getoption(version, headers_31, headers_23).keys() +def objects(version=None): + '''Get a list of valid object types as given by the TYPE:ID tokens which denote the + beginning of an object definition.''' + return getoption(version, objects_31, objects_23).keys() + +def headerdict(version=None): + '''Get the header dict corresponding to some version. The dict maps headers as keys + to lists of corresponding object types. For example, BUILDING to BUILDING_WORKSHOP + and BUILDING_FURNACE.''' + return getoption(version, headers_31, headers_23) +def objectdict(version=None): + '''Get the object dict corresponding to some version. The dict maps object types as + keys to their corresponding header. For example, BUILDING_WORKSHOP to BUILDING.''' + return getoption(version, objects_31, objects_23) + +def headerforobject(type, version=None): + '''Returns the header for a particular object type given a version.''' + return objectdict(version).get(type) +def objectsforheader(header, version=None): + '''Returns the object types corresponding to a particular header given a version.''' + return headerdict(version).get(header) From 8b6c5ea14c3658e72b2a5e364f309661acb6e2ed Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 06:20:27 -0400 Subject: [PATCH 32/95] rawsqueryableobj now refers to raws.objects instead of handling things itself --- raws/queryable.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/raws/queryable.py b/raws/queryable.py index 190644c..e621c82 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -2,6 +2,7 @@ import inspect +import objects from filters import rawstokenfilter @@ -413,15 +414,10 @@ def argstokens(self, tokeniter, kwargs): def argsprops(self): # Utility function for handling arguments of getprop, allprop, and propdict methods + # TODO: refactor a bit so that the obviated until_exact_value and until_re_value are no longer returned until_exact_value = None until_re_value = None - until_value_in = None - if self.value.startswith('ITEM_'): - until_re_value = 'ITEM_.+' - elif self.value == 'WORD' or self.value == 'SYMBOL': - until_value_in = ('WORD', 'SYMBOL') - else: - until_exact_value = self.value + until_value_in = objects.objectsforheader(objects.headerforobject(self.value)) return until_exact_value, until_re_value, until_value_in @@ -431,21 +427,11 @@ def __init__(self): self.files = None def getobjheadername(self, type): - # Utility function fit for handling objects as of 0.40.24 - if type in ('WORD', 'SYMBOL', 'TRANSLATION'): - return ('LANGUAGE',) - elif type.startswith('ITEM_'): - return ('ITEM',) - elif type == 'COLOR' or type == 'SHAPE': - return ('DESCRIPTOR', 'DESCRIPTOR_%s' % type) - elif type == 'COLOR_PATTERN': - return ('DESCRIPTOR_PATTERN',) - elif type.startswith('MATGLOSS_'): - return ('MATGLOSS',) - elif type in ('TILE_PAGE', 'CREATURE_GRAPHICS'): - type = ('GRAPHICS',) + version = self if hasattr(self, 'config') or hasattr(self, 'version') else self.dir + if type is None: + return objects.headers(version) else: - return type + return objects.headerforobject(type, version) def getobj(self, pretty=None, type=None, exact_id=None): '''Get the first object token matching a given type and id. (If there's more From b74e39762ab1e6f31ca6b81e8317e8a06c686879 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 06:21:27 -0400 Subject: [PATCH 33/95] The raws.file constructor now accepts a content argument instead of a data argument Also fixed some broken code in the getobjheaders method --- raws/file.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/raws/file.py b/raws/file.py index f8c6cc5..bca7bbd 100644 --- a/raws/file.py +++ b/raws/file.py @@ -195,7 +195,7 @@ def add(self, content): class rawsfile(rawsbasefile, rawsqueryableobj): '''Represents a single file within a raws directory.''' - def __init__(self, name=None, file=None, path=None, root=None, data=None, tokens=None, dir=None, **kwargs): + def __init__(self, name=None, file=None, path=None, root=None, content=None, tokens=None, dir=None, **kwargs): '''Constructs a new raws file object. name: The name string to appear at the top of the file. Also used to determine filename. @@ -215,10 +215,10 @@ def __init__(self, name=None, file=None, path=None, root=None, data=None, tokens if file: self.read(file) if name is not None: self.name = name - if data is not None: self.data = data + if content is not None: self.data = content else: self.name = name - self.data = data + self.data = content if self.data is not None: tokens = rawstoken.parse(self.data, implicit_braces=False, file=self) @@ -479,7 +479,6 @@ def clear(self): self.tailtoken = None def getobjheaders(self, type): + match_types = self.getobjheadername(type) root = self.root() - if root is not None and root.value == 'OBJECT' and root.nargs() == 1 and root.args[0] in match_types: - return (root,) - return tuple() + return (root,) if root is not None and root.value == 'OBJECT' and root.nargs(1) and root.args[0] in match_types else tuple() From 24b9413c6e675420dde969803780a7f44b050e56 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 06:22:04 -0400 Subject: [PATCH 34/95] Autorun no longer defaults to True in pkdawson.vegan --- scripts/pkdawson/pydwarf.vegan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pkdawson/pydwarf.vegan.py b/scripts/pkdawson/pydwarf.vegan.py index 62ef9d2..d2ae836 100644 --- a/scripts/pkdawson/pydwarf.vegan.py +++ b/scripts/pkdawson/pydwarf.vegan.py @@ -73,13 +73,13 @@ def format_lua_content(content, labors): root directory. If set to None then no DFHack script will be written.''', 'auto_run': '''If set to True, and if the DFHack script is added, then a line will be added to the end of dfhack.init which runs this script on startup. - If set to False then the script will wait to be run manually.''' + If set to False then the script will wait to be run manually.''', 'entities': 'Adds the reaction to these entities. Defaults to MOUNTAIN and PLAINS.', 'add_to_file': 'Adds the reaction to this file.' }, compatibility = pydwarf.df_0_40 ) -def vegan(df, labors=default_labors, lua_file=default_lua_file, auto_run=True, entities=default_entities, add_to_file=default_file): +def vegan(df, labors=default_labors, lua_file=default_lua_file, auto_run=False, entities=default_entities, add_to_file=default_file): # Add the reactions addreaction = pydwarf.urist.getfn('pineapple.utils.addreaction') for reactionid, reactioncontent in vegan_reactions.iteritems(): From f6ebf6ea5807dc219e97d9e4a776eb22eb241d25 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 06:22:20 -0400 Subject: [PATCH 35/95] Added a handy operator overload to pydwarf.response --- pydwarf/response.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pydwarf/response.py b/pydwarf/response.py index 9967816..18b31dd 100644 --- a/pydwarf/response.py +++ b/pydwarf/response.py @@ -6,6 +6,9 @@ def __init__(self, success, status): def __str__(self): return '%s: %s' % ('SUCCESS' if self.success else 'FAILURE', self.status if self.status else ('Ran %ssuccessfully.' % ('' if self.success else 'un'))) + def __nonzero__(self): + return self.success + @staticmethod def success(status=None): return response(True, status) From 265dfd341db29661bac5ce23b26e3cc138bcc8c4 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 07:55:30 -0400 Subject: [PATCH 36/95] Moved files around and generally updated stal.armoury Planned for near future: - Delegate much of this messy functionality to existing pineapple.utils functions - Separate the attacks removal into a separately registered function (stal.armoury.items + stal.armoury.attacks ?) --- scripts/stal/armouryentities.py | 4 ++-- .../readme_armoury.txt | 0 scripts/stal/pydwarf.armourypack.py | 7 ++++--- .../armoury}/creature_standard.txt | 0 .../armoury}/entity_default.txt | 0 .../armoury}/item_ammo.txt | 0 .../armoury}/item_armor.txt | 0 .../armoury}/item_body.txt | 0 .../armoury}/item_gloves.txt | 0 .../armoury}/item_helm.txt | 0 .../armoury}/item_pants.txt | 0 .../armoury}/item_shield.txt | 0 .../armoury}/item_shoes.txt | 0 .../armoury}/item_tool.txt | 0 .../armoury}/item_weapon.txt | 0 .../armoury}/reaction_armoury.txt | 0 16 files changed, 6 insertions(+), 5 deletions(-) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => misc}/readme_armoury.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/creature_standard.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/entity_default.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_ammo.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_armor.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_body.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_gloves.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_helm.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_pants.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_shield.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_shoes.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_tool.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/item_weapon.txt (100%) rename scripts/stal/{StalsArmouryPackv1_8a_4024 => raw/armoury}/reaction_armoury.txt (100%) diff --git a/scripts/stal/armouryentities.py b/scripts/stal/armouryentities.py index a1f2f08..f327aa8 100644 --- a/scripts/stal/armouryentities.py +++ b/scripts/stal/armouryentities.py @@ -8,7 +8,7 @@ print('And so it begins.') -entities = raws.dir(path='StalsArmouryPackv1_8a_4024')['entity_default'] +entities = raws.dir(path='raw/armoury')['entity_default'] edict = {} @@ -17,7 +17,7 @@ itemtypes = ('AMMO', 'DIGGER', 'TOOL', 'WEAPON', 'ARMOR', 'PANTS', 'GLOVES', 'SHOES', 'HELM', 'SHIELD') edict[entity.args[0]] = {} entitydict = edict[entity.args[0]] - for item in entity.alluntil(value_in=itemtypes, until_exact_value='ENTITY'): + for item in entity.getprop(value_in=itemtypes): if item.value == 'AMMO': if item.value not in entitydict: entitydict[item.value] = {} forweapon = item.get(exact_value='WEAPON', reverse=True).args[0] diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/readme_armoury.txt b/scripts/stal/misc/readme_armoury.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/readme_armoury.txt rename to scripts/stal/misc/readme_armoury.txt diff --git a/scripts/stal/pydwarf.armourypack.py b/scripts/stal/pydwarf.armourypack.py index cc9be7d..59e6231 100644 --- a/scripts/stal/pydwarf.armourypack.py +++ b/scripts/stal/pydwarf.armourypack.py @@ -4,7 +4,8 @@ import raws # Armoury raws are located in this directory -armourydir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'StalsArmouryPackv1_8a_4024') +entities_json = pydwarf.rel(__file__, 'armouryentities.json') +armoury_dir = pydwarf.rel(__file__, 'raw/armoury') # Armory raws are variations on vanilla names, this records them so they can be changed back. # Without this, other mods which attempt to make changes to the same items might run into problems. @@ -55,7 +56,7 @@ ] # These are the items that each entity should have. (JSON courtesy of helper script armouryentities.py.) -with open(os.path.join(armourydir, '../armouryentities.json'), 'rb') as efile: armoury_entities = json.load(efile) +with open(entities_json, 'rb') as efile: armoury_entities = json.load(efile) @@ -189,7 +190,7 @@ def removeattacks(dfraws, remove_attacks, remove_attacks_from): ) def armourypack(dfraws, remove_entity_items=True, remove_attacks=('SCRATCH', 'BITE'), remove_attacks_from=('DWARF', 'HUMAN', 'ELF')): try: - armouryraws = raws.dir(path=armourydir, log=pydwarf.log) + armouryraws = raws.dir(root=armoury_dir, log=pydwarf.log) except: return pydwarf.failure('Unable to load armoury raws.') diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/creature_standard.txt b/scripts/stal/raw/armoury/creature_standard.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/creature_standard.txt rename to scripts/stal/raw/armoury/creature_standard.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/entity_default.txt b/scripts/stal/raw/armoury/entity_default.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/entity_default.txt rename to scripts/stal/raw/armoury/entity_default.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_ammo.txt b/scripts/stal/raw/armoury/item_ammo.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_ammo.txt rename to scripts/stal/raw/armoury/item_ammo.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_armor.txt b/scripts/stal/raw/armoury/item_armor.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_armor.txt rename to scripts/stal/raw/armoury/item_armor.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_body.txt b/scripts/stal/raw/armoury/item_body.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_body.txt rename to scripts/stal/raw/armoury/item_body.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_gloves.txt b/scripts/stal/raw/armoury/item_gloves.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_gloves.txt rename to scripts/stal/raw/armoury/item_gloves.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_helm.txt b/scripts/stal/raw/armoury/item_helm.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_helm.txt rename to scripts/stal/raw/armoury/item_helm.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_pants.txt b/scripts/stal/raw/armoury/item_pants.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_pants.txt rename to scripts/stal/raw/armoury/item_pants.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_shield.txt b/scripts/stal/raw/armoury/item_shield.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_shield.txt rename to scripts/stal/raw/armoury/item_shield.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_shoes.txt b/scripts/stal/raw/armoury/item_shoes.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_shoes.txt rename to scripts/stal/raw/armoury/item_shoes.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_tool.txt b/scripts/stal/raw/armoury/item_tool.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_tool.txt rename to scripts/stal/raw/armoury/item_tool.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/item_weapon.txt b/scripts/stal/raw/armoury/item_weapon.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/item_weapon.txt rename to scripts/stal/raw/armoury/item_weapon.txt diff --git a/scripts/stal/StalsArmouryPackv1_8a_4024/reaction_armoury.txt b/scripts/stal/raw/armoury/reaction_armoury.txt similarity index 100% rename from scripts/stal/StalsArmouryPackv1_8a_4024/reaction_armoury.txt rename to scripts/stal/raw/armoury/reaction_armoury.txt From 7f128298533693543410b2c2610ab2347cff0cf5 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 07:58:02 -0400 Subject: [PATCH 37/95] Lots of additions and changes to pineapple.utils - addtoentity now accepts a string/list/whatever containing tokens instead of the weird **kwargs thing that was going on before - addtoentity can intelligently handle receiving a single entity name string instead of an iterable containing such names - updated addreaction to comply with those changes to addtoentity - added pineapple.utils.permitobject and pineapple.utils.permitobjects - added pineapple.utils.addobject and pineapple.utils.addobjects --- scripts/pineapple/pydwarf.utils.py | 193 ++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 17 deletions(-) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 71f45ac..9747e20 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -10,29 +10,19 @@ description = '''A simple utility script which adds tokens to entities.''', arguments = { 'entities': 'Adds tokens to these entities.', - '**kwargs': ''''Should map keywords to iterables. For example, setting the argument - permitted_job=('MINER', 'CARPENTER') would result in the tokens - [PERMITTED_JOB:MINER] and [PERMITTED_JOB:CARPENTER] being added to each of the - listed entities if they aren't present already.''' + 'tokens': 'A string or collection of tokens to add to each entity.' }, compatibility = '.*' ) -def addtoentity(df, entities, **kwargs): +def addtoentity(df, entities, tokens): + if isinstance(entities, basestring): entities = (entities,) pydwarf.log.debug('Adding tokens to %d entities.' % len(entities)) - added = 0 entitytokens = df.allobj(type='ENTITY', id_in=entities) + for entitytoken in entitytokens: entitytoken.addprop(tokens) if len(entitytokens) != len(entities): - return pydwarf.failure() + return pydwarf.failure('Failed to add tokens to all given entities because only %d of %d exist.' % (len(entitytokens), len(entities))) else: - for permittype, permititems in kwargs.iteritems(): - permitvalue = permittype.upper() - pydwarf.log.debug('Handling tokens of type %s.' % permitvalue) - for permititem in permititems: - for entitytoken in entitytokens: - if not entitytoken.getprop(exact_value=permitvalue, exact_args=(permititem,)): - entitytoken.addprop(raws.token(value=permitvalue, args=[permititem])) - added += 1 - return pydwarf.success('Added %d permitted things to %d entities.' % (added, len(entitytokens))) + return pydwarf.success('Added tokens to %d entities.' % len(entitytokens)) @@ -51,7 +41,7 @@ def addtoentity(df, entities, **kwargs): compatibility = '.*' ) def addreaction(df, id, tokens, add_to_file='reaction_custom', permit_entities=None): - if permit_entities is not None and (not addtoentity(df, permit_entities, permitted_reaction=(id,)).success): + if permit_entities is not None and (not addtoentity(df, permit_entities, 'PERMITTED_REACTION:%s' % id).success): return pydwarf.failure('Failed to add permitted reactions to entites.') else: if df.getobj(type='REACTION', exact_id=id): @@ -148,3 +138,172 @@ def addhack(df, name, auto_run, **kwargs): else: return pydwarf.success('Added new file %s.' % name) + + + +@pydwarf.urist( + name = 'pineapple.utils.addobject', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Utility script for adding a new object to the raws.''', + arguments = { + 'add_to_file': '''The name of the file to add the object to. If it doesn't exist already + then the file is created anew. The string is formatted such that %(type)s is + replaced with the object_header, lower case.''', + 'tokens': '''The tokens belonging to the object to create.''', + 'type': '''Specifies the object type. If type and id are left unspecified, the first + token of the tokens argument is assumed to be the object's [TYPE:ID] token and the + type and id arguments are taken out of that.''', + 'id': '''Specifies the object id. If type and id are left unspecified, the first + token of the tokens argument is assumed to be the object's [TYPE:ID] token and the + type and id arguments are taken out of that.''', + 'permit_entities': '''For relevant object types such as reactions, buildings, and items, + if permit_entities is specified then tokens are added to those entities to permit + the added object.''', + 'item_rarity': '''Most items, when adding tokens to entities to permit them, accept an + optional second argument specifying rarity. It should be one of 'RARE', 'UNCOMMON', + 'COMMON', or 'FORCED'. This argument can be used to set that rarity.''', + 'object_header': '''When the object is added to a file which doesn't already exist, + an [OBJECT:TYPE] token must be added at its beginning. This argument, if specified, + provides the type in that token. Otherwise, when the argument is left set to None, + the type will be automatically decided.''' + }, + compatibility = '.*' +) +def addobject(df, add_to_file, tokens, type=None, id=None, permit_entities=None, item_rarity=None, object_header=None): + # If type and id weren't explicitly given then assume the first given token is the TYPE:ID header and get the info from there. + header_in_tokens = type is None and id is None + header = None + if header_in_tokens: + if isinstance(tokens, basestring): tokens = raws.token.parse(tokens) + header = tokens[0] + type = header.value + id = header.arg() + pydwarf.log.debug('Extracted object type %s and id %s from given tokens.' % (type, id)) + + # Get the applicable object dict which knows how to map TYPE:ID to its corresponding OBJECT:TYPE header. + if object_header is None: + object_header = raws.objects.headerforobject(type) + + # If add_to_file already exists, fetch it. Otherwise add it to the raws. + add_to_file = add_to_file % {'type': object_header.lower()} + if add_to_file in df: + file = df.getfile(file) + else: + file = df.add(add_to_file) + file.add(raws.token(value='OBJECT', args=[object_header])) + pydwarf.log.debug('Added new file %s to dir.' % add_to_file) + + # Add the object itself to the raws. + if not header_in_tokens: header = file.add(raws.token(value=type, args=[id])) + file.add(tokens) + + # Add tokens to entities to permit the use of this object. + if permit_entities: + response = permitobject( + df, + type = type, + id = id, + permit_entities = permit_entities, + item_rarity = item_rarity + ) + if not response: return response + + # All done! + return pydwarf.success('Added object %s to file %s.' % (header, file)) + + + +@pydwarf.urist( + name = 'pineapple.utils.addobjects', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Utility script for adding several new objects to the raws at once.''', + arguments = { + 'add_to_file': '''The name of the file to add the object to. If it doesn't exist already + then the file is created anew. The string is formatted such that %(type)s is + replaced with the object_header, lower case.''', + 'objects': '''An iterable containing tokens belonging to the objects to add.''', + '**kwargs': 'Passed on to pineapple.utils.addobject.', + }, + compatibility = '.*' +) +def addobjects(df, add_to_file, objects, **kwargs): + for obj in objects: + response = addobject(df, add_to_file, **kwargs) + if not response.success: return response + return response.success('Added %d objects.' % len(objects)) + + + +@pydwarf.urist( + name = 'pineapple.utils.permitobject', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Utility script for permitting an object with entities.''', + arguments = { + 'type': '''Specifies the object type.''', + 'id': '''Specifies the object id.''', + 'permit_entities': '''For relevant object types such as reactions, buildings, and items, + if permit_entities is specified then tokens are added to those entities to permit + the added object.''', + 'item_rarity': '''Some items, when adding tokens to entities to permit them, accept an + optional second argument specifying rarity. It should be one of 'RARE', 'UNCOMMON', + 'COMMON', or 'FORCED'. This argument can be used to set that rarity.''' + }, + compatibility = '.*' +) +def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None): + # Decide what tokens need to be added to the entities based on the object type + if type == 'REACTION': + tokens = 'PERMITTED_REACTION:%s' % id + elif type.startswith('BUILDING_'): + tokens = 'PERMITTED_BUILDING:%s' % id + elif type.startswith('ITEM_'): + value = type.split('_')[1] + args = [id, item_rarity] if item_rarity else [id] + tokens = raws.token(value=value, args=args) + else: + tokens = None + + # Actually add those tokens + if tokens is None: + return pydwarf.success('Didn\'t actually permit object [%s:%s] because objects of this type cannot be permitted.' % (type, id)) + elif not permit_entities: + return pydwarf.failure('No entities were given for permitting.') + else: + response = addtoentity( + df, + entities = permit_entities, + tokens = tokens + ) + if not response: + return response + else: + return pydwarf.success('Permitted object [%s:%s] for %d entities.' % (type, id, len(permit_entities))) + + + +@pydwarf.urist( + name = 'pineapple.utils.permitobjects', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Utility script for permitting several objects at once with entities.''', + arguments = { + 'objects': '''An iterable containing type, id tuples for objects to permit.''', + '**kwargs': 'Passed on to pineapple.utils.permitobject.', + }, + compatibility = '.*' +) +def permitobjects(df, objects, **kwargs): + for item in objects: + if isinstance(item, raws.token): + type, id = item.value, item.arg() + elif isinstance(item, basestring): + type, id = item.split(':') + else: + type, id = item + response = permitobject(df, type, id, **kwargs) + if not response: return response + return pydwarf.success('Permitted %d objects.' % len(objects)) + \ No newline at end of file From be19d3231c28323ecbe92f8c2174f37cd8faeefa Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 08:03:28 -0400 Subject: [PATCH 38/95] Added pineapple.easypatch Accepts a raws file as input and, where applicable, adds PERMITTED_X tokens and related to entities. Simple but should be really very handy for porting the less complicated mods very quickly. And in most cases an end user could just give easypatch a path to some raws file or directory of them to add and everything will just work. --- scripts/pineapple/pydwarf.easypatch.py | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts/pineapple/pydwarf.easypatch.py diff --git a/scripts/pineapple/pydwarf.easypatch.py b/scripts/pineapple/pydwarf.easypatch.py new file mode 100644 index 0000000..d15f47a --- /dev/null +++ b/scripts/pineapple/pydwarf.easypatch.py @@ -0,0 +1,74 @@ +import os + +import pydwarf +import raws + + + +@pydwarf.urist( + name = 'pineapple.easypatch', + version = '1.0.0', + author = 'Sophie Kirschner', + description = '''Given a path to a file, a directory, a content string, a tokenlist, a raws + file object, or an iterable containing a combination of these, a file or files are added + to the dir object, and these same objects can be permitted using the permitted_entities + argument.''', + arguments = { + 'files': '''The file or files to be added.''', + '**kwargs': 'Passed on to pineapple.utils.permitobjects.', + }, + compatibility = '.*' +) +def easypatch(df, files, **kwargs): + if isinstance(files, basestring): + if os.path.isfile(files): + return easypatch_filepath(df, files **kwargs) + elif os.path.isdir(files): + return easypatch_dirpath(df, files, collision_fails=False, **kwargs) + else: + return easypatch_content(df, files, **kwargs) + elif isinstance(files, raws.tokenlist): + return easypatch_tokens(df, files, **kwargs) + elif isinstance(files, raws.file): + return easypatch_file(df, files, **kwargs) + else: + for file in files: + response = easypatch(df, file, collision_fails=False, **kwargs) + if not response: return response + return pydwarf.success('Added %d files.' % len(files)) + + + +def easypatch_dirpath(df, path, loc=None, **kwargs): + for root, dirnames, filenames in os.walk(path): + for filename in filenames: + filepath = os.path.join(root, filename) + response = easypatch_filepath(df, path=filepath, loc=loc, root=root, **kwargs) + if not response: return response + return pydwarf.success('Added files from directory %s.' % path) + +def easypatch_filepath(df, path, loc=None, root=None, **kwargs): + file = raws.file(path=path, loc=loc, root=root) + return easypatch_file(df, file, **kwargs) + +def easypatch_content(df, content, loc, **kwargs): + file = raws.file(path=loc, content=content) + return easypatch_file(df, file, **kwargs) + +def easypatch_tokens(df, tokens, loc, **kwargs): + file = raws.file(path=loc, tokens=tokens) + return easypatch_file(df, file, **kwargs) + +def easypatch_file(df, file, collision_fails=True, **kwargs): + if str(file) not in df: + df.add(file) + response = pydwarf.urist.getfn('pineapple.utils.permitobjects')( + df, + objects = file.allobj(), + **kwargs + ) + return response + elif collision_fails: + return pydwarf.failure('Failed to add file because a file by the same name already exists in the dir.') + else: + return pydwarf.success('Didn\'t add the file because a file by the same name already exists in the dir.') From 4150b6089647ac621c50367ee4116f5c42a48a08 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 08:04:33 -0400 Subject: [PATCH 39/95] Shukaro scripts now call pineapple.utils functions instead of handling things on their own --- scripts/shukaro/pydwarf.creationforge.py | 21 ++++++---- scripts/shukaro/pydwarf.higherlearning.py | 34 +++++++++++----- .../creationforge/building_forge_shukaro.txt | 0 .../creationforge/reaction_forge_shukaro.txt | 0 .../building_library_shukaro.txt | 0 .../higherlearning/item_library_shukaro.txt | 0 .../reaction_library_shukaro.txt | 0 scripts/shukaro/shukaroutils.py | 40 ------------------- 8 files changed, 38 insertions(+), 57 deletions(-) rename scripts/shukaro/{ => raw}/creationforge/building_forge_shukaro.txt (100%) rename scripts/shukaro/{ => raw}/creationforge/reaction_forge_shukaro.txt (100%) rename scripts/shukaro/{ => raw}/higherlearning/building_library_shukaro.txt (100%) rename scripts/shukaro/{ => raw}/higherlearning/item_library_shukaro.txt (100%) rename scripts/shukaro/{ => raw}/higherlearning/reaction_library_shukaro.txt (100%) delete mode 100644 scripts/shukaro/shukaroutils.py diff --git a/scripts/shukaro/pydwarf.creationforge.py b/scripts/shukaro/pydwarf.creationforge.py index 6a34071..7b7a4bf 100644 --- a/scripts/shukaro/pydwarf.creationforge.py +++ b/scripts/shukaro/pydwarf.creationforge.py @@ -1,12 +1,12 @@ -import sys -import os +import pydwarf -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -import pydwarf -import shukaroutils -forgedir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'creationforge') +creation_dir = pydwarf.rel(__file__, 'raw/creationforge') + +default_entities = ('MOUNTAIN',) + + @pydwarf.urist( name = 'shukaro.creationforge', @@ -18,5 +18,10 @@ useful tool to people, even if it's just to look at the raw formatting. ''', compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) ) -def higherlearning(dfraws, entities=shukaroutils.default_entities): - return shukaroutils.addraws(pydwarf, dfraws, forgedir, entities) +def creationforge(df, entities=default_entities): + return pydwarf.urist.getfn('pineapple.easypatch')( + df, + files = creation_dir, + loc = 'raw/objects', + permit_entities = entities + ) diff --git a/scripts/shukaro/pydwarf.higherlearning.py b/scripts/shukaro/pydwarf.higherlearning.py index d8227b0..1a50348 100644 --- a/scripts/shukaro/pydwarf.higherlearning.py +++ b/scripts/shukaro/pydwarf.higherlearning.py @@ -1,12 +1,10 @@ -import sys -import os +import pydwarf -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -import pydwarf -import shukaroutils -librarydir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'higherlearning') +library_dir = pydwarf.rel(__file__, 'raw/higherlearning') + +default_entities = 'MOUNTAIN' librarian_position = ''' [POSITION:LIBRARIAN] @@ -21,7 +19,10 @@ [COLOR:5:0:0] [DUTY_BOUND] [REQUIRED_BEDROOM:1] - [REQUIRED_OFFICE:1]''' + [REQUIRED_OFFICE:1] +''' + + @pydwarf.urist( name = 'shukaro.higherlearning', @@ -49,5 +50,20 @@ }, compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) # I think it should be compatible with 0.40.x? Haven't actually tried it yet. ) -def higherlearning(dfraws, entities=shukaroutils.default_entities): - return shukaroutils.addraws(pydwarf, dfraws, librarydir, entities, librarian_position) +def higherlearning(df, entities=default_entities): + # Add librarian position + response = pydwarf.urist.getfn('pineapple.utils.addtoentity')( + df, + entities = entities, + tokens = librarian_position + ) + if not response: return response + + # Add buildings, reactions, items from raws + return pydwarf.urist.getfn('pineapple.easypatch')( + df, + files = library_dir, + loc = 'raw/objects', + permit_entities = entities + ) + \ No newline at end of file diff --git a/scripts/shukaro/creationforge/building_forge_shukaro.txt b/scripts/shukaro/raw/creationforge/building_forge_shukaro.txt similarity index 100% rename from scripts/shukaro/creationforge/building_forge_shukaro.txt rename to scripts/shukaro/raw/creationforge/building_forge_shukaro.txt diff --git a/scripts/shukaro/creationforge/reaction_forge_shukaro.txt b/scripts/shukaro/raw/creationforge/reaction_forge_shukaro.txt similarity index 100% rename from scripts/shukaro/creationforge/reaction_forge_shukaro.txt rename to scripts/shukaro/raw/creationforge/reaction_forge_shukaro.txt diff --git a/scripts/shukaro/higherlearning/building_library_shukaro.txt b/scripts/shukaro/raw/higherlearning/building_library_shukaro.txt similarity index 100% rename from scripts/shukaro/higherlearning/building_library_shukaro.txt rename to scripts/shukaro/raw/higherlearning/building_library_shukaro.txt diff --git a/scripts/shukaro/higherlearning/item_library_shukaro.txt b/scripts/shukaro/raw/higherlearning/item_library_shukaro.txt similarity index 100% rename from scripts/shukaro/higherlearning/item_library_shukaro.txt rename to scripts/shukaro/raw/higherlearning/item_library_shukaro.txt diff --git a/scripts/shukaro/higherlearning/reaction_library_shukaro.txt b/scripts/shukaro/raw/higherlearning/reaction_library_shukaro.txt similarity index 100% rename from scripts/shukaro/higherlearning/reaction_library_shukaro.txt rename to scripts/shukaro/raw/higherlearning/reaction_library_shukaro.txt diff --git a/scripts/shukaro/shukaroutils.py b/scripts/shukaro/shukaroutils.py deleted file mode 100644 index e39c61b..0000000 --- a/scripts/shukaro/shukaroutils.py +++ /dev/null @@ -1,40 +0,0 @@ -# Defines functions common to shukaro mods - -import raws - -default_entities = ('MOUNTAIN',) - -def getentities(dfraws, entities): - return dfraws.all(exact_value='ENTITY', re_args=('|'.join(entities),)) - -def addraws(pydwarf, dfraws, rawsdir, entities, extratokens=None): - # Get the entities that need to have permitted things added to them. - entitytokens = getentities(dfraws, entities) - if len(entitytokens) != len(entities): - if len(entitytokens): - pydwarf.log.error('Of entities %s passed by argument, only %s were found.' % (entities, entitytokens)) - else: - return pydwarf.failure('Found none of entities %s to which to add permitted buildings and reactions.' % entities) - - # Read the raws - try: - shukaroraws = raws.dir(path=rawsdir, log=pydwarf.log) - except: - pydwarf.log.exception('Failed to load raws from %s.' % rawsdir) - return pydwarf.failure('Failed to load raws from %s.' % rawsdir) - - # Add new buildings and reactions to entities, and whatever else needs adding - buildings = shukaroraws.all(exact_value='BUILDING_WORKSHOP', args_count=1) - reactions = shukaroraws.all(exact_value='REACTION', args_count=1) - for entity in entitytokens: - if extratokens: entity.add(extratokens) - for building in buildings: entity.add(raws.token(value='PERMITTED_BUILDING', args=(building.args[0],))) - for reaction in reactions: entity.add(raws.token(value='PERMITTED_REACTION', args=(reaction.args[0],))) - pydwarf.log.info('Added %d permitted buildings and %d permitted reactions to %d entities.' % (len(buildings), len(reactions), len(entitytokens))) - - # Add new files - for filename, rfile in shukaroraws.files.iteritems(): - if filename not in dfraws: dfraws.add(rfile.copy()) - - # All done! - return pydwarf.success() From d4d7a15d270236e9bd870e6f56431ea4d7414489 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:40:42 -0400 Subject: [PATCH 40/95] Changed behavior of iterating over a token Previously __iter__ would just return the same generator as the tokens method without any arguments. But since that seemed a little useless to me, and since being able to unpack a token like value, arg0, arg1 = token seemed more useful, __iter__ now yields the value followed by each argument. --- raws/token.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raws/token.py b/raws/token.py index c7941bc..cdba130 100644 --- a/raws/token.py +++ b/raws/token.py @@ -252,6 +252,9 @@ def __mul__(self, value): tokens.append(rawstoken.copy(self)) return tokens + def __iter__(self): + yield self.value + for arg in self.args: yield arg def __len__(self): return self.nargs() def __contains__(self, value): From 3b9812437eac1ed2d8bbedb734ffac2a1141e13b Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:41:09 -0400 Subject: [PATCH 41/95] Got rid of token.getarg, merged its functionality with token.arg --- raws/token.py | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/raws/token.py b/raws/token.py index cdba130..9e40608 100644 --- a/raws/token.py +++ b/raws/token.py @@ -316,28 +316,6 @@ def nargs(self, count=None): ''' return len(self.args) if (count is None) else (len(self.args) == count) - def getarg(self, index=0): - '''Gets argument at index, returns None if the index is out of bounds. - - index: The argument index. - - Example usage: - >>> token = raws.token('EXAMPLE:argument 0:argument 1') - >>> print token.getarg(0) - argument 0 - >>> print token.getarg(1) - argument 1 - >>> print token.getarg(2) - None - >>> print token.getarg(-1) - argument 1 - >>> print token.getarg(-2) - argument 0 - >>> print token.getarg(-3) - None - ''' - return self.args[index] if index >= -len(self.args) and index < len(self.args) else None - def setarg(self, index, value=None): '''Sets argument at index, also verifies that the input contains no illegal characters. If the index argument is set but not value, then the index is assumed to be referring to @@ -483,12 +461,25 @@ def setsuffix(self, value): if any([char in valuestr for char in rawstoken.illegal_external_chars]): raise ValueError('Failed to set token suffix to %s because the string contains illegal characters.' % valuestr) self.suffix = value - def arg(self): - '''When a token is expected to have only one argument, this method can be used - to access it. It there's one argument it will be returned, otherwise an - exception will be raised. + def arg(self, index=None): + '''When an index is given, the argument at that index is returned. If left + set to None then the first argument is returned if the token has exactly one + argument, otherwise an exception is raised. Example usage: + >>> token = raws.token('EXAMPLE:argument 0:argument 1') + >>> print token.getarg(0) + argument 0 + >>> print token.getarg(1) + argument 1 + >>> print token.getarg(2) + None + >>> print token.getarg(-1) + argument 1 + >>> print token.getarg(-2) + argument 0 + >>> print token.getarg(-3) + None >>> token_a = raws.token('EXAMPLE:x') >>> token_b = raws.token('EXAMPLE:x:y:z') >>> print token_a.arg() @@ -499,10 +490,11 @@ def arg(self): ... print 'token_b doesn\'t have the correct number of arguments!' ... token_b doesn't have the correct number of arguments!''' - if len(self.args) == 1: + if index is None: + if len(self.args) != 1: raise ValueError('Failed to retrieve token argument because it doesn\'t have exactly one.') return self.args[0] else: - raise ValueError('Failed to retrieve token argument because it doesn\'t have exactly one.') + return self.args[index] def equals(self, other): '''Returns True if two tokens have identical values and arguments, False otherwise. From 2727d80fea37740e88a122a5efa1f949cef73b07 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:42:48 -0400 Subject: [PATCH 42/95] Added type_in argument to getobj, allobj methods. --- raws/queryable.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/raws/queryable.py b/raws/queryable.py index e621c82..1245603 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -433,7 +433,15 @@ def getobjheadername(self, type): else: return objects.headerforobject(type, version) - def getobj(self, pretty=None, type=None, exact_id=None): + def headersfortype(type=None, type_in=None): + headers = set() + if type_in: + for type in type_in: headers.update(self.getobjheaders(type)) + if type: + headers.update(self.getobjheaders(type)) + return headers + + def getobj(self, pretty=None, type=None, exact_id=None, type_in=None, re_id=None, id_in=None): '''Get the first object token matching a given type and id. (If there's more than one result for any given query then I'm afraid you've done something silly with your raws.) This method should work properly with things like @@ -456,12 +464,18 @@ def getobj(self, pretty=None, type=None, exact_id=None): ''' type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) - for objecttoken in self.getobjheaders(type): - obj = objecttoken.get(exact_value=type, exact_args=(exact_id,)) + for objecttoken in self.headersfortype(type, type_in): + obj = objecttoken.get( + exact_value = type, + exact_args = (exact_id,) if exact_id else None, + re_args = (re_id,) if re_id else None, + arg_in = ((0, id_in),) if id_in else None, + args_count = 1 + ) if obj: return obj return None - def allobj(self, pretty=None, type=None, exact_id=None, re_id=None, id_in=None): + def allobj(self, pretty=None, type=None, exact_id=None, type_in=None, re_id=None, id_in=None): '''Gets all objects matching a given type and optional id or id regex. Example usage: @@ -486,7 +500,7 @@ def allobj(self, pretty=None, type=None, exact_id=None, re_id=None, id_in=None): type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) results = rawstokenlist() - for objecttoken in self.getobjheaders(type): + for objecttoken in self.headersfortype(type, type_in): for result in objecttoken.all( exact_value = type, exact_args = (exact_id,) if exact_id else None, @@ -533,7 +547,7 @@ def objpretty(pretty, type, id): return pretty, type else: return type, id - + class rawstokenlist(list, rawsqueryable): From dfbd52f83d6cba0fa49146513dbed8f5d9afd84e Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:43:42 -0400 Subject: [PATCH 43/95] Added a bunch of handy removal methods to raws.queryable and raws.queryableobj --- raws/queryable.py | 71 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/raws/queryable.py b/raws/queryable.py index 1245603..2d2ba8c 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -31,7 +31,8 @@ class rawsqueryable(object): named normally.) ''' % query_tokeniter_docstring - def __iter__(self): return self.tokens() + def __iter__(self): + return self.tokens() def __contains__(self, item): if isinstance(item, basestring): @@ -86,6 +87,53 @@ def islice(self, slice): yield token else: return + + def removefirst(self, *args, **kwargs): + token = self.get(*args, **kwargs) + if token is not None: token.remove() + return token + def removelast(self, *args, **kwargs): + token = self.getlast(*args, **kwargs) + if token is not None: token.remove() + return token + def removeuntil(self, *args, **kwargs): + token = self.until(*args, **kwargs) + if token is not None: token.remove() + return token + def removefirstuntil(self, *args, **kwargs): + token = self.getuntil(*args, **kwargs) + if token is not None: token.remove() + return token + def removelastuntil(self, *args, **kwargs): + token = self.getlastuntil(*args, **kwargs) + if token is not None: token.remove() + return token + + def removeall(self, *args, **kwargs): + tokens = self.all(*args, **kwargs) + for token in tokens: token.remove() + return tokens + def removealluntil(self, *args, **kwargs): + tokens = self.alluntil(*args, **kwargs) + for token in tokens: token.remove() + return tokens + + def removeprop(self, *args, **kwargs): + token = self.getprop(*args, **kwargs) + if token is not None: token.remove() + return token + def removelastprop(self, *args, **kwargs): + token = self.getlastprop(*args, **kwargs) + if token is not None: token.remove() + return token + def removeallprop(self, *args, **kwargs): + tokens = self.allprop(*args, **kwargs) + for token in tokens: token.remove() + return tokens + def removeselfandprops(self, *args, **kwargs): + tokens = [self] + self.removeallprop(*args, **kwargs) + self.remove() + return tokens def query(self, filters, tokeniter=None, **kwargs): '''Executes a query on some iterable containing tokens. @@ -277,7 +325,7 @@ def alluntil(self, pretty=None, until=None, tokeniter=None, **kwargs): ) return self.query(filters, tokeniter, **tokens_args)[1].result - def getprop(self, pretty=None, **kwargs): + def getprop(self, *args, **kwargs): '''Gets the first token matching the arguments, but stops at the next token with the same value as this one. Should be sufficient in almost all cases to get a token representing a property of an object, when @@ -293,9 +341,9 @@ def getprop(self, pretty=None, **kwargs): ''' until_exact_value, until_re_value, until_value_in = self.argsprops() - return self.getuntil(pretty=pretty, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) + return self.getuntil(*args, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) - def getlastprop(self, pretty=None, **kwargs): + def getlastprop(self, *args, **kwargs): '''Gets the last token matching the arguments, but stops at the next token with the same value as this one. Should be sufficient in almost all cases to get a token representing a property of an object, when @@ -311,9 +359,9 @@ def getlastprop(self, pretty=None, **kwargs): ''' until_exact_value, until_re_value, until_value_in = self.argsprops() - return self.getlastuntil(pretty=pretty, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) + return self.getlastuntil(*args, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) - def allprop(self, pretty=None, **kwargs): + def allprop(self, *args, **kwargs): '''Gets the all tokens matching the arguments, but stops at the next token with the same value as this one. Should be sufficient in almost all cases to get a token representing a property of an object, when @@ -330,7 +378,7 @@ def allprop(self, pretty=None, **kwargs): ''' until_exact_value, until_re_value, until_value_in = self.argsprops() - return self.alluntil(pretty=pretty, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) + return self.alluntil(*args, until_exact_value=until_exact_value, until_re_value=until_re_value, until_value_in=until_value_in, **kwargs) def propdict(self, always_list=True, value_keys=True, full_keys=True, **kwargs): '''Returns a dictionary with token values mapped as keys to the tokens @@ -441,6 +489,15 @@ def headersfortype(type=None, type_in=None): headers.update(self.getobjheaders(type)) return headers + def removeobj(self, *args, **kwargs): + obj = self.getobj(*args, **kwargs) + if obj: obj.removeselfandprops() + return obj + def removeallobj(self, *args, **kwargs): + objects = self.allobj(*args, **kwargs) + for obj in objects: obj.removeselfandprops() + return objects + def getobj(self, pretty=None, type=None, exact_id=None, type_in=None, re_id=None, id_in=None): '''Get the first object token matching a given type and id. (If there's more than one result for any given query then I'm afraid you've done something From f9b38129a69f18be8c2a2ceb9c60f86b622f2a14 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:45:25 -0400 Subject: [PATCH 44/95] Improvements to pineapple.utils.addtoentity and pineapple.utils.permitobject - Added a check_existing argument to addtoentity which can cancel token adding if the action would create a duplicate - Tiny change to permitobject in the interest of cleaner code --- scripts/pineapple/pydwarf.utils.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 9747e20..fdb3de9 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -10,15 +10,25 @@ description = '''A simple utility script which adds tokens to entities.''', arguments = { 'entities': 'Adds tokens to these entities.', - 'tokens': 'A string or collection of tokens to add to each entity.' + 'tokens': 'A string or collection of tokens to add to each entity.', + 'check_existing': '''If set to True then before adding the tokens an entity will first + be checked for whether it already has any of the given tokens. If it does then none + of the given tokens will be added to that entity.''' }, compatibility = '.*' ) -def addtoentity(df, entities, tokens): +def addtoentity(df, entities, tokens, check_existing=True): if isinstance(entities, basestring): entities = (entities,) + if isinstance(tokens, basestring): tokens = raws.token.parse(tokens) pydwarf.log.debug('Adding tokens to %d entities.' % len(entities)) entitytokens = df.allobj(type='ENTITY', id_in=entities) - for entitytoken in entitytokens: entitytoken.addprop(tokens) + + for entitytoken in entitytokens: + if check_existing and any(entitytoken.getprop(match_token=token) for token in tokens): + pydwarf.log.debug('Skipping entity %s because it already contains a token that would have been added.' % entitytoken) + else: + entitytoken.addprop(tokens) + if len(entitytokens) != len(entities): return pydwarf.failure('Failed to add tokens to all given entities because only %d of %d exist.' % (len(entitytokens), len(entities))) else: @@ -256,9 +266,9 @@ def addobjects(df, add_to_file, objects, **kwargs): def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None): # Decide what tokens need to be added to the entities based on the object type if type == 'REACTION': - tokens = 'PERMITTED_REACTION:%s' % id + tokens = raws.token(exact_value='PERMITTED_REACTION', args=[id]) elif type.startswith('BUILDING_'): - tokens = 'PERMITTED_BUILDING:%s' % id + tokens = raws.token(exact_value='PERMITTED_BUILDING', args=[id]) elif type.startswith('ITEM_'): value = type.split('_')[1] args = [id, item_rarity] if item_rarity else [id] From d78dd7081d0a1c1055d6db936bab231f68476fe6 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:48:51 -0400 Subject: [PATCH 45/95] Added an "arg" argument to raws.token init method Now arg="X" could be used as a shorter substitute for args=["X"] --- raws/token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raws/token.py b/raws/token.py index 9e40608..1b18aa1 100644 --- a/raws/token.py +++ b/raws/token.py @@ -16,7 +16,7 @@ class rawstoken(rawsqueryable): '''Don't allow these characters in a token's prefix or suffix.''' illegal_external_chars = '[' - def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, prefix=None, suffix=None, prev=None, next=None, file=None): + def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, arg=None, prefix=None, suffix=None, prev=None, next=None, file=None): '''Constructs a token object. %s (However, a tokens argument is illegal here and attempting to create @@ -66,6 +66,7 @@ def __init__(self, auto=None, pretty=None, token=None, value=None, args=None, pr prefix = token.prefix suffix = token.suffix + if arg: self.setargs([arg]) if value: self.setvalue(value) # value for the token if args: self.setargs(args) # arguments for the token if prefix: self.setprefix(prefix) # non-token text between the preceding token and this one From bdcfd45aba72a6704f6d53a122b2a341206163f1 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:49:36 -0400 Subject: [PATCH 46/95] Updated pineapple.greensteel to be friendly with changes made to pineapple.utils --- scripts/pineapple/pydwarf.greensteel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pineapple/pydwarf.greensteel.py b/scripts/pineapple/pydwarf.greensteel.py index 9c0ad9b..ebe10c6 100644 --- a/scripts/pineapple/pydwarf.greensteel.py +++ b/scripts/pineapple/pydwarf.greensteel.py @@ -1,5 +1,5 @@ -import os import pydwarf +import raws @@ -35,7 +35,7 @@ def greensteel(df, entities=default_entities): return pydwarf.urist.getfn('pineapple.utils.addtoentity')( df, entities = entities, - permitted_reaction = added_reactions + tokens = [raws.token(value='PERMITTED_REACTION', arg=reaction) for reaction in added_reactions] ) except: pydwarf.log.exception('Failed to add greensteel raws.') From 1de01911c324cc320be24dd4531d05934556bc93 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:50:40 -0400 Subject: [PATCH 47/95] Improved raws.tokenfilter constructor Specifically, exact_arg, re_arg, and arg_in are now smart enough to handle a single iterable as opposed to an iterable of iterables. --- raws/filters.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raws/filters.py b/raws/filters.py index 9ecfde9..034c2b6 100644 --- a/raws/filters.py +++ b/raws/filters.py @@ -151,6 +151,11 @@ def __init__(self, self.limit = limit self.limit_terminates = limit_terminates + # Make exact_arg, re_arg, and arg_in easier: Allow a single iterable with index and value, don't always require an iterable of them. + if self.exact_arg and isinstance(self.exact_arg[0], int): self.exact_arg = (self.exact_arg,) + if self.re_arg and isinstance(self.re_arg[0], int): self.re_arg = (self.re_arg,) + if self.arg_in and isinstance(self.arg_in[0], int): self.arg_in = (self.arg_in,) + self.anchor() def anchor(self): From e467f90ccb1deb2281618d1843ac05e01ea39c2e Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:52:27 -0400 Subject: [PATCH 48/95] Fixed raws.file constructor, also added a readpath argument The readpath argument determines, when a path is given but not a file object, whether to read the file at that path immediately. --- raws/file.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/raws/file.py b/raws/file.py index bca7bbd..c3d0365 100644 --- a/raws/file.py +++ b/raws/file.py @@ -30,9 +30,9 @@ def factory(path, **kwargs): return rawsreffile(path=path, **kwargs) def __str__(self): - name = ''.join((self.name, self.ext)) if self.ext else self.name - path = os.path.join(self.loc, name) if self.loc else name - return path.replace('\\', '/') + name = ''.join((self.name, self.ext)) if self.ext and self.name else self.name + path = os.path.join(self.loc, name) if self.loc and name else name + return path.replace('\\', '/') if path else '' def __repr__(self): return str(self) @@ -59,7 +59,12 @@ def setpath(self, path, root=None, loc=None, name=None, ext=None): root = os.path.abspath(root) if root else None self.path = path self.rootpath = root - self.name, self.ext = (os.path.splitext(os.path.basename(path)) if os.path.isfile(path) else (os.path.basename(path), None)) if path else (None, None) + if not path: + self.name, self.ext = None, None + elif os.path.isfile(path): + self.name, self.ext = os.path.splitext(os.path.basename(path)) + else: + self.name, self.ext = os.path.basename(path), None if root and path and root != path and path.startswith(root): self.loc = os.path.dirname(os.path.relpath(path, root)) else: @@ -195,7 +200,7 @@ def add(self, content): class rawsfile(rawsbasefile, rawsqueryableobj): '''Represents a single file within a raws directory.''' - def __init__(self, name=None, file=None, path=None, root=None, content=None, tokens=None, dir=None, **kwargs): + def __init__(self, name=None, file=None, path=None, root=None, content=None, tokens=None, dir=None, readpath=True, **kwargs): '''Constructs a new raws file object. name: The name string to appear at the top of the file. Also used to determine filename. @@ -207,6 +212,7 @@ def __init__(self, name=None, file=None, path=None, root=None, content=None, tok ''' self.dir = dir + self.data = None self.setpath(path=path, root=root, **kwargs) self.roottoken = None @@ -214,18 +220,20 @@ def __init__(self, name=None, file=None, path=None, root=None, content=None, tok if file: self.read(file) - if name is not None: self.name = name - if content is not None: self.data = content - else: - self.name = name - self.data = content + elif path and readpath: + self.read(path) + + if name is not None: self.name = name + if content is not None: self.data = content if self.data is not None: tokens = rawstoken.parse(self.data, implicit_braces=False, file=self) self.settokens(tokens, setfile=False) elif tokens is not None: self.settokens(tokens, setfile=True) - + + if name: self.name = name + if (not self.path) and (not self.ext): self.ext = '.txt' self.kind = 'raw' From efca3b6076f7cdff0ab530094fc110d5c33e2966 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:53:00 -0400 Subject: [PATCH 49/95] Fixed conditional broken by pydwarf.response __nonzero__ override --- pydwarf/urist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 4ca057f..6cb599a 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -52,7 +52,7 @@ def eval(self, func, args=None): log.info('Running script %s%s.' % (name, ('with args %s' % args) if args else '')) try: response = func(self.dfraws, **args) if args else func(self.dfraws) # Call the function - if response: + if response is not None: # Handle success/failure response log.info(str(response)) (self.successes if response.success else self.failures).append(uristinstance if uristinstance else func) From bca3efcf8d5ee9aad68b4726f91a9c1a5f14594a Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:54:35 -0400 Subject: [PATCH 50/95] Fixed arguments in dir.__setitem__ --- raws/dir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raws/dir.py b/raws/dir.py index a93cf92..6e69e38 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -41,6 +41,7 @@ def __setitem__(self, name, content): self.add(file=name, replace=True, tokens=content.tokens()) elif isinstance(content, basestring): self.add(file=name, replace=True, data=content) + self.add(file=name, replace=True, content=content) else: self.add(file=name, tokens=content) From 0238a023870fc441643d12cb576d6856975f831b Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Thu, 2 Jul 2015 15:59:00 -0400 Subject: [PATCH 51/95] Little fixes to raws.dir - Fixed dumb mistake made in the last commit, adding a line but not removing the one it was supposed to replace (oops) - Added consideration to dir.__contains__ for when a file object is passed instead of a name - Also made dir.getfile not break if perchange a file object is passed - Added an iterfiles method - Fixed removal of files from dir not removing entries from the filenames dict --- raws/dir.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/raws/dir.py b/raws/dir.py index 6e69e38..afa6e2e 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -40,13 +40,15 @@ def __setitem__(self, name, content): elif isinstance(content, rawsqueryable): self.add(file=name, replace=True, tokens=content.tokens()) elif isinstance(content, basestring): - self.add(file=name, replace=True, data=content) self.add(file=name, replace=True, content=content) else: self.add(file=name, tokens=content) def __contains__(self, item): - return str(item) in self.files or str(item) in self.filenames + if isinstance(item, rawsbasefile): + return item in self.files.itervalues() + else: + return str(item) in self.files or str(item) in self.filenames def getfile(self, name, create=None, conflicts=False): '''Gets the file with a given name. If no file by that name is found, @@ -55,6 +57,9 @@ def getfile(self, name, create=None, conflicts=False): file is created and associated with that name, and then its add method is called using the value for create as its argument.''' + if isinstance(name, rawsbasefile): + return name if name in self.files.itervalues() else None + file = self.files.get(name) if file is None: file = self.filenames.get(name) @@ -70,6 +75,9 @@ def getfile(self, name, create=None, conflicts=False): file.add(create) return file + def iterfiles(self, *args, **kwargs): + return self.files.itervalues(*args, **kwargs) + def add(self, auto=None, **kwargs): if auto is not None: return self.addbyauto(auto, **kwargs) @@ -187,6 +195,7 @@ def addfiletodicts(self, file, replace=False): def remove(self, file=None): if isinstance(file, basestring): file = self.getfile(file) if (file not in self.files) or (file.dir is not self) or (file is None): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) + self.filenames[file.name].remove(file) self.files[str(file)].dir = None del self.files[str(file)] From 751132317a84c05aa62036330c90aa38898271f4 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 10:23:03 -0400 Subject: [PATCH 52/95] Improvements to pineapple.utils - Added some helpful logging statements - Fixed a typo in permitobject - Took check_existing away from addtoentity because it was broken (maybe add it back later? too much work for too little benefit just now.) - Fixed issue where if tokens were passed to addtoentity instead of a string everything died (TODO: handle other iterables containing tokens also, currently those would break too) --- scripts/pineapple/pydwarf.utils.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index fdb3de9..02d5f0a 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -11,23 +11,17 @@ arguments = { 'entities': 'Adds tokens to these entities.', 'tokens': 'A string or collection of tokens to add to each entity.', - 'check_existing': '''If set to True then before adding the tokens an entity will first - be checked for whether it already has any of the given tokens. If it does then none - of the given tokens will be added to that entity.''' }, compatibility = '.*' ) -def addtoentity(df, entities, tokens, check_existing=True): +def addtoentity(df, entities, tokens): if isinstance(entities, basestring): entities = (entities,) - if isinstance(tokens, basestring): tokens = raws.token.parse(tokens) - pydwarf.log.debug('Adding tokens to %d entities.' % len(entities)) + pydwarf.log.debug('Adding tokens to entities %s.' % ', '.join(str(ent) for ent in entities)) entitytokens = df.allobj(type='ENTITY', id_in=entities) for entitytoken in entitytokens: - if check_existing and any(entitytoken.getprop(match_token=token) for token in tokens): - pydwarf.log.debug('Skipping entity %s because it already contains a token that would have been added.' % entitytoken) - else: - entitytoken.addprop(tokens) + entitytoken.addprop(tokens) + if isinstance(tokens, raws.queryable): tokens = raws.token.copy(tokens) if len(entitytokens) != len(entities): return pydwarf.failure('Failed to add tokens to all given entities because only %d of %d exist.' % (len(entitytokens), len(entities))) @@ -266,9 +260,9 @@ def addobjects(df, add_to_file, objects, **kwargs): def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None): # Decide what tokens need to be added to the entities based on the object type if type == 'REACTION': - tokens = raws.token(exact_value='PERMITTED_REACTION', args=[id]) + tokens = raws.token(value='PERMITTED_REACTION', args=[id]) elif type.startswith('BUILDING_'): - tokens = raws.token(exact_value='PERMITTED_BUILDING', args=[id]) + tokens = raws.token(value='PERMITTED_BUILDING', args=[id]) elif type.startswith('ITEM_'): value = type.split('_')[1] args = [id, item_rarity] if item_rarity else [id] @@ -276,6 +270,8 @@ def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None) else: tokens = None + pydwarf.log.debug('Permitting object [%s:%s] for %d entities.' % (type, id, len(permit_entities))) + # Actually add those tokens if tokens is None: return pydwarf.success('Didn\'t actually permit object [%s:%s] because objects of this type cannot be permitted.' % (type, id)) @@ -306,6 +302,7 @@ def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None) compatibility = '.*' ) def permitobjects(df, objects, **kwargs): + pydwarf.log.debug('Permitting %d objects.' % len(objects)) for item in objects: if isinstance(item, raws.token): type, id = item.value, item.arg() From 131eb7ca9595e6029506173b966a61b252ad2187 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 10:23:41 -0400 Subject: [PATCH 53/95] Everybody loves TODO comments --- scripts/pineapple/pydwarf.utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 02d5f0a..7bddfac 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -21,7 +21,7 @@ def addtoentity(df, entities, tokens): for entitytoken in entitytokens: entitytoken.addprop(tokens) - if isinstance(tokens, raws.queryable): tokens = raws.token.copy(tokens) + if isinstance(tokens, raws.queryable): tokens = raws.token.copy(tokens) # TODO: What about other iterables containing token objects, e.g. lists and tuples? if len(entitytokens) != len(entities): return pydwarf.failure('Failed to add tokens to all given entities because only %d of %d exist.' % (len(entitytokens), len(entities))) From a1917e165a2e300b3063e7a27fb1d431262734e2 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 10:23:59 -0400 Subject: [PATCH 54/95] Fixed bug in the dir.remove method --- raws/dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raws/dir.py b/raws/dir.py index afa6e2e..74d44a5 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -194,7 +194,7 @@ def addfiletodicts(self, file, replace=False): def remove(self, file=None): if isinstance(file, basestring): file = self.getfile(file) - if (file not in self.files) or (file.dir is not self) or (file is None): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) + if (file is None) or (file.dir is not self) or not any(file is f for f in self.iterfiles()): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) self.filenames[file.name].remove(file) self.files[str(file)].dir = None del self.files[str(file)] From ad02938346a9bc5d8514d4827764cf54a703cd67 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 10:24:39 -0400 Subject: [PATCH 55/95] Replaced dict.get(stuff) with dict[stuff] in raws.objects --- raws/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raws/objects.py b/raws/objects.py index dc30014..6989f43 100644 --- a/raws/objects.py +++ b/raws/objects.py @@ -122,7 +122,7 @@ def objectdict(version=None): def headerforobject(type, version=None): '''Returns the header for a particular object type given a version.''' - return objectdict(version).get(type) + return objectdict(version)[type] def objectsforheader(header, version=None): '''Returns the object types corresponding to a particular header given a version.''' - return headerdict(version).get(header) + return headerdict(version)[header] From a31fa3f942e5b9b0dc4b875d88b2c718cbdda5bf Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 10:24:57 -0400 Subject: [PATCH 56/95] Tiny improvements to pineapple.easypatch --- scripts/pineapple/pydwarf.easypatch.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/pineapple/pydwarf.easypatch.py b/scripts/pineapple/pydwarf.easypatch.py index d15f47a..c696f30 100644 --- a/scripts/pineapple/pydwarf.easypatch.py +++ b/scripts/pineapple/pydwarf.easypatch.py @@ -59,12 +59,13 @@ def easypatch_tokens(df, tokens, loc, **kwargs): file = raws.file(path=loc, tokens=tokens) return easypatch_file(df, file, **kwargs) -def easypatch_file(df, file, collision_fails=True, **kwargs): - if str(file) not in df: - df.add(file) +def easypatch_file(df, file, collision_fails=True, replace=False, **kwargs): + if replace or str(file) not in df: + df.add(file, replace=replace) + objects = file.allobj() response = pydwarf.urist.getfn('pineapple.utils.permitobjects')( df, - objects = file.allobj(), + objects = objects, **kwargs ) return response From 8c78db8b24aec00bd7ce668ef181f74907df1901 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 11:08:16 -0400 Subject: [PATCH 57/95] Rewrote stal.armoury - Fixed all items being common - Fixed reactions not being permitted for the pertinent entities - Made the readme better - Generally made the code much cleaner thanks to added functionality, pineapple.utils, and most importantly more experience with raws stuff since I first wrote the script --- scripts/stal/armouryentities.json | 1663 ++++++++++++++++++++------- scripts/stal/armouryentities.py | 21 +- scripts/stal/pydwarf.armourypack.py | 252 ++-- scripts/stal/readme.md | 72 ++ scripts/stal/readme.txt | 15 - 5 files changed, 1451 insertions(+), 572 deletions(-) create mode 100644 scripts/stal/readme.md delete mode 100644 scripts/stal/readme.txt diff --git a/scripts/stal/armouryentities.json b/scripts/stal/armouryentities.json index 78d33d0..e09a117 100644 --- a/scripts/stal/armouryentities.json +++ b/scripts/stal/armouryentities.json @@ -1,483 +1,1318 @@ { "MOUNTAIN": { "SHOES": [ - "ITEM_SHOES_CHAUSSE_CHAIN", - "ITEM_SHOES_GREAVES_LOWER", - "ITEM_SHOES_BOOTS", - "ITEM_SHOES_SHOES", - "ITEM_SHOES_ANKLE", - "ITEM_SHOES_CARBATINAE", - "ITEM_SHOES_SOCKS" - ], + [ + "ITEM_SHOES_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_SHOES_GREAVES_LOWER", + "COMMON" + ], + [ + "ITEM_SHOES_BOOTS", + "COMMON" + ], + [ + "ITEM_SHOES_SHOES", + "FORCED" + ], + [ + "ITEM_SHOES_ANKLE", + "COMMON" + ], + [ + "ITEM_SHOES_CARBATINAE", + "COMMON" + ], + [ + "ITEM_SHOES_SOCKS", + "FORCED" + ] + ], "ARMOR": [ - "ITEM_ARMOR_PLATE", - "ITEM_ARMOR_HAUBERK", - "ITEM_ARMOR_LAMELLAR", - "ITEM_ARMOR_JERKIN", - "ITEM_ARMOR_GAMBESON", - "ITEM_ARMOR_CLOAK", - "ITEM_ARMOR_CAPE", - "ITEM_ARMOR_COAT", - "ITEM_ARMOR_VEST", - "ITEM_ARMOR_DRESS", - "ITEM_ARMOR_ROBE", - "ITEM_ARMOR_SHIRT", - "ITEM_ARMOR_TUNIC" - ], + [ + "ITEM_ARMOR_PLATE", + "COMMON" + ], + [ + "ITEM_ARMOR_HAUBERK", + "COMMON" + ], + [ + "ITEM_ARMOR_LAMELLAR", + "COMMON" + ], + [ + "ITEM_ARMOR_JERKIN", + "COMMON" + ], + [ + "ITEM_ARMOR_GAMBESON", + "COMMON" + ], + [ + "ITEM_ARMOR_CLOAK", + "COMMON" + ], + [ + "ITEM_ARMOR_CAPE", + "COMMON" + ], + [ + "ITEM_ARMOR_COAT", + "COMMON" + ], + [ + "ITEM_ARMOR_VEST", + "COMMON" + ], + [ + "ITEM_ARMOR_DRESS", + "COMMON" + ], + [ + "ITEM_ARMOR_ROBE", + "COMMON" + ], + [ + "ITEM_ARMOR_SHIRT", + "COMMON" + ], + [ + "ITEM_ARMOR_TUNIC", + "COMMON" + ] + ], "WEAPON": [ - "ITEM_WEAPON_SWORD_ARMING_SHORT", - "ITEM_WEAPON_SWORD_FALCHION", - "ITEM_WEAPON_DAGGER_FIGHTING", - "ITEM_WEAPON_AXE_HATCHET", - "ITEM_WEAPON_AXE_BATTLE", - "ITEM_WEAPON_AXE_HAMMER", - "ITEM_WEAPON_MACE_GOEDENDAG", - "ITEM_WEAPON_MACE_FLAIL", - "ITEM_WEAPON_MACE_FLAIL_SPIKED", - "ITEM_WEAPON_MACE_FLAIL_2H", - "ITEM_WEAPON_MACE_PERNACH", - "ITEM_WEAPON_HAMMER_WAR", - "ITEM_WEAPON_HAMMER_PICK", - "ITEM_WEAPON_HAMMER_MAUL", - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_SPEAR_BOAR", - "ITEM_WEAPON_AXE_BARDICHE", - "ITEM_WEAPON_CROSSBOW", - "ITEM_WEAPON_CROSSBOW_BAYONET", - "ITEM_WEAPON_CROSSBOW_SIEGE", - "ITEM_WEAPON_AXE_TRAINING", - "ITEM_WEAPON_SWORD_TRAINING", - "ITEM_WEAPON_SPEAR_TRAINING", - "ITEM_WEAPON_HAMMER_TRAINING", - "ITEM_WEAPON_MACE_TRAINING" - ], + [ + "ITEM_WEAPON_SWORD_ARMING_SHORT" + ], + [ + "ITEM_WEAPON_SWORD_FALCHION" + ], + [ + "ITEM_WEAPON_DAGGER_FIGHTING" + ], + [ + "ITEM_WEAPON_AXE_HATCHET" + ], + [ + "ITEM_WEAPON_AXE_BATTLE" + ], + [ + "ITEM_WEAPON_AXE_HAMMER" + ], + [ + "ITEM_WEAPON_MACE_GOEDENDAG" + ], + [ + "ITEM_WEAPON_MACE_FLAIL" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_SPIKED" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_2H" + ], + [ + "ITEM_WEAPON_MACE_PERNACH" + ], + [ + "ITEM_WEAPON_HAMMER_WAR" + ], + [ + "ITEM_WEAPON_HAMMER_PICK" + ], + [ + "ITEM_WEAPON_HAMMER_MAUL" + ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_SPEAR_BOAR" + ], + [ + "ITEM_WEAPON_AXE_BARDICHE" + ], + [ + "ITEM_WEAPON_CROSSBOW" + ], + [ + "ITEM_WEAPON_CROSSBOW_BAYONET" + ], + [ + "ITEM_WEAPON_CROSSBOW_SIEGE" + ], + [ + "ITEM_WEAPON_AXE_TRAINING" + ], + [ + "ITEM_WEAPON_SWORD_TRAINING" + ], + [ + "ITEM_WEAPON_SPEAR_TRAINING" + ], + [ + "ITEM_WEAPON_HAMMER_TRAINING" + ], + [ + "ITEM_WEAPON_MACE_TRAINING" + ] + ], "SHIELD": [ - "ITEM_SHIELD_KITE", - "ITEM_SHIELD_TOWER" - ], + [ + "ITEM_SHIELD_KITE" + ], + [ + "ITEM_SHIELD_TOWER" + ] + ], "GLOVES": [ - "ITEM_GLOVES_MITTENS", - "ITEM_GLOVES_GAUNTLETS", - "ITEM_GLOVES_GLOVES", - "ITEM_GLOVES_MITTENS_CHAIN", - "ITEM_GLOVES_GLOVES_CHAIN", - "ITEM_GLOVES_WRAP" - ], + [ + "ITEM_GLOVES_MITTENS", + "COMMON" + ], + [ + "ITEM_GLOVES_GAUNTLETS", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES", + "COMMON" + ], + [ + "ITEM_GLOVES_MITTENS_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_WRAP", + "FORCED" + ] + ], "HELM": [ - "ITEM_HELM_GREAT", - "ITEM_HELM_KETTLE", - "ITEM_HELM_BASCINET", - "ITEM_HELM_BARBUTE", - "ITEM_HELM_SALLET", - "ITEM_HELM_SKULLCAP", - "ITEM_HELM_COIF", - "ITEM_HELM_CAP", - "ITEM_HELM_CAP_ARMING", - "ITEM_HELM_HOOD" - ], + [ + "ITEM_HELM_GREAT", + "COMMON" + ], + [ + "ITEM_HELM_KETTLE", + "COMMON" + ], + [ + "ITEM_HELM_BASCINET", + "COMMON" + ], + [ + "ITEM_HELM_BARBUTE", + "COMMON" + ], + [ + "ITEM_HELM_SALLET", + "COMMON" + ], + [ + "ITEM_HELM_SKULLCAP", + "COMMON" + ], + [ + "ITEM_HELM_COIF", + "COMMON" + ], + [ + "ITEM_HELM_CAP", + "COMMON" + ], + [ + "ITEM_HELM_CAP_ARMING", + "COMMON" + ], + [ + "ITEM_HELM_HOOD", + "COMMON" + ] + ], "DIGGER": [ - "ITEM_WEAPON_PICK" - ], + [ + "ITEM_WEAPON_PICK" + ] + ], "TOOL": [ - "ITEM_TOOL_NEST_BOX", - "ITEM_TOOL_JUG", - "ITEM_TOOL_LARGE_POT", - "ITEM_TOOL_HIVE", - "ITEM_TOOL_MINECART", - "ITEM_TOOL_WHEELBARROW", - "ITEM_TOOL_STEPLADDER" - ], - "AMMO": { - "ITEM_WEAPON_CROSSBOW_SIEGE": [ - "ITEM_AMMO_QUARREL" - ], - "ITEM_WEAPON_CROSSBOW_BAYONET": [ + [ + "ITEM_TOOL_NEST_BOX" + ], + [ + "ITEM_TOOL_JUG" + ], + [ + "ITEM_TOOL_LARGE_POT" + ], + [ + "ITEM_TOOL_HIVE" + ], + [ + "ITEM_TOOL_MINECART" + ], + [ + "ITEM_TOOL_WHEELBARROW" + ], + [ + "ITEM_TOOL_STEPLADDER" + ] + ], + "AMMO": [ + [ "ITEM_AMMO_BOLTS_BARB" - ], - "ITEM_WEAPON_CROSSBOW": [ + ], + [ "ITEM_AMMO_BOLTS_BARB" + ], + [ + "ITEM_AMMO_QUARREL" ] - }, + ], "PANTS": [ - "ITEM_PANTS_GREAVES_UPPER", - "ITEM_PANTS_BRACCAE", - "ITEM_PANTS_PANTS", - "ITEM_PANTS_CHAUSSE", - "ITEM_PANTS_CHAUSSE_CHAIN", - "ITEM_PANTS_SKIRT", - "ITEM_PANTS_BRAIES", - "ITEM_PANTS_LOINCLOTH" + [ + "ITEM_PANTS_GREAVES_UPPER", + "COMMON" + ], + [ + "ITEM_PANTS_BRACCAE", + "COMMON" + ], + [ + "ITEM_PANTS_PANTS", + "COMMON" + ], + [ + "ITEM_PANTS_CHAUSSE", + "COMMON" + ], + [ + "ITEM_PANTS_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_PANTS_SKIRT", + "COMMON" + ], + [ + "ITEM_PANTS_BRAIES", + "COMMON" + ], + [ + "ITEM_PANTS_LOINCLOTH", + "FORCED" + ] ] - }, + }, "SKULKING": { "SHOES": [ - "ITEM_SHOES_SOCKS" - ], + [ + "ITEM_SHOES_SOCKS", + "FORCED" + ] + ], "ARMOR": [ - "ITEM_ARMOR_TUNIC" - ], + [ + "ITEM_ARMOR_TUNIC", + "FORCED" + ] + ], "WEAPON": [ - "ITEM_WEAPON_SEAX_LONG", - "ITEM_WEAPON_SEAX_NARROW", - "ITEM_WEAPON_SEAX_BROAD", - "ITEM_WEAPON_SEAX_SHORT", - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_SLING_STAFF", - "ITEM_WEAPON_ATLATL", - "ITEM_WEAPON_SWORD_TRAINING", - "ITEM_WEAPON_SPEAR_TRAINING" - ], + [ + "ITEM_WEAPON_SEAX_LONG" + ], + [ + "ITEM_WEAPON_SEAX_NARROW" + ], + [ + "ITEM_WEAPON_SEAX_BROAD" + ], + [ + "ITEM_WEAPON_SEAX_SHORT" + ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_SLING_STAFF" + ], + [ + "ITEM_WEAPON_ATLATL" + ], + [ + "ITEM_WEAPON_SWORD_TRAINING" + ], + [ + "ITEM_WEAPON_SPEAR_TRAINING" + ] + ], "GLOVES": [ - "ITEM_GLOVES_WRAP" - ], + [ + "ITEM_GLOVES_WRAP", + "FORCED" + ] + ], "HELM": [ - "ITEM_HELM_HOOD", - "ITEM_HELM_CAP" - ], - "AMMO": { - "ITEM_WEAPON_ATLATL": [ - "ITEM_AMMO_DARTS" - ], - "ITEM_WEAPON_SLING_STAFF": [ + [ + "ITEM_HELM_HOOD", + "FORCED" + ], + [ + "ITEM_HELM_CAP", + "FORCED" + ] + ], + "AMMO": [ + [ "ITEM_AMMO_BULLET" + ], + [ + "ITEM_AMMO_DARTS" ] - }, + ], "PANTS": [ - "ITEM_PANTS_BRACCAE", - "ITEM_PANTS_LOINCLOTH" + [ + "ITEM_PANTS_BRACCAE", + "FORCED" + ], + [ + "ITEM_PANTS_LOINCLOTH", + "FORCED" + ] ] - }, + }, "EVIL": { "SHOES": [ - "ITEM_SHOES_CHAUSSE_CHAIN", - "ITEM_SHOES_GREAVES_LOWER", - "ITEM_SHOES_BOOTS", - "ITEM_SHOES_SHOES", - "ITEM_SHOES_ANKLE", - "ITEM_SHOES_CARBATINAE", - "ITEM_SHOES_SANDALS", - "ITEM_SHOES_SOCKS" - ], + [ + "ITEM_SHOES_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_SHOES_GREAVES_LOWER", + "UNCOMMON" + ], + [ + "ITEM_SHOES_BOOTS", + "COMMON" + ], + [ + "ITEM_SHOES_SHOES", + "COMMON" + ], + [ + "ITEM_SHOES_ANKLE", + "COMMON" + ], + [ + "ITEM_SHOES_CARBATINAE", + "FORCED" + ], + [ + "ITEM_SHOES_SANDALS", + "COMMON" + ], + [ + "ITEM_SHOES_SOCKS", + "FORCED" + ] + ], "ARMOR": [ - "ITEM_ARMOR_PLATE", - "ITEM_ARMOR_HAUBERK", - "ITEM_ARMOR_JERKIN", - "ITEM_ARMOR_ROBE", - "ITEM_ARMOR_SHIRT", - "ITEM_ARMOR_TUNIC", - "ITEM_ARMOR_CLOAK" - ], + [ + "ITEM_ARMOR_PLATE", + "UNCOMMON" + ], + [ + "ITEM_ARMOR_HAUBERK", + "COMMON" + ], + [ + "ITEM_ARMOR_JERKIN", + "COMMON" + ], + [ + "ITEM_ARMOR_ROBE", + "COMMON" + ], + [ + "ITEM_ARMOR_SHIRT", + "COMMON" + ], + [ + "ITEM_ARMOR_TUNIC", + "COMMON" + ], + [ + "ITEM_ARMOR_CLOAK", + "COMMON" + ] + ], "WEAPON": [ - "ITEM_WEAPON_SEAX_LONG", - "ITEM_WEAPON_SEAX_NARROW", - "ITEM_WEAPON_SEAX_BROAD", - "ITEM_WEAPON_SEAX_SHORT", - "ITEM_WEAPON_AXE_HATCHET", - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_WHIP", - "ITEM_WEAPON_WHIP_HOOKED", - "ITEM_WEAPON_WHIP_KNOTTED", - "ITEM_WEAPON_WHIP_SCOURGE", - "ITEM_WEAPON_MACE_KNOBBED", - "ITEM_WEAPON_MACE_FLAIL", - "ITEM_WEAPON_MACE_FLAIL_SPIKED", - "ITEM_WEAPON_MACE_FLAIL_2H", - "ITEM_WEAPON_HAMMER_MAUL", - "ITEM_WEAPON_SLING_STAFF", - "ITEM_WEAPON_ATLATL", - "ITEM_WEAPON_SWORD_TRAINING", - "ITEM_WEAPON_SPEAR_TRAINING", - "ITEM_WEAPON_AXE_TRAINING", - "ITEM_WEAPON_MACE_TRAINING" - ], + [ + "ITEM_WEAPON_SEAX_LONG" + ], + [ + "ITEM_WEAPON_SEAX_NARROW" + ], + [ + "ITEM_WEAPON_SEAX_BROAD" + ], + [ + "ITEM_WEAPON_SEAX_SHORT" + ], + [ + "ITEM_WEAPON_AXE_HATCHET" + ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_WHIP" + ], + [ + "ITEM_WEAPON_WHIP_HOOKED" + ], + [ + "ITEM_WEAPON_WHIP_KNOTTED" + ], + [ + "ITEM_WEAPON_WHIP_SCOURGE" + ], + [ + "ITEM_WEAPON_MACE_KNOBBED" + ], + [ + "ITEM_WEAPON_MACE_FLAIL" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_SPIKED" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_2H" + ], + [ + "ITEM_WEAPON_HAMMER_MAUL" + ], + [ + "ITEM_WEAPON_SLING_STAFF" + ], + [ + "ITEM_WEAPON_ATLATL" + ], + [ + "ITEM_WEAPON_SWORD_TRAINING" + ], + [ + "ITEM_WEAPON_SPEAR_TRAINING" + ], + [ + "ITEM_WEAPON_AXE_TRAINING" + ], + [ + "ITEM_WEAPON_MACE_TRAINING" + ] + ], "SHIELD": [ - "ITEM_SHIELD_SHIELD", - "ITEM_SHIELD_KITE" - ], + [ + "ITEM_SHIELD_SHIELD" + ], + [ + "ITEM_SHIELD_KITE" + ] + ], "GLOVES": [ - "ITEM_GLOVES_MITTENS", - "ITEM_GLOVES_GAUNTLETS", - "ITEM_GLOVES_GLOVES", - "ITEM_GLOVES_MITTENS_CHAIN", - "ITEM_GLOVES_GLOVES_CHAIN", - "ITEM_GLOVES_WRAP" - ], + [ + "ITEM_GLOVES_MITTENS", + "COMMON" + ], + [ + "ITEM_GLOVES_GAUNTLETS", + "UNCOMMON" + ], + [ + "ITEM_GLOVES_GLOVES", + "COMMON" + ], + [ + "ITEM_GLOVES_MITTENS_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_WRAP", + "FORCED" + ] + ], "HELM": [ - "ITEM_HELM_BARBUTE", - "ITEM_HELM_SALLET", - "ITEM_HELM_SKULLCAP", - "ITEM_HELM_CAP", - "ITEM_HELM_CAP_ARMING", - "ITEM_HELM_HOOD", - "ITEM_HELM_SCARF_HEAD" - ], + [ + "ITEM_HELM_BARBUTE", + "COMMON" + ], + [ + "ITEM_HELM_SALLET", + "COMMON" + ], + [ + "ITEM_HELM_SKULLCAP", + "COMMON" + ], + [ + "ITEM_HELM_CAP", + "COMMON" + ], + [ + "ITEM_HELM_CAP_ARMING", + "COMMON" + ], + [ + "ITEM_HELM_HOOD", + "COMMON" + ], + [ + "ITEM_HELM_SCARF_HEAD", + "COMMON" + ] + ], "DIGGER": [ - "ITEM_WEAPON_PICK" - ], - "AMMO": { - "ITEM_WEAPON_ATLATL": [ - "ITEM_AMMO_DARTS" - ], - "ITEM_WEAPON_SLING_STAFF": [ + [ + "ITEM_WEAPON_PICK" + ] + ], + "AMMO": [ + [ "ITEM_AMMO_BULLET" + ], + [ + "ITEM_AMMO_DARTS" ] - }, + ], "PANTS": [ - "ITEM_PANTS_BRACCAE", - "ITEM_PANTS_LOINCLOTH", - "ITEM_PANTS_PANTS" + [ + "ITEM_PANTS_BRACCAE", + "COMMON" + ], + [ + "ITEM_PANTS_LOINCLOTH", + "FORCED" + ], + [ + "ITEM_PANTS_PANTS", + "COMMON" + ] ] - }, + }, "SUBTERRANEAN_ANIMAL_PEOPLES": { "WEAPON": [ - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_BLOWGUN", - "ITEM_WEAPON_ATLATL" - ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_BLOWGUN" + ], + [ + "ITEM_WEAPON_ATLATL" + ] + ], "SHIELD": [ - "ITEM_SHIELD_BUCKLER" - ], - "AMMO": { - "ITEM_WEAPON_ATLATL": [ - "ITEM_AMMO_DARTS" - ], - "ITEM_WEAPON_BLOWGUN": [ + [ + "ITEM_SHIELD_BUCKLER" + ] + ], + "AMMO": [ + [ "ITEM_AMMO_BLOWDARTS" + ], + [ + "ITEM_AMMO_DARTS" ] - } - }, + ] + }, "PLAINS": { "SHOES": [ - "ITEM_SHOES_SHOES", - "ITEM_SHOES_CHAUSSE_CHAIN", - "ITEM_SHOES_GREAVES_LOWER", - "ITEM_SHOES_ANKLE", - "ITEM_SHOES_BOOTS", - "ITEM_SHOES_SOCKS" - ], + [ + "ITEM_SHOES_SHOES", + "COMMON" + ], + [ + "ITEM_SHOES_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_SHOES_GREAVES_LOWER", + "COMMON" + ], + [ + "ITEM_SHOES_ANKLE", + "FORCED" + ], + [ + "ITEM_SHOES_BOOTS", + "COMMON" + ], + [ + "ITEM_SHOES_SOCKS", + "FORCED" + ] + ], "ARMOR": [ - "ITEM_ARMOR_PLATE", - "ITEM_ARMOR_HAUBERK", - "ITEM_ARMOR_HAUBERGEON", - "ITEM_ARMOR_BRIGANDINE", - "ITEM_ARMOR_BYRNIE", - "ITEM_ARMOR_LAMELLAR", - "ITEM_ARMOR_CLOAK", - "ITEM_ARMOR_SURCOAT", - "ITEM_ARMOR_TABARD", - "ITEM_ARMOR_CAPE", - "ITEM_ARMOR_COAT", - "ITEM_ARMOR_VEST", - "ITEM_ARMOR_DRESS", - "ITEM_ARMOR_ROBE", - "ITEM_ARMOR_SHIRT", - "ITEM_ARMOR_TUNIC", - "ITEM_ARMOR_JERKIN", - "ITEM_ARMOR_AKETON", - "ITEM_ARMOR_GAMBESON" - ], + [ + "ITEM_ARMOR_PLATE", + "COMMON" + ], + [ + "ITEM_ARMOR_HAUBERK", + "COMMON" + ], + [ + "ITEM_ARMOR_HAUBERGEON", + "COMMON" + ], + [ + "ITEM_ARMOR_BRIGANDINE", + "COMMON" + ], + [ + "ITEM_ARMOR_BYRNIE", + "COMMON" + ], + [ + "ITEM_ARMOR_LAMELLAR", + "COMMON" + ], + [ + "ITEM_ARMOR_CLOAK", + "COMMON" + ], + [ + "ITEM_ARMOR_SURCOAT", + "COMMON" + ], + [ + "ITEM_ARMOR_TABARD", + "COMMON" + ], + [ + "ITEM_ARMOR_CAPE", + "COMMON" + ], + [ + "ITEM_ARMOR_COAT", + "COMMON" + ], + [ + "ITEM_ARMOR_VEST", + "COMMON" + ], + [ + "ITEM_ARMOR_DRESS", + "COMMON" + ], + [ + "ITEM_ARMOR_ROBE", + "COMMON" + ], + [ + "ITEM_ARMOR_SHIRT", + "COMMON" + ], + [ + "ITEM_ARMOR_TUNIC", + "COMMON" + ], + [ + "ITEM_ARMOR_JERKIN", + "COMMON" + ], + [ + "ITEM_ARMOR_AKETON", + "COMMON" + ], + [ + "ITEM_ARMOR_GAMBESON", + "COMMON" + ] + ], "WEAPON": [ - "ITEM_WEAPON_SEAX_LONG", - "ITEM_WEAPON_SEAX_NARROW", - "ITEM_WEAPON_SEAX_BROAD", - "ITEM_WEAPON_SEAX_SHORT", - "ITEM_WEAPON_DAGGER_FIGHTING", - "ITEM_WEAPON_DAGGER_RONDEL", - "ITEM_WEAPON_SWORD_RAIDER", - "ITEM_WEAPON_SWORD_ARMING_SHORT", - "ITEM_WEAPON_SWORD_ARMING", - "ITEM_WEAPON_SWORD_ARMING_LONG", - "ITEM_WEAPON_LONGSWORD", - "ITEM_WEAPON_SWORD_GREAT", - "ITEM_WEAPON_SWORD_2H", - "ITEM_WEAPON_SWORD_FALCHION", - "ITEM_WEAPON_AXE_HATCHET", - "ITEM_WEAPON_AXE_RAIDER", - "ITEM_WEAPON_AXE_HAFTED", - "ITEM_WEAPON_MACE", - "ITEM_WEAPON_MACE_MORNINGSTAR", - "ITEM_WEAPON_MACE_FLAIL", - "ITEM_WEAPON_MACE_FLAIL_SPIKED", - "ITEM_WEAPON_MACE_FLANGED", - "ITEM_WEAPON_MACE_FLAIL", - "ITEM_WEAPON_MACE_FLAIL_2H", - "ITEM_WEAPON_MACE_KNOBBED", - "ITEM_WEAPON_MACE_PERNACH", - "ITEM_WEAPON_MACE_GOEDENDAG", - "ITEM_WEAPON_HAMMER_WAR", - "ITEM_WEAPON_HAMMER_HAFTED", - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_PIKE_AWLPIKE", - "ITEM_WEAPON_SPEAR_SPETUM", - "ITEM_WEAPON_SPEAR_BOAR", - "ITEM_WEAPON_SPEAR_HEWING", - "ITEM_WEAPON_PIKE_PIKE", - "ITEM_WEAPON_PIKE_SWORD", - "ITEM_WEAPON_PIKE_GUISARME", - "ITEM_WEAPON_PIKE_HALBERD", - "ITEM_WEAPON_PIKE_VOULGE", - "ITEM_WEAPON_CROSSBOW", - "ITEM_WEAPON_LONGBOW", - "ITEM_WEAPON_ATLATL", - "ITEM_WEAPON_AXE_TRAINING", - "ITEM_WEAPON_SWORD_TRAINING", - "ITEM_WEAPON_SPEAR_TRAINING", - "ITEM_WEAPON_PIKE_TRAINING", - "ITEM_WEAPON_MACE_TRAINING", - "ITEM_WEAPON_HAMMER_TRAINING" - ], + [ + "ITEM_WEAPON_SEAX_LONG" + ], + [ + "ITEM_WEAPON_SEAX_NARROW" + ], + [ + "ITEM_WEAPON_SEAX_BROAD" + ], + [ + "ITEM_WEAPON_SEAX_SHORT" + ], + [ + "ITEM_WEAPON_DAGGER_FIGHTING" + ], + [ + "ITEM_WEAPON_DAGGER_RONDEL" + ], + [ + "ITEM_WEAPON_SWORD_RAIDER" + ], + [ + "ITEM_WEAPON_SWORD_ARMING_SHORT" + ], + [ + "ITEM_WEAPON_SWORD_ARMING" + ], + [ + "ITEM_WEAPON_SWORD_ARMING_LONG" + ], + [ + "ITEM_WEAPON_LONGSWORD" + ], + [ + "ITEM_WEAPON_SWORD_GREAT" + ], + [ + "ITEM_WEAPON_SWORD_2H" + ], + [ + "ITEM_WEAPON_SWORD_FALCHION" + ], + [ + "ITEM_WEAPON_AXE_HATCHET" + ], + [ + "ITEM_WEAPON_AXE_RAIDER" + ], + [ + "ITEM_WEAPON_AXE_HAFTED" + ], + [ + "ITEM_WEAPON_MACE" + ], + [ + "ITEM_WEAPON_MACE_MORNINGSTAR" + ], + [ + "ITEM_WEAPON_MACE_FLAIL" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_SPIKED" + ], + [ + "ITEM_WEAPON_MACE_FLANGED" + ], + [ + "ITEM_WEAPON_MACE_FLAIL" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_2H" + ], + [ + "ITEM_WEAPON_MACE_KNOBBED" + ], + [ + "ITEM_WEAPON_MACE_PERNACH" + ], + [ + "ITEM_WEAPON_MACE_GOEDENDAG" + ], + [ + "ITEM_WEAPON_HAMMER_WAR" + ], + [ + "ITEM_WEAPON_HAMMER_HAFTED" + ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_PIKE_AWLPIKE" + ], + [ + "ITEM_WEAPON_SPEAR_SPETUM" + ], + [ + "ITEM_WEAPON_SPEAR_BOAR" + ], + [ + "ITEM_WEAPON_SPEAR_HEWING" + ], + [ + "ITEM_WEAPON_PIKE_PIKE" + ], + [ + "ITEM_WEAPON_PIKE_SWORD" + ], + [ + "ITEM_WEAPON_PIKE_GUISARME" + ], + [ + "ITEM_WEAPON_PIKE_HALBERD" + ], + [ + "ITEM_WEAPON_PIKE_VOULGE" + ], + [ + "ITEM_WEAPON_CROSSBOW" + ], + [ + "ITEM_WEAPON_LONGBOW" + ], + [ + "ITEM_WEAPON_ATLATL" + ], + [ + "ITEM_WEAPON_AXE_TRAINING" + ], + [ + "ITEM_WEAPON_SWORD_TRAINING" + ], + [ + "ITEM_WEAPON_SPEAR_TRAINING" + ], + [ + "ITEM_WEAPON_PIKE_TRAINING" + ], + [ + "ITEM_WEAPON_MACE_TRAINING" + ], + [ + "ITEM_WEAPON_HAMMER_TRAINING" + ] + ], "SHIELD": [ - "ITEM_SHIELD_BUCKLER", - "ITEM_SHIELD_HEATER" - ], + [ + "ITEM_SHIELD_BUCKLER" + ], + [ + "ITEM_SHIELD_HEATER" + ] + ], "GLOVES": [ - "ITEM_GLOVES_MITTENS", - "ITEM_GLOVES_GAUNTLETS", - "ITEM_GLOVES_GLOVES", - "ITEM_GLOVES_MITTENS_CHAIN", - "ITEM_GLOVES_GLOVES_CHAIN", - "ITEM_GLOVES_WRAP" - ], + [ + "ITEM_GLOVES_MITTENS", + "COMMOND" + ], + [ + "ITEM_GLOVES_GAUNTLETS", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES", + "COMMON" + ], + [ + "ITEM_GLOVES_MITTENS_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_WRAP", + "FORCED" + ] + ], "HELM": [ - "ITEM_HELM_GREAT", - "ITEM_HELM_NASAL", - "ITEM_HELM_KETTLE", - "ITEM_HELM_BASCINET", - "ITEM_HELM_SALLET", - "ITEM_HELM_SKULLCAP", - "ITEM_HELM_COIF", - "ITEM_HELM_COIF_PADDED", - "ITEM_HELM_RAIDER", - "ITEM_HELM_CAP", - "ITEM_HELM_CAP_ARMING", - "ITEM_HELM_HOOD" - ], + [ + "ITEM_HELM_GREAT", + "COMMON" + ], + [ + "ITEM_HELM_NASAL", + "COMMON" + ], + [ + "ITEM_HELM_KETTLE", + "COMMON" + ], + [ + "ITEM_HELM_BASCINET", + "COMMON" + ], + [ + "ITEM_HELM_SALLET", + "COMMON" + ], + [ + "ITEM_HELM_SKULLCAP", + "COMMON" + ], + [ + "ITEM_HELM_COIF", + "COMMON" + ], + [ + "ITEM_HELM_COIF_PADDED", + "COMMON" + ], + [ + "ITEM_HELM_RAIDER", + "COMMON" + ], + [ + "ITEM_HELM_CAP", + "COMMON" + ], + [ + "ITEM_HELM_CAP_ARMING", + "COMMON" + ], + [ + "ITEM_HELM_HOOD", + "COMMON" + ] + ], "DIGGER": [ - "ITEM_WEAPON_PICK" - ], + [ + "ITEM_WEAPON_PICK" + ] + ], "TOOL": [ - "ITEM_TOOL_CAULDRON", - "ITEM_TOOL_LADLE", - "ITEM_TOOL_BOWL", - "ITEM_TOOL_MORTAR", - "ITEM_TOOL_PESTLE", - "ITEM_TOOL_KNIFE_CARVING", - "ITEM_TOOL_KNIFE_BONING", - "ITEM_TOOL_KNIFE_SLICING", - "ITEM_TOOL_KNIFE_MEAT_CLEAVER", - "ITEM_TOOL_FORK_CARVING", - "ITEM_TOOL_NEST_BOX", - "ITEM_TOOL_JUG", - "ITEM_TOOL_LARGE_POT", - "ITEM_TOOL_HIVE", - "ITEM_TOOL_POUCH", - "ITEM_TOOL_WHEELBARROW" - ], - "AMMO": { - "ITEM_WEAPON_CROSSBOW": [ + [ + "ITEM_TOOL_CAULDRON" + ], + [ + "ITEM_TOOL_LADLE" + ], + [ + "ITEM_TOOL_BOWL" + ], + [ + "ITEM_TOOL_MORTAR" + ], + [ + "ITEM_TOOL_PESTLE" + ], + [ + "ITEM_TOOL_KNIFE_CARVING" + ], + [ + "ITEM_TOOL_KNIFE_BONING" + ], + [ + "ITEM_TOOL_KNIFE_SLICING" + ], + [ + "ITEM_TOOL_KNIFE_MEAT_CLEAVER" + ], + [ + "ITEM_TOOL_FORK_CARVING" + ], + [ + "ITEM_TOOL_NEST_BOX" + ], + [ + "ITEM_TOOL_JUG" + ], + [ + "ITEM_TOOL_LARGE_POT" + ], + [ + "ITEM_TOOL_HIVE" + ], + [ + "ITEM_TOOL_POUCH" + ], + [ + "ITEM_TOOL_WHEELBARROW" + ] + ], + "AMMO": [ + [ "ITEM_AMMO_BOLTS" - ], - "ITEM_WEAPON_LONGBOW": [ - "ITEM_AMMO_ARROWS_HUNTING", - "ITEM_AMMO_ARROWS_HBODKIN", - "ITEM_AMMO_ARROWS_WAR", + ], + [ + "ITEM_AMMO_ARROWS_HUNTING" + ], + [ + "ITEM_AMMO_ARROWS_HBODKIN" + ], + [ + "ITEM_AMMO_ARROWS_WAR" + ], + [ "ITEM_AMMO_ARROWS_WBODKIN" - ], - "ITEM_WEAPON_ATLATL": [ + ], + [ "ITEM_AMMO_DARTS_LONG" ] - }, + ], "PANTS": [ - "ITEM_PANTS_GREAVES_UPPER", - "ITEM_PANTS_PANTS", - "ITEM_PANTS_CHAUSSE", - "ITEM_PANTS_BRACCAE", - "ITEM_PANTS_SKIRT_LONG", - "ITEM_PANTS_LOINCLOTH" + [ + "ITEM_PANTS_GREAVES_UPPER", + "COMMON" + ], + [ + "ITEM_PANTS_PANTS", + "COMMON" + ], + [ + "ITEM_PANTS_CHAUSSE", + "COMMON" + ], + [ + "ITEM_PANTS_BRACCAE", + "COMMON" + ], + [ + "ITEM_PANTS_SKIRT_LONG", + "COMMON" + ], + [ + "ITEM_PANTS_LOINCLOTH", + "FORCED" + ] ] - }, + }, "FOREST": { "SHOES": [ - "ITEM_SHOES_CHAUSSE_CHAIN", - "ITEM_SHOES_GREAVES_LOWER", - "ITEM_SHOES_BOOTS", - "ITEM_SHOES_SANDALS", - "ITEM_SHOES_SOCKS" - ], + [ + "ITEM_SHOES_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_SHOES_GREAVES_LOWER", + "COMMON" + ], + [ + "ITEM_SHOES_BOOTS", + "COMMON" + ], + [ + "ITEM_SHOES_SANDALS", + "COMMON" + ], + [ + "ITEM_SHOES_SOCKS", + "FORCED" + ] + ], "ARMOR": [ - "ITEM_ARMOR_BYRNIE", - "ITEM_ARMOR_SCALE", - "ITEM_ARMOR_LAMELLAR", - "ITEM_ARMOR_CLOAK", - "ITEM_ARMOR_CAPE", - "ITEM_ARMOR_COAT", - "ITEM_ARMOR_VEST", - "ITEM_ARMOR_DRESS", - "ITEM_ARMOR_ROBE" - ], + [ + "ITEM_ARMOR_BYRNIE", + "COMMON" + ], + [ + "ITEM_ARMOR_SCALE", + "COMMON" + ], + [ + "ITEM_ARMOR_LAMELLAR", + "COMMON" + ], + [ + "ITEM_ARMOR_CLOAK", + "COMMON" + ], + [ + "ITEM_ARMOR_CAPE", + "COMMON" + ], + [ + "ITEM_ARMOR_COAT", + "COMMON" + ], + [ + "ITEM_ARMOR_VEST", + "COMMON" + ], + [ + "ITEM_ARMOR_DRESS", + "COMMON" + ], + [ + "ITEM_ARMOR_ROBE", + "FORCED" + ] + ], "WEAPON": [ - "ITEM_WEAPON_SWORD_SABER", - "ITEM_WEAPON_SWORD_SCIMITAR", - "ITEM_WEAPON_SWORD_FALCHION", - "ITEM_WEAPON_DAGGER_FIGHTING", - "ITEM_WEAPON_AXE_BARDICHE", - "ITEM_WEAPON_MACE_FLAIL_2H", - "ITEM_WEAPON_PIKE_GLAIVE", - "ITEM_WEAPON_PIKE_SWORD", - "ITEM_WEAPON_SPEAR", - "ITEM_WEAPON_SPEAR_SPETUM", - "ITEM_WEAPON_ATLATL", - "ITEM_WEAPON_BOW", - "ITEM_WEAPON_SWORD_TRAINING", - "ITEM_WEAPON_SPEAR_TRAINING", - "ITEM_WEAPON_MACE_TRAINING", - "ITEM_WEAPON_PIKE_TRAINING" - ], + [ + "ITEM_WEAPON_SWORD_SABER" + ], + [ + "ITEM_WEAPON_SWORD_SCIMITAR" + ], + [ + "ITEM_WEAPON_SWORD_FALCHION" + ], + [ + "ITEM_WEAPON_DAGGER_FIGHTING" + ], + [ + "ITEM_WEAPON_AXE_BARDICHE" + ], + [ + "ITEM_WEAPON_MACE_FLAIL_2H" + ], + [ + "ITEM_WEAPON_PIKE_GLAIVE" + ], + [ + "ITEM_WEAPON_PIKE_SWORD" + ], + [ + "ITEM_WEAPON_SPEAR" + ], + [ + "ITEM_WEAPON_SPEAR_SPETUM" + ], + [ + "ITEM_WEAPON_ATLATL" + ], + [ + "ITEM_WEAPON_BOW" + ], + [ + "ITEM_WEAPON_SWORD_TRAINING" + ], + [ + "ITEM_WEAPON_SPEAR_TRAINING" + ], + [ + "ITEM_WEAPON_MACE_TRAINING" + ], + [ + "ITEM_WEAPON_PIKE_TRAINING" + ] + ], "SHIELD": [ - "ITEM_SHIELD_SHIELD", - "ITEM_SHIELD_BUCKLER" - ], + [ + "ITEM_SHIELD_SHIELD" + ], + [ + "ITEM_SHIELD_BUCKLER" + ] + ], "GLOVES": [ - "ITEM_GLOVES_MITTENS", - "ITEM_GLOVES_GAUNTLETS", - "ITEM_GLOVES_GLOVES", - "ITEM_GLOVES_MITTENS_CHAIN", - "ITEM_GLOVES_GLOVES_CHAIN", - "ITEM_GLOVES_WRAP" - ], + [ + "ITEM_GLOVES_MITTENS", + "COMMON" + ], + [ + "ITEM_GLOVES_GAUNTLETS", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES", + "COMMON" + ], + [ + "ITEM_GLOVES_MITTENS_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_GLOVES_CHAIN", + "COMMON" + ], + [ + "ITEM_GLOVES_WRAP", + "FORCED" + ] + ], "HELM": [ - "ITEM_HELM_NASAL", - "ITEM_HELM_BARBUTE", - "ITEM_HELM_SALLET", - "ITEM_HELM_COIF", - "ITEM_HELM_SKULLCAP", - "ITEM_HELM_HOOD", - "ITEM_HELM_SCARF_HEAD", - "ITEM_HELM_WRAP", - "ITEM_HELM_BANDANA" - ], - "AMMO": { - "ITEM_WEAPON_BOW": [ - "ITEM_AMMO_ARROWS_HUNTING", - "ITEM_AMMO_ARROWS_HBARB", - "ITEM_AMMO_ARROWS_WAR", - "ITEM_AMMO_ARROWS_WBARB" - ], - "ITEM_WEAPON_ATLATL": [ - "ITEM_AMMO_DARTS", + [ + "ITEM_HELM_NASAL", + "COMMON" + ], + [ + "ITEM_HELM_BARBUTE", + "COMMON" + ], + [ + "ITEM_HELM_SALLET", + "COMMON" + ], + [ + "ITEM_HELM_COIF", + "COMMON" + ], + [ + "ITEM_HELM_SKULLCAP", + "COMMON" + ], + [ + "ITEM_HELM_HOOD", + "COMMON" + ], + [ + "ITEM_HELM_SCARF_HEAD", + "COMMON" + ], + [ + "ITEM_HELM_WRAP", + "FORCED" + ], + [ + "ITEM_HELM_BANDANA", + "COMMON" + ] + ], + "AMMO": [ + [ + "ITEM_AMMO_DARTS" + ], + [ "ITEM_AMMO_DARTS_LONG" + ], + [ + "ITEM_AMMO_ARROWS_HUNTING" + ], + [ + "ITEM_AMMO_ARROWS_HBARB" + ], + [ + "ITEM_AMMO_ARROWS_WAR" + ], + [ + "ITEM_AMMO_ARROWS_WBARB" ] - }, + ], "PANTS": [ - "ITEM_PANTS_BRACCAE", - "ITEM_PANTS_GREAVES_UPPER", - "ITEM_PANTS_PANTS", - "ITEM_PANTS_CHAUSSE", - "ITEM_PANTS_CHAUSSE_CHAIN", - "ITEM_PANTS_SKIRT_SHORT", - "ITEM_PANTS_SKIRT", - "ITEM_PANTS_SKIRT_LONG", - "ITEM_PANTS_LOINCLOTH" + [ + "ITEM_PANTS_BRACCAE", + "COMMON" + ], + [ + "ITEM_PANTS_GREAVES_UPPER", + "COMMON" + ], + [ + "ITEM_PANTS_PANTS", + "COMMON" + ], + [ + "ITEM_PANTS_CHAUSSE", + "COMMON" + ], + [ + "ITEM_PANTS_CHAUSSE_CHAIN", + "COMMON" + ], + [ + "ITEM_PANTS_SKIRT_SHORT", + "COMMON" + ], + [ + "ITEM_PANTS_SKIRT", + "COMMON" + ], + [ + "ITEM_PANTS_SKIRT_LONG", + "COMMON" + ], + [ + "ITEM_PANTS_LOINCLOTH", + "FORCED" + ] ] } -} +} \ No newline at end of file diff --git a/scripts/stal/armouryentities.py b/scripts/stal/armouryentities.py index f327aa8..ea068ad 100644 --- a/scripts/stal/armouryentities.py +++ b/scripts/stal/armouryentities.py @@ -8,27 +8,22 @@ print('And so it begins.') -entities = raws.dir(path='raw/armoury')['entity_default'] +armouryraws = raws.dir(root='raw/armoury') + +itemtypes = ('AMMO', 'DIGGER', 'TOOL', 'WEAPON', 'ARMOR', 'PANTS', 'GLOVES', 'SHOES', 'HELM', 'SHIELD') edict = {} -for entity in entities.all(exact_value='ENTITY'): +for entity in armouryraws.allobj('ENTITY'): print('Entity: %s' % entity) - itemtypes = ('AMMO', 'DIGGER', 'TOOL', 'WEAPON', 'ARMOR', 'PANTS', 'GLOVES', 'SHOES', 'HELM', 'SHIELD') edict[entity.args[0]] = {} entitydict = edict[entity.args[0]] - for item in entity.getprop(value_in=itemtypes): - if item.value == 'AMMO': - if item.value not in entitydict: entitydict[item.value] = {} - forweapon = item.get(exact_value='WEAPON', reverse=True).args[0] - if forweapon not in entitydict[item.value]: entitydict[item.value][forweapon] = [] - entitydict[item.value][forweapon].append(item.args[0]) - else: - if item.value not in entitydict: entitydict[item.value] = [] - entitydict[item.value].append(item.args[0]) + for item in entity.allprop(value_in=itemtypes): + if item.value not in entitydict: entitydict[item.value] = [] + entitydict[item.value].append(item.args) print(edict) -with open('armouryentities.json', 'wb') as efile: json.dump(edict, efile) +with open('armouryentities.json', 'wb') as efile: json.dump(edict, efile, indent=4) print('All done!') diff --git a/scripts/stal/pydwarf.armourypack.py b/scripts/stal/pydwarf.armourypack.py index 59e6231..a4d4144 100644 --- a/scripts/stal/pydwarf.armourypack.py +++ b/scripts/stal/pydwarf.armourypack.py @@ -44,133 +44,25 @@ # These are the only item types we care about. armoury_items = [ - 'ITEM_AMMO', - 'ITEM_ARMOR', - 'ITEM_HELM', - 'ITEM_GLOVES' - 'ITEM_PANTS', - 'ITEM_SHIELD', - 'ITEM_SHOES', - 'ITEM_TOOL', - 'ITEM_WEAPON', + 'AMMO', + 'ARMOR', + 'HELM', + 'GLOVES', + 'PANTS', + 'SHIELD', + 'SHOES', + 'TOOL', + 'WEAPON', ] +armoury_item_objects = ['ITEM_%s' % item for item in armoury_items] # These are the items that each entity should have. (JSON courtesy of helper script armouryentities.py.) with open(entities_json, 'rb') as efile: armoury_entities = json.load(efile) -def additemstoraws(dfraws, armouryraws): - pydwarf.log.debug('Building dict to index relevant items in df raws...') - dfitems = {str(token): token for token in dfraws.all(value_in=armoury_items)} - pydwarf.log.debug('Dict dfitems contains %d tokens.' % len(dfitems)) - - # Add new items to raws and edit already-present ones - for filename in armouryraws.files.keys(): - if filename.startswith('item_'): - for armouryitem in armouryraws[filename].all(value_in=armoury_items, args_count=1): - pydwarf.log.debug('Handling armoury item %s...' % armouryitem) - - # Account for names that were changed from the normal DF raws - if armouryitem.args[0] in weird_armoury_names: armouryitem.args[0] = weird_armoury_names[armouryitem.args[0]] - - # Get the tokens belonging to this item - armourytokens = armouryitem.alluntil(until_exact_value=armouryitem.value) - # Get the current item with the same ID (if present) - dfitem = dfitems.get(str(armouryitem)) - - # Replace item properties - if dfitem and armouryitem: - pydwarf.log.debug('Replacing properties of item %s...' % armouryitem) - for removeitem in dfitem.alluntil(until_re_value='ITEM_.+'): removeitem.remove() - dfitem.add(tokens=raws.token.copy(armourytokens)) - del dfitems[str(armouryitem)] - - # Add new item - elif armourytokens: - pydwarf.log.debug('Adding new item %s...' % armouryitem) - if filename not in dfraws.files: dfraws.add(filename) - armouryitem.prefix = '\n\n' # Makes outputted raws a bit neater - dfraws[filename].add(token=armouryitem) - dfraws[filename].add(tokens=raws.token.copy(armourytokens)) - - # This really shouldn't happen, but check for it anyway - else: - pydwarf.log.error('Found no tokens belonging to armoury item %s.' % armouryitem) - -def additemstoents(dfraws, armouryraws, remove_entity_items): - # Screw around with which items are allowed for which entities - for entityname, aentity in armoury_entities.iteritems(): - dfentity = dfraws.get(exact_value='ENTITY', exact_args=[entityname]) - entityitems = {} - if dfentity: - pydwarf.log.debug('Handling entity %s...' % dfentity) - - # Maintain this dict because it makes ammo easier to add - weapons = {} - - # If we're removing items, just remove all the present ones in one go before adding things back - if remove_entity_items: - for itemtoken in dfentity.alluntil(value_in=armoury_entities, args_count=1, until_exact_value='ENTITY'): itemtoken.remove() - - # Time to add the items! - for itemtype, items in aentity.iteritems(): - if itemtype != 'AMMO': - for itemname in items: - # Account for names that were changed from the normal DF raws - if itemname in weird_armoury_names: itemname = weird_armoury_names[itemname] - # Add the token if it isn't there already - dftoken = None if remove_entity_items else dfentity.getuntil(exact_value=itemtype, exact_args=[itemname], until_exact_value='ENTITY') - if remove_entity_items or not dftoken: dftoken = dfentity.add(raws.token(value=itemtype, args=[itemname])) - if itemtype == 'WEAPON': weapons[itemname] = dftoken - - # Now add the ammunition - if 'AMMO' in aentity: - for weaponname, ammos in aentity['AMMO'].iteritems(): - weapontoken = weapons.get(weaponname) - if not weapontoken: weapontoken = dfentity.get(exact_value='WEAPON', exact_args=[weaponname]) - if weapontoken: - ammotokens = {token.args[0]: token for token in weapontoken.alluntil(exact_value='AMMO', args_count=1, until_except_value='AMMO')} - for addammo in ammos: - if addammo not in ammotokens: weapontoken.add(raws.token(value='AMMO', args=[addammo])) - else: - pydwarf.log.error('Failed to add ammo %s to weapon %s.' % ammos, weaponname) - - else: - pydwarf.log.error('Failed to find entity %s for editing.' % entityname) - -def addreactions(dfraws, armouryraws): - # Add raws file containing new reactions - armouryreactions = armouryraws['reaction_armoury'] - if armouryreactions: - if 'stal_reaction_armoury' not in dfraws.files: - armouryreactions.header = 'stal_reaction_armoury' - dfraws.files['stal_reaction_armoury'] = armouryreactions - else: - pydwarf.log.error('DF raws already contain stal_reaction_armory.') - else: - pydwarf.log.error('Couldn\'t load reaction_armoury raws file for reading.') - -def removeattacks(dfraws, remove_attacks, remove_attacks_from): - # Removes e.g. bite and scratch attacks from e.g. dwarves, humans, and elves - if remove_attacks is not None and remove_attacks_from is not None: - for species in remove_attacks_from: - creaturetoken = dfraws.get(exact_value='CREATURE', exact_args=[species]) - if creaturetoken: - for attacktype in remove_attacks: - attacktokens = creaturetoken.alluntil(exact_value='ATTACK', exact_arg=((0, attacktype),), until_exact_value='CREATURE') - for attacktoken in attacktokens: - subtokens = attacktoken.alluntil(until_re_value='(?!ATTACK_).+') - for subtoken in subtokens: subtoken.remove() - attacktoken.remove() - pydwarf.log.debug('Removed attack %s from creature %s.' % (attacktype, species)) - else: - pydwarf.log.error('Couldn\'t find creature %s to remove bite and scratch attacks from.' % species) - - - @pydwarf.urist( - name = 'stal.armoury', + name = 'stal.armoury.items', version = '1.0.0', author = ('Stalhansch', 'Sophie Kirschner'), description = 'Attempts to improve the balance and realism of combat.', @@ -179,24 +71,124 @@ def removeattacks(dfraws, remove_attacks, remove_attacks_from): entities should be removed from those entities or not. If you're also using other mods that make changes to weapons and armour and such it may be desireable to set this flag to False. Otherwise, for best results, the flag should be set to True. - Defaults to True''', - 'remove_attacks': '''Removes these attacks from species listed in remove_attacks_from. - Defaults to scratch and bite.''', - 'remove_attacks_from': '''If set to True, specified remove_attacks are removed - from the species in the list to improve combat balancing. If set to None those - attacks will not be touched. Defaults to dwarves, humans, and elves.''' + Defaults to True''' }, compatibility = pydwarf.df_revision_range('0.40.14', '0.40.24') ) -def armourypack(dfraws, remove_entity_items=True, remove_attacks=('SCRATCH', 'BITE'), remove_attacks_from=('DWARF', 'HUMAN', 'ELF')): +def armoury(df, remove_entity_items=True): try: + pydwarf.log.debug('Loading armoury raws from %s.' % armoury_dir) armouryraws = raws.dir(root=armoury_dir, log=pydwarf.log) except: return pydwarf.failure('Unable to load armoury raws.') + + # Persist a list of armoury item tokens because we're going to be needing them for a few things + armouryitemtokens = armouryraws.allobj(type_in=armoury_item_objects) + + # Rename items in the armoury raws in accordance with a manually compiled list of naming oddities + for item in armouryitemtokens.all(arg_in=(0, weird_armoury_names)): + item.args[0] = weird_armoury_names[item.args[0]] + + # Remove any existing items in the raws which share an id with the items about to be added + pydwarf.log.debug('Removing obsolete items.') + df.removeallobj(type_in=armoury_item_objects, id_in=[token.arg() for token in armouryitemtokens]) - additemstoraws(dfraws, armouryraws) - additemstoents(dfraws, armouryraws, remove_entity_items) - addreactions(dfraws, armouryraws) - removeattacks(dfraws, remove_attacks, remove_attacks_from) + # Look for files made empty as a result (of which there should be a few) and remove them + removedfiles = [] + for file in df.files.values(): + if isinstance(file, raws.file) and len(file) <= 1: + pydwarf.log.debug('Removing emptied file %s.' % file) + file.remove() + removedfiles.append(file) + # Get a list of entity tokens corresponding to the relevant names + entitytokens = [df.getobj('ENTITY', entity) for entity in armoury_entities] + + # If remove_entity_items is set to True, remove all existing tokens which permit relevant items + # Otherwise, just remove the ones with conflict with those about to be added + for entitytoken in entitytokens: + pydwarf.log.debug('Removing permission tokens from %s.' % entitytoken) + entitytoken.removeallprop( + value_in = armoury_items, + arg_in = ( + None if remove_entity_items else (0, [token.args[0] for token in armouryitemtokens]) + ) + ) + + # Now add all the armoury raw files that have items in them + try: + for file in armouryraws.iterfiles(): + if file.kind == 'raw' and file.name.startswith('item_'): + pydwarf.log.debug('Adding file %s to raws.' % file) + copy = file.copy() + copy.loc = 'raw/objects' + copy.name += '_armoury_stal' + df.add(copy) + except: + pydwarf.log.exception('Encountered exception.') + return pydwarf.failure('Failed to add armoury item raws.') + + # Add new permitted item tokens + try: + for entitytoken in entitytokens: + pydwarf.log.debug('Permitting items for %s.' % entitytoken) + for itemtype, items in armoury_entities[entitytoken.arg()].iteritems(): + for item in items: + entitytoken.add( + raws.token( + value = itemtype, + args = [weird_armoury_names.get(item[0], item[0])] + item[1:] + ) + ) + except: + pydwarf.log.exception('Encountered exception.') + return pydwarf.failure('Failed to permit items for entities.') + + # And add the new reactions + try: + pydwarf.log.debug('Adding new reactions.') + reactions = armouryraws['reaction_armoury'].copy() + reactions.loc = 'raw/objects' + reactions.name = 'reaction_armoury_stal' + response = pydwarf.urist.getfn('pineapple.easypatch')( + df, + reactions, + permit_entities = armoury_entities.keys() + ) + if not response: return response + except: + pydwarf.log.exception('Encountered exception.') + return pydwarf.failure('Failed to add new reactions.') + + # All done! return pydwarf.success() + + + +@pydwarf.urist( + name = 'stal.armoury.attacks', + version = '1.0.0', + author = ('Stalhansch', 'Sophie Kirschner'), + description = '''Removes attacks from creatures. By default, as a way to improve balance in + combat, scratch and bite attacks are removed from dwarves, humans, and elves.''', + arguments = { + 'remove_attacks': '''Removes these attacks from species listed in remove_attacks_from. + Defaults to scratch and bite.''', + 'remove_attacks_from': '''If set to True, specified remove_attacks are removed + from the species in the list to improve combat balancing. If set to None those + attacks will not be touched. Defaults to dwarves, humans, and elves.''' + }, + compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) +) +def removeattacks(df, remove_attacks=('SCRATCH', 'BITE'), remove_attacks_from=('DWARF', 'HUMAN', 'ELF')): + removed = 0 + for creature in df.allobj(type='CREATURE', id_in=remove_attacks_from): + for attack in creature.allprop(exact_value='ATTACK', arg_in=((0, remove_attacks),)): + pydwarf.log.debug('Removing attack %s from creature %s.' % (attack, creature)) + for token in attack.alluntil(until_re_value='(?!ATTACK_).+'): token.remove() + attack.remove() + removed += 1 + if removed: + return pydwarf.success('Removed %d attacks from %d creatures.' % (removed, len(remove_attacks_from))) + else: + return pydwarf.failure('Removed no attacks from creatures.') diff --git a/scripts/stal/readme.md b/scripts/stal/readme.md new file mode 100644 index 0000000..18abdd6 --- /dev/null +++ b/scripts/stal/readme.md @@ -0,0 +1,72 @@ +# Stal's Armoury Pack + +Created by Stalhansch. (www.bay12forums.com/smf/index.php?topic=142993.0) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +## Introduction + +A couple of years ago I became rather obsessed with understanding the Dwarf Fortress combat system. I was often annoyed by how armour didn't have as much of an impact in battle as it should. Swords and axes were prone to severing limbs straight through and spears easily pierced the entirety of legs, even when made of the same material as the armour they were seemingly ignoring. I was further bothered by ranged being the end-all of combat more often than not. + +This led me to many a night of searches and inspections of raws from several different mods, trying to figure out how they attempted to solved this perennial problem of mine. I ultimately stumbled across Lucelle's mod. After playing around with Lucelle's mod, I was inspired to try and fix the problem myself. + +This small project is the result of almost 2 years of tweaking and too many hours spent testing in the Arena. I think it's finally come to a point where it is as balanced and sensical as it's going to be with only myself to scrutinise (One of my primary goals was to have weapons behave as they would in real life, but this is Dwarf Fortress and there will be anomalies). Of course, my idea of balance is only my own, and this is what has brought me to release this project to the public. I invite you to either help me improve this mod with critique and suggestions or simply enjoy it any other way you see fit. + +## Scripts + +### stal.armoury.attacks + +Removes the ridiculously effective bite and scratch attacks from humans, dwarves and elves. + +### stal.armoury.items + +Adds new items to make combat more balanced and varied. + +#### Weapons + +**Diggers:** Pickaxe, Mattock. + +**Daggers:** Short seax, Rondel dagger, Fighting knife. + +**One-handed swords:** Long seax, Narrow seax, Broad seax, Short arming sword, Arming sword, Long arming sword, Sea-raider sword, Saber, Scimitar, Falchion. + +**Two-handed swords:** Bastard sword, Great sword, Two-handed sword. + +**One-handed axes:** Hatchet, Adze, Battle axe, Sea-raider axe, Axehammer. + +**Two-handed axes:** Hafted sea-raider axe, Bardiche. + +**One-handed maces:** Round mace, Knobbed mace, Flanged mace, Pernach, Morningstar, Flail, Spiked flail. + +**Two-handed maces:** Hafted flail, Goedendag. + +**One-handed hammers:** War hammer, War pick. + +**Two-handed hammers:** Hafted war hammer, Maul. + +**Lashes:** Whip, Hooked whip, Knotted whip, Scourge. + +**Spears:** Spear, Boar spear, Partisan, Hewing spear. + +**Stabbing pikes:** Awl pike, Pike, Guisarme. + +**Slashing pikes:** Glaive, Sword staff, Halberd, Voulge. + +**Ranged:** Crossbow, Bayoneted crossbow, Siege crossbow, Recurve bow, Longbow, Blowgun, Atlatl, Amentum, Sling, Staff sling. + +**Ammo:** Bolt, Barbed bolt, Quarrel, Hunting arrow, Barbed hunting arrow, Bodkin hunting arrow, War arrow, Barbed war arrow, Bodkin war arrow, Dart, Long dart, Javelin, Bullet, Blowdart. + +**Training weapons:** Training axe, Training sword, Training spear, Training hammer, Training mace, Training pike. + +#### Attire + +**Armor:** Breastplate, Scale armor, Lamellar armor, Brigandine, Hauberk, Byrnie, Haubergeon, Armor, Gambeson, Aketon, Cloak, Surcoat, Tabard, Cape, Coat, Robe, Dress, Vest, Shirt, Tunic. + +**Helmets:** Great helm, Bascinet, Sea-raider helmet, Sallet, Barbute, Kettle helmet, Nasal helmet, Skull cap, Coif, Padded coif, Hood, Arming cap, Cap, Headscarf, Couvrechef, Head wrap. + +**Gloves:** Gauntlet, Mail glove, Mail mitten, Mitten, Glove, Hand wrap. + +**Shoes:** Lower greave, Lower chausse, Boot, Turnshoe, Carbatina, Ankle boot, Caliga, Leg wrap. + +**Pants:** Upper greaves, Upper chausses, Hose, Trousers, Braccae, Short skirt, Long skirt, Braies, Loincloth. + +**Shields:** Buckler, Round shield, Heater shield, Kite shield, Tower shield. diff --git a/scripts/stal/readme.txt b/scripts/stal/readme.txt deleted file mode 100644 index ba3ff37..0000000 --- a/scripts/stal/readme.txt +++ /dev/null @@ -1,15 +0,0 @@ -Armoury Pack v1.8 created by Stalhansch. (www.bay12forums.com/smf/index.php?topic=142993.0) -Armoury Pack v1.8 ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) - -A couple of years ago I became rather obsessed with understanding the Dwarf Fortress combat system. I was often annoyed by how armour didn't have as much of an impact in battle as it should. Swords and axes were prone to severing limbs straight through and spears easily pierced the entirety of legs, even when made of the same material as the armour they were seemingly ignoring. I was further bothered by ranged being the end-all of combat more often than not. - -This led me to many a night of searches and inspections of raws from several different mods, trying to figure out how they attempted to solved this perennial problem of mine. I ultimately stumbled across Lucelle's mod. After playing around with Lucelle's mod, I was inspired to try and fix the problem myself. - -This small project is the result of almost 2 years of tweaking and too many hours spent testing in the Arena. I think it's finally come to a point where it is as balanced and sensical as it's going to be with only myself to scrutinise (One of my primary goals was to have weapons behave as they would in real life, but this is Dwarf Fortress and there will be anomalies). Of course, my idea of balance is only my own, and this is what has brought me to release this project to the public. I invite you to either help me improve this mod with critique and suggestions or simply enjoy it any other way you see fit. - -v1.8 Changelog: -- Fixed some typos. -- Some weapons with only one attack option no longer specify with which part of the weapon the attack is executed. -- Added [CHAIN_METAL_TEXT] to some items of armour that were lacking it. -- Renamed civilian "chausses" to "hose" for better distinction between the cloth and armour variants. -- Renamed and tweaked both shoe-type chausses and pants-type chausses to fit in the same manner as greaves. From 751b17fb1144dc5ea3c45e5594a29d9b5faf5d24 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 11:57:35 -0400 Subject: [PATCH 58/95] Fixed issues related to calling getobj or allobj without any types --- raws/queryable.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/raws/queryable.py b/raws/queryable.py index 2d2ba8c..025dfc3 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -479,16 +479,19 @@ def getobjheadername(self, type): if type is None: return objects.headers(version) else: - return objects.headerforobject(type, version) + return (objects.headerforobject(type, version),) - def headersfortype(type=None, type_in=None): - headers = set() + def headersfortype(self, type=None, type_in=None): + if type or (type_in is None): + headers = self.getobjheaders(type) + else: + headers = [] if type_in: - for type in type_in: headers.update(self.getobjheaders(type)) - if type: - headers.update(self.getobjheaders(type)) + for itertype in type_in: + for header in self.getobjheaders(itertype): + if not any(header is h for h in headers): headers.append(header) return headers - + def removeobj(self, *args, **kwargs): obj = self.getobj(*args, **kwargs) if obj: obj.removeselfandprops() @@ -521,9 +524,12 @@ def getobj(self, pretty=None, type=None, exact_id=None, type_in=None, re_id=None ''' type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) - for objecttoken in self.headersfortype(type, type_in): + headers = self.headersfortype(type, type_in) + if type is None and type_in is None: type_in = objects.objects() + for objecttoken in headers: obj = objecttoken.get( exact_value = type, + value_in = type_in, exact_args = (exact_id,) if exact_id else None, re_args = (re_id,) if re_id else None, arg_in = ((0, id_in),) if id_in else None, @@ -557,9 +563,12 @@ def allobj(self, pretty=None, type=None, exact_id=None, type_in=None, re_id=None type, exact_id = rawsqueryableobj.objpretty(pretty, type, exact_id) results = rawstokenlist() - for objecttoken in self.headersfortype(type, type_in): + headers = self.headersfortype(type, type_in) + if type is None and type_in is None: type_in = objects.objects() + for objecttoken in headers: for result in objecttoken.all( exact_value = type, + value_in = type_in, exact_args = (exact_id,) if exact_id else None, re_args = (re_id,) if re_id else None, arg_in = ((0, id_in),) if id_in else None, From 7c048643d9a53d4ee4aec9d00e1d104fddf1a5a3 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Fri, 3 Jul 2015 11:58:28 -0400 Subject: [PATCH 59/95] Removed particularly noisy logging statement from scripts __init__ --- scripts/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/__init__.py b/scripts/__init__.py index 4154d7c..ff54b1d 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -14,7 +14,6 @@ path = os.path.join(root, filename) modulename = '.'.join(os.path.basename(filename).split('.')[1:-1]) if filename.endswith('.py') and filename.startswith('pydwarf.'): - pydwarf.log.debug('Loading script %s from %s...' % (modulename, path)) try: with open(path, 'U') as modulefile: module = imp.load_module(modulename, modulefile, path, ('.py', 'U', imp.PY_SOURCE)) From d95747c905f3c41ff66941f21b318d19f4329b52 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 10:19:05 -0400 Subject: [PATCH 60/95] Removed pineapple.utils.addreaction, replaced calls to it with calls to addobject. --- .../pineapple/pydwarf.bauxitetoaluminum.py | 21 ++++++++------- scripts/pineapple/pydwarf.boneflux.py | 25 +++++++++-------- scripts/pineapple/pydwarf.castanvil.py | 23 ++++++++-------- scripts/pineapple/pydwarf.utils.py | 27 ------------------- scripts/pineapple/pydwarf.woodmechanisms.py | 19 ++++++------- scripts/pkdawson/pydwarf.vegan.py | 3 ++- 6 files changed, 46 insertions(+), 72 deletions(-) diff --git a/scripts/pineapple/pydwarf.bauxitetoaluminum.py b/scripts/pineapple/pydwarf.bauxitetoaluminum.py index 876d276..a5a58c3 100644 --- a/scripts/pineapple/pydwarf.bauxitetoaluminum.py +++ b/scripts/pineapple/pydwarf.bauxitetoaluminum.py @@ -2,14 +2,7 @@ -alumtobaux_reaction = ''' - [NAME:smelt aluminum from bauxite] - [SMELTER] - [REAGENT:1:STONE:NO_SUBTYPE:STONE:BAUXITE] - [REAGENT:1:STONE:NO_SUBTYPE:STONE:CRYOLITE] - [PRODUCT:100:1:BAR:NO_SUBTYPE:METAL:ALUMINUM] - [FUEL] -''' +alumtobaux_reaction = default_entities = ['MOUNTAIN'] @@ -47,10 +40,18 @@ def bauxitetoaluminum(df, aluminum_value=0.75, entities=default_entities, add_to return pydwarf.failure() # Add the reaction - return pydwarf.urist.getfn('pineapple.utils.addreaction')( + return pydwarf.urist.getfn('pineapple.utils.addobject')( df, + type = 'REACTION', id = 'SMELT_BAUXITE_ALUMINUM_PINEAPPLE', - tokens = alumtobaux_reaction, + tokens = ''' + [NAME:smelt aluminum from bauxite] + [SMELTER] + [REAGENT:1:STONE:NO_SUBTYPE:STONE:BAUXITE] + [REAGENT:1:STONE:NO_SUBTYPE:STONE:CRYOLITE] + [PRODUCT:100:1:BAR:NO_SUBTYPE:METAL:ALUMINUM] + [FUEL] + ''', add_to_file = add_to_file, permit_entities = entities ) diff --git a/scripts/pineapple/pydwarf.boneflux.py b/scripts/pineapple/pydwarf.boneflux.py index e9296f5..f241983 100644 --- a/scripts/pineapple/pydwarf.boneflux.py +++ b/scripts/pineapple/pydwarf.boneflux.py @@ -2,17 +2,6 @@ -boneflux_reaction = ''' - [NAME:%(name)s] - [BUILDING:KILN:NONE] - [REAGENT:bone:%(bones)s:NONE:NONE:NONE:NONE] - [USE_BODY_COMPONENT] - [ANY_BONE_MATERIAL] - [PRODUCT:100:1:BOULDER:NONE:INORGANIC:%(product)s] - [FUEL] - [SKILL:SMELT] -''' - default_product_id = 'CALCITE' default_reaction_name = 'make calcite from bones' @@ -41,10 +30,20 @@ compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) ) def boneflux(df, product_id=default_product_id, reaction_name=default_reaction_name, bone_count=default_bone_count, entities=default_entities, add_to_file=default_file): - return pydwarf.urist.getfn('pineapple.utils.addreaction')( + return pydwarf.urist.getfn('pineapple.utils.addobject')( df, + type = 'REACTION', id = 'BONE_TO_FLUX_PINEAPPLE', - tokens = boneflux_reaction % { + tokens = ''' + [NAME:%(name)s] + [BUILDING:KILN:NONE] + [REAGENT:bone:%(bones)s:NONE:NONE:NONE:NONE] + [USE_BODY_COMPONENT] + [ANY_BONE_MATERIAL] + [PRODUCT:100:1:BOULDER:NONE:INORGANIC:%(product)s] + [FUEL] + [SKILL:SMELT] + ''' % { 'name': reaction_name, 'product': product_id, 'bones': bone_count diff --git a/scripts/pineapple/pydwarf.castanvil.py b/scripts/pineapple/pydwarf.castanvil.py index 2f5a681..7c9a96b 100644 --- a/scripts/pineapple/pydwarf.castanvil.py +++ b/scripts/pineapple/pydwarf.castanvil.py @@ -2,16 +2,7 @@ -cast_anvil_reaction = ''' - [NAME:cast iron anvil] - [BUILDING:SMELTER:NONE] - [REAGENT:A:%d:BAR:NO_SUBTYPE:METAL:IRON] - [PRODUCT:100:1:ANVIL:NONE:METAL:IRON] - [FUEL] - [SKILL:SMELT] -''' - -default_entities = ['MOUNTAIN'] +default_entities = 'MOUNTAIN' default_anvil_cost = 5 # Cost in iron bars @@ -35,10 +26,18 @@ ) def castanvil(df, anvil_cost=default_anvil_cost, entities=default_entities, add_to_file=default_file): # Super easy using pineapple.utils - return pydwarf.urist.getfn('pineapple.utils.addreaction')( + return pydwarf.urist.getfn('pineapple.utils.addobject')( df, + type = 'REACTION', id = 'CAST_IRON_ANVIL_PINEAPPLE', - tokens = cast_anvil_reaction % (anvil_cost * 150), + tokens = ''' + [NAME:cast iron anvil] + [BUILDING:SMELTER:NONE] + [REAGENT:A:%d:BAR:NO_SUBTYPE:METAL:IRON] + [PRODUCT:100:1:ANVIL:NONE:METAL:IRON] + [FUEL] + [SKILL:SMELT] + ''' % (anvil_cost * 150), add_to_file = add_to_file, permit_entities = entities ) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 7bddfac..4adfb9c 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -30,33 +30,6 @@ def addtoentity(df, entities, tokens): -@pydwarf.urist( - name = 'pineapple.utils.addreaction', - version = '1.0.1', - author = 'Sophie Kirschner', - description = '''A simple utility script which adds a single reaction.''', - arguments = { - 'id': 'ID of the reaction to add.', - 'tokens': 'The tokens that should be added immediately after the REACTION:ID token.', - 'add_to_file': 'Adds the reaction to this file. Defaults to reaction_custom.', - 'permit_entities': '''An iterable containing entities to which to add - PERMITTED_REACTION tokens to. When set to None, no such tokens will be added.''' - }, - compatibility = '.*' -) -def addreaction(df, id, tokens, add_to_file='reaction_custom', permit_entities=None): - if permit_entities is not None and (not addtoentity(df, permit_entities, 'PERMITTED_REACTION:%s' % id).success): - return pydwarf.failure('Failed to add permitted reactions to entites.') - else: - if df.getobj(type='REACTION', exact_id=id): - return pydwarf.failure('Reaction %s already exists.' % id) - else: - rfile = df.getfile(add_to_file, create='OBJECT:REACTION') - rfile.add(raws.token(value='REACTION', args=[id], prefix='\n\n')).add(tokens) - return pydwarf.success('Added reaction %s to file %s and entities %s.' % (id, add_to_file, permit_entities)) - - - @pydwarf.urist( name = 'pineapple.utils.objecttokens', version = '1.0.0', diff --git a/scripts/pineapple/pydwarf.woodmechanisms.py b/scripts/pineapple/pydwarf.woodmechanisms.py index 5264758..9f380f1 100644 --- a/scripts/pineapple/pydwarf.woodmechanisms.py +++ b/scripts/pineapple/pydwarf.woodmechanisms.py @@ -2,13 +2,7 @@ -wood_mechanisms_reaction = ''' - [NAME:craft wooden mechanisms] - [BUILDING:CRAFTSMAN:NONE] - [REAGENT:A:%d:WOOD:NONE:NONE:NONE] - [PRODUCT:100:1:TRAPPARTS:NONE:GET_MATERIAL_FROM_REAGENT:A:NONE] - [SKILL:MECHANICS] -''' +wood_mechanisms_reaction = default_log_count = 1 @@ -32,10 +26,17 @@ compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) ) def woodmechanisms(df, log_count=default_log_count, entities=default_entities, add_to_file=default_file): - return pydwarf.urist.getfn('pineapple.utils.addreaction')( + return pydwarf.urist.getfn('pineapple.utils.addobject')( df, + type = 'REACTION', id = 'WOODEN_MECHANISMS_PINEAPPLE', - tokens = wood_mechanisms_reaction % log_count, + tokens = ''' + [NAME:craft wooden mechanisms] + [BUILDING:CRAFTSMAN:NONE] + [REAGENT:A:%d:WOOD:NONE:NONE:NONE] + [PRODUCT:100:1:TRAPPARTS:NONE:GET_MATERIAL_FROM_REAGENT:A:NONE] + [SKILL:MECHANICS] + ''' % log_count, add_to_file = add_to_file, permit_entities = entities ) diff --git a/scripts/pkdawson/pydwarf.vegan.py b/scripts/pkdawson/pydwarf.vegan.py index d2ae836..1b2b3ed 100644 --- a/scripts/pkdawson/pydwarf.vegan.py +++ b/scripts/pkdawson/pydwarf.vegan.py @@ -81,11 +81,12 @@ def format_lua_content(content, labors): ) def vegan(df, labors=default_labors, lua_file=default_lua_file, auto_run=False, entities=default_entities, add_to_file=default_file): # Add the reactions - addreaction = pydwarf.urist.getfn('pineapple.utils.addreaction') + addreaction = pydwarf.urist.getfn('pineapple.utils.addobject') for reactionid, reactioncontent in vegan_reactions.iteritems(): pydwarf.log.debug('Adding reaction %s.' % reactionid) response = addreaction( df, + type = 'REACTION', id = reactionid, tokens = reactioncontent, add_to_file = add_to_file, From a75b5681373eeb6a9467b0359b7bfdc7ecd198d0 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 10:19:52 -0400 Subject: [PATCH 61/95] Updated pineapple.greensteel to use easypatch instead of addtoentity --- scripts/pineapple/pydwarf.greensteel.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/scripts/pineapple/pydwarf.greensteel.py b/scripts/pineapple/pydwarf.greensteel.py index ebe10c6..6eac8b6 100644 --- a/scripts/pineapple/pydwarf.greensteel.py +++ b/scripts/pineapple/pydwarf.greensteel.py @@ -4,14 +4,8 @@ greendir = pydwarf.rel(__file__, 'raw/greensteel') -added_reactions = ( - 'GREEN_STEEL_MAKING_ADAMANT_PINEAPPLE', - 'GREEN_STEEL_MAKING_ADMANTINE_PINEAPPLE', -) - - -default_entities = ['MOUNTAIN'] +default_entities = 'MOUNTAIN' @@ -30,13 +24,9 @@ ) def greensteel(df, entities=default_entities): # Add greensteel raws - try: - df.add(path=greendir, loc='raw/objects') - return pydwarf.urist.getfn('pineapple.utils.addtoentity')( - df, - entities = entities, - tokens = [raws.token(value='PERMITTED_REACTION', arg=reaction) for reaction in added_reactions] - ) - except: - pydwarf.log.exception('Failed to add greensteel raws.') - return pydwarf.failure() + return pydwarf.urist.getfn('pineapple.easypatch')( + df, + files = greendir, + loc = 'raw/objects', + permit_entities = entities + ) From 73f4d56d9a1cdf3183b4ed22f6d70423326e6f15 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 10:20:27 -0400 Subject: [PATCH 62/95] Updated pineapple scripts readme --- scripts/pineapple/readme.md | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/scripts/pineapple/readme.md b/scripts/pineapple/readme.md index 8ffce6e..54a6215 100644 --- a/scripts/pineapple/readme.md +++ b/scripts/pineapple/readme.md @@ -38,6 +38,20 @@ Can be used to merge and apply changes made to modified raws. Attempting to appl Adds discipline as a natural skill to creatures that shouldn't be running away so easily. Creatures given bonuses include civilized creatures, trainable creatures, evil creatures, megabeasts, and more. +## easypatch + +Add a file or a bunch of tokens and the script handles entity permissions all on its own. Good for adding small mods, for example: + +``` json +"pineapple.easypatch" { + "args": { + "files": "path/to/raws", + "loc": "raw/objects", + "permit_entities": "MOUNTAIN" + } +} +``` + ## flybears A simple example script which adds a `[FLIER]` tag to all female vanilla bears. @@ -94,15 +108,31 @@ With default settings it becomes possible to craft items using the normally unco ### addtoentity -A utility script intended for adding things like `[PERMITTED_REACTION]` to entities. +Utility script intended for adding things like `[PERMITTED_REACTION:ID]` to entities. -### addreaction +### objecttokens -A utility script which adds a reaction as well as `[PERMITTED_REACTION]` tokens to entities. +Utility script which abstracts adding tokens to or removing them from objects, either all at once or given an iterable containing object IDs. -### objecttokens +### addhack + +Utility script for adding a DFHack script. It can also add a line to dfhack.init to automatically run the script when the game starts. + +### addobject + +Utility script adds an object and handles permitting reactions and buildings and such with entities. + +### addobject + +Utility script adds multiple objects using pineapple.utils.addobject. + +### permitobject + +Utility script adds tokens like `[PERMITTED_REACTION:ID]` to entities given an object. + +### permitobjects -A utility script which abstracts adding tokens to or removing them from objects, either all at once or given an iterable containing object IDs. +Utility script permits multiple objects using pineapple.utils.permitobject. ## woodmechanisms From dfee80ab2c92df0ce73b551ed6ceb8b9dbba2a96 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:14:34 -0400 Subject: [PATCH 63/95] Added a simple tutorial for setting up and running PyDwarf --- docs/config.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/config.md diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..557a35d --- /dev/null +++ b/docs/config.md @@ -0,0 +1,23 @@ +# Configuring PyDwarf + +The steps given here are specifically for Windows, but the procedure on other operating systems will be almost identical. + +- First thing, ensure that [Python 2.7](https://www.python.org/download/releases/2.7.8/) is installed on your computer. If this is the only version of Python you have installed then the commands the following steps will tell you to run should look exactly like they're presented, and are run by opening a command line in PyDwarf's root directory, typing the given text, then hitting enter. If you have other versions of Python installed as well, you'll likely need to do some tinkering with environment variables that's outside the scope of this example. + +- In order to keep everything working as smoothly as possible, the first thing you should do is to copy your Dwarf Fortress directory to another location before running PyDwarf. Navigate to the directory containing your Dwarf Fortress folder, copy it, and paste it somewhere. It may be easiest to place the copy in the same location and append `_vanilla` to the end of the directory's name. + +- Right-click on `config.json`, located in PyDwarf's root directory, and select `Open with`. In the `Open with` menu, select `Choose default program` and find Notepad in the list of programs. Upon selecting Notepad, a window will appear showing the contents of `config.json`. + +- Now you're looking at a JSON file. It's assigns several parameters in the format of `"name": value,` and the most important ones right now are the ones named `input`, `output`, `backup`, and `scripts`. You can see that most of the values are text information, but `scripts` in particular is assigned a list of values contained within square brackets. + +- Set the value for `input`, which is a file path, to the location of that copy of Dwarf Fortress you made in a previous step. This tells PyDwarf where to read your files from so that they can be worked upon by various mods. + +- Set the value for `output`, which is also a file path, to the location of the Dwarf Fortress folder that you play with. This is where PyDwarf will write your files to when it's finished modifying them. + +- Set the value for `backup`, another file path, to somewhere for the Dwarf Fortress files to be backed up to. This could be something like the name of the `input` folder with `_backup` appended. This helps to ensure that if something weird goes wrong - and don't worry, it really shouldn't - you'll still have a copy of your original files lying around somewhere. + +- And the really fun part is the `scripts` parameter. Here names of scripts are given in the order that they should be run. It's also possible to pass arguments to scripts here, which change the way it behaves. One way to get a list of the available scripts is to run `python manager.py --list`, and one way to see documentation regarding one of these scripts to describe its purpose and usage is to run `python manager.py --meta script.name`. + +- For the sake of example, you can try adding an item to the end of the `scripts` list, the text (including quotes) `"pineapple.subplants"`. In doing so, be sure to add a comma to the end of the previous line, these commas serve to separate items in the list. + +- And finally, to actually run PyDwarf and apply the mods, run `python manager.py`. You're all done! Run Dwarf Fortress and generate a new world to see the changes to your raws take effect. From b57c4a665bc5be3f1b42d56c4bf9f3747ec8cbd6 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:17:37 -0400 Subject: [PATCH 64/95] Moved tutorial.md into the docs folder --- tutorial.md => docs/tutorial.md | 348 ++++++++++++++++---------------- 1 file changed, 174 insertions(+), 174 deletions(-) rename tutorial.md => docs/tutorial.md (98%) diff --git a/tutorial.md b/docs/tutorial.md similarity index 98% rename from tutorial.md rename to docs/tutorial.md index acae9bb..ab6cba1 100644 --- a/tutorial.md +++ b/docs/tutorial.md @@ -1,174 +1,174 @@ -# Introduction - -Welcome to PyDwarf! This tool is currently quite early in development and so please don't be surprised if there are bugs, missing features, or if this tutorial simply lags behind. - -There's a lot of stuff that PyDwarf automates and most of it is highly customizeable. The scope of this tutorial is in showing how to configure PyDwarf, how to create and register a new mod with it, to describe the tools available to you in writing that mod, and how to apply it to Dwarf Fortress's raws. If there are questions this tutorial fails to answer, answers can be found within PyDwarf's source code and documentation. - -Feature requests, bugs, and generally incomprehensible behavior should be reported using the PyDwarf repository's issues page, found [here](https://github.com/pineapplemachine/PyDwarf/issues). - - - -# Configuring PyDwarf - -PyDwarf's manager needs to be told things like where to find input raws, where to output new raws to, and which scripts to run. By default, these varaiables are set only using the information in the `config.json` file located in PyDwarf's root directory and using any command line arguments passed to the script upon execution. You can exert more power over the configuration by modifying the configuration override script `override_config.py`, which should assign some dictionary to an `export` attribute. The settings assigned there will override those defined in `config.json`. - -For the typical user, what this all means is essentially limited to setting the paths in their `config.json` file and adding to the list the scripts they want to run. For the advanced user, this may mean writing their own config override script that dynamically sets configuration variables. For example, my own configuration file is shared between two operating systems. So in my script I describe where my Dwarf Fortress directory is located based on which OS the script is being run for. - -The scripts specified in a config object's scripts attribute (or in the scripts array of a json config file) are very flexible. There are several ways that a script can be added to this list: - -* Describe the name of a specific script. For example, `flybears`. If there could be other scripts by the same name, it's important to differentiate between them by including the namespace, e.g. `pineapple.flybears`. These scripts must be within files located in the scripts/ directory in order to be made available in this way. -* Describe a namespace. For example, `pineapple.*`. This will run every script in the pineapple namespace. Namespaces can have a hierarchy: `pineapple.a.*` would run the scripts named `pineapple.a.script` and `pineapple.a.x.script` but not the scripts `pineapple.script` or `pineapple.b.script`. -* Provide a direct reference to a function or an urist object. (This only works in Python override scripts, not in `config.json`.) -* A dictionary containing various attributes to describe functionality. Attributes which will receive special handling are these: - * `name`: The name of a script or a namespace. Understood the same way that a lone string would be. - * `func`: Works only in Python override scripts, not in the json file: Specify a particular function to run via its reference. - * `args`: The script or function will be run using these arguments, passed via Python's `**kwargs` functionality. - * `match`: Urist metadata must match every attribute here. For example, `"match": {"version": "1.0"}` would match only a script which includes `version = "1.0"` in its metadata. - * `ignore_df_version`: Normally, if the current Dwarf Fortress version given in config isn't covered by a function's `compatibility` metadata (if specified), PyDwarf will refuse to run that function. If this flag is set to `true`, e.g. `"ignore_df_version": true`, then the script(s) specified will be run regardless of their compatibility. - -For convenient reference, this is an example config.json file: - -``` json -{ - "input": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawvanilla/objects", - "output": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/raw/objects", - "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawbak/", - - "version": "auto", - "dfhackdir": "auto", - "dfhackver": "auto", - - "scripts": [ - {"name": "pineapple.deerappear", "args": {"tile": "'d'", "color": [6, 0, 1]}}, - {"name": "pineapple.noexotic", "match": {"version": "alpha"}}, - "pineapple.nograzers", - "putnam.materialsplus", - "smeeprocket.transgender", - "witty.restrictednobles" - ], - - "packages": [ - "scripts" - ] -} -``` - -Here is the purpose of each of those attributes: - -* `input`: The directory containing inputted raws. -* `output`: Which directory to output the raws to after the scripts have run and modified them. If set to `null` (in json) or `None` (in Python) then the output directory will be the same as the input. -* `backup`: Before anything else is done, if it's not `null` or `None`, the input raws will be copied and saved to this directory. -* `version`: Specifies the Dwarf Fortress version. For example, `"version": "0.40.24"`. If set to `auto` then PyDwarf will attempt to detect the Dwarf Fortress version automatically. This should succeed as long as either the `input` or `output` directory is somewhere inside Dwarf Fortress's directory. -* `dfhackdir`: The location of a hack/ directory, if any, within the Dwarf Fortress directory -* `dfhackver`: The version of DFHack contained within a hack/ directory, if any, within the Dwarf Fortress directory -* `scripts`: Lists the scripts that should be run. -* `packages`: Lists the Python packages that should be imported. In essence, it specifies for PyDwarf that it should look for scripts inside the `scripts` package, in this case a directory containing an `__init__.py` file. This is an advanced feature and the typical user won't need to worry about this. - - - -# Applying Mods - -Once PyDwarf has been configured, applying the mods specified in its configuration is as simple as running `manager.py`. With Python 2.7 installed, the most straightforward way to do this for many users will be to open a terminal or command prompt in PyDwarf's root directory and run `python manager.py`. - -PyDwarf's configuration can also be passed as command line arguments when running manager.py. These are the arguments it accepts, all of which supersede any identically-named options set in `config.json` or `config.py` when specified. - -* `-i` or `--input`: Specifies raws input directory. -* `-o` or `--output`: Specifies raws output directory. -* `-b` or `--backup`: Specifies raws backup directory. -* `-ver` or `--version`: Specifies Dwarf Fortress version. -* `-hdir` or `--dfhackdir`: Specifies DFHack directory. -* `-hver` or `--dfhackver`: Specifies DFHack version. -* `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) -* `-p` or `--packages`: The list of Python packages to import. -* `-c` or `--config`: Reads configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. See `config_override.py` for an example. -* `-v` or `--verbose`: Sets the logging level for standard output to `DEBUG`. (By default, fully verbose logs are written to the `logs/` directory regardless of this flag.) -* `--log`: Specifies the log file path. -* `--list`: Lists registered scripts in alphabetical order. -* `--meta`: When given names of scripts as arguments, shows each script's metadata in a readable format. When given no arguments, metadata for all registered scripts is displayed. -* `--jscripts`: More complicated alternative to `--scripts` which accepts a json array just like the `scripts` attribute in `config.json`. -* `-h` or `--help`: Shows a summary of each argument's purpose. - -![Example gif of running PyDwarf from the command line](http://www.pineapplemachine.com/pydwarf/terminal_example.gif) - - - -# Creating a Mod - -Given the default settings, scripts must be located in PyDwarf's scripts/ directory to be registered and allowed to run. They must also have a `*.py` extension and be prefixed with `pydwarf.*`. For example, the script `scripts/pineapple/pydwarf.flybears.py` is loaded because it's in the scripts/ directory and its name follows the expected pattern. - -It's recommended (but not strictly necessary) that scripts be placed in directories corresponding to their authors. For example, scripts written by myself are placed in the `scripts/pineapple/` directory. - -Once a Python script has been created which is located in the correct place and which follows the naming convention, at least one function should be placed in it. The first argument of the function must accept a raws.dir object and the remainder must be named arguments which, in most cases, should be assigned default values for the sake of ease-of-use. The function should be immediately preceded by a `@pydwarf.urist()` decorator. Metadata can be assigned by passing named arguments to the decorator. There is no metadata that absolutely must be specified. For example, if no name is given in the decorator, the name of the decorated function is used instead. Additionally, the line `import pydwarf` should be placed at the top of the file. Sometimes, more complex scripts may require the classes provided by PyDwarf's `raws` package. In such cases, `import raws` should be placed at the top of the file together with `import pydwarf`. - -Here's an example script which, though it does nothing, would be properly understood by PyDwarf. - -```python -import pydwarf - -@pydwarf.urist( - name = 'pineapple.example', - author = 'Sophie Kirschner', - description = 'This script doesn\'t actually do anything!' -) -def examplefunction(df): - pass -``` - -For more complete documentation regarding what metadata gets special consideration, refer to documentation in `pydwarf/urist.py`. - - - -# Experimenting with PyDwarf - -It's easy to start playing around with PyDwarf's raws querying and modification functionality! Navigate a terminal to PyDwarf's directory and run `python`, then `import raws` and set `df = raws.dir(path='...')` where `...` refers to a directory like `path/to/dwarf/fortress/raw/objects`. All of PyDwarf's raws functionality will be exposed to you. Here's an example of what you can do from here: - -``` bash -client-170:PyDwarf pineapple$ python -Python 2.7.8 (v2.7.8:ee879c0ffa11, Jun 29 2014, 21:07:35) -[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin -Type "help", "copyright", "credits" or "license" for more information. ->>> import raws ->>> df = raws.dir(path='raw/objects') ->>> elf = df.getobj('CREATURE:ELF') ->>> description = elf.get('DESCRIPTION') ->>> print description -[DESCRIPTION:A medium-sized creature dedicated to the ruthless protection of nature.] ->>> description.setarg('A medium-sized creature undeserving of life.') ->>> print description -[DESCRIPTION:A medium-sized creature undeserving of life.] ->>> df.write(path='raw/objects') ->>> quit() -client-170:PyDwarf pineapple$ grep 'CREATURE:ELF' raw/objects/creature_standard.txt -A 4 -[CREATURE:ELF] - [DESCRIPTION:A medium-sized creature undeserving of life.] - [NAME:elf:elves:elven] - [CASTE_NAME:elf:elves:elven] - [CREATURE_TILE:'e'][COLOR:3:0:0] -``` - - - -# The Modder's Toolbox - -There are three very important classes provided by PyDwarf's `raws` package. They are `raws.dir`, which contains instances of `raws.file`, which itself contains instances of `raws.token`. `raws.dir` describes an entire directory of raws. `raws.file` describes a single raws file. And `raws.token` describes a single token within a raws file. (Those tokens are alternatively called "tags": They're contained within Dwarf Fortress raws and they look like, for example, `[TOKEN:ARGUMENTS]`. Each one of these possesses a number of methods that can be used to find specific tokens, and some methods used to add or remove tokens or files. - -These are the methods you'll probably be using most often. For others, or for more information regarding the ones listed, look at `raws/queryable.py`. It defines significantly more, more specialized, methods than these in addition to a generalized ```query``` method upon which all other queries are built. This is where the various methods are defined and most thoroughly documented. To start getting a more comprehensive idea of the available methods and their usefulness I strongly recommend looking at the many scripts already located within the `scripts/` directory. - -* `df.get`: Returns the first token matching the query. For example, `df.get('ENTITY:MOUNTAIN')` would return the `[ENTITY:MOUNTAIN]` token. -* `df.all`: Returns all tokens matching the query. For example, `df.all('PET_EXOTIC')` would return all `[PET_EXOTIC]` tokens. -* `df.until`: Returns all tokens up until the first token matching the query. For example, `df.get('ENTITY:MOUNTAIN').until('ENTITY:FOREST')` would return all tokens between `[ENTITY:MOUNTAIN]` and `[ENTITY:FOREST]`. -* `df.getobj`: Returns the first object of a given type and id. For example, `df.getobj('CREATURE:DWARF')` would return the `[CREATURE:DWARF]` token in `creature_standard`. (And never the identical token in `entity_default`!) Unlike the previous methods, this one can only be used on `raws.dir` objects. -* `df.allobj`: Returns all objects of a given type. For example, `df.allobj('ITEM_WEAPON')` would return all weapons. Like `df.getobj`, this method can only be used on `raws.dir` objects. -* `df.add`: Adds a new token or tokens after the `raws.token` this method is called for or, in the case of a `raws.file` object, appends the tokens at the end of the file. For example, `df.getobj('CREATURE:DWARF').add('FLIER')` would make dwarves fly. This method cannot be used for `raws.dir` objects. -* `df.remove`: When called for a `raws.token` object, that token is removed from the file containing it. When called for a `raws.file` object, that file is removed from the `raws.dir` object containing it. For example, `df.getobj('CREATURE:CAT').get('ADOPTS_OWNER').remove()` would cause cats to no longer annoyingly adopt their own dwarves. This method cannot be used for `raws.dir` objects. - -Unlike a `raws.dir` object, when such a query is performed on a `raws.file` or a `raws.token`, the query stops at the end of that file. (For a `raws.dir` object the query is run for every token in every file.) While `df.get('CREATURE:DEER')` would return the token in `creature_large_temperate`, `df['descriptor_shape_standard'].get('CREATURE:DEER')` would return `None`. - -Here's a simple example combining the various querying methods. This snippet would add a `[FLIER]` token to the female caste of each vanilla species of bear. (In summary: It makes female bears fly.) - -```python -for bear in df.allobj(type='CREATURE', re_id='BEAR_.+'): - bear.get('CASTE:FEMALE').add('FLIER') -``` - -![Image of a flying female bear](https://github.com/pineapplemachine/PyDwarf/blob/master/images/logo_transparent.png?raw=true) +# Introduction + +Welcome to PyDwarf! This tool is currently quite early in development and so please don't be surprised if there are bugs, missing features, or if this tutorial simply lags behind. + +There's a lot of stuff that PyDwarf automates and most of it is highly customizeable. The scope of this tutorial is in showing how to configure PyDwarf, how to create and register a new mod with it, to describe the tools available to you in writing that mod, and how to apply it to Dwarf Fortress's raws. If there are questions this tutorial fails to answer, answers can be found within PyDwarf's source code and documentation. + +Feature requests, bugs, and generally incomprehensible behavior should be reported using the PyDwarf repository's issues page, found [here](https://github.com/pineapplemachine/PyDwarf/issues). + + + +# Configuring PyDwarf + +PyDwarf's manager needs to be told things like where to find input raws, where to output new raws to, and which scripts to run. By default, these varaiables are set only using the information in the `config.json` file located in PyDwarf's root directory and using any command line arguments passed to the script upon execution. You can exert more power over the configuration by modifying the configuration override script `override_config.py`, which should assign some dictionary to an `export` attribute. The settings assigned there will override those defined in `config.json`. + +For the typical user, what this all means is essentially limited to setting the paths in their `config.json` file and adding to the list the scripts they want to run. For the advanced user, this may mean writing their own config override script that dynamically sets configuration variables. For example, my own configuration file is shared between two operating systems. So in my script I describe where my Dwarf Fortress directory is located based on which OS the script is being run for. + +The scripts specified in a config object's scripts attribute (or in the scripts array of a json config file) are very flexible. There are several ways that a script can be added to this list: + +* Describe the name of a specific script. For example, `flybears`. If there could be other scripts by the same name, it's important to differentiate between them by including the namespace, e.g. `pineapple.flybears`. These scripts must be within files located in the scripts/ directory in order to be made available in this way. +* Describe a namespace. For example, `pineapple.*`. This will run every script in the pineapple namespace. Namespaces can have a hierarchy: `pineapple.a.*` would run the scripts named `pineapple.a.script` and `pineapple.a.x.script` but not the scripts `pineapple.script` or `pineapple.b.script`. +* Provide a direct reference to a function or an urist object. (This only works in Python override scripts, not in `config.json`.) +* A dictionary containing various attributes to describe functionality. Attributes which will receive special handling are these: + * `name`: The name of a script or a namespace. Understood the same way that a lone string would be. + * `func`: Works only in Python override scripts, not in the json file: Specify a particular function to run via its reference. + * `args`: The script or function will be run using these arguments, passed via Python's `**kwargs` functionality. + * `match`: Urist metadata must match every attribute here. For example, `"match": {"version": "1.0"}` would match only a script which includes `version = "1.0"` in its metadata. + * `ignore_df_version`: Normally, if the current Dwarf Fortress version given in config isn't covered by a function's `compatibility` metadata (if specified), PyDwarf will refuse to run that function. If this flag is set to `true`, e.g. `"ignore_df_version": true`, then the script(s) specified will be run regardless of their compatibility. + +For convenient reference, this is an example config.json file: + +``` json +{ + "input": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawvanilla/objects", + "output": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/raw/objects", + "backup": "E:/Sophie/Desktop/Files/Games/Dwarf Fortress/df_40_24_win/rawbak/", + + "version": "auto", + "dfhackdir": "auto", + "dfhackver": "auto", + + "scripts": [ + {"name": "pineapple.deerappear", "args": {"tile": "'d'", "color": [6, 0, 1]}}, + {"name": "pineapple.noexotic", "match": {"version": "alpha"}}, + "pineapple.nograzers", + "putnam.materialsplus", + "smeeprocket.transgender", + "witty.restrictednobles" + ], + + "packages": [ + "scripts" + ] +} +``` + +Here is the purpose of each of those attributes: + +* `input`: The directory containing inputted raws. +* `output`: Which directory to output the raws to after the scripts have run and modified them. If set to `null` (in json) or `None` (in Python) then the output directory will be the same as the input. +* `backup`: Before anything else is done, if it's not `null` or `None`, the input raws will be copied and saved to this directory. +* `version`: Specifies the Dwarf Fortress version. For example, `"version": "0.40.24"`. If set to `auto` then PyDwarf will attempt to detect the Dwarf Fortress version automatically. This should succeed as long as either the `input` or `output` directory is somewhere inside Dwarf Fortress's directory. +* `dfhackdir`: The location of a hack/ directory, if any, within the Dwarf Fortress directory +* `dfhackver`: The version of DFHack contained within a hack/ directory, if any, within the Dwarf Fortress directory +* `scripts`: Lists the scripts that should be run. +* `packages`: Lists the Python packages that should be imported. In essence, it specifies for PyDwarf that it should look for scripts inside the `scripts` package, in this case a directory containing an `__init__.py` file. This is an advanced feature and the typical user won't need to worry about this. + + + +# Applying Mods + +Once PyDwarf has been configured, applying the mods specified in its configuration is as simple as running `manager.py`. With Python 2.7 installed, the most straightforward way to do this for many users will be to open a terminal or command prompt in PyDwarf's root directory and run `python manager.py`. + +PyDwarf's configuration can also be passed as command line arguments when running manager.py. These are the arguments it accepts, all of which supersede any identically-named options set in `config.json` or `config.py` when specified. + +* `-i` or `--input`: Specifies raws input directory. +* `-o` or `--output`: Specifies raws output directory. +* `-b` or `--backup`: Specifies raws backup directory. +* `-ver` or `--version`: Specifies Dwarf Fortress version. +* `-hdir` or `--dfhackdir`: Specifies DFHack directory. +* `-hver` or `--dfhackver`: Specifies DFHack version. +* `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) +* `-p` or `--packages`: The list of Python packages to import. +* `-c` or `--config`: Reads configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. See `config_override.py` for an example. +* `-v` or `--verbose`: Sets the logging level for standard output to `DEBUG`. (By default, fully verbose logs are written to the `logs/` directory regardless of this flag.) +* `--log`: Specifies the log file path. +* `--list`: Lists registered scripts in alphabetical order. +* `--meta`: When given names of scripts as arguments, shows each script's metadata in a readable format. When given no arguments, metadata for all registered scripts is displayed. +* `--jscripts`: More complicated alternative to `--scripts` which accepts a json array just like the `scripts` attribute in `config.json`. +* `-h` or `--help`: Shows a summary of each argument's purpose. + +![Example gif of running PyDwarf from the command line](http://www.pineapplemachine.com/pydwarf/terminal_example.gif) + + + +# Creating a Mod + +Given the default settings, scripts must be located in PyDwarf's scripts/ directory to be registered and allowed to run. They must also have a `*.py` extension and be prefixed with `pydwarf.*`. For example, the script `scripts/pineapple/pydwarf.flybears.py` is loaded because it's in the scripts/ directory and its name follows the expected pattern. + +It's recommended (but not strictly necessary) that scripts be placed in directories corresponding to their authors. For example, scripts written by myself are placed in the `scripts/pineapple/` directory. + +Once a Python script has been created which is located in the correct place and which follows the naming convention, at least one function should be placed in it. The first argument of the function must accept a raws.dir object and the remainder must be named arguments which, in most cases, should be assigned default values for the sake of ease-of-use. The function should be immediately preceded by a `@pydwarf.urist()` decorator. Metadata can be assigned by passing named arguments to the decorator. There is no metadata that absolutely must be specified. For example, if no name is given in the decorator, the name of the decorated function is used instead. Additionally, the line `import pydwarf` should be placed at the top of the file. Sometimes, more complex scripts may require the classes provided by PyDwarf's `raws` package. In such cases, `import raws` should be placed at the top of the file together with `import pydwarf`. + +Here's an example script which, though it does nothing, would be properly understood by PyDwarf. + +```python +import pydwarf + +@pydwarf.urist( + name = 'pineapple.example', + author = 'Sophie Kirschner', + description = 'This script doesn\'t actually do anything!' +) +def examplefunction(df): + pass +``` + +For more complete documentation regarding what metadata gets special consideration, refer to documentation in `pydwarf/urist.py`. + + + +# Experimenting with PyDwarf + +It's easy to start playing around with PyDwarf's raws querying and modification functionality! Navigate a terminal to PyDwarf's directory and run `python`, then `import raws` and set `df = raws.dir(path='...')` where `...` refers to a directory like `path/to/dwarf/fortress/raw/objects`. All of PyDwarf's raws functionality will be exposed to you. Here's an example of what you can do from here: + +``` bash +client-170:PyDwarf pineapple$ python +Python 2.7.8 (v2.7.8:ee879c0ffa11, Jun 29 2014, 21:07:35) +[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin +Type "help", "copyright", "credits" or "license" for more information. +>>> import raws +>>> df = raws.dir(path='raw/objects') +>>> elf = df.getobj('CREATURE:ELF') +>>> description = elf.get('DESCRIPTION') +>>> print description +[DESCRIPTION:A medium-sized creature dedicated to the ruthless protection of nature.] +>>> description.setarg('A medium-sized creature undeserving of life.') +>>> print description +[DESCRIPTION:A medium-sized creature undeserving of life.] +>>> df.write(path='raw/objects') +>>> quit() +client-170:PyDwarf pineapple$ grep 'CREATURE:ELF' raw/objects/creature_standard.txt -A 4 +[CREATURE:ELF] + [DESCRIPTION:A medium-sized creature undeserving of life.] + [NAME:elf:elves:elven] + [CASTE_NAME:elf:elves:elven] + [CREATURE_TILE:'e'][COLOR:3:0:0] +``` + + + +# The Modder's Toolbox + +There are three very important classes provided by PyDwarf's `raws` package. They are `raws.dir`, which contains instances of `raws.file`, which itself contains instances of `raws.token`. `raws.dir` describes an entire directory of raws. `raws.file` describes a single raws file. And `raws.token` describes a single token within a raws file. (Those tokens are alternatively called "tags": They're contained within Dwarf Fortress raws and they look like, for example, `[TOKEN:ARGUMENTS]`. Each one of these possesses a number of methods that can be used to find specific tokens, and some methods used to add or remove tokens or files. + +These are the methods you'll probably be using most often. For others, or for more information regarding the ones listed, look at `raws/queryable.py`. It defines significantly more, more specialized, methods than these in addition to a generalized ```query``` method upon which all other queries are built. This is where the various methods are defined and most thoroughly documented. To start getting a more comprehensive idea of the available methods and their usefulness I strongly recommend looking at the many scripts already located within the `scripts/` directory. + +* `df.get`: Returns the first token matching the query. For example, `df.get('ENTITY:MOUNTAIN')` would return the `[ENTITY:MOUNTAIN]` token. +* `df.all`: Returns all tokens matching the query. For example, `df.all('PET_EXOTIC')` would return all `[PET_EXOTIC]` tokens. +* `df.until`: Returns all tokens up until the first token matching the query. For example, `df.get('ENTITY:MOUNTAIN').until('ENTITY:FOREST')` would return all tokens between `[ENTITY:MOUNTAIN]` and `[ENTITY:FOREST]`. +* `df.getobj`: Returns the first object of a given type and id. For example, `df.getobj('CREATURE:DWARF')` would return the `[CREATURE:DWARF]` token in `creature_standard`. (And never the identical token in `entity_default`!) Unlike the previous methods, this one can only be used on `raws.dir` objects. +* `df.allobj`: Returns all objects of a given type. For example, `df.allobj('ITEM_WEAPON')` would return all weapons. Like `df.getobj`, this method can only be used on `raws.dir` objects. +* `df.add`: Adds a new token or tokens after the `raws.token` this method is called for or, in the case of a `raws.file` object, appends the tokens at the end of the file. For example, `df.getobj('CREATURE:DWARF').add('FLIER')` would make dwarves fly. This method cannot be used for `raws.dir` objects. +* `df.remove`: When called for a `raws.token` object, that token is removed from the file containing it. When called for a `raws.file` object, that file is removed from the `raws.dir` object containing it. For example, `df.getobj('CREATURE:CAT').get('ADOPTS_OWNER').remove()` would cause cats to no longer annoyingly adopt their own dwarves. This method cannot be used for `raws.dir` objects. + +Unlike a `raws.dir` object, when such a query is performed on a `raws.file` or a `raws.token`, the query stops at the end of that file. (For a `raws.dir` object the query is run for every token in every file.) While `df.get('CREATURE:DEER')` would return the token in `creature_large_temperate`, `df['descriptor_shape_standard'].get('CREATURE:DEER')` would return `None`. + +Here's a simple example combining the various querying methods. This snippet would add a `[FLIER]` token to the female caste of each vanilla species of bear. (In summary: It makes female bears fly.) + +```python +for bear in df.allobj(type='CREATURE', re_id='BEAR_.+'): + bear.get('CASTE:FEMALE').add('FLIER') +``` + +![Image of a flying female bear](https://github.com/pineapplemachine/PyDwarf/blob/master/images/logo_transparent.png?raw=true) From 15ec078ce5e1aff088588998e8a782d4438c92dd Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:18:12 -0400 Subject: [PATCH 65/95] Fixed typo in pineapple readme --- scripts/pineapple/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/pineapple/readme.md b/scripts/pineapple/readme.md index 54a6215..fbcc6fd 100644 --- a/scripts/pineapple/readme.md +++ b/scripts/pineapple/readme.md @@ -43,7 +43,8 @@ Adds discipline as a natural skill to creatures that shouldn't be running away s Add a file or a bunch of tokens and the script handles entity permissions all on its own. Good for adding small mods, for example: ``` json -"pineapple.easypatch" { +{ + "name": "pineapple.easypatch", "args": { "files": "path/to/raws", "loc": "raw/objects", From eadd083fe4380341db091bfbb5aee508334822a9 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:18:25 -0400 Subject: [PATCH 66/95] Updated readme --- readme.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 63eb5fa..65cd2d7 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,17 @@ # PyDwarf +I have an enormous love for Dwarf Fortress and for its mods. In the years I've been playing and following this game, I think that there's always been a gap where a really powerful raws mod manager could be. This is the niche PyDwarf is intended to fill: I want to give mod authors the power to access and modify Dwarf Fortress's files with an elegance hitherto unseen, and I want to give users the ability to apply these mods with a minimum of effort. + PyDwarf is licensed via the exceptionally permissive [zlib/libpng license](https://github.com/pineapplemachine/PyDwarf/blob/master/license.txt). It's written for [Python 2.7](https://www.python.org/download/releases/2.7.8/) and is intended as a mod tool for the wonderful game [Dwarf Fortress](http://www.bay12games.com/dwarves/). -I have an enormous love for Dwarf Fortress and for its mods. In the years I've been playing and following this game, I think that there's always been a gap where a really powerful raws mod manager could be. This is the niche PyDwarf is intended to fill: I want to give mod authors the power to query and modify Dwarf Fortress's raws files with an elegance hitherto unseen, and I want to give users the ability to apply these mods with a minimum of effort. +## Configuring PyDwarf + +PyDwarf is easy to configure! Open `config.json` with your favorite plain text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. -PyDwarf is easy to configure! Open `config.json` with your favorite text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. +Here's a [step-by-step tutorial](docs/config.md) describing what this process might entail. -If you're interested in writing your own mods for PyDwarf or in understanding its more advanced features, take a look at [the tutorial](https://github.com/pineapplemachine/PyDwarf/blob/master/tutorial.md)! +## Modding using PyDwarf -You can see a list of your installed mods by running `python manager.py --list` and you can view some documentation for a particular script by running, for example, `python manager.py --meta "pineapple.flybears"`. +If you're interested in writing your own mods for PyDwarf or in understanding its more advanced features, take a look at [this tutorial](docs/tutorial.md), which goes into detail about how to use PyDwarf and how to write mods using it. -![Image of a flying female bear](https://github.com/pineapplemachine/PyDwarf/blob/master/images/logo_transparent.png?raw=true) +![Image of a flying female bear](images/logo_transparent.png) From b82b5f4847b1b9ec8ad1db87643c3cea1205e182 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:21:56 -0400 Subject: [PATCH 67/95] Added an add method to raws.tokenlist Simply calls add with the same arguments for each token in the list --- raws/queryable.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raws/queryable.py b/raws/queryable.py index 025dfc3..721a1da 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -624,6 +624,9 @@ def tokens(self, range=None, reverse=False): if range is not None and range <= count: break yield self.__getitem__(i) + def add(self, *args, **kwargs): + for token in self.tokens: token.add(*args, **kwargs) + def __str__(self): if len(self) == 0: return '' From c97cdfcebd1172e311b511dc0740b1e7e57b086a Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:22:27 -0400 Subject: [PATCH 68/95] Tiny improvement to raws.token.addprop Uses *arg and **kwargs instead of previous mess now --- raws/token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raws/token.py b/raws/token.py index 1b18aa1..73357cc 100644 --- a/raws/token.py +++ b/raws/token.py @@ -659,7 +659,7 @@ def add(self, auto=None, pretty=None, token=None, tokens=None, reverse=False): else: raise ValueError('Failed to add token or tokens because no object was specified.') - def addprop(self, auto=None, **kwargs): + def addprop(self, *args, **kwargs): '''When this token is an object token like CREATURE:X or INORGANIC:X, a new token is usually added immediately afterwards. However, if a token like COPY_TAGS_FROM or USE_MATERIAL_TEMPLATE exists underneath the object, then @@ -689,7 +689,7 @@ def addprop(self, auto=None, **kwargs): aftervalues = ('COPY_TAGS_FROM',) addafter = self.getlastprop(value_in=aftervalues) if not addafter: addafter = self - addafter.add(auto=auto, **kwargs) + addafter.add(*args, **kwargs) @staticmethod def firstandlast(tokens, setfile=None): From fed4fb7e9e0c731aa1470d95ca5e45d2ff81c6c0 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Sun, 5 Jul 2015 13:23:59 -0400 Subject: [PATCH 69/95] Rebuilt html documentation Even if it is way shitty still --- docs/index.html | 172 ++++++++++++++++++++++++------------------------ 1 file changed, 87 insertions(+), 85 deletions(-) diff --git a/docs/index.html b/docs/index.html index f2f3375..8ecf0a2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,12 +1,12 @@ -PyDwarf docs

Warning: This is a shitty WIP.

pydwarf.session

__init__

None

eval

None

failed

None

funcs

None

handle

None

handleall

None

inlist

None

successful

None

pydwarf.urist

__call__

None

__hash__

None

__init__

None

allregistered

None

cullcandidates

None

cullcandidates_compatibility

None

cullcandidates_dependency

None

cullcandidates_duplicates

None

cullcandidates_match

None

depsatisfied

None

doc

Make a pretty metadata string.

doclist

None

forfunc

None

get

None

getfn

None

getname

None

getregistered

None

info

None

list

None

matches

None

meta

None

splitname

None

raws.boolfilter

__and__

None

__contains__

None

__init__

None

__invert__

None

__or__

None

__xor__

None

all

None

any

None

basematch

None

copy

None

invert

None

inverted

None

match

None

none

None

one

None

raws.color

black

None

blue

None

brown

None

color

None

cyan

None

dgray

None

green

None

lblue

None

lcyan

None

lgray

None

lgreen

None

lmagenta

None

lred

None

magenta

None

red

None

white

None

yellow

None

raws.dir

__class__

type(object) -> the object's type
-type(name, bases, dict) -> a new type

__contains__

None

__delattr__

x.__delattr__('name') <==> del x.name

__format__

default object formatter

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

None

__hash__

x.__hash__() <==> hash(x)

__init__

Constructor for rawsdir object.

__iter__

None

__new__

T.__new__(S, ...) -> a new object with type S, a subtype of T

__reduce__

helper for pickle

__reduce_ex__

helper for pickle

__setattr__

x.__setattr__('name', value) <==> x.name = value

__setitem__

None

__sizeof__

__sizeof__() -> int
+PyDwarf docs

Warning: This is a shitty WIP.

pydwarf.session

__init__

None

backup

None

configure

None

eval

None

failed

None

funcs

None

handle

None

handleall

None

inlist

None

successful

None

write

None

pydwarf.urist

__call__

None

__hash__

None

__init__

None

allregistered

None

cullcandidates

None

cullcandidates_compatibility

None

cullcandidates_dependency

None

cullcandidates_duplicates

None

cullcandidates_match

None

depsatisfied

None

doc

Make a pretty metadata string.

doclist

None

forfunc

None

get

None

getfn

None

getname

None

getregistered

None

info

None

list

None

matches

None

meta

None

splitname

None

raws.boolfilter

__and__

None

__contains__

None

__init__

None

__invert__

None

__or__

None

__xor__

None

all

None

any

None

basematch

None

copy

None

invert

None

inverted

None

match

None

none

None

one

None

raws.color

black

None

blue

None

brown

None

color

None

cyan

None

dgray

None

green

None

lblue

None

lcyan

None

lgray

None

lgreen

None

lmagenta

None

lred

None

magenta

None

red

None

white

None

yellow

None

raws.dir

__class__

type(object) -> the object's type
+type(name, bases, dict) -> a new type

__contains__

None

__delattr__

x.__delattr__('name') <==> del x.name

__enter__

None

__exit__

None

__format__

default object formatter

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

None

__hash__

x.__hash__() <==> hash(x)

__init__

Constructor for rawsdir object.

__iter__

None

__new__

T.__new__(S, ...) -> a new object with type S, a subtype of T

__reduce__

helper for pickle

__reduce_ex__

helper for pickle

__setattr__

x.__setattr__('name', value) <==> x.name = value

__setitem__

None

__sizeof__

__sizeof__() -> int
 size of object in memory, in bytes

__subclasshook__

Abstract classes can override this to customize issubclass().
 
 This is invoked early on by abc.ABCMeta.__subclasscheck__().
 It should return True, False or NotImplemented.  If it returns
 NotImplemented, the normal algorithm is used.  Otherwise, it
 overrides the normal algorithm (and the outcome is cached).
-

addfile

None

addpath

None

all

None

allobj

Gets all objects matching a given type and optional id or id regex.
+

add

None

addbyauto

None

addbybincontent

None

addbydir

None

addbydirpath

None

addbyfile

None

addbyfilepath

None

addbyname

None

addbytokens

None

addfile

Deprecated: As of v1.0.2. Use the add method instead.

addfilestodicts

None

addfiletodicts

Internal: Used to add a file to files and filenames dictionaries.

addtodicts

None

all

None

allobj

Gets all objects matching a given type and optional id or id regex.
         
         Example usage:
             >>> pants = df.allobj('ITEM_PANTS')
@@ -39,11 +39,11 @@
             >>> print hematite.allprop('ENVIRONMENT') # Gets only the ENVIRONMENT tokens belonging to hematite
             [ENVIRONMENT:SEDIMENTARY:VEIN:100]
             [ENVIRONMENT:IGNEOUS_EXTRUSIVE:VEIN:100]
-        

alluntil

None

argsprops

None

argstokens

None

argsuntil

None

get

None

getfile

Gets the file with a given name. If no file by that name is found,
+        

alluntil

None

argsprops

None

argstokens

None

argsuntil

None

clean

None

filebyfilepath

None

filebyname

None

filesbydir

None

filesbydirpath

None

get

None

getdestforfileop

Internal

getfile

Gets the file with a given name. If no file by that name is found,
         None is returned instead. If creature is set to something other than
         None, the behavior when no file by some name exists is altered: A new
         file is created and associated with that name, and then its add
-        method is called using the value for create as its argument.

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
+        method is called using the value for create as its argument.

getitems

None

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
         token with the same value as this one. Should be sufficient in almost
         all cases to get a token representing a property of an object, when
         this method is called for a token representing an object. **kwargs
@@ -105,7 +105,7 @@
             [WAFERS]
             >>> print iron.getprop('WAFERS') # Stops at the next INORGANIC token, doesn't pick up adamantine's WAFERS token
             None
-        

getuntil

None

islice

None

list

Convenience method acts as a shortcut for raws.tokenlist(obj.tokens(*args, **kwargs)).
+        

getuntil

None

headersfortype

None

islice

None

iterfiles

None

list

Convenience method acts as a shortcut for raws.tokenlist(obj.tokens(*args, **kwargs)).
         
         Example usage:
             >>> elf = df.getobj('CREATURE:ELF')
@@ -152,16 +152,30 @@
             [TILE:156]
             >>> print props.get('NOT_A_TOKEN')
             None
-        

query

None

read

Reads raws from all text files in the specified directory.

removefile

None

setfile

None

slice

None

tokens

Iterate through all tokens.

until

None

write

Writes raws to the specified directory.

raws.file

__class__

type(object) -> the object's type
-type(name, bases, dict) -> a new type

__contains__

None

__delattr__

x.__delattr__('name') <==> del x.name

__eq__

None

__format__

default object formatter

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

None

__hash__

x.__hash__() <==> hash(x)

__init__

Constructs a new raws file object.
-        
-        header: The header string to appear at the top of the file. Also used to determine filename.
+        

query

None

read

Reads raws from all text files in the specified directory.

remove

None

removeall

None

removeallobj

None

removeallprop

None

removealluntil

None

removefile

Deprecated: As of v1.0.2. Use the remove method instead.

removefirst

None

removefirstuntil

None

removelast

None

removelastprop

None

removelastuntil

None

removeobj

None

removeprop

None

removeselfandprops

None

removeuntil

None

slice

None

tokens

Iterate through all tokens.

until

None

write

Writes raws to the specified directory.

raws.file

__class__

type(object) -> the object's type
+type(name, bases, dict) -> a new type

__contains__

None

__delattr__

x.__delattr__('name') <==> del x.name

__enter__

None

__eq__

None

__exit__

None

__format__

default object formatter

__ge__

None

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

Overrides object[...] behavior. Accepts a number of different types for the item argument, each resulting in different behavior.
+        
+        object[...]
+            Returns the same as object.list().
+        object[str]
+            Returns the same as object.get(str).
+        object[int]
+            Returns the same as object.index(int).
+        object[slice]
+            Returns the same as object.slice(slice).
+        object[iterable]
+            Returns a flattened list containing object[member] in order for each member of iterable.
+        object[anything else]
+            Raises an exception.
+        

__gt__

None

__hash__

None

__init__

Constructs a new raws file object.
+        
+        name: The name string to appear at the top of the file. Also used to determine filename.
         data: A string to be parsed into token data.
         path: A path to the file from which this object is being parsed, if any exists.
         tokens: An iterable of tokens from which to construct the object; these tokens will be its initial contents.
-        rfile: A file-like object from which to automatically read the header and data attributes.
+        file: A file-like object from which to automatically read the name and data attributes.
         dir: Which raws.dir object this file belongs to.
-        

__iter__

None

__len__

None

__ne__

None

__new__

T.__new__(S, ...) -> a new object with type S, a subtype of T

__reduce__

helper for pickle

__reduce_ex__

helper for pickle

__setattr__

x.__setattr__('name', value) <==> x.name = value

__sizeof__

__sizeof__() -> int
+        

__iter__

None

__le__

None

__len__

None

__lt__

None

__ne__

None

__new__

T.__new__(S, ...) -> a new object with type S, a subtype of T

__nonzero__

None

__reduce__

helper for pickle

__reduce_ex__

helper for pickle

__setattr__

x.__setattr__('name', value) <==> x.name = value

__sizeof__

__sizeof__() -> int
 size of object in memory, in bytes

__subclasshook__

Abstract classes can override this to customize issubclass().
 
 This is invoked early on by abc.ABCMeta.__subclasscheck__().
@@ -183,8 +197,7 @@
             [ITEM_FOOD:ITEM_FOOD_ROAST]
             [NAME:roast]
             [LEVEL:4]
-            >>> tokens = item_food.add('
-hi! [THIS][IS][AN][EXAMPLE]')
+            >>> tokens = item_food.add('hi! [THIS][IS][AN][EXAMPLE]')
             >>> print tokens
             hi! [THIS][IS][AN][EXAMPLE]
             >>> print item_food.list()
@@ -197,8 +210,7 @@
             [LEVEL:3]
             [ITEM_FOOD:ITEM_FOOD_ROAST]
             [NAME:roast]
-            [LEVEL:4]
-            hi! [THIS][IS][AN][EXAMPLE]
+            [LEVEL:4]hi! [THIS][IS][AN][EXAMPLE]
         

all

None

allobj

Gets all objects matching a given type and optional id or id regex.
         
         Example usage:
@@ -232,7 +244,7 @@
             >>> print hematite.allprop('ENVIRONMENT') # Gets only the ENVIRONMENT tokens belonging to hematite
             [ENVIRONMENT:SEDIMENTARY:VEIN:100]
             [ENVIRONMENT:IGNEOUS_EXTRUSIVE:VEIN:100]
-        

alluntil

None

argsprops

None

argstokens

None

argsuntil

None

clear

Remove all tokens from this file.
+        

alluntil

None

argsprops

None

argstokens

None

argsuntil

None

bin

None

clear

Remove all tokens from this file.
         
         Example usage:
             >>> item_pants = df.getfile('item_pants')
@@ -241,7 +253,7 @@
             >>> item_pants.clear()
             >>> print item_pants.length()
             0
-        

copy

Makes a copy of a file and its contents.
+        

content

None

copy

Makes a copy of a file and its contents.
         
         Example usage:
             >>> item_food = df.getfile('item_food')
@@ -265,17 +277,7 @@
             [LEVEL:4][EXAMPLE:TOKEN]
             >>> print item_food == food_copy
             False
-        

equals

None

get

None

getheader

Get the file header.
-        
-        Example usage:
-            >>> dwarf = df.getobj('CREATURE:DWARF')
-            >>> creature_standard = dwarf.file
-            >>> print creature_standard.getheader()
-            creature_standard
-            >>> creature_standard.setheader('example_header')
-            >>> print creature_standard.getheader()
-            example_header
-        

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
+        

dest

Internal: Given a root directory that this file would be written to, get the full path of where this file belongs.

equals

None

factory

None

get

None

getitems

None

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
         token with the same value as this one. Should be sufficient in almost
         all cases to get a token representing a property of an object, when
         this method is called for a token representing an object. **kwargs
@@ -287,7 +289,17 @@
             [ITEMS_SOFT]
             >>> print iron.getlastprop(re_value='ITEMS_.+') # Gets the last ITEMS_ token which belongs to iron
             [ITEMS_SCALED]
-        

getlastuntil

None

getobj

Get the first object token matching a given type and id. (If there's more 
+        

getlastuntil

None

getname

Get the file name.
+        
+        Example usage:
+            >>> dwarf = df.getobj('CREATURE:DWARF')
+            >>> creature_standard = dwarf.file
+            >>> print creature_standard.getname()
+            creature_standard
+            >>> creature_standard.setheader('example_header')
+            >>> print creature_standard.getname()
+            example_header
+        

getobj

Get the first object token matching a given type and id. (If there's more 
             than one result for any given query then I'm afraid you've done something
             silly with your raws.) This method should work properly with things like
             CREATURE:X tokens showing up in entity_default. Should almost always be
@@ -318,7 +330,7 @@
             [WAFERS]
             >>> print iron.getprop('WAFERS') # Stops at the next INORGANIC token, doesn't pick up adamantine's WAFERS token
             None
-        

getuntil

None

index

None

islice

None

length

Get the number of tokens in this file.
+        

getuntil

None

headersfortype

None

index

None

islice

None

length

Get the number of tokens in this file.
         
         Example usage:
             >>> print df.getfile('creature_standard').length()
@@ -374,7 +386,7 @@
             [TILE:156]
             >>> print props.get('NOT_A_TOKEN')
             None
-        

query

None

read

Internal: Given a file-like object, reads header and data from it.

remove

Remove this file from the raws.dir object to which it belongs.
+        

query

None

raw

None

read

Given a path or file-like object, reads name and data.

ref

None

reloc

None

remove

Remove this file from the raws.dir object to which it belongs.
         
         Example usage:
             >>> dwarf = df.getobj('CREATURE:DWARF')
@@ -387,21 +399,21 @@
             None
             >>> print df.getfile('creature_standard')
             None
-        

root

Gets the first token in the file.
+        

removeall

None

removeallobj

None

removeallprop

None

removealluntil

None

removefirst

None

removefirstuntil

None

removelast

None

removelastprop

None

removelastuntil

None

removeobj

None

removeprop

None

removeselfandprops

None

removeuntil

None

root

Gets the first token in the file.
         
         Example usage:
             >>> creature_standard = df.getfile('creature_standard')
             >>> print creature_standard.root()
             [OBJECT:CREATURE]
-        

setheader

Set the file header.
+        

setname

Set the file name.
         
         Example usage:
             >>> dwarf = df.getobj('CREATURE:DWARF')
             >>> creature_standard = dwarf.file
-            >>> print creature_standard.getheader()
+            >>> print creature_standard.getname()
             creature_standard
             >>> creature_standard.setheader('example_header')
-            >>> print creature_standard.getheader()
+            >>> print creature_standard.getname()
             example_header
         

setpath

None

settokens

Internal: Utility method for setting the root and tail tokens given an iterable.

slice

None

tail

Gets the last token in the file.
         
@@ -414,7 +426,7 @@
         reverse: If False, starts from the first token and iterates forwards. If True,
             starts from the last token and iterates backwards. Defaults to False.
         **kwargs: Other named arguments are passed on to the raws.token.tokens method.
-        

until

None

write

Internal: Given a file-like object, writes the file's contents to that file.

raws.token

__add__

Concatenates and returns a raws.tokenlist object.
+        

until

None

write

Given a path to a directory or a file-like object, writes the file's contents to that file.

raws.token

__add__

Concatenates and returns a raws.tokenlist object.
         
         Example usage:
             >>> one = raws.token('NUMBER:ONE')
@@ -451,7 +463,21 @@
             False
             >>> print elf >= elf
             True
-        

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

None

__gt__

Returns True if this token appears after the other token in a file.
+        

__getattribute__

x.__getattribute__('name') <==> x.name

__getitem__

Overrides object[...] behavior. Accepts a number of different types for the item argument, each resulting in different behavior.
+        
+        object[...]
+            Returns the same as object.list().
+        object[str]
+            Returns the same as object.get(str).
+        object[int]
+            Returns the same as object.index(int).
+        object[slice]
+            Returns the same as object.slice(slice).
+        object[iterable]
+            Returns a flattened list containing object[member] in order for each member of iterable.
+        object[anything else]
+            Raises an exception.
+        

__gt__

Returns True if this token appears after the other token in a file.
         
         Example usage:
             >>> creature_standard = df.getfile('creature_standard')
@@ -461,7 +487,7 @@
             False
             >>> print elf < goblin
             True
-        

__hash__

None

__init__

None

__iter__

None

__le__

Returns True if this token appears before the other token in a file, or if this and the other refer to the same token.
+        

__hash__

None

__iadd__

None

__init__

None

__iter__

None

__le__

Returns True if this token appears before the other token in a file, or if this and the other refer to the same token.
         
         Example usage:
             >>> creature_standard = df.getfile('creature_standard')
@@ -520,7 +546,7 @@
             >>> token.addarg('hi!')
             >>> print token
             [EXAMPLE:hi!]
-        

addone

Internal: Utility method called by add when adding a single token

addprop

When this token is an object token like CREATURE:X or INORGANIC:X, a
+        

addargs

None

addone

Internal: Utility method called by add when adding a single token

addprop

When this token is an object token like CREATURE:X or INORGANIC:X, a
         new token is usually added immediately afterwards. However, if a token like
         COPY_TAGS_FROM or USE_MATERIAL_TEMPLATE exists underneath the object, then
         the specified tag is only added after that. **kwargs are passed on to the
@@ -554,11 +580,24 @@
             >>> print hematite.allprop('ENVIRONMENT') # Gets only the ENVIRONMENT tokens belonging to hematite
             [ENVIRONMENT:SEDIMENTARY:VEIN:100]
             [ENVIRONMENT:IGNEOUS_EXTRUSIVE:VEIN:100]
-        

alluntil

None

arg

When a token is expected to have only one argument, this method can be used
-        to access it. It there's one argument it will be returned, otherwise an
-        exception will be raised.
+        

alluntil

None

arg

When an index is given, the argument at that index is returned. If left
+        set to None then the first argument is returned if the token has exactly one
+        argument, otherwise an exception is raised.
         
         Example usage:
+            >>> token = raws.token('EXAMPLE:argument 0:argument 1')
+            >>> print token.getarg(0)
+            argument 0
+            >>> print token.getarg(1)
+            argument 1
+            >>> print token.getarg(2)
+            None
+            >>> print token.getarg(-1)
+            argument 1
+            >>> print token.getarg(-2)
+            argument 0
+            >>> print token.getarg(-3)
+            None
             >>> token_a = raws.token('EXAMPLE:x')
             >>> token_b = raws.token('EXAMPLE:x:y:z')
             >>> print token_a.arg()
@@ -574,7 +613,7 @@
             >>> token = raws.token('EXAMPLE:a:b:c')
             >>> print token.argsstr()
             a:b:c
-        

argstokens

None

argsuntil

None

auto

Internal: Convenience function for handling method arguments

containsarg

None

copy

Copies some token or iterable collection of tokens.
+        

argstokens

None

argsuntil

None

auto

Internal: Convenience function for handling method arguments

clearargs

None

containsarg

None

copy

Copies some token or iterable collection of tokens.
         
         Example usage:
             >>> token = raws.token('EXAMPLE:a:b:c')
@@ -623,25 +662,7 @@
             >>> tokens = raws.token.parse('[ONE][TWO][THREE][FOUR]')
             >>> print raws.token.firstandlast(tokens)
             ([ONE], [FOUR])
-        

follows

None

get

None

getarg

Gets argument at index, returns None if the index is out of bounds.
-        
-        index: The argument index.
-        
-        Example usage:
-            >>> token = raws.token('EXAMPLE:argument 0:argument 1')
-            >>> print token.getarg(0)
-            argument 0
-            >>> print token.getarg(1)
-            argument 1
-            >>> print token.getarg(2)
-            None
-            >>> print token.getarg(-1)
-            argument 1
-            >>> print token.getarg(-2)
-            argument 0
-            >>> print token.getarg(-3)
-            None
-        

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
+        

follows

None

get

None

getitems

None

getlast

None

getlastprop

Gets the last token matching the arguments, but stops at the next
         token with the same value as this one. Should be sufficient in almost
         all cases to get a token representing a property of an object, when
         this method is called for a token representing an object. **kwargs
@@ -703,25 +724,6 @@
                 [NAME:elf:elves:elven]
                 [CASTE_NAME:elf:elves:elven]
                 [CREATURE_TILE:'e'][COLOR:3:0:0]
-        

match

Returns True if this method matches some filter, false otherwise.
-        
-        filter: The filter to check, for example a raws.tokenfilter object.
-        **kwargs: Passed to the taws.tokenfilter constructor to nab a filter
-            to use if no filter was otherwise specified.
-        
-        Example usage:
-            >>> filter = raws.tokenfilter(exact_value='EXAMPLE')
-            >>> token_a = raws.token('HELLO:THERE')
-            >>> token_b = raws.token('EXAMPLE')
-            >>> token_c = raws.token('EXAMPLE:NUMBER:TWO')
-            >>> print token_a.match(filter)
-            False
-            >>> print token_b.match(filter)
-            True
-            >>> print token_c.match(filter)
-            True
-            >>> print token_a.match(exact_value='HELLO')
-            True
         

nargs

When count is None, returns the number of arguments the token has. (Length of
         arguments list.) Otherwise, returns True if the number of arguments is equal to the
         given count and False if not.
@@ -803,7 +805,7 @@
                 [TRANSLATION:ELF]
                 [WEAPON:ITEM_WEAPON_SPEAR]
                 [WEAPON:ITEM_WEAPON_BOW]
-        

sanitizeargstring

Internal: Utility method for sanitizing a string intended to be evaluated as an arugment for a token.

setarg

Sets argument at index, also verifies that the input contains no illegal characters.
+        

removeall

None

removeallprop

None

removealluntil

None

removefirst

None

removefirstuntil

None

removelast

None

removelastprop

None

removelastuntil

None

removeprop

None

removeselfandprops

None

removeuntil

None

setarg

Sets argument at index, also verifies that the input contains no illegal characters.
         If the index argument is set but not value, then the index is assumed to be referring to
         a value and the index is assumed to be 0.
         
@@ -819,7 +821,7 @@
             [EXAMPLE:a:b:500]
             >>> token.setarg('hi!')
             >>> print token
-            [EXAMPLE:hi!:b:500]

setprefix

Set the comment text preceding a token.
+            [EXAMPLE:hi!:b:500]

setargs

None

setprefix

Set the comment text preceding a token.
         
         value: The value to be set.
         
@@ -948,4 +950,4 @@
             to True then the query of which this filter is a member is made to terminated. If
             set to False, then this filter will only cease to accumulate results. Defaults to
             True.
-        

__invert__

None

__or__

None

__xor__

None

basematch

None

copy

None

invert

None

inverted

None

match

None

\ No newline at end of file +

__invert__

None

__or__

None

__xor__

None

anchor

None

basematch

None

copy

None

invert

None

inverted

None

match

None

\ No newline at end of file From b948e18e40785d578091be6fd29a0835df995fd4 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:14:00 -0400 Subject: [PATCH 70/95] Fixed bug in raws.token.add --- raws/token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raws/token.py b/raws/token.py index 73357cc..3ef83bc 100644 --- a/raws/token.py +++ b/raws/token.py @@ -738,7 +738,7 @@ def addall(self, tokens, reverse=False): else: first.prev = self last.next = self.next - if self.next: self.next.prev = tokens[-1] + if self.next: self.next.prev = last self.next = first return tokens From 614b5830d198eaef48f17c564390b058892175f1 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:15:50 -0400 Subject: [PATCH 71/95] Improvements to raws.queryable - Added removeuntil method in the vein of removeall, removefirst, etc. - Slightly better documentation for objpretty method - Changed the functionality of the tokenlist.add method to instead add content to the list - Ditched the previous add functionality for a much more amazing each method, which calls a function for each token in the list. Lambdas work well here. --- raws/queryable.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/raws/queryable.py b/raws/queryable.py index 721a1da..942684c 100644 --- a/raws/queryable.py +++ b/raws/queryable.py @@ -113,6 +113,10 @@ def removeall(self, *args, **kwargs): tokens = self.all(*args, **kwargs) for token in tokens: token.remove() return tokens + def removeuntil(self, *args, **kwargs): + tokens = self.until(*args, **kwargs) + for token in tokens: token.remove() + return tokens def removealluntil(self, *args, **kwargs): tokens = self.alluntil(*args, **kwargs) for token in tokens: token.remove() @@ -600,8 +604,7 @@ def objdict(self, *args, **kwargs): @staticmethod def objpretty(pretty, type, id): - '''Internal''' - # Utility method for handling getobj/allobj arguments. + '''Internal: Used for handling getobj/allobj arguments.''' if pretty is not None: if ':' in pretty: parts = pretty.split(':') @@ -624,9 +627,21 @@ def tokens(self, range=None, reverse=False): if range is not None and range <= count: break yield self.__getitem__(i) - def add(self, *args, **kwargs): - for token in self.tokens: token.add(*args, **kwargs) - + def add(self, item): + if isinstance(item, rawstoken): + self.append(item) + elif isinstance(item, rawsqueryable): + self.extend(item.tokens()) + elif isinstance(item, list): + self.extend(item) + else: + raise ValueError('Failed to add item because it was of an unrecognized type.') + + def each(self, fn, *args, **kwargs): + '''Calls a function for each entry in the list with that entry as the argument, and + appends each result to a returned tokenlist.''' + return rawstokenlist(fn(token, *args, **kwargs) for token in self) + def __str__(self): if len(self) == 0: return '' From 33fb9b1100122c14dd819df23e0b3d4d0a58c8d6 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:19:00 -0400 Subject: [PATCH 72/95] Updated putnam scripts - Simplified code by utilizing newer features - Restructured raws files - Improved readme --- scripts/putnam/pydwarf.materialsplus.py | 159 +++++++++--------- scripts/putnam/pydwarf.microreduce.py | 32 ++-- .../materialsplus}/entity_default.txt | 0 .../inorganic_alloys_mat_plus.txt | 0 .../inorganic_metals_mat_plus.txt | 0 .../inorganic_other_mat_plus.txt | 0 .../materialsplus}/inorganic_stone_gem.txt | 0 .../materialsplus}/inorganic_stone_layer.txt | 0 .../inorganic_stone_mineral.txt | 0 .../inorganic_stone_minerals_mat_plus.txt | 0 .../materialsplus}/item_mat_plus.txt | 0 .../reaction_alloys_mat_plus.txt | 0 .../reaction_production_mat_plus.txt | 0 .../microreduce}/building_macro_fantastic.txt | 0 .../microreduce}/entity_default.txt | 0 .../microreduce}/item_macro_fantastic.txt | 0 .../microreduce}/reaction_macro_fantastic.txt | 0 scripts/putnam/readme.md | 36 ++++ scripts/putnam/readme.txt | 2 - 19 files changed, 133 insertions(+), 96 deletions(-) rename scripts/putnam/{Materials Plus => raw/materialsplus}/entity_default.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_alloys_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_metals_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_other_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_stone_gem.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_stone_layer.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_stone_mineral.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/inorganic_stone_minerals_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/item_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/reaction_alloys_mat_plus.txt (100%) rename scripts/putnam/{Materials Plus => raw/materialsplus}/reaction_production_mat_plus.txt (100%) rename scripts/putnam/{Microreduce => raw/microreduce}/building_macro_fantastic.txt (100%) rename scripts/putnam/{Microreduce => raw/microreduce}/entity_default.txt (100%) rename scripts/putnam/{Microreduce => raw/microreduce}/item_macro_fantastic.txt (100%) rename scripts/putnam/{Microreduce => raw/microreduce}/reaction_macro_fantastic.txt (100%) create mode 100644 scripts/putnam/readme.md delete mode 100644 scripts/putnam/readme.txt diff --git a/scripts/putnam/pydwarf.materialsplus.py b/scripts/putnam/pydwarf.materialsplus.py index d181bcc..e33924b 100644 --- a/scripts/putnam/pydwarf.materialsplus.py +++ b/scripts/putnam/pydwarf.materialsplus.py @@ -2,12 +2,63 @@ import pydwarf import raws -# Utility function for putting new properties after an inorganic's USE_MATERIAL_TEMPLATE token, if it has one -# Otherwise, the property is just added after the INORGANIC object token. -def addaftertemplate(inorganic, addition): - template = inorganic.getuntil(exact_value='USE_MATERIAL_TEMPLATE', until_exact_value='INORGANIC') - addafter = template if template else inorganic - return addafter.add(addition) + + +mats_dir = pydwarf.rel(__file__, 'raw/materialsplus') + +default_entities = 'MOUNTAIN' + +add_paths = [os.path.join(mats_dir, path) for path in [ + 'inorganic_alloys_mat_plus.txt', + 'inorganic_metals_mat_plus.txt', + 'inorganic_other_mat_plus.txt', + 'item_mat_plus.txt' +]] + +patch_paths = [os.path.join(mats_dir, path) for path in [ + 'reaction_alloys_mat_plus.txt', + 'reaction_production_mat_plus.txt' +]] + + + +add_properties = [ + ( + # Identifier for making the log easier to understand + 'zircon', + # Regex to match inorganic IDs + '.* ZIRCON', + # Add these properties + 'MATERIAL_REACTION_PRODUCT:KROLL_PROCESS:INORGANIC:ZIRCONIUM_PUTNAM' + ), + ( + 'beryl', + '.* BERYL|HELIODOR|MORGANITE|GOSHENITE|EMERALD', + 'REACTION_CLASS:BERYLLIUM' + ), + ( + 'silicon', + 'ANDESITE|OLIVINE|HORNBLENDE|SERPENTINE|ORTHOCLASE|MICROCLINE|MICA', + 'REACTION_CLASS:SILICON' + ), + ( + 'dolomite', + 'DOLOMITE', + 'REACTION_CLASS:PIDGEON_PROCESS' + ), + ( + 'cromite', + 'CHROMITE', + '[METAL_ORE:CHROMIUM_PUTNAM:100][METAL_ORE:IRON:50]' + ), + ( + 'pyrolusite', + 'PYROLUSITE', + 'METAL_ORE:MANGANESE_PUTNAM:100' + ), +] + + @pydwarf.urist( name = 'putnam.materialsplus', @@ -16,79 +67,33 @@ def addaftertemplate(inorganic, addition): description = 'Adds a bunch of materials to the game.', compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) ) -def materialsplus(dfraws): - exceptions = 0 - addedreactions = [] +def materialsplus(df, entities=default_entities): + # Add properties to various inorganics as defined by the add_properties dict + errors = 0 + for identifier, re_id, addprops in add_properties: + additions = df.allobj(type='INORGANIC', re_id=re_id).each( + raws.token.addprop, addprops + ) + if len(additions): + pydwarf.log.debug('Added %s properties to %d inorganics.' % (identifier, len(additions))) + else: + errors += 1 + pydwarf.log.error('Failed to add %s properties because no matching inorganics were found.' % identifier) - try: - for zircon in dfraws.all(exact_value='INORGANIC', re_args=['.* ZIRCON']): - addaftertemplate(zircon, 'MATERIAL_REACTION_PRODUCT:KROLL_PROCESS:INORGANIC:ZIRCONIUM_PUTNAM') - pydwarf.log.debug('Added reaction to zircons.') - except: - pydwarf.log.exception('Failed to add reaction to zircons.') - exceptions += 1 - - try: - for beryl in dfraws.all(exact_value='INORGANIC', re_args=['.* BERYL|HELIODOR|MORGANITE|GOSHENITE|EMERALD']): - addaftertemplate(beryl, 'REACTION_CLASS:BERYLLIUM') - pydwarf.log.debug('Added reaction to beryls.') - except: - pydwarf.log.exception('Failed to add reaction to beryls.') - exceptions += 1 + for path in add_paths: + pydwarf.log.debug('Adding file at %s.' % path) + df.add(path=path, loc='raw/objects') - try: - chromite = dfraws.get('INORGANIC:CHROMITE') - pyrolusite = dfraws.get('INORGANIC:PYROLUSITE') - addaftertemplate(chromite, '[METAL_ORE:CHROMIUM_PUTNAM:100][METAL_ORE:IRON:50]') - addaftertemplate(pyrolusite, 'METAL_ORE:MANGANESE_PUTNAM:100') - pydwarf.log.debug('Added titanium ores.') - except: - pydwarf.log.exception('Failed to add titanium ores.') - exceptions += 1 + for path in patch_paths: + response = pydwarf.urist.getfn('pineapple.easypatch')( + df, + files = path, + loc = 'raw/objects', + permit_entities = entities + ) + if not response: return response - try: - for silicon in dfraws.all(exact_value='INORGANIC', re_args=['ANDESITE|OLIVINE|HORNBLENDE|SERPENTINE|ORTHOCLASE|MICROCLINE|MICA']): - addaftertemplate(silicon, 'REACTION_CLASS:SILICON') - pydwarf.log.debug('Added silicon reactions.') - except: - pydwarf.log.exception('Failed to add silicon reactions.') - exceptions += 1 - - try: - dolomite = dfraws.get('INORGANIC:DOLOMITE') - addaftertemplate(dolomite, 'REACTION_CLASS:PIDGEON_PROCESS') - pydwarf.log.debug('Added reaction to dolomite.') - except: - pydwarf.log.exception('Failed to add reaction to dolomite.') - exceptions += 1 - - for root, dirs, files in os.walk(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Materials Plus')): - for filename in files: - suffix = '_mat_plus.txt' - if filename.endswith(suffix): - path = os.path.join(root, filename) - destname = 'putnam_%s' % filename[:-len(suffix)] - rfile = dfraws.getfile(destname) - if rfile: - pydwarf.log.debug('Appending data to file %s from %s...' % (destname, path)) - with open(path, 'rb') as matplusfile: - rfile.add(pretty=matplusfile) - else: - with open(path, 'rb') as matplusfile: - rfile = dfraws.add(raws.file(name=destname, file=matplusfile)) - pydwarf.log.debug('Adding data to new file %s.' % destname) - addedreactions += rfile.all(exact_value='REACTION', args_count=1) - - try: - mountain = dfraws.get('ENTITY:MOUNTAIN') - for reaction in addedreactions: - mountain.add(raws.token(value='PERMITTED_REACTION', args=[reaction.args[0]])) - pydwarf.log.debug('Added %d permitted reactions.' % len(addedreactions)) - except: - pydwarf.log.exception('Failed to add permitted reactions.') - exceptions += 1 - - if exceptions == 0: + if not errors: return pydwarf.success() else: - return pydwarf.failure('Failed to complete %d operations.' % exceptions) + return pydwarf.failure('Failed to add inorganic properties for %d groups.' % errors) diff --git a/scripts/putnam/pydwarf.microreduce.py b/scripts/putnam/pydwarf.microreduce.py index 6c4d09e..10ac938 100644 --- a/scripts/putnam/pydwarf.microreduce.py +++ b/scripts/putnam/pydwarf.microreduce.py @@ -1,6 +1,12 @@ -import os import pydwarf -import raws + + + +reduce_dir = pydwarf.rel(__file__, 'raw/microreduce') + +default_entities = 'MOUNTAIN' + + @pydwarf.urist( name = 'putnam.microreduce', @@ -9,18 +15,10 @@ description = 'A mod to reduce the amount of micromanagement in Dwarf Fortress. One-step soap making and clothesmaking!', compatibility = pydwarf.df_0_40 ) -def microreduce(dfraws): - mountain = dfraws.get('ENTITY:MOUNTAIN') - if mountain: - # Add files - genericpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Microreduce', '%s.txt') - for filename in ('building_macro_fantastic', 'item_macro_fantastic', 'reaction_macro_fantastic'): - rfile = dfraws.add(path=genericpath % filename) - # Add PERMITTED_BUILDING and PERMITTED_REACTION tokens to ENTITY:MOUNTAIN - for building in rfile.all(re_value='BUILDING.*', args_count=1): - mountain.add(raws.token(value='PERMITTED_BUILDING', args=[building.args[0]])) - for reaction in rfile.all(exact_value='REACTION', args_count=1): - mountain.add(raws.token(value='REACTION', args=[reaction.args[0]])) - return pydwarf.success() - else: - return pydwarf.failure('Couldn\'t find ENTITY:MOUNTAIN.') +def microreduce(df, entities=default_entities): + return pydwarf.urist.getfn('pineapple.easypatch')( + df, + files = reduce_dir, + loc = 'raw/objects', + permit_entities = entities + ) diff --git a/scripts/putnam/Materials Plus/entity_default.txt b/scripts/putnam/raw/materialsplus/entity_default.txt similarity index 100% rename from scripts/putnam/Materials Plus/entity_default.txt rename to scripts/putnam/raw/materialsplus/entity_default.txt diff --git a/scripts/putnam/Materials Plus/inorganic_alloys_mat_plus.txt b/scripts/putnam/raw/materialsplus/inorganic_alloys_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_alloys_mat_plus.txt rename to scripts/putnam/raw/materialsplus/inorganic_alloys_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/inorganic_metals_mat_plus.txt b/scripts/putnam/raw/materialsplus/inorganic_metals_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_metals_mat_plus.txt rename to scripts/putnam/raw/materialsplus/inorganic_metals_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/inorganic_other_mat_plus.txt b/scripts/putnam/raw/materialsplus/inorganic_other_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_other_mat_plus.txt rename to scripts/putnam/raw/materialsplus/inorganic_other_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/inorganic_stone_gem.txt b/scripts/putnam/raw/materialsplus/inorganic_stone_gem.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_stone_gem.txt rename to scripts/putnam/raw/materialsplus/inorganic_stone_gem.txt diff --git a/scripts/putnam/Materials Plus/inorganic_stone_layer.txt b/scripts/putnam/raw/materialsplus/inorganic_stone_layer.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_stone_layer.txt rename to scripts/putnam/raw/materialsplus/inorganic_stone_layer.txt diff --git a/scripts/putnam/Materials Plus/inorganic_stone_mineral.txt b/scripts/putnam/raw/materialsplus/inorganic_stone_mineral.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_stone_mineral.txt rename to scripts/putnam/raw/materialsplus/inorganic_stone_mineral.txt diff --git a/scripts/putnam/Materials Plus/inorganic_stone_minerals_mat_plus.txt b/scripts/putnam/raw/materialsplus/inorganic_stone_minerals_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/inorganic_stone_minerals_mat_plus.txt rename to scripts/putnam/raw/materialsplus/inorganic_stone_minerals_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/item_mat_plus.txt b/scripts/putnam/raw/materialsplus/item_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/item_mat_plus.txt rename to scripts/putnam/raw/materialsplus/item_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/reaction_alloys_mat_plus.txt b/scripts/putnam/raw/materialsplus/reaction_alloys_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/reaction_alloys_mat_plus.txt rename to scripts/putnam/raw/materialsplus/reaction_alloys_mat_plus.txt diff --git a/scripts/putnam/Materials Plus/reaction_production_mat_plus.txt b/scripts/putnam/raw/materialsplus/reaction_production_mat_plus.txt similarity index 100% rename from scripts/putnam/Materials Plus/reaction_production_mat_plus.txt rename to scripts/putnam/raw/materialsplus/reaction_production_mat_plus.txt diff --git a/scripts/putnam/Microreduce/building_macro_fantastic.txt b/scripts/putnam/raw/microreduce/building_macro_fantastic.txt similarity index 100% rename from scripts/putnam/Microreduce/building_macro_fantastic.txt rename to scripts/putnam/raw/microreduce/building_macro_fantastic.txt diff --git a/scripts/putnam/Microreduce/entity_default.txt b/scripts/putnam/raw/microreduce/entity_default.txt similarity index 100% rename from scripts/putnam/Microreduce/entity_default.txt rename to scripts/putnam/raw/microreduce/entity_default.txt diff --git a/scripts/putnam/Microreduce/item_macro_fantastic.txt b/scripts/putnam/raw/microreduce/item_macro_fantastic.txt similarity index 100% rename from scripts/putnam/Microreduce/item_macro_fantastic.txt rename to scripts/putnam/raw/microreduce/item_macro_fantastic.txt diff --git a/scripts/putnam/Microreduce/reaction_macro_fantastic.txt b/scripts/putnam/raw/microreduce/reaction_macro_fantastic.txt similarity index 100% rename from scripts/putnam/Microreduce/reaction_macro_fantastic.txt rename to scripts/putnam/raw/microreduce/reaction_macro_fantastic.txt diff --git a/scripts/putnam/readme.md b/scripts/putnam/readme.md new file mode 100644 index 0000000..4ad4cff --- /dev/null +++ b/scripts/putnam/readme.md @@ -0,0 +1,36 @@ +# Fantastic Mini-Mods + +Created by Putnam. (http://putnam3145.github.io/) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +## Materials Plus + +Adds a bunch of materials to the game. + +New reactions in materials plus are: + +1. Steel making using manganese. 3 bars of iron and 1 bar of manganese will get you 4 bars of steel; one iron ore and one manganese ore will get you 4 steel, 3 manganese and 1 iron. +2. High-speed steel making. A slightly stronger variation of steel. Takes 6 bars of steel and one bar each of vanadium, chromium, tungsten and molybdenum and gets you 10 bars of high-speed steel. +3. Beryllium refining. Makes a pair of tokens that automatically goes through the process as available. The process requires: + 1. one [beryllium-bearing boulder OR 4 beryllium-bearing gems](https://github.com/Putnam3145/Fantastic-Mini-Mods/wiki/Materials-plus-Reaction-info) + 2. two fluorite gems + 3. a bucket of sulfuric acid, which itself requires + 1. a boulder of brimstone + 2. any item that contains vanadium (will be preserved) + 3. a bucket + 4. and a boulder of rock salt. This will get you 4 bars of beryllium. Beryllium is very light and stronger than iron, so it should be worth it. +4. The Kroll Process to refine zirconium or titanium. Makes a pair of tokens that automatically goes through the process as available. The process requires: + 1. one [titanium/zirconium-bearing boulder OR 4 titanium/zirconium-bearing gems](https://github.com/Putnam3145/Fantastic-Mini-Mods/wiki/Materials-plus-Reaction-info) + 2. a bar of coke + 3. two bars of a reducer metal (magnesium only for now), which itself requires: + 1. A [magnesium-bearing boulder](https://github.com/Putnam3145/Fantastic-Mini-Mods/wiki/Materials-plus-Reaction-info) + 2. A [silicon-bearing boulder](https://github.com/Putnam3145/Fantastic-Mini-Mods/wiki/Materials-plus-Reaction-info) + 4. This will all get you 4 bars of zirconium/titanium and give you back one bar of the reducer metal. + +## Microreduce + +Reactions included to reduce micromanagement in microreduce are: + +* Steel making from ore; take 4 bars of coal and a boulder of iron ore to make 4 bars of steel. +* Soap macro; automatically makes the lye required to make soap and makes soap as soon as materials are available. +* Full set of clothes; makes a shirt, pants and pair of shoes from 4 cloth items, without needing any micromanagement to make all that individually. diff --git a/scripts/putnam/readme.txt b/scripts/putnam/readme.txt deleted file mode 100644 index 0ed8460..0000000 --- a/scripts/putnam/readme.txt +++ /dev/null @@ -1,2 +0,0 @@ -Fantastic Mini-Mods created by Putnam. (http://putnam3145.github.io/) -Materials Plus and Microreduce ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) From 471c110b016e92d5781996bbb1c489f47ad441fe Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:19:21 -0400 Subject: [PATCH 73/95] Changed importing of objects module in raws --- raws/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raws/__init__.py b/raws/__init__.py index 809208f..222684b 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -31,8 +31,8 @@ from file import rawsbinfile as binfile from file import rawsfile as file # TODO: rename to "rawfile" from dir import rawsdir as dir -from objects import objects, headers, objectdict, headerdict, headerforobject, objectsforheader from copytree import copytree +import objects import color filter = tokenfilter From f6825ddb494d7f1173b407971417c19ed628b0f8 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:19:37 -0400 Subject: [PATCH 74/95] Fixed bug in dir.remove --- raws/dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raws/dir.py b/raws/dir.py index 74d44a5..6f5e7b3 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -183,7 +183,7 @@ def addfiletodicts(self, file, replace=False): if str(file) in self.files: if not replace: raise KeyError('Failed to add file %s to dir because it already contains a file by the same name.' % file) - self.remove(file) + self.remove(self.files[str(file)]) file.dir = self From b41c625a98c4d8c4e232b7bc5d38b6c5d543ea21 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:19:55 -0400 Subject: [PATCH 75/95] More descriptive errors for some raws.dir methods --- raws/dir.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/raws/dir.py b/raws/dir.py index 6f5e7b3..13a3714 100644 --- a/raws/dir.py +++ b/raws/dir.py @@ -95,8 +95,10 @@ def add(self, auto=None, **kwargs): path = kwargs['path'] if os.path.isfile(path): return self.addbyfilepath(**kwargs) - else: + elif os.path.isdir(path): return self.addbydirpath(**kwargs) + else: + raise ValueError('Failed to add file because the path %s refers to neither a file nor a directory.' % path) elif 'filepath' in kwargs: kwargs['path'] = kwargs['filepath'] del kwargs['filepath'] @@ -193,8 +195,12 @@ def addfiletodicts(self, file, replace=False): self.filenames[file.name].append(file) def remove(self, file=None): + if file is None: raise KeyError('Failed to remove file because no file was given.') if isinstance(file, basestring): file = self.getfile(file) - if (file is None) or (file.dir is not self) or not any(file is f for f in self.iterfiles()): raise KeyError('Failed to remove file %s from dir because it doesn\'t belong to the dir.' % file) + + if file.dir is not self: raise KeyError('Failed to remove file because it belongs to a different dir.') + if not any(file is f for f in self.iterfiles()): raise KeyError('Failed to remove file because it doesn\'t belong to this dir.') + self.filenames[file.name].remove(file) self.files[str(file)].dir = None del self.files[str(file)] From 87ac1098ceea261d35bf161e79faf8e9a636197b Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:20:27 -0400 Subject: [PATCH 76/95] Improved smeeprocket readme --- scripts/smeeprocket/readme.md | 6 ++++++ scripts/smeeprocket/readme.txt | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 scripts/smeeprocket/readme.md delete mode 100644 scripts/smeeprocket/readme.txt diff --git a/scripts/smeeprocket/readme.md b/scripts/smeeprocket/readme.md new file mode 100644 index 0000000..8f7174e --- /dev/null +++ b/scripts/smeeprocket/readme.md @@ -0,0 +1,6 @@ +# Transgender and Intersex + +Created by SmeepRocket. (http://www.bay12forums.com/smf/index.php?topic=146312.0) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +This adds transmales, transfemales, intersex males, and intersex females to the dwarf fortress fortress mode game. This does not add it to adventure mode. diff --git a/scripts/smeeprocket/readme.txt b/scripts/smeeprocket/readme.txt deleted file mode 100644 index 19ae8f6..0000000 --- a/scripts/smeeprocket/readme.txt +++ /dev/null @@ -1,4 +0,0 @@ -Transgender and Intersex mod created by SmeepRocket. (http://www.bay12forums.com/smf/index.php?topic=146312.0) -Transgender and Intersex mod ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) - -This adds transmales, transfemales, intersex males, and intersex females to the dwarf fortress fortress mode game. This does not add it to adventure mode. From 7a724e3dac17590c2c9a58331df9f67b583406ae Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:20:43 -0400 Subject: [PATCH 77/95] Removed logging statement I found annoying --- pydwarf/urist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 6cb599a..1efa59f 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -65,7 +65,6 @@ def eval(self, func, args=None): return False else: - log.info('Finished running script %s.' % name) return True def funcs(self, info): From f347f1190ceb41160bef5b89c6725d5de5fcb9eb Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:21:09 -0400 Subject: [PATCH 78/95] Better placement of an important list in pydwarf.config --- pydwarf/config.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pydwarf/config.py b/pydwarf/config.py index aac9128..a308cd0 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -16,6 +16,16 @@ datetimeformat = '%Y.%m.%d.%H.%M.%S' timestamp = datetime.now().strftime(datetimeformat) +auto_paths = [ + 'gamelog.txt', 'errorlog.txt', + 'stderr.log', 'stdout.log', + 'raw/objects', 'raw/graphics', + 'data/art', 'data/init', 'data/speech', + 'dfhack.init', 'dfhack.init-example', 'dfhack.history', + 'hack/lua', 'hack/plugins', 'hack/raw', 'hack/ruby', 'hack/scripts', + 'stonesense', +] + class config: @@ -122,14 +132,7 @@ def setuppackages(self): def setuppaths(self): if self.paths == 'auto' or self.paths == ['auto'] or self.paths == ('auto',): - self.paths = [ - 'gamelog.txt', 'errorlog.txt', - 'stderr.log', 'stdout.log', - 'raw/objects', 'raw/graphics', - 'data/art', 'data/init', 'data/speech', - 'dfhack.init', 'dfhack.init-example', 'dfhack.history', - 'hack/lua', 'hack/plugins', 'hack/raw', 'hack/ruby', 'hack/scripts', - ] + self.paths = auto_paths def setupversion(self): # Handle automatic version detection From 2a04ea6c44fc09ee99d90f2996bacb091e4b3f9c Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:25:06 -0400 Subject: [PATCH 79/95] Fixes and updates in various pineapple scripts - Fixed typo in bauxitetoaluminum - Fixed typo in easypatch - Also fixed typo in woodmechanisms - Updated metalitems to use recent features to do the work instead of working things out for itself - Updated noaquifers to also use new features, code is more terse now - Same for noexotic - Ditto for nograzers - Fixed issue with utils.addhack related to requiring a name argument - Fixed a typo in utils.addobject - Slightly better documentation for utils.permitobjects --- .../pineapple/pydwarf.bauxitetoaluminum.py | 2 - scripts/pineapple/pydwarf.easypatch.py | 2 +- scripts/pineapple/pydwarf.metalitems.py | 41 ++++++++----------- scripts/pineapple/pydwarf.noaquifers.py | 5 ++- scripts/pineapple/pydwarf.noexotic.py | 9 ++-- scripts/pineapple/pydwarf.nograzers.py | 9 ++-- scripts/pineapple/pydwarf.utils.py | 12 +++--- scripts/pineapple/pydwarf.woodmechanisms.py | 2 - 8 files changed, 37 insertions(+), 45 deletions(-) diff --git a/scripts/pineapple/pydwarf.bauxitetoaluminum.py b/scripts/pineapple/pydwarf.bauxitetoaluminum.py index a5a58c3..73a23f4 100644 --- a/scripts/pineapple/pydwarf.bauxitetoaluminum.py +++ b/scripts/pineapple/pydwarf.bauxitetoaluminum.py @@ -2,8 +2,6 @@ -alumtobaux_reaction = - default_entities = ['MOUNTAIN'] default_file = 'raw/objects/reaction_smelter_bauxtoalum_pineapple.txt' diff --git a/scripts/pineapple/pydwarf.easypatch.py b/scripts/pineapple/pydwarf.easypatch.py index c696f30..d375a9d 100644 --- a/scripts/pineapple/pydwarf.easypatch.py +++ b/scripts/pineapple/pydwarf.easypatch.py @@ -22,7 +22,7 @@ def easypatch(df, files, **kwargs): if isinstance(files, basestring): if os.path.isfile(files): - return easypatch_filepath(df, files **kwargs) + return easypatch_filepath(df, files, **kwargs) elif os.path.isdir(files): return easypatch_dirpath(df, files, collision_fails=False, **kwargs) else: diff --git a/scripts/pineapple/pydwarf.metalitems.py b/scripts/pineapple/pydwarf.metalitems.py index 962627b..d605e8d 100644 --- a/scripts/pineapple/pydwarf.metalitems.py +++ b/scripts/pineapple/pydwarf.metalitems.py @@ -1,4 +1,5 @@ import pydwarf +import raws @@ -31,29 +32,19 @@ compatibility = (pydwarf.df_0_3x, pydwarf.df_0_40) ) def metalitems(df, metals=default_metals, items=default_item_tokens): - # Handle each metal - modified = 0 - for inorganictoken in df.allobj('INORGANIC'): - if inorganictoken.args[0] in metals: - metal = inorganictoken.args[0] - pydwarf.log.debug('Handling metal %s...' % metal) - itemtokens = inorganictoken.allprop(value_in=items) - if len(itemtokens) < len(items): - pydwarf.log.debug('Adding tokens to metal %s...' % metal) - # Remove existing item tokens from the list (To avoid making duplicates) - for itemtoken in itemtokens: - itemtoken.remove() - # Add new ones - templatetoken = inorganictoken.getlastprop('USE_MATERIAL_TEMPLATE') - addaftertoken = templatetoken if templatetoken else inorganictoken - for item in items: - addaftertoken.add(item) - modified += 1 - else: - pydwarf.log.debug('Metal %s already allows all the item types specified, skipping.' % metal) - - # All done - if modified > 0: - return pydwarf.success('Added tokens to %d metals.' % modified) + # Turn the item names into a list of tokens + itemtokens = [raws.token(value=item) for item in items] + + # Apply to each metal + affected = df.allobj(type='INORGANIC', id_in=metals).each( + lambda token: ( + token.removeallprop(value_in=items), # Remove existing tokens first to prevent duplicates when adding + token.addprop(raws.token.copy(itemtokens)) # And now add the specified tokens + ) + ) + + # All done! + if affected: + return pydwarf.success('Affected %d metals.' % len(affected)) else: - return pydwarf.failure('No tokens were added to any metals.') + return pydwarf.failure('Affected no metals.') diff --git a/scripts/pineapple/pydwarf.noaquifers.py b/scripts/pineapple/pydwarf.noaquifers.py index dc7fc6b..d5e5937 100644 --- a/scripts/pineapple/pydwarf.noaquifers.py +++ b/scripts/pineapple/pydwarf.noaquifers.py @@ -8,9 +8,10 @@ compatibility = (pydwarf.df_0_27, pydwarf.df_0_28, pydwarf.df_0_3x, pydwarf.df_0_40) ) def noaquifers(df): - aquifers = df.all('AQUIFER') + # Do the removing + aquifers = df.removeall('AQUIFER') + # All done! if len(aquifers): - for aquifer in aquifers: aquifer.remove() return pydwarf.success('Removed %d AQUIFER tokens.' % len(aquifers)) else: return pydwarf.failure('Found no AQUIFER tokens.') diff --git a/scripts/pineapple/pydwarf.noexotic.py b/scripts/pineapple/pydwarf.noexotic.py index 99e2d14..b2248f6 100644 --- a/scripts/pineapple/pydwarf.noexotic.py +++ b/scripts/pineapple/pydwarf.noexotic.py @@ -8,10 +8,11 @@ compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) ) def noexotic(df): - pets = df.all('PET_EXOTIC') - mounts = df.all('MOUNT_EXOTIC') - for token in pets: token.value = 'PET' - for token in mounts: token.value = 'MOUNT' + # Do the removing + pets = df.all('PET_EXOTIC').each(lambda token: token.setvalue('PET')) + mounts = df.all('MOUNT_EXOTIC').each(lambda token: token.setvalue('MOUNT')) + + # All done! if len(pets) or len(mounts): return pydwarf.success('Replaced %d PET_EXOTIC and %d MOUNT_EXOTIC tokens.' % (len(pets), len(mounts))) else: diff --git a/scripts/pineapple/pydwarf.nograzers.py b/scripts/pineapple/pydwarf.nograzers.py index 276547f..6fa141a 100644 --- a/scripts/pineapple/pydwarf.nograzers.py +++ b/scripts/pineapple/pydwarf.nograzers.py @@ -8,10 +8,11 @@ compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) ) def nograzers(df): - grazers = df.all(exact_value='GRAZER') - standardgrazers = df.all('STANDARD_GRAZER') - for grazer in grazers: grazer.remove() - for grazer in standardgrazers: grazer.remove() + # Do the removing + grazers = df.removeall('GRAZER') + standardgrazers = df.removeall('STANDARD_GRAZER') + + # All done! if len(grazers) or len(standardgrazers): return pydwarf.success('Removed %d GRAZER and %d STANDARD_GRAZER tokens.' % (len(grazers), len(standardgrazers))) else: diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 4adfb9c..391dde4 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -81,7 +81,6 @@ def objecttokens(df, object_type, token, add_to=None, remove_from=None): author = 'Sophie Kirschner', description = '''Utility script for adding a new DFHack script.''', arguments = { - 'name': 'The file name of the script to add.', 'auto_run': '''If set to True, a line will be added to dfhack.init containing only the name of the added script. If set to None, no such line will be added. If set to an arbitrary string, that string will be added as a new line at the end of @@ -91,9 +90,11 @@ def objecttokens(df, object_type, token, add_to=None, remove_from=None): }, compatibility = '.*' ) -def addhack(df, name, auto_run, **kwargs): +def addhack(df, auto_run, **kwargs): + name = kwargs.get('name', kwargs.get('path', 'unnamed')) + pydwarf.log.debug('Adding new file %s.' % name) - file = df.add(name=name, **kwargs) + file = df.add(**kwargs) if auto_run: if auto_run is True: auto_run = '\n%s' % file.name @@ -165,7 +166,7 @@ def addobject(df, add_to_file, tokens, type=None, id=None, permit_entities=None, # If add_to_file already exists, fetch it. Otherwise add it to the raws. add_to_file = add_to_file % {'type': object_header.lower()} if add_to_file in df: - file = df.getfile(file) + file = df.getfile(add_to_file) else: file = df.add(add_to_file) file.add(raws.token(value='OBJECT', args=[object_header])) @@ -269,7 +270,8 @@ def permitobject(df, type=None, id=None, permit_entities=None, item_rarity=None) author = 'Sophie Kirschner', description = '''Utility script for permitting several objects at once with entities.''', arguments = { - 'objects': '''An iterable containing type, id tuples for objects to permit.''', + 'objects': '''An iterable containing either tokens or type, id tuples representing objects + to be permitted.''', '**kwargs': 'Passed on to pineapple.utils.permitobject.', }, compatibility = '.*' diff --git a/scripts/pineapple/pydwarf.woodmechanisms.py b/scripts/pineapple/pydwarf.woodmechanisms.py index 9f380f1..bd5c0d3 100644 --- a/scripts/pineapple/pydwarf.woodmechanisms.py +++ b/scripts/pineapple/pydwarf.woodmechanisms.py @@ -2,8 +2,6 @@ -wood_mechanisms_reaction = - default_log_count = 1 default_entities = ['MOUNTAIN', 'PLAINS'] From c0b00b5de81a78d553842d99f3e3908a144d43e9 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:25:20 -0400 Subject: [PATCH 80/95] Shukaro scripts readme edit --- scripts/shukaro/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/shukaro/readme.md b/scripts/shukaro/readme.md index 7b01dfc..6cb98d8 100644 --- a/scripts/shukaro/readme.md +++ b/scripts/shukaro/readme.md @@ -1,7 +1,7 @@ # Creation Forge Created by Shukaro. (http://www.bay12forums.com/smf/index.php?topic=60815) -Forge ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) This is a simple workshop I modded in to help test custom reactions, buildings, and creatures. It's used to create various different items so that you don't have to set up an entire fortress to test some reactions. Hopefully it's a useful tool to people, even if it's just to look at the raw formatting. @@ -9,7 +9,7 @@ This is a simple workshop I modded in to help test custom reactions, buildings, # Dwarven Higher Learning Created by Shukaro. (http://www.bay12forums.com/smf/index.php?topic=60853.0) -Ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) ## Introduction From d5b58a8db74122b89c6098658063dba8c4ffeee1 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:26:49 -0400 Subject: [PATCH 81/95] Added new umiman.smallthings mods - Added umiman.smallthings.threats and umiman.smallthings.nofamily which weren't part of the ModBase port I was originally working off of, since PyDwarf can handle those sorts of mods now - Updated readme --- scripts/umiman/data/smallthings/no_family.txt | 39 ++++ scripts/umiman/data/smallthings/threat.txt | 184 ++++++++++++++++++ scripts/umiman/pydwarf.smallthings.py | 60 ++++-- scripts/umiman/readme.md | 38 ++++ scripts/umiman/readme.txt | 22 --- 5 files changed, 305 insertions(+), 38 deletions(-) create mode 100644 scripts/umiman/data/smallthings/no_family.txt create mode 100644 scripts/umiman/data/smallthings/threat.txt create mode 100644 scripts/umiman/readme.md delete mode 100644 scripts/umiman/readme.txt diff --git a/scripts/umiman/data/smallthings/no_family.txt b/scripts/umiman/data/smallthings/no_family.txt new file mode 100644 index 0000000..51da486 --- /dev/null +++ b/scripts/umiman/data/smallthings/no_family.txt @@ -0,0 +1,39 @@ +I have no family to speak of +I have no family +I have no children, nor lover or mother or father +I am alone +Family is for the weak +I have no material ties to this world +Where I am from, it is considered rude for a stranger to ask such personal questions +Family is meaningless +I refuse to answer that question +Why do you ask me such things? +Family... would be nice +I could've had a family +That question is painful, don't ask it again +What would it matter to you? +I live alone +I have no family for now, but maybe in the future +I have no family now, but my heart is set on another. Ask me in the future when I succeed +... +Tell me about YOUR family +Stop asking me these things +I'm afraid I don't remember +Wh-why do you ask me if I have family? O-o-of course I have a family! Ha! Ha! Ha! What kind of silly fool wouldn't have a family? It must be quite silly to not have a family, wouldn't it? Hahahahaha!!! +That question is pointless +Leave me alone already +I don't have a family and I don't care for one either +I don't have a family. Let's just leave it at that +I have always only depended on myself +I only need strength. Love is but an illusion +Why don't you join me for some drinks and we'll discuss this further at my home +My friends are to me what family is to you +Why do you ask? Are you looking for love? +That's a complicated question with a complicated answer. Ask me something else +Stop asking all these senseless questions! +Don't you have something more important to do? +Family only gets in the way, wouldn't you agree? +My family is in a far away land +I came here to forget my past, please don't ask again +By the Gods, you are annoying +Family, huh? Family... family... family... \ No newline at end of file diff --git a/scripts/umiman/data/smallthings/threat.txt b/scripts/umiman/data/smallthings/threat.txt new file mode 100644 index 0000000..3e2e690 --- /dev/null +++ b/scripts/umiman/data/smallthings/threat.txt @@ -0,0 +1,184 @@ +Prepare to die! +Your blood shall be spilled this day! +I shall delight in your torture! +I shall feast on your entrails! +I damn you to oblivion! +I am going to split you like a ripe melon! +Pray while you still can! +Pray to your maker! +Submit to the cold embrace of death! +Your head will hang on my wall! +Give in to darkness and despair! +You shall burn in eternal hell! +Your life belongs to me! +I will spill your blood across the ground and dance upon your cooling corpse! +What noise will you make when I tear off your limbs one by one? +My god will torture your soul forever when I present him with your charred corpse! +You shall pay for your trespass with your life! +Your agony shall certainly please my god! +I will bet on how many organs I can remove before you stop making noise! +It has been long since I have seen one of your kind die! +After I tear off your limbs, you will squirm until I crush you in my endless mercy! +You will beg for death before I am done! +Die! Die! DIE! +You will be proud to die at my hands! +Perhaps you will be a worthy prey! +There is no escape! +Are you yet afraid? If not, you will be! +You will know nothing but terror when I am through! +I shall scatter your shredded corpse across the land for all to see! +Your body will fall like a limp bag! +Scream now, for you will soon be unable to speak! +Your screams of agony will give me orgastic dreams this night! +I will show you the true meaning of horror! +Have dread, your life ends now! +Yet another [AUDIENCE:RACE] shall kneel before me! +Your innards will soon fly! +Your flesh shall soon meet my weapon! +You will pay for this! +WHAAAAAGGGGHHHH! +Time to die, meat! +I will crush your brains to paste! +I shall exterminate you, vermin! +You will never stop me! +Feel my wrath! +The gods will find your soul unworthy! +Catch this! With your face! +I shall soon teach you in the ways of pain! +That's right! Run! +Your struggles will hardly trouble me! +You blighter! +I will not let this trespass stand! +Your eyes will burst like sickly boils! +You will soon wear your intestines around your neck! +Blood for the Blood God! +Prepare to be blown apart! +I shall draw first blood here! +I will smack you upside the head with a cave fish! +Your universe will become pain! +The stars have foreseen my victory over you, worm! +Soon your life shall be snuffed! +I shall knock you from the frozen wastes to the burning deserts! +You will be stomped into the ground! +I will bash you until your horrid face is unrecognizable! +Can you conceive the wrath you have called down? +You are making me angry, fool. You would not like me when I am angry. +I will wear you lungs as my vest! +I will knit your hair into a merkin! (look it up, but you might be sorry...) +Your suffering will be legendary! +I have been waiting for this! +Perhaps you would like a moment to change your trousers? Ha ha ha ha! +Do you hear that sound? It is the sound of your doom, growing closer, closer, closer! +I would ask your name, but you will be another anonymous corpse soon enough! +Now that we are introduced, let us fight! +You will not take me today! +I guess I must kill you now. So be it! +I will strike you so hard, your ancestors will feel it! +You should just kill yourself now to save us both time, you know... +I will reduce you to a pile of gore! +Stand and fight! +Show me what you've got! +You should've stayed home! +Death is the only escape. +Life is meaningless. It is in death that we are truly tested. +You seek death? +Be still! +You will find no peace in death. +One foot in the grave, firmly planted. +Sheathe your sword, warrior, lest ye fall on it when stricken +What was yours is mine. Your land, your people, and now your life +BEG! for death, and I'll make it quick +This is MY world, you are not welcome in MY world +Your cries for mercy will not move me. +You are a Maggot! hatched from a mutant Maggot egg! +Your death shall mirror your pathetic life! +I've got a blade with your name on it. +Do not trifle with me, worm. +I'm through playing games with you. +Cross me, and DIE. +I will shatter your soul. +Ahh, a fresh crop of, victims. +HaHa, run weaklings, it pleases me. +Your soul is mine! +Can nooone offer me a challenge? +Come over here, I promise I will heal you. +If the Gods had wanted you to live, they would not have created me! +I am going to strangle you with your own frilly training bra! +To the abyss with thee! +I'm going to stick one arm down your throat, one arm up your arse, and twiddle my thumbs in your guts! +Hm hm hm hm hm hm... heh heh heh heh... Ha ha ha ha ha... Hahahahaha... HAHAHAHAHAHAHAHAHAAAAAA!!!!!!!!!!! +Behold! POWER! +Run! Run you little fool! Run as far as your little legs can take you! Make this fun! +Every kill I make leads me on the road to salvation for every murder I have sown. Free me in death! +Say your last words, [AUDIENCE:RACE]. +You DARE challenge me? +Count your steps child, you shall be dead by the tenth. +Not even the power of Armok himself can save you now. +Fool! Perish at my hand! +This is your atonement! +Lavish these final moments, they will be beautiful. +Don't panic. I don't want your bile polluting your flesh. +Bear witness to power incarnate! +You will scream for torture before I'm done with you! +My body SCREAMS for your blood! It is time to drink! +I sharpened my teeth for this? +Imbecile. The time for talk is over. +It does not matter who is right... only who wins. +We shall not be finished... for a long, long time yet. +Cower before my might and I might just crush you quickly. +Call upon all the gods you want. Even they bow before my strength! +Hah! Was that an attack? +You will cry tears of blood today! +This is an act of mercy on my part; I sin so you may be absolved. +Will you not die for me? +Fight with honor! Die with dignity! +Do you not see? You are dead already. +The string of fate has led us to this battle. Prepare yourself. +I can see the insatiable thirst for violence looming within you. Good... +Even if you do not deserve it, I will grant you a fair fight. +Why aren't you running yet? +You are nothing more than gravel beneath my feet. +Away with thee and ye might live to die another die. +It has been awhile since I last feasted on [AUDIENCE:RACE]. +Be a man. Do the right thing. +Mercy is a trait you'd best hope is universal. +Extreme death... +I have waiting long for this moment. Do not disappoint me. +It's time to suffer! +This hand yearns for your soul! +I shall strike you down! +You have already made your final mistake! +Your next move will be your last! +You have not felt suffering yet! +ROAAAAAAAAAAARRRRR!!!! +I will smack your head around like a little ball! +Say hello to my little friend! +Your blood will flow like the crimson sea! +I will purge you from these lands! +No more words! We fight! +You don't believe my might? Then it is time for a live demonstration. +I will rip your beating heart out of your chest! +This will be over soon. +Mortal combat!!!! +You had a chance to turn back long before this. Now you will pay the price for your arrogance. +You are already shivering in fright? Pathetic. +This is your final hour! +I will kill you and the very land you walk on! +Unbridle your fear. This is your last chance to let it show. +You are but a stepping stone in my path to glory. +I see you are wearing your own metal coffin. Makes things easier for both of us. +Fear me, for I am your apocalypse! +It pains me to know that your unclean blood will stain this place. +Your skull had better not be thick enough to nick my weapon. +Listen, [AUDIENCE:RACE]! The whispers of the wind speak only of your death. The world itself begs for your blood! +Once I've killed you, I will hunt down your friends and your family and they will curse your name before they die long and painful deaths. +I grant you a moment to consider your path and repent of your sins. Good. Now you will perish. +If only there were a bounty on your head. Then, I'd soon be a little richer and a LOT happier! +I salute you before your death. +Ugh, you're covered in blood and vomit! I had been hoping to clean my blade on your cloth once it had slaked itself on your blood. +I am destined to be your last foe. +Embrace the end that awaits you! +May the jackals feast on your flesh when I am through with you! +May you find your forebears in the world beyond. +Look into my eyes and see that there is no victory for you. My determination is absolute. You WILL fall here and now and by my hand! +Finally, you have come to me. Know that I am your doom, [AUDIENCE:RACE]. Your training means nothing, for I am better than you. Your strength means nothing, for I am stronger than you. Your speed means nothing for I am faster than you. Your anger, fear and determination will wash against me and turn their flows to your destruction. Hope will die in your heart even as I wrench the life from your body. Yes, worm, I will end you. \ No newline at end of file diff --git a/scripts/umiman/pydwarf.smallthings.py b/scripts/umiman/pydwarf.smallthings.py index 1c06932..affe7e1 100644 --- a/scripts/umiman/pydwarf.smallthings.py +++ b/scripts/umiman/pydwarf.smallthings.py @@ -1,15 +1,18 @@ -import os import pydwarf import raws -smalldir = pydwarf.rel(__file__, 'raw/smallthings') + + +threats_path = pydwarf.rel(__file__, 'data/smallthings/threat.txt') +nofamily_path = pydwarf.rel(__file__, 'data/smallthings/no_family.txt') +small_dir = pydwarf.rel(__file__, 'raw/smallthings') # A bit of esoteric code which makes smallraws only be read once def getsmallraws(): if 'smallraws' not in globals(): - globals()['smallraws'] = raws.dir(root=smalldir, log=pydwarf.log) + globals()['smallraws'] = raws.dir(root=small_dir, log=pydwarf.log) return smallraws @@ -46,19 +49,17 @@ def getsmallraws(): fleshed out descriptions of the things your dwarves like, as well as more varied ones. With five each, it's pretty rare to see the same two twice. Hopefully I don't have any repeating prefstrings. - - Originally created by Umiman. Ported to ModBase by Fieari. Ported again to - PyDwarf by Sophie.''', + ''', compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) ) -def prefstring(dfraws): +def prefstring(df): # Get the smallthings ModBase raws, which is where this data will be coming from smallraws = getsmallraws() if not smallraws: return pydwarf.failure('Failed to read smallthings raws.') # Get all creatures smallcreatures = smallraws.allobj('CREATURE') - dfcreaturesdict = dfraws.objdict('CREATURE') + dfcreaturesdict = df.objdict('CREATURE') # Add the new prefstrings failedcreatures = 0 @@ -91,24 +92,22 @@ def prefstring(dfraws): Basically, I added maybe 100 or so new engravings you can potentially see on your floors, walls, studded armour, images, and the like. Keep in mind maybe one or two metagame just a tad but it's funny! I swear! - - Originally created by Umiman. Ported to ModBase by Fieari. Ported again to - PyDwarf by Sophie.''', + ''', compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) ) -def engraving(dfraws): - if 'descriptor_shape_umiman' in dfraws: return pydwarf.failure('File descriptor_shape_umiman already exists.') +def engraving(df): + if 'descriptor_shape_umiman' in df: return pydwarf.failure('File descriptor_shape_umiman already exists.') # Get the smallthings ModBase raws, which is where this data will be coming from smallraws = getsmallraws() if not smallraws: return pydwarf.failure('Failed to read smallthings raws.') # Get existing words and shapes - dfwordsdict = dfraws.objdict('WORD') - dfshapesdict = dfraws.objdict('SHAPE') + dfwordsdict = df.objdict('WORD') + dfshapesdict = df.objdict('SHAPE') # Add a new file for the new shapes - dfshapesfile = dfraws.add('raw/objects/descriptor_shape_umiman.txt') + dfshapesfile = df.add('raw/objects/descriptor_shape_umiman.txt') dfshapesfile.add('OBJECT:DESCRIPTOR_SHAPE') shapesadded = 0 @@ -147,3 +146,32 @@ def engraving(dfraws): # All done! return pydwarf.success('Added %s new shapes.' % shapesadded) + +@pydwarf.urist( + name = 'umiman.smallthings.speech.threats', + version = '1.0.0', + author = ('Umiman', 'Sophie Kirschner'), + description = '''Awhile back I asked the community to contribute to fill out the + threat.txt which is used in adventurer when someone threatens you. I.E: in vanilla, + when you face a megabeast or someone who has killed a named creature, they will + talk about who they killed and then say, "prepare to die!!!". That's all they said. + Boring. This compilation has some of the best threats (around 150 and counting) + compiled from that thread and should make killing things too proud of their own + achievements a lot more fun. + ''', + compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) +) +def threats(df): + df.add(path=threats_path, loc='data/speech', kind=raws.binfile, replace=True) + return pydwarf.success() + +@pydwarf.urist( + name = 'umiman.smallthings.speech.nofamily', + version = '1.0.0', + author = ('Umiman', 'Sophie Kirschner'), + description = '''Adds more dialog options to no_family.txt.''', + compatibility = (pydwarf.df_0_2x, pydwarf.df_0_3x, pydwarf.df_0_40) +) +def nofamily(df): + df.add(path=nofamily_path, loc='data/speech', kind=raws.binfile, replace=True) + return pydwarf.success() diff --git a/scripts/umiman/readme.md b/scripts/umiman/readme.md new file mode 100644 index 0000000..eff04c0 --- /dev/null +++ b/scripts/umiman/readme.md @@ -0,0 +1,38 @@ +# Smallthings + +Orignially created by Umiman. (http://www.bay12forums.com/smf/index.php?topic=18552.0) +Rewritten for ModBase by Fieari. (http://dffd.bay12games.com/file.php?id=271) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +A collection of descriptive mods which aim to add more spice to Dwarf Fortress's text and dialogue. + +## engraving + +Has this been done before? While I think it's impossible to change any of the inbuilt engraving stuff like, "this is a picture of a dwarf and a tentacle demon. The dwarf is embracing the tentacle demon", it is possible to edit and add to more basic ones such as "this is a picture of a crescent moon". Basically, I added maybe 200 or so new engravings you can potentially see on your floors, walls, studded armour, images, and the like. Keep in mind maybe one or two metagame just a tad but it's funny! I swear! + +## prefstring + +This mod simply just adds to the number of prefstrings for everything in the game to a minimum of five each. Prefstrings are the stuff that tell your dwarves what to like about a creature. For example, "He likes large roaches for their ability to disgust". With this mod, you'll see more fleshed out descriptions of the things your dwarves like, as well as more varied ones. With five each, it's pretty rare to see the same two twice. Hopefully I don't have any repeating prefstrings + +Plant prefstrings still need to be added. + +Many of these are collected from the community. The philosophy behind each included addition is as follows: + +* The mod should be universal. I.E: if someone were to use this mod in conjunction with others, they should not only be compatible, but not break the illusion. +* The mod should be in the vein of the Dwarf Fortress universe and not break the illusion that you are in it (except in rare cases of comedy). +* The mod should be as descriptively neutral as possible and so work for goblins just as it would for elves or dwarves so as to not break the illusion. +* Try to put things Toady would have put in if he had the time. +* Only focus on what the mod is supposed to do. Hence, my small things mod will not do ANYTHING except add descriptive words and text. +* Maintain the illusion.] + +## speech + +Awhile back I asked the community to contribute to fill out the threat.txt which is used in adventurer when someone threatens you. I.E: in vanilla, when you face a megabeast or someone who has killed a named creature, they will talk about who they killed and then say, "prepare to die!!!". That's all they said. Boring. This compilation has some of the best threats (around 150 and counting) compiled from that thread and should make killing things too proud of their own achievements a lot more fun. + +### nofamily + +Adds dialog options to no_family.txt, the vanilla version of which contains only "I have no family to speak of". + +### threats + +Adds dialog options to threats.txt, the vanilla version of which contains only "Prepare to die!". diff --git a/scripts/umiman/readme.txt b/scripts/umiman/readme.txt deleted file mode 100644 index 8de3ddc..0000000 --- a/scripts/umiman/readme.txt +++ /dev/null @@ -1,22 +0,0 @@ -Smallthings orignially created by Umiman. (http://www.bay12forums.com/smf/index.php?topic=18552.0) -Smallthings ported to ModBase by Fieari. (http://dffd.bay12games.com/file.php?id=271) -Smallthings ModBase version ported to PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) - -Creator; umiman and the Bay12games Forum Community -Converted to ModBase format by Fieari - -A collection of descriptive mods which aim to add more spice to Dwarf Fortress's text and dialogue. - -* Prefstring minimod: Adds more flavour text to a dwarf's profile. Dwarves now have at least 5 proper reasons to like something instead of the current basic text. -* Engraving minimod: Adds over 200 new engravings so you won't have to constantly see "This is an engraving of a square" - -Plant prefstrings still need to be added. - -Many of these are collected from the community. The philosophy behind each included addition is as follows: - -1. The mod should be universal. I.E: if someone were to use this mod in conjunction with others, they should not only be compatible, but not break the illusion. -2. The mod should be in the vein of the Dwarf Fortress universe and not break the illusion that you are in it (except in rare cases of comedy). -3. The mod should be as descriptively neutral as possible and so work for goblins just as it would for elves or dwarves so as to not break the illusion. -4. Try to put things Toady would have put in if he had the time. -5. Only focus on what the mod is supposed to do. Hence, my small things mod will not do ANYTHING except add descriptive words and text. -6. Maintain the illusion.] From 4477d162e42649c40f83a85ac328382f84820c2c Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:27:20 -0400 Subject: [PATCH 82/95] Renamed pydwarf.armourypack.py to pydwarf.armoury.py Feels a little cleaner this way --- scripts/stal/{pydwarf.armourypack.py => pydwarf.armoury.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/stal/{pydwarf.armourypack.py => pydwarf.armoury.py} (100%) diff --git a/scripts/stal/pydwarf.armourypack.py b/scripts/stal/pydwarf.armoury.py similarity index 100% rename from scripts/stal/pydwarf.armourypack.py rename to scripts/stal/pydwarf.armoury.py From acd9a4e3636fff08f9aad8bdc584b946f6fc4722 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Mon, 6 Jul 2015 17:27:44 -0400 Subject: [PATCH 83/95] Added State of Decay scripts under omniclasm.decay --- .../hack/decay/deteriorateclothes.rb | 66 +++++++++++ .../hack/decay/deterioratecorpses.rb | 78 +++++++++++++ .../omniclasm/hack/decay/deterioratefood.rb | 70 ++++++++++++ scripts/omniclasm/hack/decay/starvingdead.rb | 84 ++++++++++++++ scripts/omniclasm/pydwarf.decay.py | 107 ++++++++++++++++++ scripts/omniclasm/readme.md | 56 +++++++++ 6 files changed, 461 insertions(+) create mode 100644 scripts/omniclasm/hack/decay/deteriorateclothes.rb create mode 100644 scripts/omniclasm/hack/decay/deterioratecorpses.rb create mode 100644 scripts/omniclasm/hack/decay/deterioratefood.rb create mode 100644 scripts/omniclasm/hack/decay/starvingdead.rb create mode 100644 scripts/omniclasm/pydwarf.decay.py create mode 100644 scripts/omniclasm/readme.md diff --git a/scripts/omniclasm/hack/decay/deteriorateclothes.rb b/scripts/omniclasm/hack/decay/deteriorateclothes.rb new file mode 100644 index 0000000..dc61965 --- /dev/null +++ b/scripts/omniclasm/hack/decay/deteriorateclothes.rb @@ -0,0 +1,66 @@ +class DeteriorateClothes + + def initialize + end + + def process + return false unless @running + + items = [df.world.items.other[:GLOVES], + df.world.items.other[:ARMOR], + df.world.items.other[:SHOES], + df.world.items.other[:PANTS], + df.world.items.other[:HELM]] + + items.each { |type| + type.each { |i| + if (i.subtype.armorlevel == 0 and i.flags.on_ground == true and i.wear > 0) + i.wear_timer *= i.wear + 0.5 + if (i.wear > 2) + i.flags.garbage_collect = true + end + end + } + } + + + end + + def start + @onupdate = df.onupdate_register('deteriorateclothes', 1200, 1200) { process } + @running = true + + puts "Deterioration of old clothes commencing..." + + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + @running ? 'Running.' : 'Stopped.' + end + +end + +case $script_args[0] +when 'start' + if ($DeteriorateClothes) + $DeteriorateClothes.stop + end + + $DeteriorateClothes = DeteriorateClothes.new + $DeteriorateClothes.start + +when 'end', 'stop' + $DeteriorateClothes.stop + +else + if $DeteriorateClothes + puts $DeteriorateClothes.status + else + puts 'Not loaded.' + end +end \ No newline at end of file diff --git a/scripts/omniclasm/hack/decay/deterioratecorpses.rb b/scripts/omniclasm/hack/decay/deterioratecorpses.rb new file mode 100644 index 0000000..982dc02 --- /dev/null +++ b/scripts/omniclasm/hack/decay/deterioratecorpses.rb @@ -0,0 +1,78 @@ +class DeteriorateCorpses + + def initialize + end + + def process + return false unless @running + + df.world.items.other[:ANY_CORPSE].each { |i| + if (i.flags.dead_dwarf == false) + i.wear_timer += 1 + if (i.wear_timer > 24 + rand(8)) + i.wear_timer = 0 + i.wear += 1 + end + if (i.wear > 3) + i.flags.garbage_collect = true + end + + end + + } + + df.world.items.other[:REMAINS].each { |i| + if (i.flags.dead_dwarf == false) + i.wear_timer += 1 + if (i.wear_timer > 6) + i.wear_timer = 0 + i.wear += 1 + end + if (i.wear > 3) + i.flags.garbage_collect = true + end + + end + + } + + end + + def start + @onupdate = df.onupdate_register('deterioratecorpses', 1200, 1200) { process } + @running = true + + puts "Deterioration of body parts commencing..." + + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + @running ? 'Running.' : 'Stopped.' + end + +end + +case $script_args[0] +when 'start' + if ($DeteriorateCorpses) + $DeteriorateCorpses.stop + end + + $DeteriorateCorpses = DeteriorateCorpses.new + $DeteriorateCorpses.start + +when 'end', 'stop' + $DeteriorateCorpses.stop + +else + if $DeteriorateCorpses + puts $DeteriorateCorpses.status + else + puts 'Not loaded.' + end +end \ No newline at end of file diff --git a/scripts/omniclasm/hack/decay/deterioratefood.rb b/scripts/omniclasm/hack/decay/deterioratefood.rb new file mode 100644 index 0000000..697adc0 --- /dev/null +++ b/scripts/omniclasm/hack/decay/deterioratefood.rb @@ -0,0 +1,70 @@ +class DeteriorateFood + + def initialize + end + + def process + return false unless @running + + items = [df.world.items.other[:FISH], + df.world.items.other[:FISH_RAW], + df.world.items.other[:EGG], + df.world.items.other[:CHEESE], + df.world.items.other[:PLANT], + df.world.items.other[:PLANT_GROWTH], + df.world.items.other[:FOOD]] + + items.each { |type| + type.each { |i| + i.wear_timer += 1 + if (i.wear_timer > 24 + rand(8)) + i.wear_timer = 0 + i.wear += 1 + end + if (i.wear > 3) + i.flags.garbage_collect = true + end + } + } + + + end + + def start + @onupdate = df.onupdate_register('deterioratefood', 1200, 1200) { process } + @running = true + + puts "Deterioration of food commencing..." + + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + @running ? 'Running.' : 'Stopped.' + end + +end + +case $script_args[0] +when 'start' + if ($DeteriorateFood) + $DeteriorateFood.stop + end + + $DeteriorateFood = DeteriorateFood.new + $DeteriorateFood.start + +when 'end', 'stop' + $DeteriorateFood.stop + +else + if $DeteriorateFood + puts $DeteriorateFood.status + else + puts 'Not loaded.' + end +end \ No newline at end of file diff --git a/scripts/omniclasm/hack/decay/starvingdead.rb b/scripts/omniclasm/hack/decay/starvingdead.rb new file mode 100644 index 0000000..193628d --- /dev/null +++ b/scripts/omniclasm/hack/decay/starvingdead.rb @@ -0,0 +1,84 @@ +class StarvingDead + + def initialize + @threshold = 1 + @die_threshold = 3 + end + + def process + return false unless @running + + month_length = 67200 + + if (@undead_count >= 25) + month_length *= 25 / @undead_count + end + + @undead_count = 0 + df.world.units.active.each { |u| + if (u.enemy.undead and not u.flags1.dead) + @undead_count += 1 + if (u.curse.time_on_site > month_length * @threshold) + u.body.physical_attrs.each { |att| + att.value = att.value - (att.value * 0.02) + } + end + + if (u.curse.time_on_site > month_length * @die_threshold) + u.flags1.dead = true + u.curse.rem_tags2.FIT_FOR_ANIMATION = true + end + + end + + } + + end + + def start + @onupdate = df.onupdate_register('starvingdead', 1200, 1200) { process } + @running = true + @undead_count = 0 + + if ($script_args[1] and $script_args[1].gsub(/[^0-9\.]/,'').to_f > 0) + @threshold = $script_args[1].gsub(/[^0-9\.]/,'').to_f + end + + if ($script_args[2] and $script_args[2].gsub(/[^0-9\.]/,'').to_f > 0) + @die_threshold = $script_args[2].gsub(/[^0-9\.]/,'').to_f + end + + puts "Starving Dead starting...weakness starts at #{@threshold} months, true death at #{@die_threshold} months" + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + @running ? 'Running.' : 'Stopped.' + end + +end + +case $script_args[0] +when 'start' + + if ($StarvingDead) + $StarvingDead.stop + end + + $StarvingDead = StarvingDead.new + $StarvingDead.start + +when 'end', 'stop' + $StarvingDead.stop + +else + if $StarvingDead + puts $StarvingDead.status + else + puts 'Not loaded.' + end +end \ No newline at end of file diff --git a/scripts/omniclasm/pydwarf.decay.py b/scripts/omniclasm/pydwarf.decay.py new file mode 100644 index 0000000..53a717b --- /dev/null +++ b/scripts/omniclasm/pydwarf.decay.py @@ -0,0 +1,107 @@ +import pydwarf +import raws + + + +decay_dir = pydwarf.rel(__file__, 'hack/decay') + + + +@pydwarf.urist( + name = 'omniclasm.decay.starvingdead', + version = '1.0.0', + author = ('Omniclasm', 'Sophie Kirschner'), + description = '''With this script running, all undead that have been on the map for a + time (default: 1 month) start to gradually decay, losing strength, speed, and + toughness. After they have been on the map for even longer (default: 3 months), + they collapse upon themselves, never to be reanimated. + ''', + arguments = { + 'start': 'Number of months before decay sets in.', + 'die': 'Number of months before collapsing entirely.', + 'auto_run': 'If set to True then the script will be started automatically upon startup.' + }, + compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) +) +def starvingdead(df, start=1, die=3, auto_run=True): + return pydwarf.urist.getfn('pineapple.utils.addhack')( + df, + auto_run = 'starvingdead start %d %d' % (start, die) if auto_run is True else auto_run, + loc = 'hack/scripts', + path = pydwarf.rel(decay_dir, 'starvingdead.rb'), + kind = raws.reffile + ) + + + +@pydwarf.urist( + name = 'omniclasm.decay.deteriorate.corpses', + version = '1.0.0', + author = ('Omniclasm', 'Sophie Kirschner'), + description = '''In long running forts, especially evil biomes, you end up with a lot of + toes, teeth, fingers, and limbs scattered all over the place. Various corpses from + various sieges, stray kitten corpses, probably some heads. This script causes all of + those to rot away into nothing after several months. + ''', + arguments = { + 'auto_run': 'If set to True then the script will be started automatically upon startup.' + }, + compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) +) +def deterioratecorpses(df, auto_run=True): + return pydwarf.urist.getfn('pineapple.utils.addhack')( + df, + auto_run = 'deterioratecorpses start' if auto_run is True else auto_run, + loc = 'hack/scripts', + path = pydwarf.rel(decay_dir, 'deterioratecorpses.rb'), + kind = raws.reffile + ) + + + +@pydwarf.urist( + name = 'omniclasm.decay.deteriorate.clothes', + version = '1.0.0', + author = ('Omniclasm', 'Sophie Kirschner'), + description = '''This script is fairly straight forward. All of those slightly worn wool + shoes that dwarves scatter all over the place will deteriorate at a greatly increased + rate, and eventually just crumble into nothing. As warm and fuzzy as a dining room + full of used socks makes your dwarves feel, your FPS does not like it. + ''', + arguments = { + 'auto_run': 'If set to True then the script will be started automatically upon startup.' + }, + compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) +) +def deteriorateclothes(df, auto_run=True): + return pydwarf.urist.getfn('pineapple.utils.addhack')( + df, + auto_run = 'deteriorateclothes start' if auto_run is True else auto_run, + loc = 'hack/scripts', + path = pydwarf.rel(decay_dir, 'deteriorateclothes.rb'), + kind = raws.reffile + ) + + + +@pydwarf.urist( + name = 'omniclasm.decay.deteriorate.food', + version = '1.0.0', + author = ('Omniclasm', 'Sophie Kirschner'), + description = '''With this script running, all food and plants wear out and disappear after + several months. Barrels and stockpiles will keep them from rotting, but it won't keep + them from decaying. + ''', + arguments = { + 'auto_run': 'If set to True then the script will be started automatically upon startup.' + }, + compatibility = (pydwarf.df_0_40, pydwarf.df_0_3x) +) +def deterioratefood(df, auto_run=True): + return pydwarf.urist.getfn('pineapple.utils.addhack')( + df, + auto_run = 'deterioratefood start' if auto_run is True else auto_run, + loc = 'hack/scripts', + path = pydwarf.rel(decay_dir, 'deterioratefood.rb'), + kind = raws.reffile + ) diff --git a/scripts/omniclasm/readme.md b/scripts/omniclasm/readme.md new file mode 100644 index 0000000..71de172 --- /dev/null +++ b/scripts/omniclasm/readme.md @@ -0,0 +1,56 @@ +# State of Decay + +Created by Omniclasm. (http://www.bay12forums.com/smf/index.php?topic=150782.0) +Rewritten for PyDwarf by Sophie Kirschner. (http://www.pineapplemachine.com) + +Collection of DFHack scripts that are somewhere between a "mod" and a "fps booster". Fully modular, you can pick and choose which scripts you want or don't want. + +## starvingdead + +This was the original script, and probably has the least impact on vanilla gameplay. It mostly helps prevent undead cascades in the caverns. What is an undead cascade? An undead cascade is when a forgotten beast kills a (living) troglodyte, then the now reanimated troglodyte kills the forgotten beast, then they both proceed to kill every living thing in the cavern. Then every previously living thing reanimates. Then they kill every other living thing. Then those reanimate as well, and next thing you know, you have 300+ undead roaming the caverns and destroying your FPS. + +So, how does this script prevent that? With this script running, all undead that have been on the map for a time (default: 1 month) start to gradually decay, losing strength, speed, and toughness. After they have been on the map for even longer (default: 3 months), they collapse upon themselves, never to be reanimated. + +Usage: +``` shell +starvingdead start +starvingdead stop +``` + +## deteriorate + +These modules have a general concept of "use it or lose it". If something isn't being used, it quickly wears away until it no longer exists, freeing up some valuable FPS. + +### corpses + +In long running forts, especially evil biomes, you end up with a lot of toes, teeth, fingers, and limbs scattered all over the place. Various corpses from various sieges, stray kitten corpses, probably some heads. Basically, your map will look like a giant pile of assorted body parts, all of which individually eat up a small part of your FPS, which collectively eat up quite a bit. + +In addition, this script also targets various butchery byproducts. Enjoying your thriving animal industry? Your FPS does not. Those thousands of skulls, bones, hooves, and wool eat up precious FPS that could be used to kill goblins and elves. Whose corpses will also get destroyed by the script to kill more goblins and elves. + +This script causes all of those to rot away into nothing after several months. + +Usage: +``` shell +deterioratecorpses start +deterioratecorpses stop +``` + +### clothes + +This script is fairly straight forward. All of those slightly worn wool shoes that dwarves scatter all over the place will deteriorate at a greatly increased rate, and eventually just crumble into nothing. As warm and fuzzy as a dining room full of used socks makes your dwarves feel, your FPS does not like it. + +Usage: +``` shell +deteriorateclothes start +deteriorateclothes stop +``` + +### food + +This script is...pretty far reaching. However, almost all long running forts I've had end up sitting on thousands and thousands of food items. Several thousand cooked meals, three thousand plump helmets, just as many fish and meat. It gets pretty absurd. And your FPS doesn't like it. With this script running, all food and plants wear out and disappear after several months. Barrels and stockpiles will keep them from rotting, but it won't keep them from decaying. No more sitting on a hundred years worth of food. No more keeping barrels of pig tails sitting around until you decide to use them. Either use it, eat it, or lose it. Seeds, are excluded from this, if you aren't planning on using your pig tails, hold onto the seeds for a rainy day. + +Usage: +``` shell +deterioratefood start +deterioratefood stop +``` From c91f07415f3c3f2677c89bc27474c7394eb0a955 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 07:54:54 -0400 Subject: [PATCH 84/95] Updated headers in manager.py and __init__ files of packages --- manager.py | 13 +++++++--- pydwarf/__init__.py | 14 +++++++++-- raws/__init__.py | 20 ++++++++++++--- scripts/__init__.py | 59 +++++++++++++++++++++++++++------------------ 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/manager.py b/manager.py index e6f93a0..e8d3184 100644 --- a/manager.py +++ b/manager.py @@ -1,3 +1,12 @@ +#!/usr/bin/env python + +__author__ = 'Sophie Kirschner' +__license__ = 'zlib/libpng' +__email__ = 'sophiek@pineapplemachine.com' +__version__ = '1.0.1' + + + import re import os import shutil @@ -9,10 +18,6 @@ -__version__ = '1.0.1' - - - jsonconfigpath = 'config.json' diff --git a/pydwarf/__init__.py b/pydwarf/__init__.py index 0d0f5c4..c804485 100644 --- a/pydwarf/__init__.py +++ b/pydwarf/__init__.py @@ -1,3 +1,12 @@ +#!/usr/bin/env python + +__author__ = 'Sophie Kirschner' +__license__ = 'zlib/libpng' +__email__ = 'sophiek@pineapplemachine.com' +__version__ = '1.0.1' + + + ''' The pydwarf package acts as a layer of abstraction over the raws package, providing functionality for mod management and application. @@ -8,14 +17,15 @@ pydwarf.urist: A combined decorator and global repository for PyDwarf plugins. pydwarf.session: Ideally for use by a mod manager, abstracts the handling and execution of PyDwarf plugins. pydwarf.config: Provides an object to simplify config loading and application. +pydwarf.helpers: Contains some miscellaneous utility functions. ''' + + from log import * from version import * from response import * from urist import * from config import * from helpers import * - -__version__ = '1.0.1' diff --git a/raws/__init__.py b/raws/__init__.py index 222684b..51baaed 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -1,10 +1,18 @@ +#!/usr/bin/env python + +__author__ = 'Sophie Kirschner' +__license__ = 'zlib/libpng' +__email__ = 'sophiek@pineapplemachine.com' +__version__ = '1.0.1' + + + ''' The raws package provides querying and modification functionality for Dwarf Fortress raws. raws.dir: An entire directory of raws files, stored as a dictionary of files. raws.dfhack: Possessed by dir objects as a hack attribute, the class exposes methods for interacting with DFHack files. -raws.file: A single raws file, stored as a linked list. raws.token: A single token within a raws file, for example [CREATURE:DWARF] or [INORGANIC:IRON]. raws.tokenlist: Extends Python's inbuilt list class with additional, specialized functionality. raws.queryable: Many raws classes extend this class, which provides token querying functionality. @@ -13,6 +21,11 @@ raws.boolfilter: Can be used in place of a tokenfilter for operations like Filter A OR Filter B. raws.color: Contains a convenience class and objects for dealing with colors in the DF raws. raws.copytree: A general utility method for copying an entire directory and its contents from one location to another. +raws.objecs: Contains information and helper functions for knowing which object types belong to which headers, such as how [BUILDING_WORKSHOP:ID] belongs to [OBJECT:BUILDING]. +raws.file: A single raws file, stored as a linked list. +raws.reffile: A file stored as a reference to a source file. +raws.binfile: A file stored in a string, as its binary content. +raws.basefile: A base class which other file types inherit from. raws.filter: A convenience alias for raws.tokenfilter. raws.parse: A convenience alias for raws.token.parse, which accepts an input string and parses it into a tokenlist. @@ -20,6 +33,8 @@ ''' + + from filters import rawstokenfilter as tokenfilter from filters import rawsboolfilter as boolfilter from queryable import rawsqueryable as queryable @@ -36,8 +51,5 @@ import color filter = tokenfilter - parse = token.parse parseone = token.parseone - -__version__ = '1.0.1' diff --git a/scripts/__init__.py b/scripts/__init__.py index ff54b1d..fe9950a 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -1,23 +1,36 @@ -# Imports all scripts in this directory - -import sys -sys.path.append('../') - -import os -import imp -import pydwarf - -__all__ = [] - -for root, dirs, files in os.walk(os.path.dirname(os.path.realpath(__file__))): - for filename in files: - path = os.path.join(root, filename) - modulename = '.'.join(os.path.basename(filename).split('.')[1:-1]) - if filename.endswith('.py') and filename.startswith('pydwarf.'): - try: - with open(path, 'U') as modulefile: - module = imp.load_module(modulename, modulefile, path, ('.py', 'U', imp.PY_SOURCE)) - globals()[modulename] = module - __all__.append(modulename) - except: - pydwarf.log.exception('Failed to load script from %s' % path) +#!/usr/bin/env python + +__author__ = 'Sophie Kirschner' +__license__ = 'zlib/libpng' +__email__ = 'sophiek@pineapplemachine.com' +__version__ = '1.0.1' + + + +'''Recursively imports every PyDwarf script in this directory.''' + + + +import sys +sys.path.append('../') + +import os +import imp +import pydwarf + + + +__all__ = [] + +for root, dirs, files in os.walk(os.path.dirname(os.path.realpath(__file__))): + for filename in files: + path = os.path.join(root, filename) + modulename = '.'.join(os.path.basename(filename).split('.')[1:-1]) + if filename.endswith('.py') and filename.startswith('pydwarf.'): + try: + with open(path, 'U') as modulefile: + module = imp.load_module(modulename, modulefile, path, ('.py', 'U', imp.PY_SOURCE)) + globals()[modulename] = module + __all__.append(modulename) + except: + pydwarf.log.exception('Failed to load script from %s' % path) From 19ff08d1f6474311e9908c4d38476e51915b101f Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 07:55:03 -0400 Subject: [PATCH 85/95] Updated config.md --- docs/config.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/config.md b/docs/config.md index 557a35d..0b12929 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,22 +2,26 @@ The steps given here are specifically for Windows, but the procedure on other operating systems will be almost identical. -- First thing, ensure that [Python 2.7](https://www.python.org/download/releases/2.7.8/) is installed on your computer. If this is the only version of Python you have installed then the commands the following steps will tell you to run should look exactly like they're presented, and are run by opening a command line in PyDwarf's root directory, typing the given text, then hitting enter. If you have other versions of Python installed as well, you'll likely need to do some tinkering with environment variables that's outside the scope of this example. +- First thing, ensure that [Python 2.7](https://www.python.org/download/releases/2.7.8/) is installed on your computer. If you have another version of Python already installed this can be a little complicated but there are lots of [helpful resources](http://stackoverflow.com/questions/4583367/how-to-run-multiple-python-version-on-windows) available to guide you through it. -- In order to keep everything working as smoothly as possible, the first thing you should do is to copy your Dwarf Fortress directory to another location before running PyDwarf. Navigate to the directory containing your Dwarf Fortress folder, copy it, and paste it somewhere. It may be easiest to place the copy in the same location and append `_vanilla` to the end of the directory's name. +- In order to keep everything working as smoothly as possible, you should copy your Dwarf Fortress directory to another location before messing about with PyDwarf. Navigate to the directory containing your Dwarf Fortress folder, copy it, and paste it somewhere. It may be easiest to place the copy in the same location and append `_original` to the end of the directory's name. After doing this you might, for example, have a folder at `C:/df_40_24_win` and another at `C:/df_40_24_win_original`. -- Right-click on `config.json`, located in PyDwarf's root directory, and select `Open with`. In the `Open with` menu, select `Choose default program` and find Notepad in the list of programs. Upon selecting Notepad, a window will appear showing the contents of `config.json`. +- If you haven't already, you'll need to download PyDwarf and extract the archive somewhere. It much doesn't matter where, though I recommend you *don't* put it inside your Dwarf Fortress directory. The most important files located in here are named `manager.py`, which is for actually running PyDwarf, and `config.json`, for telling it precisely what to do when it runs. -- Now you're looking at a JSON file. It's assigns several parameters in the format of `"name": value,` and the most important ones right now are the ones named `input`, `output`, `backup`, and `scripts`. You can see that most of the values are text information, but `scripts` in particular is assigned a list of values contained within square brackets. +- You'll want to open the `config.json` file, located in PyDwarf's root directory, with a text editor such as Notepad. And then you'll be looking at a JSON file. It assigns several parameters in the format of `"name": value,` and the most important ones right now are the ones named `input`, `output`, `backup`, and `scripts`. You can see that most of the values are text information enclosed within quotes, but `scripts` in particular is assigned a list of values contained within square brackets. -- Set the value for `input`, which is a file path, to the location of that copy of Dwarf Fortress you made in a previous step. This tells PyDwarf where to read your files from so that they can be worked upon by various mods. +- Set the value for `input`, which is a file path, to the location of that copy of Dwarf Fortress you made in a previous step. This tells PyDwarf where to read your files from so that they can be worked upon by various mods. For example, this file path might be something like `C:/df_40_24_win_original`. -- Set the value for `output`, which is also a file path, to the location of the Dwarf Fortress folder that you play with. This is where PyDwarf will write your files to when it's finished modifying them. +- And set the value for `output`, which is also a file path, to the location of the Dwarf Fortress folder that you play with. This is where PyDwarf will write your files to when it's finished modifying them. This path might look like `C:/df_40_24_win`. -- Set the value for `backup`, another file path, to somewhere for the Dwarf Fortress files to be backed up to. This could be something like the name of the `input` folder with `_backup` appended. This helps to ensure that if something weird goes wrong - and don't worry, it really shouldn't - you'll still have a copy of your original files lying around somewhere. +- As well as the value for `backup`, another file path, to somewhere for the Dwarf Fortress files to be backed up to. This could be something like the name of the `input` folder with `_backup` appended. This helps to ensure that if something weird goes wrong - and don't worry, it really shouldn't - you'll still have a copy of your original files lying around somewhere. The path might look like `C:/df_40_24_win_backup`. - And the really fun part is the `scripts` parameter. Here names of scripts are given in the order that they should be run. It's also possible to pass arguments to scripts here, which change the way it behaves. One way to get a list of the available scripts is to run `python manager.py --list`, and one way to see documentation regarding one of these scripts to describe its purpose and usage is to run `python manager.py --meta script.name`. - For the sake of example, you can try adding an item to the end of the `scripts` list, the text (including quotes) `"pineapple.subplants"`. In doing so, be sure to add a comma to the end of the previous line, these commas serve to separate items in the list. -- And finally, to actually run PyDwarf and apply the mods, run `python manager.py`. You're all done! Run Dwarf Fortress and generate a new world to see the changes to your raws take effect. +- Installing new mods for PyDwarf is really simple, if you want to use one that didn't come packaged with it. Simply place the uncompressed files, which should include at least one with a name like `pydwarf.scriptname.py`, anywhere in the `scripts` directory located within PyDwarf's root directory. If you had installed a mod named `this.is.a.script`, for example, you could tell PyDwarf to include it by adding it in the same way as the previous script: In the `scripts` list of `config.json`, you'd need to add a line to the list that looks like `"this.is.a.script"`. + +- And finally, to actually run PyDwarf and apply the mods, run `python manager.py`. It will helpfully tell you if anything went wrong and, if so, what exactly happened. But PyDwarf is a piece of work and sometimes those errors may be hard to understand. If you're not sure how to fix it, you can always post in the [GitHub issue tracker](https://github.com/pineapplemachine/PyDwarf/issues) or the [Bay12 forum topic](http://www.bay12forums.com/smf/index.php?topic=150857.msg6239158#msg6239158) to ask for help. + +- After running `manager.py`, if everything went smoothly, you're all done! Now you can run Dwarf Fortress and generate a new world to see the changes take effect. From 1832e55c6fc5090569e2364d6839c05018e0ec6d Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 07:56:48 -0400 Subject: [PATCH 86/95] Replaced all instances of "dfhackver" with "hackversion" Seems much cleaner this way --- config.json | 2 +- config_override.py | 2 +- docs/tutorial.md | 6 +++--- manager.py | 4 ++-- pydwarf/config.py | 20 ++++++++++---------- pydwarf/urist.py | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config.json b/config.json index adb03d0..ab4278f 100644 --- a/config.json +++ b/config.json @@ -9,7 +9,7 @@ "paths": "auto", "version": "auto", - "dfhackver": "auto", + "hackversion": "auto", "scripts": [ { diff --git a/config_override.py b/config_override.py index 8ccb74d..23095ef 100644 --- a/config_override.py +++ b/config_override.py @@ -15,7 +15,7 @@ 'backup': 'backup', 'paths': 'auto', 'version': 'auto', - 'dfhackver': 'auto', + 'hackversion': 'auto', 'scripts': [ { 'name': 'pineapple.deerappear', diff --git a/docs/tutorial.md b/docs/tutorial.md index ab6cba1..6b13848 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -36,7 +36,7 @@ For convenient reference, this is an example config.json file: "version": "auto", "dfhackdir": "auto", - "dfhackver": "auto", + "hackversion": "auto", "scripts": [ {"name": "pineapple.deerappear", "args": {"tile": "'d'", "color": [6, 0, 1]}}, @@ -60,7 +60,7 @@ Here is the purpose of each of those attributes: * `backup`: Before anything else is done, if it's not `null` or `None`, the input raws will be copied and saved to this directory. * `version`: Specifies the Dwarf Fortress version. For example, `"version": "0.40.24"`. If set to `auto` then PyDwarf will attempt to detect the Dwarf Fortress version automatically. This should succeed as long as either the `input` or `output` directory is somewhere inside Dwarf Fortress's directory. * `dfhackdir`: The location of a hack/ directory, if any, within the Dwarf Fortress directory -* `dfhackver`: The version of DFHack contained within a hack/ directory, if any, within the Dwarf Fortress directory +* `hackversion`: The version of DFHack contained within a hack/ directory, if any, within the Dwarf Fortress directory * `scripts`: Lists the scripts that should be run. * `packages`: Lists the Python packages that should be imported. In essence, it specifies for PyDwarf that it should look for scripts inside the `scripts` package, in this case a directory containing an `__init__.py` file. This is an advanced feature and the typical user won't need to worry about this. @@ -77,7 +77,7 @@ PyDwarf's configuration can also be passed as command line arguments when runnin * `-b` or `--backup`: Specifies raws backup directory. * `-ver` or `--version`: Specifies Dwarf Fortress version. * `-hdir` or `--dfhackdir`: Specifies DFHack directory. -* `-hver` or `--dfhackver`: Specifies DFHack version. +* `-hver` or `--hackversion`: Specifies DFHack version. * `-s` or `--scripts`: The list of scripts to run. (Only names and namespaces may be specified in this way, not dictionaries.) * `-p` or `--packages`: The list of Python packages to import. * `-c` or `--config`: Reads configuration from the json file given by the path. Can also refer to a Python file or package, which will be imported and used for configuration. See `config_override.py` for an example. diff --git a/manager.py b/manager.py index e8d3184..5ca1788 100644 --- a/manager.py +++ b/manager.py @@ -32,7 +32,7 @@ def __main__(args=None): pydwarf.log.debug('With pydwarf version %s.' % pydwarf.__version__) pydwarf.log.debug('With raws version %s.' % raws.__version__) pydwarf.log.debug('With Dwarf Fortress version %s.' % conf.version) - pydwarf.log.debug('With DFHack version %s.' % conf.dfhackver) + pydwarf.log.debug('With DFHack version %s.' % conf.hackversion) # Handle flags that completely change behavior if args.list: @@ -126,7 +126,7 @@ def parseargs(): parser.add_argument('-p', '--packages', help='import packages containing PyDwarf scripts', nargs='+', type=str) parser.add_argument('-c', '--config', help='run with json config file if the extension is json, otherwise treat as a Python package, import, and override settings using export dict', type=str) parser.add_argument('-v', '--verbose', help='set stdout logging level to DEBUG', action='store_true') - parser.add_argument('-hver', '--dfhackver', help='indicate DFHack version', type=str) + parser.add_argument('-hver', '--hackversion', help='indicate DFHack version', type=str) parser.add_argument('--log', help='output log file to path', type=str) parser.add_argument('--list', help='list available scripts', action='store_true') parser.add_argument('--jscripts', help='specify scripts given a json array', type=str) diff --git a/pydwarf/config.py b/pydwarf/config.py index a308cd0..55d2259 100644 --- a/pydwarf/config.py +++ b/pydwarf/config.py @@ -29,9 +29,9 @@ class config: - def __init__(self, version=None, paths=None, dfhackver=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): + def __init__(self, version=None, paths=None, hackversion=None, input=None, output=None, backup=None, scripts=[], packages=[], verbose=False, log='logs/%s.txt' % timestamp): self.version = version # Dwarf Fortress version, for handling script compatibility metadata - self.dfhackver = dfhackver # DFHack version + self.hackversion = hackversion # DFHack version self.input = input # Raws are loaded from this input directory self.output = output # Raws are written to this output directory self.backup = backup # Raws are backed up to this directory before any changes are made @@ -105,8 +105,8 @@ def setup(self, logger=False): self.setuppaths() # Handle version == 'auto' self.setupversion() - # Handle dfhackver == 'auto' - self.setupdfhackver() + # Handle hackversion == 'auto' + self.setuphackversion() # Import packages self.setuppackages() @@ -148,8 +148,8 @@ def setupversion(self): else: log.info('Managing Dwarf Fortress version %s.' % self.version) - def setupdfhackver(self): - if self.dfhackver == 'auto': + def setuphackversion(self): + if self.hackversion == 'auto': log.debug('Attempting to automatically detect DFHack version.') dfhackdir = findfile(name='hack', paths=(self.input, self.output)) @@ -161,12 +161,12 @@ def setupdfhackver(self): newspath = os.path.join(dfhackdir, 'NEWS') if os.path.isfile(newspath): - with open(newspath, 'rb') as news: self.dfhackver = news.readline().strip() + with open(newspath, 'rb') as news: self.hackversion = news.readline().strip() - if self.dfhackver is None: + if self.hackversion is None: log.error('Unable to detect DFHack version.') else: - log.debug('Detected DFHack version %s.' % self.dfhackver) + log.debug('Detected DFHack version %s.' % self.hackversion) - elif self.dfhackver is None: + elif self.hackversion is None: log.warning('No DFHack version was specified.') diff --git a/pydwarf/urist.py b/pydwarf/urist.py index 1efa59f..c3f0896 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -24,7 +24,7 @@ def configure(self, raws, conf): self.conf = conf self.dfraws = raws.dir(root=conf.input, dest=conf.output, paths=conf.paths, version=conf.version, log=log) self.dfversion = conf.version - self.hackversion = conf.dfhackver + self.hackversion = conf.hackversion def successful(self, info): return self.inlist(info, self.successes) From b00d7973c705864151f29032d8556b511990c66f Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 16:09:59 -0400 Subject: [PATCH 87/95] Replaced instances of raws.file with raws.rawfile This should serve to better distinguish it from the newer basefile, reffile, and binfile classes. --- raws/__init__.py | 7 ++++--- scripts/pineapple/pydwarf.diff.py | 10 +++++----- scripts/pineapple/pydwarf.easypatch.py | 8 ++++---- scripts/stal/pydwarf.armoury.py | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/raws/__init__.py b/raws/__init__.py index 51baaed..39818c4 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -12,7 +12,6 @@ The raws package provides querying and modification functionality for Dwarf Fortress raws. raws.dir: An entire directory of raws files, stored as a dictionary of files. -raws.dfhack: Possessed by dir objects as a hack attribute, the class exposes methods for interacting with DFHack files. raws.token: A single token within a raws file, for example [CREATURE:DWARF] or [INORGANIC:IRON]. raws.tokenlist: Extends Python's inbuilt list class with additional, specialized functionality. raws.queryable: Many raws classes extend this class, which provides token querying functionality. @@ -22,7 +21,8 @@ raws.color: Contains a convenience class and objects for dealing with colors in the DF raws. raws.copytree: A general utility method for copying an entire directory and its contents from one location to another. raws.objecs: Contains information and helper functions for knowing which object types belong to which headers, such as how [BUILDING_WORKSHOP:ID] belongs to [OBJECT:BUILDING]. -raws.file: A single raws file, stored as a linked list. + +raws.rawfile: A single raws file, stored as a linked list. raws.reffile: A file stored as a reference to a source file. raws.binfile: A file stored in a string, as its binary content. raws.basefile: A base class which other file types inherit from. @@ -35,6 +35,7 @@ +# TODO: rename classes internally to reflect what they're exported as here e.g. rawsbinfile -> binfile from filters import rawstokenfilter as tokenfilter from filters import rawsboolfilter as boolfilter from queryable import rawsqueryable as queryable @@ -44,7 +45,7 @@ from file import rawsbasefile as basefile from file import rawsreffile as reffile from file import rawsbinfile as binfile -from file import rawsfile as file # TODO: rename to "rawfile" +from file import rawsfile as rawfile from dir import rawsdir as dir from copytree import copytree import objects diff --git a/scripts/pineapple/pydwarf.diff.py b/scripts/pineapple/pydwarf.diff.py index 0d1ac88..3177107 100644 --- a/scripts/pineapple/pydwarf.diff.py +++ b/scripts/pineapple/pydwarf.diff.py @@ -44,14 +44,14 @@ def __str__(self): }, compatibility = '.*' ) -def diff(dfraws, paths): +def diff(df, paths): # Get all the files in the mods newfiles = [] for path in paths: if os.path.isfile(path) and path.endswith('.txt'): with open(path, 'rb') as rfilestream: - rfiles = (raws.file(rfile=rfilestream, path=path),) + rfiles = (raws.rawfile(rfile=rfilestream, path=path),) elif os.path.isdir(path): rfiles = raws.dir(path=path).files.values() else: @@ -70,8 +70,8 @@ def diff(dfraws, paths): currentfiletokens = None if newfile.header in currentfiletokensdict: currentfiletokens = currentfiletokensdict[newfile.header] - elif newfile.header in dfraws.files: - currentfiletokens = list(dfraws.getfile(newfile.header)) + elif newfile.header in df.files: + currentfiletokens = list(df.getfile(newfile.header)) currentfiletokensdict[newfile.header] = currentfiletokens # Do a diff @@ -87,7 +87,7 @@ def diff(dfraws, paths): # File doesn't exist yet, don't bother with a diff else: pydwarf.log.debug('File didn\'t exist yet, adding...') - dfraws.add(newfile) + df.add(newfile) for fileheader, fileops in operations.iteritems(): # Do some handling for potentially conflicting replacements diff --git a/scripts/pineapple/pydwarf.easypatch.py b/scripts/pineapple/pydwarf.easypatch.py index d375a9d..a3c5f61 100644 --- a/scripts/pineapple/pydwarf.easypatch.py +++ b/scripts/pineapple/pydwarf.easypatch.py @@ -29,7 +29,7 @@ def easypatch(df, files, **kwargs): return easypatch_content(df, files, **kwargs) elif isinstance(files, raws.tokenlist): return easypatch_tokens(df, files, **kwargs) - elif isinstance(files, raws.file): + elif isinstance(files, raws.rawfile): return easypatch_file(df, files, **kwargs) else: for file in files: @@ -48,15 +48,15 @@ def easypatch_dirpath(df, path, loc=None, **kwargs): return pydwarf.success('Added files from directory %s.' % path) def easypatch_filepath(df, path, loc=None, root=None, **kwargs): - file = raws.file(path=path, loc=loc, root=root) + file = raws.rawfile(path=path, loc=loc, root=root) return easypatch_file(df, file, **kwargs) def easypatch_content(df, content, loc, **kwargs): - file = raws.file(path=loc, content=content) + file = raws.rawfile(path=loc, content=content) return easypatch_file(df, file, **kwargs) def easypatch_tokens(df, tokens, loc, **kwargs): - file = raws.file(path=loc, tokens=tokens) + file = raws.rawfile(path=loc, tokens=tokens) return easypatch_file(df, file, **kwargs) def easypatch_file(df, file, collision_fails=True, replace=False, **kwargs): diff --git a/scripts/stal/pydwarf.armoury.py b/scripts/stal/pydwarf.armoury.py index a4d4144..e51fbbc 100644 --- a/scripts/stal/pydwarf.armoury.py +++ b/scripts/stal/pydwarf.armoury.py @@ -96,7 +96,7 @@ def armoury(df, remove_entity_items=True): # Look for files made empty as a result (of which there should be a few) and remove them removedfiles = [] for file in df.files.values(): - if isinstance(file, raws.file) and len(file) <= 1: + if isinstance(file, raws.rawfile) and len(file) <= 1: pydwarf.log.debug('Removing emptied file %s.' % file) file.remove() removedfiles.append(file) From f420cd967736f75840976c46cc1d49bbea357e38 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 16:54:11 -0400 Subject: [PATCH 88/95] Improved the way urist documentation is created and handled Got a quite nice docs/scripts.md out of it too --- docs/{ => bin}/build.py | 4 +- docs/bin/scripts.bat | 2 + docs/bin/scripts.sh | 2 + docs/{config.md => configuring.md} | 0 docs/scripts.md | 551 +++++++++++++++++++++++++++++ manager.py | 15 +- pydwarf/urist.py | 88 ++--- pydwarf/uristdoc.py | 192 ++++++++++ readme.md | 2 +- 9 files changed, 792 insertions(+), 64 deletions(-) rename docs/{ => bin}/build.py (97%) create mode 100644 docs/bin/scripts.bat create mode 100644 docs/bin/scripts.sh rename docs/{config.md => configuring.md} (100%) create mode 100644 docs/scripts.md create mode 100644 pydwarf/uristdoc.py diff --git a/docs/build.py b/docs/bin/build.py similarity index 97% rename from docs/build.py rename to docs/bin/build.py index b656f3a..cd0f558 100644 --- a/docs/build.py +++ b/docs/bin/build.py @@ -3,14 +3,14 @@ import sys import os -sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../..')) import inspect import raws import pydwarf -output = 'index.html' +output = '../index.html' items = { 'raws.token': raws.token, diff --git a/docs/bin/scripts.bat b/docs/bin/scripts.bat new file mode 100644 index 0000000..36d56d9 --- /dev/null +++ b/docs/bin/scripts.bat @@ -0,0 +1,2 @@ +cd ../../ +python manager.py --meta --metaformat "md" --writedoc "docs/scripts.md" diff --git a/docs/bin/scripts.sh b/docs/bin/scripts.sh new file mode 100644 index 0000000..36d56d9 --- /dev/null +++ b/docs/bin/scripts.sh @@ -0,0 +1,2 @@ +cd ../../ +python manager.py --meta --metaformat "md" --writedoc "docs/scripts.md" diff --git a/docs/config.md b/docs/configuring.md similarity index 100% rename from docs/config.md rename to docs/configuring.md diff --git a/docs/scripts.md b/docs/scripts.md new file mode 100644 index 0000000..1e673d0 --- /dev/null +++ b/docs/scripts.md @@ -0,0 +1,551 @@ +# Scripts + +## omniclasm.decay.deteriorate.clothes + +Created by Omniclasm and Sophie Kirschner. + +This script is fairly straight forward. All of those slightly worn wool shoes that dwarves scatter all over the place will deteriorate at a greatly increased rate, and eventually just crumble into nothing. As warm and fuzzy as a dining room full of used socks makes your dwarves feel, your FPS does not like it. + +#### Arguments: + +* **auto_run:** If set to True then the script will be started automatically upon startup. + +## omniclasm.decay.deteriorate.corpses + +Created by Omniclasm and Sophie Kirschner. + +In long running forts, especially evil biomes, you end up with a lot of toes, teeth, fingers, and limbs scattered all over the place. Various corpses from various sieges, stray kitten corpses, probably some heads. This script causes all of those to rot away into nothing after several months. + +#### Arguments: + +* **auto_run:** If set to True then the script will be started automatically upon startup. + +## omniclasm.decay.deteriorate.food + +Created by Omniclasm and Sophie Kirschner. + +With this script running, all food and plants wear out and disappear after several months. Barrels and stockpiles will keep them from rotting, but it won't keep them from decaying. + +#### Arguments: + +* **auto_run:** If set to True then the script will be started automatically upon startup. + +## omniclasm.decay.starvingdead + +Created by Omniclasm and Sophie Kirschner. + +With this script running, all undead that have been on the map for a time (default: 1 month) start to gradually decay, losing strength, speed, and toughness. After they have been on the map for even longer (default: 3 months), they collapse upon themselves, never to be reanimated. + +#### Arguments: + +* **start:** Number of months before decay sets in. + +* **die:** Number of months before collapsing entirely. + +* **auto_run:** If set to True then the script will be started automatically upon startup. + +## pineapple.adoptsowner + +Created by Sophie Kirschner. + +todo + +#### Arguments: + +* **add_to:** If set to None, no ADOPTS_OWNER tokens are added. If set to '\*', tokens are added to all creatures. If set to an iterable containing IDs of creatures, ADOPTS_OWNER will be added to each of those creatures. Defaults to None. + +* **remove_from:** If set to None, no ADOPTS_OWNER tokens are removed. If set to '\*', all ADOPTS_OWNER tokens are removed. If set to an iterable containing IDs of creatures, ADOPTS_OWNER will be removed from each of those creatures. Defaults to '\*'. + +## pineapple.bauxitetoaluminum + +Created by Sophie Kirschner. + +Adds a reaction to the smelter to allow the creation of aluminum bars from bauxite (as ore) and cryolite (as flux). Credit to this forum discussion for the reaction and general inspiration: http://www.bay12forums.com/smf/index.php?topic=31523.0 + +#### Arguments: + +* **entities:** Adds the reaction to these entities. Defaults to only MOUNTAIN. + +* **aluminum_value:** Multiplies the MATERIAL_VALUE of aluminum by this much. Defaults to 0.75 to account for the increased availability of aluminum as a consequence of the new reaction. + +* **add_to_file:** Adds the reaction to this file. + +## pineapple.boneflux + +Created by Sophie Kirschner. + +Adds a reaction to the kiln which consumes bones and produces flux. Inspired by/stolen from Rubble's Bone Flux mod. + +#### Arguments: + +* **reaction_name:** The name of the reaction to be shown in the kiln. + +* **entities:** Adds the reaction to these entities. Defaults to MOUNTAIN and PLAINS. + +* **add_to_file:** Adds the reaction to this file. + +* **bone_count:** The number of bones required in the reaction. + +* **product_id:** ID of the boulder to get out of the reaction. Defaults to CALCITE. + +## pineapple.butcherinorganic + +Created by Sophie Kirschner. + +Allows butchering of some inorganics, get things like wood or stone from some corpses. Inspired by/stolen from Igfig's Modest Mod. + +#### Arguments: + +* **templates:** Associates material template names as keys with items as values. Each named template will be given a BUTCHER_SPECIAL:ITEM:NONE token, where ITEM is the value given. Defaults to adding logs, bars, and boulders to wood, metal, and stone templates respectively. + +## pineapple.castanvil + +Created by Sophie Kirschner. + +Adds a reaction to the smelter which makes it possible to create an iron anvil without already having a forge. Inspired by/stolen from Rubble's Cast Anvil mod. + +#### Arguments: + +* **entities:** Adds the reaction to these entities. Defaults to only MOUNTAIN. + +* **add_to_file:** Adds the reaction to this file. + +* **anvil_cost:** The cost in iron bars to create an anvil in this way. Defaults to 5. + +## pineapple.cavegrass + +Created by Sophie Kirschner. + +Changes the grasses in each cavern level to make the different levels more visually distinct, as well as adding a much greater variety. With default arguments the first cavern will be primarly green and yellow, the second blue and cyan, the third red and gray. Inspired by/stolen from Rubble's Cave Color mod. + +#### Arguments: + +* **grasses:** A dictionary specifying which grasses to change and modify. See the default_grasses dict for documentation. + +## pineapple.deerappear + +Created by Sophie Kirschner. + +Changes the appearance of each deer from a brown D to yellow d. + +#### Arguments: + +* **tile:** Set the tile that the deer's appeance will be set to. + +* **color:** Set the arguments that the deer's color token will be given. + +* **creature:** Change the creature whose appearance will be modified. (Heresy!) + +## pineapple.diff + +Created by Sophie Kirschner. + +Merges and applies changes made to some modded raws via diff checking. Should be reasonably smart about automatic conflict resolution but if it complains then I recommend giving things a manual checkover afterwards. Also, the token-based diff'ing approach should work much better than any line-based diff. Using this tool to apply mods made to other versions of Dwarf Fortress probably won't work so well. + +#### Arguments: + +* **paths:** Should be an iterable containing paths to individual raws files or to directories containing many. Files that do not yet exist in the raws will be added anew. Files that do exist will be compared to the current raws and the according additions/removals will be made. At least one path must be given. + +## pineapple.discipline + +Created by Sophie Kirschner. + +Applies natural discipline skill bonuses to creatures that should probably have them. Credit to Mictlantecuhtli for creating the mod which inspired this one. www.bay12forums.com/smf/index.php?topic=140460.0 + +#### Arguments: + +* **discipline_bonus:** A dict mapping property names to values: For each of these tokens that a creature possesses the corresponding bonuses are summed. The resulting value, rounded up, is used to determine the skill bonus. + +* **entity_bonus:** Handled separately, adds this value to the bonus for creatures which are listed as being members of any entity. + +* **badger_bonus:** Also handled separately, adds this skill bonus to badgers. + +## pineapple.easypatch + +Created by Sophie Kirschner. + +Given a path to a file, a directory, a content string, a tokenlist, a raws file object, or an iterable containing a combination of these, a file or files are added to the dir object, and these same objects can be permitted using the permitted_entities argument. + +#### Arguments: + +* **files:** The file or files to be added. + +* **\*\*kwargs:** Passed on to pineapple.utils.permitobjects. + +## pineapple.flybears + +Created by Sophie Kirschner. + +Example script which causes all female bears to fly. + +## pineapple.greensteel + +Created by Sophie Kirschner. + +Adds an alloy which is lighter and sharper than steel but not so much as adamantine. It can be made from similar ingredients as steel with the addition of adamantine bars or a new adamant ore. + +#### Arguments: + +* **entities:** The entities which should be permitted this reaction. Defaults to only MOUNTAIN. + +## pineapple.maxage + +Created by Sophie Kirschner. + +Applies a MAXAGE to most vanilla creatures which don't already have one. + +#### Arguments: + +* **output_needs_age:** When True, creatures that have no MAXAGE and aren't specified in the ages dict will be outputted to the log. Can maybe be helpful for debugging things. + +* **apply_default_age:** Most creatures that don't have a MAXAGE and aren't specified in the ages dict will have this default applied to their MAXAGE. It should be an iterable containing arguments same as values in the ages dict. This will not be applied to wagons, to megabeasts, to undead, or to nonexistent creatures. + +* **ages:** A dictionary mapping creature names as keys to what that creature's arguments should be for MAXAGE: That is, it should look like (minimum_lifespan, maximum_lifespan). + +## pineapple.metalitems + +Created by Sophie Kirschner. + +Allows the forging of every type of metal item from the specified metals. + +#### Arguments: + +* **items:** These are the items that the listed metals will always be allowed for. + +* **metals:** These metals will be made to allow forging of each item specified. + +## pineapple.noaquifers + +Created by Sophie Kirschner. + +Removes all AQUIFER tokens. + +## pineapple.noexotic + +Created by Sophie Kirschner. + +Replaces all [PET_EXOTIC] and [MOUNT_EXOTIC] tags with their non-exotic counterparts. + +## pineapple.nograzers + +Created by Sophie Kirschner. + +Removes all [GRAZER] and [STANDARD_GRAZER] tokens. + +## pineapple.nomaxage + +Created by Sophie Kirschner. + +Removes MAXAGE tokens from creatures. + +#### Arguments: + +* **apply_to_creatures:** Also removes MAXAGE from these creatures regardless of whether they possess any of the properties in required_property. Set to None to apply to no other creatures. Defaults to None. + +* **required_property:** An iterable containing token values, e.g. ('INTELLIGENT', 'CAN_LEARN'): for each creature having both a MAXAGE token and one or more of these tokens, that creature's MAXAGE token will be removed. If set to None, then no MAXAGE tokens will be removed in this way. If set to ['\*'], MAXAGE tokens will be removed from all creatures. + +## pineapple.orientation + +Created by Sophie Kirschner. + +Causes all creatures of some type to have a single sexuality, heterosexual being the default. (You boring snob!) + +#### Arguments: + +* **mode:** Accepts one of these strings as its value, or None: "hetero", the default, makes the creatures exclusively straight. "gay" makes the creatures exclusively gay. "bi" makes the creatures exclusively bisexual. "ace" makes the creatures exclusively asexual. Can alternatively be set as a custom tuple same as those found in the mode_info dict: The list/tuple should contain six values corresponding to (disinterest in the same gender, romantic (but not marriage) interest in the same, commitment to the same, disinterest in the other gender, romantic interest in the other, commitment to the other). + +* **creatures:** An iterable containing creatures whose sexuality should be affected. Set to None to affect all creatures. + +## pineapple.skillrust + +Created by Sophie Kirschner. + +Modifies skill rust for given creatures. Disables it entirely by default. + +#### Arguments: + +* **rates:** What the skill rust rates are to be changed to. It must be a tuple or list containing three values. The default is ('NONE', 'NONE', 'NONE'), which disables skill rust entirely. Dwarf Fortress's default rates are ('8', '16', '16'). Lower numbers indicate faster skill rust. + +* **creatures:** An iterable containing creatures for which to disable skill rust. + +## pineapple.stoneclarity + +Created by Sophie Kirschner. + +Allows powerful editing of the appearances of stone, ore, and gems. + +#### Arguments: + +* **rules:** By default makes all flux stone white, makes all fuel use \*, makes all ore use £ unmined and \* in stockpiles, makes cobaltite use % unmined and • in stockpiles, makes all gems use ☼. Specify an object other than default_rules to customize behavior, and refer to default_rules as an example of how rules are expected to be represented + +* **query:** This query is run for each inorganic found and looks for tokens that should be recognized as indicators that some inorganic belongs to some group. Refer to the default query for more information. + +* **fuels:** If left unspecified, stoneclarity will attempt to automatically detect which inorganics are fuels. If you know that no prior script added new inorganics which can be made into coke then you can cut down a on execution time by setting fuels to fuels_vanilla. + +## pineapple.subplants + +Created by Sophie Kirschner. + +Makes all subterranean plants grow year-round. + +## pineapple.useablemats + +Created by Sophie Kirschner. + +Causes scales, feathers, and chitin to become useful for crafting. Inspired by/stolen from Rubble's Usable Scale/Feathers/Chitin fixes. + +#### Arguments: + +* **scales:** Recognized when using the default options dict. If set to True, scales will be made to act more like leather for crafting purposes. + +* **feathers:** Recognized when using the default options dict. If set to True, feathers will be useable for making soft items, such as clothing. + +* **options:** A dictionary associating option names with tuples where the first element is the name of a MATERIAL_TEMPLATE and the second is tokens to be added to that template. Option names, when passed as a keyword argument and set to False, will cause that option to be disabled. + +* **chitin:** Recognized when using the default options dict. If set to True, chitin will be made to act more like shells for crafting purposes. + +## pineapple.utils.addhack + +Created by Sophie Kirschner. + +Utility script for adding a new DFHack script. + +#### Arguments: + +* **\*\*kwargs:** Other named arguments will be passed on to the dir.add method used to create the file object corresponding to the added script. + +* **auto_run:** If set to True, a line will be added to dfhack.init containing only the name of the added script. If set to None, no such line will be added. If set to an arbitrary string, that string will be added as a new line at the end of dfhack.init. + +## pineapple.utils.addobject + +Created by Sophie Kirschner. + +Utility script for adding a new object to the raws. + +#### Arguments: + +* **item_rarity:** Most items, when adding tokens to entities to permit them, accept an optional second argument specifying rarity. It should be one of 'RARE', 'UNCOMMON', 'COMMON', or 'FORCED'. This argument can be used to set that rarity. + +* **add_to_file:** The name of the file to add the object to. If it doesn't exist already then the file is created anew. The string is formatted such that %(type)s is replaced with the object_header, lower case. + +* **permit_entities:** For relevant object types such as reactions, buildings, and items, if permit_entities is specified then tokens are added to those entities to permit the added object. + +* **tokens:** The tokens belonging to the object to create. + +* **object_header:** When the object is added to a file which doesn't already exist, an [OBJECT:TYPE] token must be added at its beginning. This argument, if specified, provides the type in that token. Otherwise, when the argument is left set to None, the type will be automatically decided. + +* **type:** Specifies the object type. If type and id are left unspecified, the first token of the tokens argument is assumed to be the object's [TYPE:ID] token and the type and id arguments are taken out of that. + +* **id:** Specifies the object id. If type and id are left unspecified, the first token of the tokens argument is assumed to be the object's [TYPE:ID] token and the type and id arguments are taken out of that. + +## pineapple.utils.addobjects + +Created by Sophie Kirschner. + +Utility script for adding several new objects to the raws at once. + +#### Arguments: + +* **\*\*kwargs:** Passed on to pineapple.utils.addobject. + +* **add_to_file:** The name of the file to add the object to. If it doesn't exist already then the file is created anew. The string is formatted such that %(type)s is replaced with the object_header, lower case. + +* **objects:** An iterable containing tokens belonging to the objects to add. + +## pineapple.utils.addtoentity + +Created by Sophie Kirschner. + +A simple utility script which adds tokens to entities. + +#### Arguments: + +* **tokens:** A string or collection of tokens to add to each entity. + +* **entities:** Adds tokens to these entities. + +## pineapple.utils.objecttokens + +Created by Sophie Kirschner. + +Utility script for adding or removing tokens from objects. + +#### Arguments: + +* **add_to:** If set to None, no tokens tokens are added. If set to '\*', tokens are added to all objects. If set to an iterable containing IDs of objects, tokens will be added to each of those objects. + +* **token:** The token to be added or removed. + +* **object_type:** The type of object which should be affected. + +* **remove_from:** If set to None, no matching tokens are removed. If set to '\*', all matching tokens are removed. If set to an iterable containing IDs of objects, matching tokens will be removed from each of those objects. + +## pineapple.utils.permitobject + +Created by Sophie Kirschner. + +Utility script for permitting an object with entities. + +#### Arguments: + +* **permit_entities:** For relevant object types such as reactions, buildings, and items, if permit_entities is specified then tokens are added to those entities to permit the added object. + +* **type:** Specifies the object type. + +* **item_rarity:** Some items, when adding tokens to entities to permit them, accept an optional second argument specifying rarity. It should be one of 'RARE', 'UNCOMMON', 'COMMON', or 'FORCED'. This argument can be used to set that rarity. + +* **id:** Specifies the object id. + +## pineapple.utils.permitobjects + +Created by Sophie Kirschner. + +Utility script for permitting several objects at once with entities. + +#### Arguments: + +* **\*\*kwargs:** Passed on to pineapple.utils.permitobject. + +* **objects:** An iterable containing either tokens or type, id tuples representing objects to be permitted. + +## pineapple.woodmechanisms + +Created by Sophie Kirschner. + +Allows construction of wooden mechanisms at the craftdwarf's workshop. Inspired by/stolen from Rubble's Wooden Mechanisms mod. + +#### Arguments: + +* **entities:** Adds the reaction to these entities. Defaults to MOUNTAIN and PLAINS. + +* **add_to_file:** Adds the reaction to this file. + +* **log_count:** The number of logs required in the reaction. + +## pkdawson.vegan + +Created by Patrick Dawson and Sophie Kirschner. + +Adds reactions to the craftdwarf's workshop for making quivers and backpacks from cloth, which normally require leather. Also adds a DFHack script which disables non-vegan labors using autolabor. + +#### Arguments: + +* **lua_file:** The DFHack script will be added to this path, relative to DF's root directory. If set to None then no DFHack script will be written. + +* **entities:** Adds the reaction to these entities. Defaults to MOUNTAIN and PLAINS. + +* **add_to_file:** Adds the reaction to this file. + +* **labors:** These labors will be disabled using a DFHack script. If set to None then no DFHack script will be written. The default labors are BUTCHER, TRAPPER, DISSECT_VERMIN, LEATHER, TANNER, MAKE_CHEESE, MILK, FISH, CLEAN_FISH, DISSECT_FISH, HUNT, BONE_CARVE, SHEARER, BEEKEEPING, WAX_WORKING, GELD. + +* **auto_run:** If set to True, and if the DFHack script is added, then a line will be added to the end of dfhack.init which runs this script on startup. If set to False then the script will wait to be run manually. + +## putnam.materialsplus + +Created by Putnam and Sophie Kirschner. + +Adds a bunch of materials to the game. + +## putnam.microreduce + +Created by Putnam and Sophie Kirschner. + +A mod to reduce the amount of micromanagement in Dwarf Fortress. One-step soap making and clothesmaking! + +## shukaro.creationforge + +Created by Shukaro and Sophie Kirschner. + +This is a simple workshop I modded in to help test custom reactions, buildings, and creatures. It's used to create various different items so that you don't have to set up an entire fortress to test some reactions. Hopefully it's a useful tool to people, even if it's just to look at the raw formatting. + +## shukaro.higherlearning + +Created by Shukaro and Sophie Kirschner. + +Have you ever wondered to yourself, "Man, my dwarves are such idiots, I wish I could chisel some intelligence into their heads."? No? Then, er, disregard that last bit. What I present to you, here and now, no strings attached, is a workshop to solve a problem that you probably didn't even know you had! I call it, the Dwarven Higher Learning Mod. Now, what this thingawazzit does, is give your dwarves an opportunity to polish up some skills that they may have trouble practicing elsewhere. You know the situation, Urist McDoctor has your legendary axedwarf on your table, and he's got no idea how to stop him from bleeding out from wounds caused by rogue fluffy wamblers. Or you have precious little metal available on the glacier you so stupidly bravely embarked on, so you can't afford to waste it on dabbling weaponsmiths who've never handled a hammer before in their lives. This mod's workshops allow training through the time-honored traditions of; hitting rocks until your hands bleed, performing repetitive actions that will sap your will to live, practicing your skills on subjects that are worth less than most peasants (and won't sue for malpractice), and studying the works of your fellow dwarves, knowing full-well that you'll never be quite as good as them. + +#### Arguments: + +* **entities:** An iterable containing names of entities the workshops will be added to. Defaults to only MOUNTAIN. + +## smeeprocket.transgender + +Created by SmeepRocket and Sophie Kirschner. + +Adds transgender and intersex castes to creatures. + +#### Arguments: + +* **beards:** If True, all dwarf castes will be given beards. If False, none of the added castes will have beards for any species. Defaults to True. + +* **frequency:** Higher numbers cause rarer incidence of added castes. + +* **species:** An iterable containing each species that should be given transgender and intersex castes. + +## stal.armoury.attacks + +Created by Stalhansch and Sophie Kirschner. + +Removes attacks from creatures. By default, as a way to improve balance in combat, scratch and bite attacks are removed from dwarves, humans, and elves. + +#### Arguments: + +* **remove_attacks:** Removes these attacks from species listed in remove_attacks_from. Defaults to scratch and bite. + +* **remove_attacks_from:** If set to True, specified remove_attacks are removed from the species in the list to improve combat balancing. If set to None those attacks will not be touched. Defaults to dwarves, humans, and elves. + +## stal.armoury.items + +Created by Stalhansch and Sophie Kirschner. + +Attempts to improve the balance and realism of combat. + +#### Arguments: + +* **remove_entity_items:** Determines whether items that would be made unavailable to entities should be removed from those entities or not. If you're also using other mods that make changes to weapons and armour and such it may be desireable to set this flag to False. Otherwise, for best results, the flag should be set to True. Defaults to True + +## umiman.smallthings.engraving + +Created by Umiman, Fieari, and Sophie Kirschner. + +Has this been done before? While I think it's impossible to change any of the inbuilt engraving stuff like, "this is a picture of a dwarf and a tentacle demon. The dwarf is embracing the tentacle demon", it is possible to edit and add to more basic ones such as "this is a picture of a crescent moon". Basically, I added maybe 100 or so new engravings you can potentially see on your floors, walls, studded armour, images, and the like. Keep in mind maybe one or two metagame just a tad but it's funny! I swear! + +## umiman.smallthings.prefstring + +Created by Umiman, Fieari, and Sophie Kirschner. + +This mod simply just adds to the number of prefstrings for everything in the game to a minimum of five each. Prefstrings are the stuff that tell your dwarves what to like about a creature. For example, "He likes large roaches for their ability to disgust". With this mod, you'll see more fleshed out descriptions of the things your dwarves like, as well as more varied ones. With five each, it's pretty rare to see the same two twice. Hopefully I don't have any repeating prefstrings. + +## umiman.smallthings.speech.nofamily + +Created by Umiman and Sophie Kirschner. + +Adds more dialog options to no_family.txt. + +## umiman.smallthings.speech.threats + +Created by Umiman and Sophie Kirschner. + +Awhile back I asked the community to contribute to fill out the threat.txt which is used in adventurer when someone threatens you. I.E: in vanilla, when you face a megabeast or someone who has killed a named creature, they will talk about who they killed and then say, "prepare to die!!!". That's all they said. Boring. This compilation has some of the best threats (around 150 and counting) compiled from that thread and should make killing things too proud of their own achievements a lot more fun. + +## witty.restrictednobles.custom + +Created by Witty and Sophie Kirschner. + +Allows allowing and preventing various species from becoming dwarven nobles. + +#### Arguments: + +* **inclusions:** An iterable containing each species that should be specified as allowed. If any is allowed in this way, any species not specifically allowed will be disallowed. + +* **exclusions:** An iterable containing each species that should be disallowed. All species not disallowed in this way will be able to become dwarven nobles. + +## witty.restrictednobles.standard + +Created by Witty and Sophie Kirschner. + +Witty: This is a pretty simple mod I've been meaning to make for a while. This should restrict all nobles of a given dwarven civ to dwarves and only dwarves. edit: taking into consideration that non-dwarves will be functional fort citizens as of the next version, I've decided to go with another option. The newest addition will now only exclude goblins from dwarven positions, since their current worldgen behavior still makes them the most likely to dominate dwarven nobility. But now the occasional elf or human king will get their fair dues. The dwarf-only "module" will still come packaged. Note this will require a new world to take effect. All raw changes will be indicated by the WM insignia. Sophie: By default, this script will only prevent goblins from becoming nobles. Set the onlydwarves flag to True in order to prevent all other races as well. + +#### Arguments: + +* **onlydwarves:** Defaults to False. If True, only dwarves will be allowed to hold positions in Dwarven forts and civs. If False, only goblins will be prevented from holding those positions. \ No newline at end of file diff --git a/manager.py b/manager.py index 5ca1788..22f7ee5 100644 --- a/manager.py +++ b/manager.py @@ -35,11 +35,18 @@ def __main__(args=None): pydwarf.log.debug('With DFHack version %s.' % conf.hackversion) # Handle flags that completely change behavior + specialtext = None + if args.list: - pydwarf.urist.list() - exit(0) + items = pydwarf.urist.list() + specialtext = '\n'.join(items) elif args.meta is not None: - pydwarf.urist.doclist(args.meta) + specialtext = pydwarf.urist.doclist(args.meta, format=args.metaformat) + + if specialtext is not None: + pydwarf.log.info('\n\n%s\n' % specialtext) + if args.writedoc: + with open(args.writedoc, 'wb') as writedoc: writedoc.write(specialtext) exit(0) # Verify that input directory exists @@ -131,6 +138,8 @@ def parseargs(): parser.add_argument('--list', help='list available scripts', action='store_true') parser.add_argument('--jscripts', help='specify scripts given a json array', type=str) parser.add_argument('--meta', help='show metadata for scripts', nargs='*', type=str) + parser.add_argument('--metaformat', help='how to format shown metadata', type=str) + parser.add_argument('--writedoc', help='write data given by --list or --meta to a file path in addition to the log', type=str) args = parser.parse_args() if args.jscripts is not None: args.scripts = json.loads(args.jscripts) if args.scripts is None else (args.scripts + json.loads(args.jscripts)) diff --git a/pydwarf/urist.py b/pydwarf/urist.py index c3f0896..b48bffe 100644 --- a/pydwarf/urist.py +++ b/pydwarf/urist.py @@ -4,6 +4,7 @@ import version as versionutils from log import log +import uristdoc @@ -174,8 +175,8 @@ def __hash__(self): def getname(self): return '.'.join((self.namespace, self.name)) if self.namespace else self.name - def meta(self, key): - return self.metadata.get(key) + def meta(self, key, default=None): + return self.metadata.get(key, default) def matches(self, match): return all([self.meta(i) == j for i, j in match.iteritems()]) if match else True @@ -344,7 +345,6 @@ def splitname(name): @staticmethod def list(): - log.info('Listing registered scripts.') names = {} total = 0 for uristlist in urist.registered.itervalues(): @@ -353,69 +353,41 @@ def list(): if uname not in names: names[uname] = [] names[uname].append(uristinstance) total += 1 - log.info('Found %d registered scripts in total.' % total) - for name, uristlist in sorted(names.items()): - log.info('Found %d script%s named %s.' % (len(uristlist), 's' if len(uristlist) > 1 else '', name)) + # log.info('Found %d registered scripts in total.' % total) + # for name, uristlist in sorted(names.items()): + # log.info('Found %d script%s named %s.' % (len(uristlist), 's' if len(uristlist) > 1 else '', name)) + return sorted(names.keys()) - def doc(self): + def doc(self, format=None): '''Make a pretty metadata string.''' - doc = '' + template = uristdoc.template.format.get(format if format else 'txt') + if template is None: raise KeyError('Failed to create documentation string because the format %s was unrecognized.' % format) - # Utility function - def normalize(string): return ' '.join([l.strip() for l in str(string).split('\n')]) - - # Title - author = self.meta('author') - version = self.meta('version') - if author and not isinstance(author, basestring): author = ', '.join(author) - versionstr = (' %s' % version) if version else '' - authorstr = (' by %s' % author) if author else '' - doc += 'Script:\n %s%s%s.' % (self.getname(), versionstr, authorstr) - - # Description - desc = self.meta('description') - if desc: - doc += '\n\nDescription:\n%s' % textwrap.fill(' %s' % normalize(desc)) - - # Arguments - args = self.meta('arguments') - if args: - doc += '\n\nArguments:' - for argname, arginfo in args.iteritems(): - doc += '\n%s' % textwrap.fill(' %s: %s' % (argname, normalize(arginfo))) - - # Dependencies - deps = self.meta('dependency') - if deps: - if not isinstance(deps, basestring): deps = ', '.join(deps) - doc += '\nDepends on:\n%s' % textwrap.fill(' %s' % deps) - - # Compatibility - compat = self.meta('compatibility') - if compat: - doc += '\n\nDF version compatibility regex:\n %s' % str(compat) - - # Everything else - othermeta = [] - for key, value in self.metadata.iteritems(): - if key not in ('name', 'namespace', 'author', 'version', 'description', 'arguments', 'dependency', 'compatibility'): othermeta.append((key, value)) - if len(othermeta): - doc += '\n\nOther metadata:' - for key, value in othermeta: - doc += '\n%s' % textwrap.fill(' %s: %s' % (key, normalize(value))) - - # All done! - return doc + handled_metadata_keys = ('name', 'namespace', 'author', 'version', 'description', 'arguments', 'dependency', 'compatibility') + return template.full( + name = self.getname(), + version = self.meta('version'), + author = self.meta('author'), + description = self.meta('description'), + compatibility = self.meta('compatibility'), + dependencies = self.meta('dependency'), + arguments = self.meta('arguments'), + metadata = {key: value for key, value in self.metadata.iteritems() if key not in handled_metadata_keys} + ) + @staticmethod - def doclist(names=[]): - log.info('Showing metadata for scripts.') + def doclist(names=[], delimiter='\n\n', format=None): urists = [] if len(names): for name in names: urists += urist.getregistered(*urist.splitname(name)) else: urists = urist.allregistered() - for uristinstance in urists: - log.info('\n\n%s\n' % uristinstance.doc()) - \ No newline at end of file + items = sorted(ur.doc(format=format) for ur in urists) + template = uristdoc.template.format.get(format if format else 'txt') + if items and template: + text = template.concat(items) + else: + text = delimiter.join(items) + return text diff --git a/pydwarf/uristdoc.py b/pydwarf/uristdoc.py new file mode 100644 index 0000000..c1bfe53 --- /dev/null +++ b/pydwarf/uristdoc.py @@ -0,0 +1,192 @@ +import os +import textwrap + + +def __main__(): + template.format['txt'] = txttemplate() + template.format['md'] = mdtemplate() + template.format['html'] = htmltemplate() + + + +class template: + '''Internal: Helpful class for handling different formats used by the urist.doc method.''' + + format = {} + + def preprocess(self, item): + if isinstance(item, basestring): + return self.preprocesstext(item) + elif isinstance(item, list) or isinstance(item, tuple): + return [self.preprocess(i) for i in item] + elif isinstance(item, dict): + return {self.preprocess(key): self.preprocess(value) for key, value in item.iteritems()} + else: + return item + def preprocesstext(self, text): + return text + + def concat(self, items): + return '\n\n'.join(items) + + def join(self, items, delimiter=',', natural=False): + if not items: + return None + elif isinstance(items, basestring): + return items + elif len(items) == 1: + return items[0] + elif natural: + if len(items) == 2: + return ' and '.join(str(item) for item in items) + else: + return '%s%s and %s' % ( + ('%s ' % delimiter).join(str(item) for item in items[:-1]), + delimiter, + items[-1] + ) + else: + return delimiter.join(str(item) for item in items) + def fmt(self, string, format): + return string % format if string and format else None + + def header(self, **kwargs): + return None + def dependencies(self, **kwargs): + return None + def arguments(self, **kwargs): + return None + def metadata(self, **kwargs): + return None + + def norm(self, text): + return ' '.join([line.strip() for line in str(text).split('\n')]) + def wrap(self, text): + return textwrap.fill(text) if self.wraptext else text + + def full(self, delimiter=None, dependencies=None, arguments=None, metadata=None, **kwargs): + if delimiter is None: delimiter = self.delimiter + dependencies = (dependencies,) if dependencies and isinstance(dependencies, basestring) else dependencies + + kwargs = {key: self.preprocess(value) for key, value in kwargs.iteritems()} + dependencies = self.preprocess(dependencies) + arguments = self.preprocess(arguments) + metadata = self.preprocess(metadata) + + body = delimiter.join(item for item in ( + self.header(**kwargs), + self.dependencies(dependencies) if dependencies else None, + self.arguments(arguments) if arguments else None, + self.metadata(metadata) if metadata else None + ) if item) + return '\n'.join(self.wrap(line) for line in body.split('\n')) if self.wraptext else body + + + +class txttemplate(template): + def __init__(self): + self.delimiter = '\n\n' + self.wraptext = True + + def header(self, name=None, version=None, author=None, description=None, **kwargs): + headeritems = ( + name, + self.fmt('version %s', version), + self.fmt('by %s', self.join(author)) + ) + header = 'Script %s:' % ' '.join(item for item in headeritems if item) + return '%s\n\n%s' % (header, self.norm(description)) if description else header + + def dependencies(self, dependencies): + return 'Dependencies:\n%s' % '\n'.join(dependencies) + def arguments(self, arguments): + return 'Arguments:\n%s' % '\n'.join((' %s: %s' % (key, self.norm(value)) for key, value in arguments.iteritems())) + def metadata(self, metadata): + return 'Metadata:\n%s' % '\n'.join((' %s: %s' % (key, self.norm(value)) for key, value in metadata.iteritems())) + + + +class mdtemplate(template): + def __init__(self): + self.delimiter = '\n\n' + self.wraptext = False + + def preprocesstext(self, text): + return text.replace('*', '\*').replace('#', '\#') + + def concat(self, items): + return '''# Scripts\n\n%s''' % '\n\n'.join(items) + + def header(self, name=None, version=None, author=None, description=None, **kwargs): + headeritems = ( + self.fmt('## %s', name), + #self.fmt('Version %s', version), + self.fmt('Created by %s.', self.join(author, natural=True)), + self.norm(description) + ) + return '\n\n'.join(item for item in headeritems if item) + + def dependencies(self, dependencies): + return '#### Dependencies:\n\n%s' % ' \n'.join('* %s' % dep for dep in dependencies) + def arguments(self, arguments): + return '#### Arguments:\n\n%s' % '\n\n'.join(('* **%s:** %s' % (key, self.norm(value)) for key, value in arguments.iteritems())) + def metadata(self, metadata): + return '#### Metadata:\n\n%s' % '\n\n'.join(('* **%s:** %s' % (key, self.norm(value)) for key, value in metadata.iteritems())) + + + +class htmltemplate(template): + def __init__(self): + self.delimiter = '\n' + self.wraptext = False + + def preprocesstext(self, text): + return text.replace('&', '&').replace('<', '<').replace('>', '>') + + def concat(self, items): + return ''' + + + + Scripts + + + + +

Scripts

+ %s + + + ''' % '\n\n'.join('
%s
' % item for item in items) + + def header(self, name=None, version=None, author=None, description=None, **kwargs): + headeritems = ( + self.fmt('

%s

', name), + #self.fmt('

Version %s

', version), + self.fmt('

Created by %s.

', self.join(author, natural=True)), + self.norm(description) + ) + return '\n'.join(item for item in headeritems if item) + + def dependencies(self, dependencies): + return '

Dependencies

\n
    %s
' % '\n'.join('
  • %s
  • ' % dep for dep in dependencies) + def arguments(self, arguments): + return '

    Arguments

    \n
      %s
    ' % '\n'.join(('
  • %s: %s
  • ' % (key, self.norm(value)) for key, value in arguments.iteritems())) + def metadata(self, metadata): + return '

    Metadata

    \n
      %s
    ' % '\n'.join(('
  • %s: %s
  • ' % (key, self.norm(value)) for key, value in metadata.iteritems())) + + + +__main__() diff --git a/readme.md b/readme.md index 65cd2d7..08b6e2e 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ PyDwarf is licensed via the exceptionally permissive [zlib/libpng license](https PyDwarf is easy to configure! Open `config.json` with your favorite plain text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. -Here's a [step-by-step tutorial](docs/config.md) describing what this process might entail. +Here's a [step-by-step tutorial](docs/configuring.md) describing what this process might entail. ## Modding using PyDwarf From f12db3dce526dd4138ac8d83e50b17f15aa175cd Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 16:55:36 -0400 Subject: [PATCH 89/95] Readme edit --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 08b6e2e..c083b4c 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ PyDwarf is licensed via the exceptionally permissive [zlib/libpng license](https PyDwarf is easy to configure! Open `config.json` with your favorite plain text editor and tell it where to find your Dwarf Fortress raws, edit the list of scripts to reflect the mods you want to run and in what order you'd like them to run. Once the configuration is to your liking simply run `manager.py`, with Python 2.7 installed this can be done by opening a command line in the PyDwarf directory and running `python manager.py`. -Here's a [step-by-step tutorial](docs/configuring.md) describing what this process might entail. +Here's a [step-by-step tutorial](docs/configuring.md) describing what this process might entail, and here's a full list of the scripts which [come bundled with PyDwarf](docs/scripts.md). ## Modding using PyDwarf From bc6fc573df937dcf1583a9f72f03535d0dd52d73 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 17:19:26 -0400 Subject: [PATCH 90/95] Replaced a bit of silliness with a lambda in putnam.materialsplus --- scripts/putnam/pydwarf.materialsplus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/putnam/pydwarf.materialsplus.py b/scripts/putnam/pydwarf.materialsplus.py index e33924b..f2074fe 100644 --- a/scripts/putnam/pydwarf.materialsplus.py +++ b/scripts/putnam/pydwarf.materialsplus.py @@ -72,7 +72,7 @@ def materialsplus(df, entities=default_entities): errors = 0 for identifier, re_id, addprops in add_properties: additions = df.allobj(type='INORGANIC', re_id=re_id).each( - raws.token.addprop, addprops + lambda token: token.addprop(addprops) ) if len(additions): pydwarf.log.debug('Added %s properties to %d inorganics.' % (identifier, len(additions))) From 95f23e28202e0d4daa6f78691b74ca14f8d13981 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 17:19:46 -0400 Subject: [PATCH 91/95] Updated manager, raws, and pydwarf versions to 1.0.2 --- manager.py | 2 +- pydwarf/__init__.py | 2 +- raws/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manager.py b/manager.py index 22f7ee5..3054b4b 100644 --- a/manager.py +++ b/manager.py @@ -3,7 +3,7 @@ __author__ = 'Sophie Kirschner' __license__ = 'zlib/libpng' __email__ = 'sophiek@pineapplemachine.com' -__version__ = '1.0.1' +__version__ = '1.0.2' diff --git a/pydwarf/__init__.py b/pydwarf/__init__.py index c804485..12b81e8 100644 --- a/pydwarf/__init__.py +++ b/pydwarf/__init__.py @@ -3,7 +3,7 @@ __author__ = 'Sophie Kirschner' __license__ = 'zlib/libpng' __email__ = 'sophiek@pineapplemachine.com' -__version__ = '1.0.1' +__version__ = '1.0.2' diff --git a/raws/__init__.py b/raws/__init__.py index 39818c4..f640bf8 100644 --- a/raws/__init__.py +++ b/raws/__init__.py @@ -3,7 +3,7 @@ __author__ = 'Sophie Kirschner' __license__ = 'zlib/libpng' __email__ = 'sophiek@pineapplemachine.com' -__version__ = '1.0.1' +__version__ = '1.0.2' From 0c53da886ffc9e92555876fa89fe016984b07ab0 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 17:20:00 -0400 Subject: [PATCH 92/95] Added a favicon for html things to use --- images/favicon.png | Bin 0 -> 264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/favicon.png diff --git a/images/favicon.png b/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..5960997b3e72bf06919ff251e48c2f2f4b645790 GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP~JG+RS%4ao$K%kIpW=KSdbAE1aYF-JD%fR4Vl$uzQ znxasiS(2gP?&%wlqL<1J6wmQ=aSX9Iy>zl8-vI><7THVx>u=8UnfORd*W5Qv;P9oA zuTT1(DmLxiUzw1!vWZ)8&Vjh3;NQFzQ*7VZyD$)(# z@MGn*I Date: Tue, 7 Jul 2015 17:30:10 -0400 Subject: [PATCH 93/95] Updated version numbers for pineapple scripts --- scripts/pineapple/pydwarf.bauxitetoaluminum.py | 2 +- scripts/pineapple/pydwarf.boneflux.py | 2 +- scripts/pineapple/pydwarf.castanvil.py | 2 +- scripts/pineapple/pydwarf.cavegrass.py | 2 +- scripts/pineapple/pydwarf.diff.py | 2 +- scripts/pineapple/pydwarf.greensteel.py | 2 +- scripts/pineapple/pydwarf.metalitems.py | 2 +- scripts/pineapple/pydwarf.noaquifers.py | 2 +- scripts/pineapple/pydwarf.noexotic.py | 2 +- scripts/pineapple/pydwarf.nograzers.py | 2 +- scripts/pineapple/pydwarf.utils.py | 2 +- scripts/pineapple/pydwarf.woodmechanisms.py | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/pineapple/pydwarf.bauxitetoaluminum.py b/scripts/pineapple/pydwarf.bauxitetoaluminum.py index 73a23f4..c5d5868 100644 --- a/scripts/pineapple/pydwarf.bauxitetoaluminum.py +++ b/scripts/pineapple/pydwarf.bauxitetoaluminum.py @@ -10,7 +10,7 @@ @pydwarf.urist( name = 'pineapple.bauxitetoaluminum', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''Adds a reaction to the smelter to allow the creation of aluminum bars from bauxite (as ore) and cryolite (as flux). Credit to this forum discussion for the diff --git a/scripts/pineapple/pydwarf.boneflux.py b/scripts/pineapple/pydwarf.boneflux.py index f241983..efdb57b 100644 --- a/scripts/pineapple/pydwarf.boneflux.py +++ b/scripts/pineapple/pydwarf.boneflux.py @@ -16,7 +16,7 @@ @pydwarf.urist( name = 'pineapple.boneflux', - version = '1.0.1', + version = '1.0.2', author = 'Sophie Kirschner', description = '''Adds a reaction to the kiln which consumes bones and produces flux. Inspired by/stolen from Rubble's Bone Flux mod.''', diff --git a/scripts/pineapple/pydwarf.castanvil.py b/scripts/pineapple/pydwarf.castanvil.py index 7c9a96b..1558dba 100644 --- a/scripts/pineapple/pydwarf.castanvil.py +++ b/scripts/pineapple/pydwarf.castanvil.py @@ -12,7 +12,7 @@ @pydwarf.urist( name = 'pineapple.castanvil', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''Adds a reaction to the smelter which makes it possible to create an iron anvil without already having a forge. diff --git a/scripts/pineapple/pydwarf.cavegrass.py b/scripts/pineapple/pydwarf.cavegrass.py index d8e02e4..ef609b6 100644 --- a/scripts/pineapple/pydwarf.cavegrass.py +++ b/scripts/pineapple/pydwarf.cavegrass.py @@ -103,7 +103,7 @@ @pydwarf.urist( name = 'pineapple.cavegrass', - version = '1.0.1', + version = '1.0.2', author = 'Sophie Kirschner', description = '''Changes the grasses in each cavern level to make the different levels more visually distinct, as well as adding a much greater variety. diff --git a/scripts/pineapple/pydwarf.diff.py b/scripts/pineapple/pydwarf.diff.py index 3177107..4684b96 100644 --- a/scripts/pineapple/pydwarf.diff.py +++ b/scripts/pineapple/pydwarf.diff.py @@ -29,7 +29,7 @@ def __str__(self): @pydwarf.urist( name = 'pineapple.diff', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''Merges and applies changes made to some modded raws via diff checking. Should be reasonably smart about automatic conflict resolution but if it complains diff --git a/scripts/pineapple/pydwarf.greensteel.py b/scripts/pineapple/pydwarf.greensteel.py index 6eac8b6..4b8b0fb 100644 --- a/scripts/pineapple/pydwarf.greensteel.py +++ b/scripts/pineapple/pydwarf.greensteel.py @@ -11,7 +11,7 @@ @pydwarf.urist( name = 'pineapple.greensteel', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''Adds an alloy which is lighter and sharper than steel but not so much as adamantine. It can be made from similar ingredients as steel with the addition diff --git a/scripts/pineapple/pydwarf.metalitems.py b/scripts/pineapple/pydwarf.metalitems.py index d605e8d..1ae872e 100644 --- a/scripts/pineapple/pydwarf.metalitems.py +++ b/scripts/pineapple/pydwarf.metalitems.py @@ -22,7 +22,7 @@ @pydwarf.urist( name = 'pineapple.metalitems', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = 'Allows the forging of every type of metal item from the specified metals.', arguments = { diff --git a/scripts/pineapple/pydwarf.noaquifers.py b/scripts/pineapple/pydwarf.noaquifers.py index d5e5937..cec19c1 100644 --- a/scripts/pineapple/pydwarf.noaquifers.py +++ b/scripts/pineapple/pydwarf.noaquifers.py @@ -2,7 +2,7 @@ @pydwarf.urist( name = 'pineapple.noaquifers', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = 'Removes all AQUIFER tokens.', compatibility = (pydwarf.df_0_27, pydwarf.df_0_28, pydwarf.df_0_3x, pydwarf.df_0_40) diff --git a/scripts/pineapple/pydwarf.noexotic.py b/scripts/pineapple/pydwarf.noexotic.py index b2248f6..2f2747c 100644 --- a/scripts/pineapple/pydwarf.noexotic.py +++ b/scripts/pineapple/pydwarf.noexotic.py @@ -2,7 +2,7 @@ @pydwarf.urist( name = 'pineapple.noexotic', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = 'Replaces all [PET_EXOTIC] and [MOUNT_EXOTIC] tags with their non-exotic counterparts.', compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) diff --git a/scripts/pineapple/pydwarf.nograzers.py b/scripts/pineapple/pydwarf.nograzers.py index 6fa141a..f21dfce 100644 --- a/scripts/pineapple/pydwarf.nograzers.py +++ b/scripts/pineapple/pydwarf.nograzers.py @@ -2,7 +2,7 @@ @pydwarf.urist( name = 'pineapple.nograzers', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = 'Removes all [GRAZER] and [STANDARD_GRAZER] tokens.', compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) diff --git a/scripts/pineapple/pydwarf.utils.py b/scripts/pineapple/pydwarf.utils.py index 391dde4..07b837e 100644 --- a/scripts/pineapple/pydwarf.utils.py +++ b/scripts/pineapple/pydwarf.utils.py @@ -5,7 +5,7 @@ @pydwarf.urist( name = 'pineapple.utils.addtoentity', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''A simple utility script which adds tokens to entities.''', arguments = { diff --git a/scripts/pineapple/pydwarf.woodmechanisms.py b/scripts/pineapple/pydwarf.woodmechanisms.py index bd5c0d3..95a9784 100644 --- a/scripts/pineapple/pydwarf.woodmechanisms.py +++ b/scripts/pineapple/pydwarf.woodmechanisms.py @@ -12,7 +12,7 @@ @pydwarf.urist( name = 'pineapple.woodmechanisms', - version = '1.0.0', + version = '1.0.1', author = 'Sophie Kirschner', description = '''Allows construction of wooden mechanisms at the craftdwarf's workshop. Inspired by/stolen from Rubble's Wooden Mechanisms mod.''', From 81d3d3db043947b264a9341388dc52280bffebdb Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 17:31:44 -0400 Subject: [PATCH 94/95] Updated version numbers for putnam and shukaro scripts --- scripts/putnam/pydwarf.materialsplus.py | 2 +- scripts/putnam/pydwarf.microreduce.py | 2 +- scripts/shukaro/pydwarf.creationforge.py | 2 +- scripts/shukaro/pydwarf.higherlearning.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/putnam/pydwarf.materialsplus.py b/scripts/putnam/pydwarf.materialsplus.py index f2074fe..e2de445 100644 --- a/scripts/putnam/pydwarf.materialsplus.py +++ b/scripts/putnam/pydwarf.materialsplus.py @@ -62,7 +62,7 @@ @pydwarf.urist( name = 'putnam.materialsplus', - version = '1.0.0', + version = '1.0.1', author = ('Putnam', 'Sophie Kirschner'), description = 'Adds a bunch of materials to the game.', compatibility = (pydwarf.df_0_34, pydwarf.df_0_40) diff --git a/scripts/putnam/pydwarf.microreduce.py b/scripts/putnam/pydwarf.microreduce.py index 10ac938..aef4b3f 100644 --- a/scripts/putnam/pydwarf.microreduce.py +++ b/scripts/putnam/pydwarf.microreduce.py @@ -10,7 +10,7 @@ @pydwarf.urist( name = 'putnam.microreduce', - version = '1.0.0', + version = '1.0.1', author = ('Putnam', 'Sophie Kirschner'), description = 'A mod to reduce the amount of micromanagement in Dwarf Fortress. One-step soap making and clothesmaking!', compatibility = pydwarf.df_0_40 diff --git a/scripts/shukaro/pydwarf.creationforge.py b/scripts/shukaro/pydwarf.creationforge.py index 7b7a4bf..c99f547 100644 --- a/scripts/shukaro/pydwarf.creationforge.py +++ b/scripts/shukaro/pydwarf.creationforge.py @@ -10,7 +10,7 @@ @pydwarf.urist( name = 'shukaro.creationforge', - version = '1.0.0', + version = '1.0.1', author = ('Shukaro', 'Sophie Kirschner'), description = '''This is a simple workshop I modded in to help test custom reactions, buildings, and creatures. It's used to create various different items so that you diff --git a/scripts/shukaro/pydwarf.higherlearning.py b/scripts/shukaro/pydwarf.higherlearning.py index 1a50348..08f5388 100644 --- a/scripts/shukaro/pydwarf.higherlearning.py +++ b/scripts/shukaro/pydwarf.higherlearning.py @@ -26,7 +26,7 @@ @pydwarf.urist( name = 'shukaro.higherlearning', - version = '1.0.0', + version = '1.0.1', author = ('Shukaro', 'Sophie Kirschner'), description = '''Have you ever wondered to yourself, "Man, my dwarves are such idiots, I wish I could chisel some intelligence into their heads."? No? Then, er, disregard From 86f14bcb401702ebe8d3e202131cd78698263fb4 Mon Sep 17 00:00:00 2001 From: Sophie Kirschner Date: Tue, 7 Jul 2015 17:34:21 -0400 Subject: [PATCH 95/95] Updated changelog --- changelog.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/changelog.txt b/changelog.txt index f4568eb..3a5c998 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,31 @@ +v1.0.2: Magma Forge + +Fixed the vagueness of most exceptions in the raws and pydwarf packages, most of them should now be reasonably forthcoming about what went wrong and why. +Fixed bug in raws.token.add which was arising from a dumb typo and really should have been caught long before. +Renamed raws.dir.addfile to raws.dir.add and raws.dir.setfile to raws.dir.set. +Renamed raws.file to raws.rawfile to make things less confusing in light of some other additions. +Renamed stal.armourypack to stal.armoury because why not. +Removed pineapple.utils.addreaction, which has been superseded by the newer and shinier pineapple.utils.addobject. +Added a great deal of functionality to the raws.dir object, which is now capable of tracking an entire DF directory as opposed to just its raw/objects subdirectory. +Added three new file classes: raws.basefile, raws.reffile, and raws.binfile. +Added more documentation and tutorials, big surprise. (Also added and neglected to document a bunch of new functionality while I was at it, he he he.) +Added a number of handy new operator overloads and fiddled a few existing ones while I was at it. +Added methods like raws.queryable.removefirst, raws.queryable.removeall, and quite a few more. +Added script pkdawson.vegan, a port of df-vegan. +Added scripts underneath omniclasm.decay, a port of State of Decay. +Added scripts umiman.smallthings.threats and umiman.smallthings.nofamily, ports of parts of smallthings that PyDwarf couldn't support until now. +Added scripts pineapple.easypatch, pineapple.utils.addobject, pineapple.utils.addobjects, pineapple.utils.permitobject, pineapple.utils.permitobjects, and pineapple.utils.addhack. +Added raws.objects module which helps with knowing some things about how raws files are supposed to be structured. +Added a very nifty raws.tokenlist.each method which did great wonders for terseness of some mods. +Improved general flow and structure of manager code against pydwarf.session code, moved things around and generally tidied up. +Improved the __getitem__ method for raws.queryable objects so that now it can handle ellipses and slices and some other stuff too. +Improved the way token arguments are internally handled, and made a few changes to pertinent token methods. +Improved pineapple.utils.addentity and made a pretty big change to how it accepts arguments in the process. +Improved raws.queryable.getobj and raws.queryable.allobj methods, now they can do even more stuff than before. +Improved the raws.tokenfilter constructor, which is now decent enough to handle passing a single tuple/whatever to exact_arg, re_arg, or arg_in rather than an iterable of them. +Improved stal.armoury, putnam.materialsplus, putnam.microreduce, shukaro.higherlearning, and shukaro.creationforge by rewriting each of them entirely. +Tweaked almost every script in some way, really, mostly in the interest of utilizing newer and more awesome functionality. + v1.0.1: Strange Mood Fixed pineapple.boneflux reaction by being less bad at string formatting