Initial development commit after filing off Victrix Abyssi's serial numbers
authorMartin Read <martin@blackswordsonics.com>
Sat, 8 Mar 2014 00:42:43 +0000 (00:42 +0000)
committerMartin Read <martin@blackswordsonics.com>
Sat, 8 Mar 2014 00:42:43 +0000 (00:42 +0000)
Oh, I also have already started the item database changes; no set of magic
ribbons here.

48 files changed:
COPYING [new file with mode: 0644]
Doxyfile [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile [new file with mode: 0644]
cave.cc [new file with mode: 0644]
combat.cc [new file with mode: 0644]
combat.hh [new file with mode: 0644]
configure [new file with mode: 0755]
coord.cc [new file with mode: 0644]
coord.hh [new file with mode: 0644]
core.hh [new file with mode: 0644]
default.permobjs [new file with mode: 0644]
default.permons [new file with mode: 0644]
display-nc.cc [new file with mode: 0644]
display.hh [new file with mode: 0644]
fov.cc [new file with mode: 0644]
fov.hh [new file with mode: 0644]
log.cc [new file with mode: 0644]
main.cc [new file with mode: 0644]
map.cc [new file with mode: 0644]
map.hh [new file with mode: 0644]
mapgen.hh [new file with mode: 0644]
mon2.cc [new file with mode: 0644]
monsters.cc [new file with mode: 0644]
monsters.hh [new file with mode: 0644]
notes.txt [new file with mode: 0644]
notify-local-tty.cc [new file with mode: 0644]
notify.hh [new file with mode: 0644]
objects.cc [new file with mode: 0644]
objects.hh [new file with mode: 0644]
obumbrata.6 [new file with mode: 0644]
obumbrata.hh [new file with mode: 0644]
permobj.hh [new file with mode: 0644]
permon.hh [new file with mode: 0644]
player.hh [new file with mode: 0644]
pmon2.cc [new file with mode: 0644]
pmon_comp [new file with mode: 0755]
pmon_id.hh [new file with mode: 0644]
pobj_comp [new file with mode: 0755]
pobj_id.hh [new file with mode: 0644]
rng.cc [new file with mode: 0644]
rng.hh [new file with mode: 0644]
shrine.cc [new file with mode: 0644]
sorcery.cc [new file with mode: 0644]
sorcery.hh [new file with mode: 0644]
u.cc [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..09d44d2
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+
+Copyright 2005-2014 Martin Read
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Doxyfile b/Doxyfile
new file mode 100644 (file)
index 0000000..db91177
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,1792 @@
+# Doxyfile 1.8.1.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = "Obumbrata et Velata"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  =
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..dadebd6
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,46 @@
+COPYING
+MANIFEST
+Makefile
+configure
+Doxyfile
+man/victrix-abyssi.6
+cave.cc
+combat.cc
+combat.hh
+coord.cc
+coord.hh
+core.hh
+default.permobjs
+default.permons
+display.hh
+display-nc.cc
+fov.cc
+fov.hh
+log.cc
+main.cc
+map.cc
+map.hh
+mapgen.hh
+mon2.cc
+monsters.cc
+monsters.hh
+notes.txt
+notify.hh
+notify-local-tty.cc
+objects.cc
+objects.hh
+permobj.hh
+permon.hh
+player.hh
+pmon2.cc
+pmon_comp
+pobj_comp
+rng.cc
+rng.hh
+shrine.cc
+sorcery.cc
+sorcery.hh
+u.cc
+util.c
+util.h
+victrix-abyssi.hh
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1b54e00
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,131 @@
+#!/usr/bin/make -f
+#
+# Makefile for Obumbrata et Velata
+
+include dirs.mk
+include features.mk
+
+vpath %.o .
+GENERATED_OBJS:=permobj.o permons.o 
+GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh
+GENERATED_MAKES:=dirs.mk features.mk
+HANDWRITTEN_OBJS:=cave.o combat.o coord.o display-nc.o fov.o log.o main.o map.o monsters.o mon2.o notify-local-tty.o objects.o pmon2.o rng.o shrine.o sorcery.o u.o util.o
+OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS)
+GAME:=obumbrata
+MAJVERS:=1
+MINVERS:=0
+PATCHVERS:=0
+COMMON_CFLAGS:=-Wall -Wwrite-strings -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS)-std=gnu11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
+COMMON_CXXFLAGS:=-Wall -Wwrite-strings -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS) -std=gnu++11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
+PRODUCTION_CFLAGS:=$(COMMON_CFLAGS)
+DEVELOPMENT_CFLAGS:=$(COMMON_CFLAGS) -O2 -g -Werror
+PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS)
+DEVELOPMENT_CXXFLAGS:=$(COMMON_CXXFLAGS) -O2 -g -Werror
+LIBS=-lpanelw -lncursesw -lxdg-basedir -lm
+ARCHIVEDIR:=$(GAME)-$(MAJVERS).$(MINVERS).$(PATCHVERS)
+ARCHIVENAME:=$(GAME)_$(MAJVERS).$(MINVERS).$(PATCHVERS)
+COMMON_LDFLAGS:=-Wl,-z,relro
+DEVELOPMENT_LDFLAGS:=$(COMMON_LDFLAGS) -g
+
+## PHONY targets in this section, please
+
+.PHONY: all archive clean code-docs code-docs-clean debianize-archive install my-debworkflow my-debclean spotless
+
+all: $(GAME)
+
+archive: clean ./permobj.cc ./pobj_id.hh
+       mkdir $(ARCHIVEDIR)
+       cp `cat MANIFEST` $(ARCHIVEDIR)
+       tar czf $(ARCHIVENAME).tar.gz $(ARCHIVEDIR)
+       rm -r $(ARCHIVEDIR)
+
+debianize-archive: archive
+       mv $(ARCHIVENAME).tar.gz $(ARCHIVENAME).orig.tar.gz
+
+my-debworkflow: my-debclean debianize-archive
+       mkdir archive-test
+       cp $(ARCHIVENAME).orig.tar.gz archive-test
+       (cd archive-test && tar xzf $(ARCHIVENAME).orig.tar.gz)
+       cp -R debian archive-test/$(ARCHIVEDIR)/debian
+       (cd archive-test/$(ARCHIVEDIR) && debuild -uc -us)
+
+my-debclean:
+       -rm -rf archive-test
+
+clean:
+       -rm -rf $(ARCHIVEDIR)
+       -rm -f *.o $(GAME) *.tar.gz
+
+code-docs:
+       doxygen Doxyfile
+
+code-docs-clean:
+       -rm -rf html latex
+
+gitclean: spotless
+       -rm -rf $(GENERATED_MAKES)
+
+generated-clean: 
+       -rm -f $(GENERATED_SOURCE)
+
+install: all
+       echo "man6dir is $(man6dir)"
+       install -D $(GAME) $(DESTDIR)$(gamesdir)/$(GAME)
+       install -D $(GAME).6 $(DESTDIR)$(man6dir)/$(GAME).6
+
+spotless: clean code-docs-clean my-debclean generated-clean
+
+## Real targets only after this point please
+
+$(GAME): $(OBJS)
+       $(CXX) $(DEVELOPMENT_LDFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $(GAME)
+
+%.o: $(srcdir)/%.cc
+       $(CXX) -c $(DEVELOPMENT_CXXFLAGS) $(CPPFLAGS) $(CXXFLAGS) $< -o $@
+
+%.o: $(srcdir)/%.c
+       $(CC) -c $(DEVELOPMENT_CFLAGS) $(CPPFLAGS) $(CFLAGS) $< -o $@
+
+## Dependencies for autogeneration
+permobj.cc pobj_id.hh: $(srcdir)/default.permobjs $(srcdir)/pobj_comp
+       $(srcdir)/pobj_comp $<
+
+permons.cc pmon_id.hh: $(srcdir)/default.permons $(srcdir)/pmon_comp
+       $(srcdir)/pmon_comp $<
+## Dependencies for the build
+cave.o: $(srcdir)/cave.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
+
+combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh
+
+display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/$(GAME).hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+log.o: $(srcdir)/log.cc $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/util.h
+
+main.o: $(srcdir)/main.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+map.o: $(srcdir)/map.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/core.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+monsters.o: $(srcdir)/monsters.cc $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+mon2.o: $(srcdir)/mon2.cc $(srcdir)/$(GAME).hh $(srcdir)/sorcery.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+notify-local-tty.o: $(srcdir)/notify-local-tty.cc $(srcdir)/$(GAME).hh $(srcdir)/combat.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/sorcery.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+permobj.o: ./permobj.cc $(srcdir)/core.hh $(srcdir)/permobj.hh
+
+permons.o: ./permons.cc $(srcdir)/core.hh $(srcdir)/permon.hh
+
+pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+objects.o: $(srcdir)/objects.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+shrine.o: $(srcdir)/shrine.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
+sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+u.o: $(srcdir)/u.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+util.o: $(srcdir)/util.c $(srcdir)/util.h
+
+# vim:ts=8:sts=8:sw=8:noexpandtab
diff --git a/cave.cc b/cave.cc
new file mode 100644 (file)
index 0000000..42b83a8
--- /dev/null
+++ b/cave.cc
@@ -0,0 +1,114 @@
+/*! \file cave.cc
+ *  \brief Basic cave-style level generation algorithm
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+void place_cave_stairs(void)
+{
+    Coord c;
+    Coord d;
+    int stair_tries = 0;
+    do
+    {
+        c = lvl.random_point(1);
+    } while (!(terrain_props[lvl.terrain_at(c)].flags & TFLAG_floor) ||
+             (lvl.flags_at(c) & MAPFLAG_HARDWALL));
+    lvl.add_stairs_at(c, STAIRS_DOWN, lvl.self.naive_next());
+    do
+    {
+        d = lvl.random_point(1);
+        ++stair_tries;
+    } while (!(terrain_props[lvl.terrain_at(d)].flags & TFLAG_floor) ||
+             (lvl.flags_at(d) & MAPFLAG_HARDWALL) ||
+             (d.dist_cheb(c) < (10 - (stair_tries / 40))));
+    lvl.add_stairs_at(d, STAIRS_UP, lvl.self.naive_prev());
+}
+
+/*! \brief Excavate a cave level.
+ *
+ *  This algorithm runs two random walks on the map.
+ */
+void build_level_cave(void)
+{
+    Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
+    int num_pools;
+    int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
+
+    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    //run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    if ((lvl.theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
+    {
+        num_pools = inc_flat(1, 4);
+        walk_data[0] = 2;
+        walk_data[1] = LAVA;
+    }
+    else if ((depth > 10) && !zero_die(3))
+    {
+        num_pools = inc_flat(1, 4);
+        walk_data[0] = 2;
+        walk_data[1] = WATER;
+    }
+    else
+    {
+        num_pools = 0;
+    }
+    while (num_pools > 0)
+    {
+        int pool_size = inc_flat(9, 36);
+        do {
+            c = lvl.random_point(2);
+        } while (lvl.terrain_at(c) != FLOOR);
+        run_random_walk(c, excavation_write, walk_data, pool_size);
+        --num_pools;
+    }
+    place_cave_stairs();
+}
+
+/*! \brief Excavate a cave level with intrusions */
+void build_level_intrusions(void)
+{
+    Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
+    int i;
+    int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
+
+    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    for (i = 0; i < 6; ++i)
+    {
+        place_random_intrusion(WALL);
+    }
+    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    /* and now the stairs */
+    place_cave_stairs();
+}
+
+/* cave.cc */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/combat.cc b/combat.cc
new file mode 100644 (file)
index 0000000..8a14113
--- /dev/null
+++ b/combat.cc
@@ -0,0 +1,440 @@
+/*! \file combat.cc
+ *  \brief combat mechanics
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "monsters.hh"
+#include "objects.hh"
+
+
+/*! \brief Do the baseline melee damage calculation
+ *
+ * \return The computed basic damage output of the player's weapon/hands
+ */
+int player_melee_base(void)
+{
+    int dmgbase;
+    int damage;
+    if (u.weapon != NO_OBJ)
+    {
+        dmgbase = permobjs[objects[u.weapon].obj_id].power + (u.body / 10);
+        damage = dmgbase / 3 + one_die(dmgbase - dmgbase / 3);
+    }
+    else
+    {
+        damage = u.body / 10;
+    }
+    return damage;
+}
+
+/*! \brief Calculate the effect of the player's  damage amplifier ring
+ */
+bool ring_effectiveness(int mon, int ring_pobj, int damage, int *bonus_damage, int *vamp_healing)
+{
+    bool rv = false;
+    int pm = monsters[mon].mon_id;
+    *vamp_healing = 0;
+    switch (ring_pobj)
+    {
+    case PO_FIRE_RING:
+        if (!pmon_resists_fire(pm))
+        {
+            *bonus_damage = dice(2, 4) + ((damage + 1) / 2);
+            rv = true;
+        }
+        break;
+    case PO_VAMPIRE_RING:
+        if (!pmon_resists_necro(pm))
+        {
+            *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
+            *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
+            rv = true;
+        }
+        break;
+    case PO_FROST_RING:
+        if (!pmon_resists_cold(pm))
+        {
+            *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
+            rv = true;
+        }
+        break;
+    default:
+        break;
+    }
+    return rv;
+}
+
+/*! \brief Common damage path for uhitm(), player_power_attack()...
+ * 
+ * \param mon Monster hit
+ * \param damage Rolled damage before rings are applied
+ */
+void resolve_player_melee(int mon, int damage)
+{
+    bool ring_eff;
+    int ring_bonus;
+    int healing = 0;
+    if (u.ring != NO_OBJ)
+    {
+        ring_eff = ring_effectiveness(mon, objects[u.ring].obj_id, damage, &ring_bonus, &healing);
+        if (ring_eff)
+        {
+            notify_ring_boost(mon, objects[u.ring].obj_id);
+            damage += ring_bonus;
+        }
+    }
+    notify_player_hurt_mon(mon, damage);
+    damage_mon(mon, damage, true);
+    if (healing > 0)
+    {
+        heal_u(healing, 0, 1);
+    }
+    if (u.weapon != NO_OBJ)
+    {
+        damage_obj(u.weapon);
+    }
+}
+
+/*! \brief Resolve the "Power Attack" combo move
+ *
+ * \param delta Direction in which player is attacking.
+ */
+Action_cost player_power_attack(Offset delta)
+{
+    Coord c = u.pos + delta;
+    int damage;
+    int mon = lvl.mon_at(c);
+    /* Power Attack: Always hit, do +75% damage. */
+    notify_player_combo_powatk(mon);
+    damage = (player_melee_base() * 7) / 4;
+    resolve_player_melee(mon, damage);
+    return Cost_std;
+}
+
+Action_cost player_attack(Offset delta)
+{
+    Coord c = u.pos + delta;
+    if (wielding_ranged_weapon())
+    {
+        ushootm(delta);
+    }
+    else if (lvl.mon_at(c) != NO_MON)
+    {
+        uhitm(lvl.mon_at(c));
+    }
+    else
+    {
+        notify_no_attackee();
+        return Cost_none;
+    }
+    return Cost_std;
+}
+
+int uhitm(int mon)
+{
+    Mon *mp;
+    int tohit;
+    int damage;
+    int hitbase = u.agility + u.level;
+    mp = monsters + mon;
+    tohit = hitbase / 3 + zero_die(hitbase - hitbase / 3);
+    if (tohit < mp->defence)
+    {
+        notify_player_miss(mon);
+        return 0;       /* Missed. */
+    }
+    notify_player_hit_mon(mon);
+    damage = player_melee_base();
+    resolve_player_melee(mon, damage);
+    return 1;   /* Hit. */
+}
+
+int ushootm(Offset step)
+{
+    /* Propagate a missile in direction (sy,sx). Attack first target in
+     * LOF. */
+    int tohit;
+    int range;
+    Coord c = u.pos + step;
+    bool done = false;
+    int mon;
+    Mon *mptr;
+    Obj *wep;
+    Permobj *pwep;
+    int damage;
+    wep = objects + u.weapon;
+    pwep = permobjs + wep->obj_id;
+    damage = one_die(pwep->power);
+    for (range = 1; !done; ++range, (c += step))
+    {
+        mon = lvl.mon_at(c);
+        if (mon != NO_MON)
+        {
+            done = true;
+            mptr = monsters + mon;
+            tohit = zero_die(u.agility + u.level - range);
+            if (range == 1)
+            {
+                /* Shooting at point-blank is tricky */
+                tohit = (tohit + 1) / 2;
+                notify_point_blank_warning();
+            }
+            if (tohit >= mptr->defence)
+            {
+                if (mon_visible(mon))
+                {
+                    notify_player_hit_mon(mon);
+                    notify_player_hurt_mon(mon, damage);
+                }
+                damage_mon(mon, damage, true);
+                if ((mptr->used) && (wep->obj_id == PO_THUNDERBOW))
+                {
+                    int kb = knockback_mon(mon, step, true, true);
+                    switch (kb)
+                    {
+                    case 0:
+                        break;
+                    case 1:
+                        break;
+                    case 2:
+                        /* message handled elsewhere */
+                        break;
+                    }
+                }
+                return 1;
+            }
+            else
+            {
+                notify_player_miss(mon);
+                return 0;
+            }
+        }
+        else if (terrain_blocks_missiles(lvl.terrain_at(c)))
+        {
+            notify_player_shot_terrain(u.weapon, c);
+            return 0;
+        }
+    }
+    return 0;
+}
+
+int mhitu(int mon, Damtyp dtype)
+{
+    int tohit;
+    int damage;
+    int unaffected;
+    Mon *mptr = monsters + mon;
+    tohit = zero_die(mptr->mtohit + 5);
+    if (tohit < u.defence)
+    {
+        /* Note: Yes, all attacks can damage your armour. Deal. */
+        if ((u.armour != NO_OBJ) && (tohit > agility_modifier()))
+        {
+            /* Monster hit your armour. */
+            damage_obj(u.armour);
+            notify_mon_hit_armour(mon);
+        }
+        else
+        {
+            notify_mon_missed_player(mon);
+        }
+        return 0;
+    }
+    damage = one_die(mptr->mdam);
+    unaffected = u.resists(dtype);
+    notify_mon_hit_player(mon);
+    if (u.armourmelt && (!zero_die(3)))
+    {
+        /* If you're subject to armourmelt, it is decreed that one
+         * blow in three hits your dust-weak armour and rips a chunk
+         * out of it. */
+        damage_obj(u.armour);
+    }
+test_unaffected:
+    if (unaffected)
+    {
+        switch (dtype)
+        {
+        case DT_PHYS:
+        case DT_HELLFIRE:
+        case DT_KNOCKBACK:
+            debug_player_resists_phys();
+            unaffected = 0;
+            /* Turn off the player's resistance, because they're
+             * not supposed to have it! */
+            u.resistances[dtype] = 0;
+            goto test_unaffected;
+        case DT_FIRE:
+        case DT_COLD:
+        case DT_NECRO:
+        case DT_POISON:
+        case DT_DROWNING:
+        case DT_ELEC:
+            /* these are the only damage types you should be completely
+             * ignoring right now */
+            notify_player_ignore_damage(dtype);
+            break;
+        default:
+            debug_bad_damage_type(dtype);
+            break;
+        }
+    }
+    else
+    {
+        notify_player_touch_effect(dtype);
+        if ((mptr->mon_id == PM_VAMPIRE) && !u.resists(DT_NECRO))
+        {
+            heal_mon(mon, damage * 2 / 5, 1);
+        } else if ((tohit - u.defence >= 5) && (mptr->mon_id == PM_SNAKE))
+        {
+            drain_body(1, "snake venom", 0);
+        }
+        damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+    }
+    return 1;
+}
+
+int mshootu(int mon)
+{
+    Mon *mptr;
+    Mon *bystander;
+    Coord c;
+    Offset delta;
+    Offset step;
+    int done;
+    int unaffected = 0;
+    int tohit;
+    int damage;
+    int evasion;
+    int defence;
+    Damtyp dtype;
+    mptr = monsters + mon;
+    c = mptr->pos;
+    /* dy, dx == trajectory of missile */
+    delta = u.pos.delta(c);
+    step = mysign(delta);
+    /* Don't get the bonus that applies to melee attacks. */
+    tohit = zero_die(mptr->rtohit);
+    dtype = permons[mptr->mon_id].rdtyp;
+    notify_mon_ranged_attack(mon);
+    if ((dtype == DT_NECRO) || (dtype == DT_ELEC))
+    {
+        /* Use agility-based defence for necromantic blasts and lightning
+         * bolts */
+        evasion = u.agility * 100;
+        if (u.armour != NO_OBJ)
+        {
+            evasion -= (u.agility * evasion_penalty(u.armour));
+        }
+        defence = evasion / 200;
+    }
+    else
+    {
+        defence = u.defence;
+    }
+    /* Move projectile one square before looking for targets. */
+    for ((done = 0), (c = mptr->pos + step); !done; c += step)
+    {
+        int victim;
+        if ((lvl.terrain_at(c) == WALL) || (lvl.terrain_at(c) == DOOR))
+        {
+            done = 1;
+        }
+        victim = lvl.mon_at(c);
+        if (c == u.pos)
+        {
+            if (tohit >= defence)
+            {
+                done = 1;
+                notify_mon_ranged_hit_player(mon);
+                unaffected = u.resists(dtype);
+                if (!unaffected)
+                {
+                    damage = one_die(mptr->rdam);
+                    damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+                }
+                return 1;
+            }
+            else
+            {
+                notify_mon_ranged_missed_player(mon);
+            }
+        }
+        else if (victim != NO_MON)
+        {
+            done = 1;
+            bystander = monsters + victim;
+            switch (dtype)
+            {
+            case DT_COLD:
+                if (pmon_resists_cold(bystander->mon_id))
+                {
+                    unaffected = 1;
+                }
+                else
+                {
+                    unaffected = 0;
+                }
+                break;
+            case DT_FIRE:
+                if (pmon_resists_fire(bystander->mon_id))
+                {
+                    unaffected = 1;
+                }
+                else
+                {
+                    unaffected = 0;
+                }
+                break;
+            case DT_NECRO:
+                if (pmon_is_undead(bystander->mon_id))
+                {
+                    unaffected = 1;
+                }
+                else
+                {
+                    unaffected = 0;
+                }
+                break;
+            default:
+                unaffected = 0;
+                break;
+            }
+            if (tohit >= bystander->defence)
+            {
+                notify_mon_ranged_hit_mon(mon, victim);
+                damage = one_die(mptr->rdam);
+                damage_mon(victim, dtype, false);
+            }
+        }
+    }
+    return 0;
+}
+
+/* combat.cc */
+// vim:cindent
diff --git a/combat.hh b/combat.hh
new file mode 100644 (file)
index 0000000..a65c4f8
--- /dev/null
+++ b/combat.hh
@@ -0,0 +1,60 @@
+/* \file combat.hh
+ * \brief Combat functions header
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef COMBAT_HH
+#define COMBAT_HH
+
+#ifndef OBUMBRATA_HH
+#include "obumbrata.hh"
+#endif
+
+#ifndef MONSTERS_HH
+#include "monsters.hh"
+#endif
+
+#define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5))
+/* XXX combat.c data and funcs */
+extern Action_cost throw_flask(int obj, Offset step);
+extern Action_cost player_attack(Offset step);
+extern Action_cost player_power_attack(Offset step);
+extern void resolve_player_melee(int mon, int damage);
+extern int mhitu(int mon, Damtyp dtyp);
+extern int uhitm(int mon);
+extern int mshootu(int mon);
+extern int ushootm(Offset step);
+
+class Combo_spec
+{
+public:
+    char const *name;
+};
+
+#endif
+
+/* combat.hh */
+// vim:cindent
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..0aaf5af
--- /dev/null
+++ b/configure
@@ -0,0 +1,162 @@
+#! /usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+use English;
+# Prefixes
+my $prefix='/usr/local';
+my $exec_prefix='$(prefix)';
+
+# Bin and lib dirs
+my $bindir='$(exec_prefix)/bin';
+my $sbindir='$(exec_prefix)/sbin';
+my $libdir='$(exec_prefix)/lib';
+my $libexecdir='$(exec_prefix)/libexec';
+my $gamesdir='$(prefix)/games';
+my $srcdir=undef;
+
+my $datarootdir='$(prefix)/share';
+my $datadir='$(datarootdir)/obumbrata';
+my $sysconfdir='$(prefix)/etc';
+my $sharedstatedir='$(prefix)/com';
+my $localstatedir='$(prefix)/var';
+
+my $includedir='$(prefix)/include';
+my $oldincludedir='/usr/include';
+
+my $docdir='$(datarootdir)/doc/obumbrata';
+my $htmldir='$(docdir)';
+my $dvidir='$(docdir)';
+my $pdfdir='$(docdir)';
+my $psdir='$(docdir)';
+
+my $localedir='$(datarootdir)/locale';
+
+my $infodir='$(datarootdir)/info';
+
+my $mandir='$(datarootdir)/man';
+my $man1dir='$(mandir)/man1';
+my $man3dir='$(mandir)/man3';
+my $man5dir='$(mandir)/man5';
+my $man6dir='$(mandir)/man6';
+my $man7dir='$(mandir)/man7';
+my $man1ext='.1';
+my $man3ext='.3';
+my $man5ext='.5';
+my $man6ext='.6';
+my $man7ext='.7';
+
+# feature control
+my $disable_statics='false';
+
+my $verbose=1;
+GetOptions(
+'prefix=s' => \$prefix, 'exec_prefix=s' => \$exec_prefix,
+'bindir=s' => \$bindir, 'libdir=s' => \$libdir,
+'srcdir=s' => \$srcdir,
+'libexecdir=s' => \$libexecdir, 'gamesdir=s' => \$gamesdir,
+'includedir=s' => \$includedir, 'oldincludedir=s' => \$oldincludedir,
+'datarootdir=s' => \$datarootdir, 'datadir=s' => \$datadir,
+'sysconfdir=s' => \$sysconfdir, 'sharedstatedir=s' => \$sharedstatedir,
+'localstatedir=s' => \$localstatedir, 'localedir=s' => \$localedir,
+'mandir=s' => \$mandir,
+'man1dir=s' => \$man1dir, 'man3dir=s' => \$man3dir,
+'man5dir=s' => \$man5dir, 'man6dir=s' => \$man6dir,
+'man7dir=s' => \$man7ext,
+'man1ext=s' => \$man1ext, 'man3ext=s' => \$man3ext,
+'man5ext=s' => \$man5ext, 'man6ext=s' => \$man6ext,
+'man7ext=s' => \$man7ext,
+'docdir=s' => \$docdir, 'htmldir=s' => \$htmldir,
+'dvidir=s' => \$dvidir, 'pdfdir=s' => \$pdfdir,
+'psdir=s' => \$psdir, 'infodir=s' => \$infodir,
+'verbose' => \$verbose,
+'disable-statics' => sub { $disable_statics='true' },
+'quiet' => sub { $verbose = 0; } );
+
+print STDOUT "Configuring Obumbrata et Velata build/install process...\n" if $verbose;
+
+my $cmdresults;
+my $probably_in_main_dir = 0;
+
+if (!defined($srcdir))
+{
+    # Heuristic check when srcdir undefined: If the main header file is here,
+    # srcdir should be "."; if the main header file is not here but is in the
+    # parent dir, srcdir should be "..".
+    if (-f "./obumbrata.hh")
+    {
+        # We're probably in srcdir.
+        $srcdir = "."
+    }
+    elsif (-f "../obumbrata.hh")
+    {
+        # We're probably in an immediate child of srcdir.
+        $srcdir = ".."
+    }
+}
+print STDOUT "Testing 'mkdir -p' ...\n" if $verbose;
+rmdir "billy/bob";
+rmdir "billy";
+$cmdresults=`mkdir -p billy/bob 2>&1`;
+if ($CHILD_ERROR != 0) {
+    print STDERR "mkdir -p billy/bob failed\n";
+    rmdir "billy";
+    die "Please install a POSIX-compliant mkdir.\n";
+}
+rmdir "billy/bob";
+rmdir "billy";
+
+if ($includedir eq $oldincludedir) { $oldincludedir = ''; }
+
+print STDOUT "Writing dirs.mk...\n" if $verbose;
+open(DIRS_MK, '>', "dirs.mk") or die $!;
+print DIRS_MK "prefix=${prefix}\n";
+print DIRS_MK "exec_prefix=${exec_prefix}\n";
+print DIRS_MK "bindir=${bindir}\n";
+print DIRS_MK "sbindir=${sbindir}\n";
+print DIRS_MK "libdir=${libdir}\n";
+print DIRS_MK "libexecdir=${libexecdir}\n";
+print DIRS_MK "gamesdir=${gamesdir}\n";
+print DIRS_MK "srcdir=${srcdir}\n";
+print DIRS_MK "datarootdir=${datarootdir}\n";
+print DIRS_MK "datadir=${datadir}\n";
+print DIRS_MK "sysconfdir=${sysconfdir}\n";
+print DIRS_MK "sharedstatedir=${sharedstatedir}\n";
+print DIRS_MK "localstatedir=${localstatedir}\n";
+print DIRS_MK "localedir=${localedir}\n";
+print DIRS_MK "includedir=${includedir}\n";
+print DIRS_MK "oldincludedir=${oldincludedir}\n";
+print DIRS_MK "mandir=${mandir}\n";
+print DIRS_MK "man1dir=${man1dir}\n";
+print DIRS_MK "man3dir=${man3dir}\n";
+print DIRS_MK "man5dir=${man5dir}\n";
+print DIRS_MK "man6dir=${man6dir}\n";
+print DIRS_MK "man7dir=${man7dir}\n";
+print DIRS_MK "man1ext=${man1ext}\n";
+print DIRS_MK "man3ext=${man3ext}\n";
+print DIRS_MK "man5ext=${man5ext}\n";
+print DIRS_MK "man6ext=${man6ext}\n";
+print DIRS_MK "man7ext=${man7ext}\n";
+print DIRS_MK "docdir=${docdir}\n";
+print DIRS_MK "htmldir=${htmldir}\n";
+print DIRS_MK "dvidir=${dvidir}\n";
+print DIRS_MK "pdfdir=${pdfdir}\n";
+print DIRS_MK "psdir=${psdir}\n";
+print DIRS_MK "infodir=${infodir}\n";
+close(DIRS_MK);
+
+print STDOUT "Writing features.mk...\n" if $verbose;
+open(FEATURES_MK, '>', "features.mk") or die $!;
+print FEATURES_MK "DISABLE_STATICS=${disable_statics}";
+close(FEATURES_MK);
+
+if (($srcdir ne ".") && ($srcdir ne ""))
+{
+    open MAKEFILE_IN, "<", "${srcdir}/Makefile";
+    my @makefile = <MAKEFILE_IN>;
+    close MAKEFILE_IN;
+    print STDOUT "Writing Makefile...\n" if $verbose;
+    open MAKEFILE_OUT, ">", "./Makefile";
+    print MAKEFILE_OUT @makefile;
+    close MAKEFILE_OUT;
+}
diff --git a/coord.cc b/coord.cc
new file mode 100644 (file)
index 0000000..b9a60f0
--- /dev/null
+++ b/coord.cc
@@ -0,0 +1,57 @@
+/* \file coord.cc
+ * \brief Positions and offsets - common constants
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "coord.hh"
+#include <limits.h>
+
+/* Coord/Offset constants */
+
+/*! \brief A Coord representing an invalid location */
+Coord const Nowhere = { INT_MIN, INT_MIN };
+/*! \brief One step northward */
+Offset const North = { -1, 0 };
+/*! \brief One step northeast */
+Offset const Northeast = { -1, 1 };
+/*! \brief One step east */
+Offset const East = { 0, 1 };
+/*! \brief One step southeast */
+Offset const Southeast = { 1, 1 };
+/*! \brief One step south */
+Offset const South = { 1, 0 };
+/*! \brief One step southwest */
+Offset const Southwest = { 1, -1 };
+/*! \brief One step west */
+Offset const West = { 0, -1 };
+/*! \brief One step northwest */
+Offset const Northwest = { -1, -1 };
+/*! \brief The additive identity offset */
+Offset const Stationary = { 0, 0 };
+
+/* coord.cc */
+// vim:cindent
diff --git a/coord.hh b/coord.hh
new file mode 100644 (file)
index 0000000..1a2016a
--- /dev/null
+++ b/coord.hh
@@ -0,0 +1,124 @@
+/* \file coord.hh
+ * \brief Positions and offsets
+ */
+
+/* Copyright 2013-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef COORD_HH
+#define COORD_HH
+
+#include <algorithm>
+
+template <typename T> T myabs(T val);
+template <typename T> T mysign(T val);
+
+template <typename T> inline T myabs(T val) { return (val < 0) ? -val : val; }
+template <typename T> inline T mysign(T val) { return (val < 0) ? -1 : ((val > 0) ? 1 : 0); }
+
+/*! \brief A two-dimensional vector
+ *
+ *  An Offset represents a vector in a plane. Addition and subtraction of
+ *  Offsets is fully defined.
+ *
+ *  The lt/le/ge/gt operators impose a total ordering, allowing Offset to
+ *  be used as the key-type of std::map.
+ */
+class Offset
+{
+public:
+    int y;
+    int x;
+    bool ecardinal(void) const { return ((y && !x) || (x && !y)); }
+    bool rcardinal(void) const { return ecardinal() || (myabs(y) == myabs(x)); }
+    int len_cheb(void) const { return std::max(myabs(y), myabs(x)); }
+    int len_taxi(void) const { return myabs(y) + myabs(x); }
+    int lensq_eucl(void) const { return y * y + x * x; }
+    Offset const& operator +=(Offset const& right) { y += right.y; x += right.x; return *this; }
+    Offset const& operator -=(Offset const& right) { y -= right.y; x -= right.x; return *this; }
+    Offset const& operator *=(int right) { y *= right; x *= right; return *this; }
+    Offset const& operator /=(int right) { y /= right; x /= right; return *this; }
+    Offset operator +(Offset const& right) const { Offset o = { y + right.y, x + right.x }; return o; }
+    Offset operator -(Offset const& right) const { Offset o = { y - right.y, x - right.x }; return o; }
+    Offset operator *(int right) const { Offset o = { y * right, x * right }; return o; }
+    Offset operator /(int right) const { Offset o = { y / right, x / right }; return o; }
+    bool operator !=(Offset const& right) const { return (y != right.y) || (x != right.x); }
+    bool operator ==(Offset const& right) const { return (y == right.y) && (x == right.x); }
+    bool operator <(Offset const& right) const { return (y < right.y) || ((y == right.y) && (x < right.x)); }
+    bool operator <=(Offset const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
+    bool operator >(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
+    bool operator >=(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
+    void clamp(int ymin, int xmin, int ymax, int xmax)
+    {
+        y = std::min(ymax, std::max(ymin, y));
+        x = std::min(xmax, std::max(xmin, x));
+    }
+};
+
+/*! \brief A two-dimensional point
+ *
+ *  A Coord approximately represents a point on a plane. Addition of Coords
+ *  to each other is undefined. Addition of an Offset to a Coord yields a
+ *  Coord. Subtraction of a Coord from another Coord yields an Offset.
+ *
+ *  The lt/le/ge/gt operators impose a total ordering, allowing Coord to
+ *  be used as the key-type of std::map.
+ */
+class Coord
+{
+public:
+    int y;
+    int x;
+    int dist_cheb(Coord const& right) const { return std::max(myabs(y - right.y), myabs(x - right.x)); }
+    int dist_taxi(Coord const& right) const { return myabs(y - right.y) + myabs(x - right.x); }
+    int distsq_eucl(Coord const& right) const { return (y - right.y) * (y - right.y) + (x - right.x) * (x - right.x); }
+    Offset delta(Coord const& right) const { Offset d = { y - right.y, x - right.x }; return d; }
+    Coord operator +(Offset const& right) const { Coord c = { y + right.y, x + right.x}; return c; }
+    Coord operator -(Offset const& right) const { Coord c = { y - right.y, x - right.x}; return c; }
+    Coord const& operator +=(Offset const& right) { y += right.y; x += right.x; return *this;}
+    Coord const& operator -=(Offset const& right) { y -= right.y; x -= right.x; return *this;}
+    bool operator !=(Coord const& right) const { return (y != right.y) || (x != right.x); }
+    bool operator ==(Coord const& right) const { return (y == right.y) && (x == right.x); }
+    bool operator <(Coord const& right) const { return (y < right.y) || ((y == right.y) && (x < right.x)); }
+    bool operator <=(Coord const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
+    bool operator >(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
+    bool operator >=(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
+    void clamp(int ymin, int xmin, int ymax, int xmax)
+    {
+        y = std::min(ymax, std::max(ymin, y));
+        x = std::min(xmax, std::max(xmin, x));
+    }
+};
+
+extern Coord const Nowhere;
+extern Offset const North, Northeast, East, Southeast, South, Southwest, West, Northwest, Stationary;
+
+template<> inline Coord myabs<Coord>(Coord val) { Coord c = { myabs(val.y), myabs(val.x) }; return c; }
+template<> inline Offset myabs<Offset>(Offset val) { Offset o = { myabs(val.y), myabs(val.x) }; return o; }
+template<> inline Coord mysign<Coord>(Coord val) { Coord c = { mysign(val.y), mysign(val.x) }; return c; }
+template<> inline Offset mysign<Offset>(Offset val) { Offset o = { mysign(val.y), mysign(val.x) }; return o; }
+#endif
+
+/* coord.hh */
+// vim:cindent
diff --git a/core.hh b/core.hh
new file mode 100644 (file)
index 0000000..de1c387
--- /dev/null
+++ b/core.hh
@@ -0,0 +1,177 @@
+/*! \file core.hh
+ *  \brief Essential predefinitions header for Obumbrata et Velata
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CORE_HH
+#define CORE_HH
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#ifndef COORD_HH
+#include "coord.hh"
+#endif
+
+#ifndef RNG_HH
+#include "rng.hh"
+#endif
+
+enum Comparison
+{
+    Lesser = -1,
+    Equal = 0,
+    Greater = 1
+};
+
+enum Pass_fail
+{
+    You_fail = -1,
+    You_pass = 0
+};
+
+enum Action_cost
+{
+    Cost_none = 0,
+    Cost_std = 1
+};
+
+enum Noisiness
+{
+    Noise_silent,
+    Noise_low,
+    Noise_std
+};
+
+#define is_vowel(ch) (((ch) == 'a') || ((ch) == 'e') || ((ch) == 'i') || ((ch) == 'o') || ((ch) == 'u'))
+
+/* XXX ENUMERATED TYPES XXX */
+
+/*! \brief Defines the displayable colour of things in the game.
+ *
+ *  Game colours after Gcol_l_cyan may alias to a colour in the range l_grey
+ *  through l_cyan depending on your display.
+ */
+enum Gamecolour
+{
+    /* l_cyan is the last "core" colour. Colours after it may alias to one
+     * of the core colours depending on the nature of your display. */
+    Gcol_l_grey, Gcol_d_grey, Gcol_red, Gcol_blue,
+    Gcol_green, Gcol_purple, Gcol_brown, Gcol_cyan,
+    Gcol_white, Gcol_l_red, Gcol_l_blue, Gcol_l_green,
+    Gcol_l_purple, Gcol_yellow, Gcol_l_cyan,
+    /* Fancy colours now! */
+    Gcol_iron, Gcol_gold, Gcol_silver,
+    /* UI customizable colours */
+    Gcol_prio_low, Gcol_prio_normal, Gcol_prio_alert, Gcol_prio_warn, Gcol_prio_bug
+};
+#define LAST_CORE_COLOUR Gcol_l_cyan
+#define LAST_FIXED_COLOUR Gcol_silver
+#define LAST_COLOUR Gcol_prio_bug
+
+/*! \brief Identification code for damage types */
+enum Damtyp {
+    DT_PHYS = 0, DT_COLD, DT_FIRE, DT_NECRO,
+    DT_ELEC, DT_HELLFIRE, DT_POISON,
+    DT_KNOCKBACK, DT_DROWNING
+};
+#define DT_COUNT (1 + DT_DROWNING)
+
+/*! \brief Identification code for player actions */
+enum Game_cmd {
+    REJECTED_ACTION = -1, WALK, STAND_STILL, GO_UP_STAIRS, GO_DOWN_STAIRS,
+    ATTACK,
+    GET_ITEM, DROP_ITEM,
+    WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING,
+    QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD,
+    EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING,
+    USE_ACTIVE_SKILL, ALLOCATE_SKILL_POINT,
+    SAVE_GAME, QUIT,
+    WIZARD_LEVELUP, WIZARD_DESCEND,
+    /* combos begin here */
+    CMD_POWER_ATTACK
+};
+
+/*! \brief Identification code for ways to die
+ * 
+ * Sadly, there are not 52 kinds of way to die.
+ */
+enum Death {
+    DEATH_KILLED, DEATH_KILLED_MON, DEATH_BODY, DEATH_AGILITY,
+    DEATH_LASH, DEATH_RIBBONS
+};
+
+/*! \brief Fell powers that might influence the Princess's fate
+ */
+enum Fell_powers {
+    FePo_iron,
+    FePo_decay,
+    FePo_bone,
+    FePo_flesh
+};
+
+#define TOTAL_FELL_POWERS (1 + FePo_flesh)
+
+#define RESIST_MASK_TEMPORARY   0x0000FFFFu
+#define RESIST_MASK_PERM_EQUIP  0xFFFF0000u
+#define RESIST_RING     0x00010000u
+#define RESIST_ARMOUR   0x00020000u
+
+/*! \brief Represent an in-game action by the player
+ *
+ * This might, at some point, get adapted to 
+ */
+struct Action
+{
+    Game_cmd cmd;
+    uint32_t details[8];
+};
+
+
+class Level;
+class Level_key;
+class Permobj;
+class Permon;
+class Obj;
+class Mon;
+
+#define NO_POBJ (-1)
+#define NO_PMON (-1)
+#define NO_OBJ (-1)
+#define NO_MON (-1)
+#define NO_REGION 0xffffffffu
+
+#endif
+
+/* core.hh */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/default.permobjs b/default.permobjs
new file mode 100644 (file)
index 0000000..8df90c8
--- /dev/null
@@ -0,0 +1,615 @@
+# default.permobjs - preferred form of modification for Obumbrata et Velata permobj database 
+# Copyright 2014 Martin Read.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+WEAPON dagger
+PLURAL daggers
+DESC A long knife, designed for stabbing.
+RARITY 25
+ASCII ')'
+UTF8 ")"
+COLOUR iron
+POWER 4
+POWER2 0
+DEPTH 1
+DAMAGEABLE
+MELEE_WEAPON
+
+WEAPON long sword
+PLURAL long swords
+DESC A steel sword of simple but sturdy construction; the blade is three feet long.
+RARITY 30
+ASCII ')'
+UTF8 ")"
+COLOUR iron
+POWER 10
+POWER2 0
+DEPTH 4
+DAMAGEABLE
+MELEE_WEAPON
+
+WEAPON mace
+PLURAL maces
+DESC A flanged lump of iron on an iron haft.
+RARITY 30
+ASCII ')'
+UTF8 ")"
+COLOUR iron
+POWER 7
+POWER2 0
+DEPTH 2
+DAMAGEABLE
+MELEE_WEAPON
+
+WEAPON runesword
+PLURAL runeswords
+DESC An eerily glowing sword engraved with many strange runes.
+RARITY 80
+ASCII ')'
+UTF8 ")"
+COLOUR l_cyan
+POWER 20
+POWER2 0
+DEPTH 12
+DAMAGEABLE
+MELEE_WEAPON
+
+WEAPON hellglaive
+PLURAL hellglaives
+DESC A black-hafted polearm with a long, wickedly serrated blade. It thrums with infernal power.
+RARITY 80
+ASCII ')'
+UTF8 ")"
+COLOUR d_grey
+POWER 25
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+DAMAGEABLE
+BREAK_REACT
+MELEE_WEAPON
+
+WEAPON plague scythe
+PLURAL plague sycthes
+DESC The few exposed patches of metal on this reeking, filth-encrusted scythe bear an iridescent patina. Only the memory of your battle with its wielder hints at the power it holds.
+RARITY 100
+ASCII ')'
+UTF8 ")"
+COLOUR l_green
+POWER 20
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+MELEE_WEAPON
+
+WEAPON tormentor's lash
+PLURAL tormentor's lashes
+DESC A whip of pale, soft leather that crackles with malefic energies.
+RARITY 80
+ASCII ')'
+UTF8 ")"
+COLOUR white
+POWER 20
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+DAMAGEABLE
+BREAK_REACT
+MELEE_WEAPON
+
+WEAPON death staff
+PLURAL death staves
+DESC A jet-black staff crowned with a skull.
+RARITY 80
+ASCII ')'
+UTF8 ")"
+COLOUR d_grey
+POWER 18
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+MELEE_WEAPON
+
+WEAPON staff of fire
+PLURAL staves of fire
+DESC A jet-black staff with a red glowing gem in its headpiece.
+RARITY 80
+ASCII ')'
+UTF8 ")"
+COLOUR d_grey
+POWER 10
+POWER2 0
+DEPTH 20
+DAMAGEABLE
+ACTIVATABLE
+MELEE_WEAPON
+
+WEAPON bow
+PLURAL bows
+DESC A recurve composite bow.
+RARITY 45 
+ASCII '('
+UTF8 "("
+COLOUR brown
+POWER 8
+POWER2 0
+DEPTH 1
+DAMAGEABLE
+RANGED_WEAPON
+WEAPON crossbow
+PLURAL crossbows
+DESC A crossbow.
+RARITY 70 
+ASCII '('
+UTF8 "("
+COLOUR brown
+POWER 16
+POWER2 0
+DEPTH 6
+DAMAGEABLE
+RANGED_WEAPON
+
+WEAPON thunderbow
+PLURAL thunderbows
+DESC A recurve composite bow decorated with eldritch signs. Arrows fired with this bow strike with staggering force.
+RARITY 70 
+ASCII '('
+UTF8 "("
+COLOUR l_cyan
+POWER 16
+POWER2 0
+DEPTH 30
+DAMAGEABLE
+RANGED_WEAPON
+
+POTION healing potion
+PLURAL healing potions
+DESC This magic elixir restores some lost hit points.
+RARITY 10 
+ASCII '!'
+UTF8 "!"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+POTION body potion
+PLURAL body potions
+DESC This magic elixir will improve your physique.
+RARITY 70 
+ASCII '!'
+UTF8 "!"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 5
+STACKABLE
+
+POTION agility potion
+PLURAL agility potions
+DESC This magic elixir will sharpen your reflexes.
+RARITY 70 
+ASCII '!'
+UTF8 "!"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 5
+STACKABLE
+
+POTION restoration potion
+PLURAL restoration potions
+DESC This magic elixir cures temporary damage to one's abilities.
+RARITY 70 
+ASCII '!'
+UTF8 "!"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+SCROLL teleport scroll
+PLURAL teleport scrolls
+DESC Reading this scroll will teleport you to a random location.
+RARITY 40 
+ASCII '?'
+UTF8 "?"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+SCROLL fire scroll
+PLURAL fire scrolls
+DESC Reading this scroll will engulf all nearby creatures (including you) in flames.
+RARITY 30 
+ASCII '?'
+UTF8 "?"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+SCROLL protection scroll
+PLURAL protection scrolls
+DESC Reading this scroll will dispel any curses afflicting you and protect you from curses for a time.
+RARITY 80 
+ASCII '?'
+UTF8 "?"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 8
+STACKABLE
+
+ARMOUR leather armour
+PLURAL suits of leather armour
+DESC A heavy leather jerkin and breeches, providing some protection.
+RARITY 25 
+ASCII '['
+UTF8 "["
+COLOUR brown
+POWER 3
+POWER2 10
+DEPTH 1
+DAMAGEABLE
+
+ARMOUR chainmail
+PLURAL suits of chainmail
+DESC A suit of interlocking metal rings, providing better protection than leather.
+RARITY 30 
+ASCII '['
+UTF8 "["
+COLOUR iron
+POWER 6
+POWER2 25
+DEPTH 3
+DAMAGEABLE
+
+ARMOUR plate armour
+PLURAL suits of plate armour
+DESC A suit of steel plates, providing better protection than chainmail.
+RARITY 40 
+ASCII '['
+UTF8 "["
+COLOUR iron
+POWER 10
+POWER2 40
+DEPTH 6
+DAMAGEABLE
+
+ARMOUR mage armour
+PLURAL suits of mage armour
+DESC A suit of glowing steel plates bearing enchantments of defence.
+RARITY 70 
+ASCII '['
+UTF8 "["
+COLOUR l_cyan
+POWER 15
+POWER2 40
+DEPTH 12
+DAMAGEABLE
+
+ARMOUR mundane robe
+PLURAL mundane robes
+DESC A simple woolen robe. It's better protection than your skin, but not by much.
+RARITY 50 
+ASCII '['
+UTF8 "["
+COLOUR green
+POWER 2
+POWER2 5
+DEPTH 1
+DAMAGEABLE
+
+ARMOUR robe of swiftness
+PLURAL robes of swiftness
+DESC A simple woolen robe that bears a potent enchantment, protecting the wearer and making him unnaturally swift.
+RARITY 70 
+ASCII '['
+UTF8 "["
+COLOUR green
+POWER 8
+POWER2 0
+DEPTH 8
+DAMAGEABLE
+
+ARMOUR robe of shadows
+PLURAL robes of shadows
+DESC A dusky grey woolen robe that bears an awesome enchantment, protecting the wearer better than steel plate.
+RARITY 90 
+ASCII '['
+UTF8 "["
+COLOUR d_grey
+POWER 14
+POWER2 -15
+DEPTH 18
+DAMAGEABLE
+
+ARMOUR dragonhide armour
+PLURAL suits of dragonhide armour
+DESC The skin of a dragon, formed into a jerkin and breeches; it turns blows like steel plate and turns away flames.
+RARITY 90 
+ASCII '['
+UTF8 "["
+COLOUR red
+POWER 12
+POWER2 10
+DEPTH 21
+DAMAGEABLE
+
+ARMOUR meteoric plate armour
+PLURAL suits of meteoric plate armour
+DESC This plate armour has been forged out of metal taken from a fallen star.
+RARITY 90 
+ASCII '['
+UTF8 "["
+COLOUR iron
+POWER 18
+POWER2 40
+DEPTH 27
+DAMAGEABLE
+
+ARMOUR sacred chainmail
+PLURAL suits of sacred chainmail
+DESC This suit of interlocking rings has been consecrated to the gods of the Light.
+RARITY 90 
+ASCII '['
+UTF8 "["
+COLOUR white
+POWER 15
+POWER2 25
+DEPTH 24
+DAMAGEABLE
+
+ARMOUR ragged shift
+PLURAL ragged shifts
+DESC This sorry-looking collection of rags is all that remains of an imposing armoured dress.
+RARITY 100 
+ASCII '['
+UTF8 "["
+COLOUR l_grey
+POWER 1
+POWER2 0
+DEPTH 1
+DRESS
+DAMAGEABLE
+
+ARMOUR battle ballgown
+PLURAL battle ballgowns
+DESC Partially armoured dresses such as this are a traditional part of a princess's wardrobe.
+RARITY 95 
+ASCII '['
+UTF8 "["
+COLOUR green
+POWER 3
+POWER2 10
+DEPTH 1
+DRESS
+DAMAGEABLE
+BREAK_REACT
+
+ARMOUR imperatrix gown
+PLURAL imperatrix gowns
+DESC This armoured, enchanted, and elaborately decorated dress would be worthy of an empress regnant.
+RARITY 95 
+ASCII '['
+UTF8 "["
+COLOUR purple
+POWER 15
+POWER2 10
+DEPTH 24
+DRESS
+DAMAGEABLE
+BREAK_REACT
+
+ARMOUR foetid vestments
+PLURAL sets of foetid vestments
+DESC These stained, reeking robes seem to harbour great power despite their half-rotten state.
+RARITY 90
+ASCII '['
+UTF8 "["
+COLOUR l_green
+POWER 15
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+DAMAGEABLE
+BREAK_REACT
+
+ARMOUR lich's robe
+PLURAL lich's robes
+DESC Separated from its ancient owner, this robe hums with magical power.
+RARITY 100
+ASCII '['
+UTF8 "["
+COLOUR purple
+POWER 15
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+
+ARMOUR infernite armour
+PLURAL suits of infernite armour
+DESC Crafted by Verant, or perhaps one of his hellish apprentices, after his fall from the light, this armour was ancient before your ancestors even came to the lands called Söťerek.
+RARITY 100
+ASCII '['
+UTF8 "["
+COLOUR red
+POWER 25
+POWER2 20
+DEPTH 30
+NOTIFY_EQUIP
+
+RING regeneration ring
+PLURAL regeneration rings
+DESC This magical ring increases the wearer's healing rate, but also increases the rate at which they must consume food.
+RARITY 70 
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+
+RING fire ring
+PLURAL fire rings
+DESC This magical ring protects the wearer from mundane and magical fire, and imbues their blows in combat with the power of fire.
+RARITY 50 
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+
+RING vampire ring
+PLURAL vampire rings
+DESC This magical ring protects the wearer from necromantic energies, and imbues their blows in combat with such energies as well.
+RARITY 90 
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 12
+
+RING frost ring
+PLURAL frost rings
+DESC This magical ring protects the wearer from mundane and magical cold, and imbues their blows in combat with the power of cold. Rumour suggests it might also allow walking on water. 
+RARITY 40 
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+
+RING teleport ring
+PLURAL teleport rings
+DESC This magical ring allows the wearer to teleport for a modest cost in nutrition.
+RARITY 70 
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+
+RING slime ring
+PLURAL slime rings
+DESC This magical ring, tarnished and filth-spotted, protects its wearer from a poison and pestilence, whether magical or natural.
+RARITY 75
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 15
+
+RING protection ring
+PLURAL protection rings
+DESC This magical ring, engraved with the sigil of Verant the Smith, substantially reduces the severity of all injuries its wearer suffers.
+RARITY 75
+ASCII '='
+UTF8 "="
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 15
+
+RING imperial seal
+PLURAL imperial seals
+DESC This rarest and most ancient of magical rings was forged in the days before the Archon Inferni was imprisoned in this dismal place. Of the nature of its power, there are not even rumours.
+RARITY 100
+ASCII '='
+UTF8 "="
+COLOUR purple
+POWER 0
+POWER2 0
+DEPTH 30
+NOTIFY_EQUIP
+
+FOOD iron ration
+PLURAL iron rations
+DESC A parcel of hardtack and beef jerky. Dull but nutritious.
+RARITY 75 
+ASCII '%'
+UTF8 "%"
+COLOUR brown
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+FOOD parcel of dried fruit
+PLURAL parcels of dried fruit
+DESC A parcel of mixed dried fruit. It sure beats hardtack and beef jerky.
+RARITY 75 
+ASCII '%'
+UTF8 "%"
+COLOUR yellow
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+FOOD round of elven waybread
+PLURAL rounds of elven waybread
+DESC A tasty, filling, nutritious piece of elven waybread.
+RARITY 85 
+ASCII '%'
+UTF8 "%"
+COLOUR white
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+FOOD devil spleen
+PLURAL devil spleens
+DESC A weirdly pulsing organ ripped from the torso of a devil.
+RARITY 100 
+ASCII '%'
+UTF8 "%"
+COLOUR l_red
+POWER 0
+POWER2 0
+DEPTH 1
+STACKABLE
+
+CARRION corpse
+PLURAL corpses
+DESC The remains of a defeated enemy.
+RARITY 100 
+ASCII '&'
+UTF8 "&"
+COLOUR l_grey
+POWER 0
+POWER2 0
+DEPTH 1
+
diff --git a/default.permons b/default.permons
new file mode 100644 (file)
index 0000000..2071cee
--- /dev/null
@@ -0,0 +1,621 @@
+# default.permons - preferred form of modification for Obumbrata et Velata permon database 
+# Copyright 2014 Martin Read.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+monster newt
+ascii 'n'
+utf8 "n"
+desc A small, not very threatening, vaguely lizard-like creature.
+colour red
+rarity 20
+power 1
+hp 3
+mtohit 0
+mdam 2
+defence 1
+exp 1
+speed 0
+STUPID
+SMALL
+QUADRUPED
+
+# rodents
+monster rat
+ascii 'r'
+utf8 "r"
+desc A large and bad-tempered rodent not noted for its dental hygeine.
+colour brown
+power 1
+rarity 15
+hp 4
+mhit 0
+mdam 2
+defence 4
+exp 2
+speed 2
+STUPID
+SMALL
+QUADRUPED
+
+# canids
+monster wolf
+plural wolves
+ascii 'c'
+utf8 "c"
+desc A hungry canid, probably weighing nearly as much as you do in your skin.
+colour brown
+rarity 30
+power 6
+hp 20
+mhit 8
+mdam 10
+defence 6
+exp 15
+speed 2
+QUADRUPED
+
+# Serpents
+monster snake
+desc Probably venomous. You'd prefer to not find out.
+ascii 'S'
+utf8 "S"
+colour red
+rarity 20
+power 6
+hp 15
+mhit 10
+mdam 3
+defence 9
+experience 40
+speed 2
+STUPID
+SERPENTINE
+
+# thugs
+monster thug
+desc Likes pushing people's faces through the backs of their heads.
+ascii 't'
+utf8 "t"
+colour brown
+rarity 30
+power 1
+hp 8
+mhit 5
+mdam 5
+defence 4
+experience 5
+speed 1
+HUMANOID
+
+monster goon
+desc Really good at pushing people's faces through the backs of their heads.
+ascii 't'
+utf8 "t"
+colour yellow
+rarity 20
+power 3
+hp 15
+mhit 6
+mdam 7
+defence 8
+experience 10
+speed 1
+HUMANOID
+
+# hunters
+monster hunter
+desc More at home in other people's forests, the glint in his eye as he nocks an arrow tells you princesses are in season.
+ascii 'h'
+utf8 "h"
+colour green
+rarity 30
+power 9
+hp 40
+mhit 6
+rhit 20
+mdam 6
+rdam 10
+shootverb shoots an arrow
+defence 10
+experience 50
+speed 1
+ARCHER
+HUMANOID
+
+# fighters
+monster duellist
+desc A talented swordsman with a fondness for single combat.
+ascii 'f'
+utf8 "f"
+colour red
+rarity 40
+power 12
+hp 60
+mhit 30
+mdam 15
+defence 15
+experience 130
+speed 1
+SMART
+HUMANOID
+
+monster warlord
+desc A truly exceptional warrior, strong of arm and fleet of foot.
+ascii 'f'
+utf8 "f"
+colour l_red
+rarity 30
+power 15
+hp 80
+mhit 25
+mdam 20
+defence 20
+experience 400
+speed 2
+SMART
+HUMANOID
+
+# goblins
+monster goblin
+desc A short, scrawny humanoid with no love for surface folk.
+ascii 'g'
+utf8 "g"
+colour brown
+rarity 20
+power 1
+hp 6
+mhit 1
+mdam 3
+defence 3
+experience 3
+speed 1
+HUMANOID
+
+# elves
+monster bad elf
+plural bad elves
+desc Tall, slender, and androgynous, with a sharp knife in hand and a malicious glint in their eye.
+ascii 'e'
+utf8 "e"
+colour l_grey
+rarity 40
+power 3
+hp 15
+mhit 10
+mdam 6
+defence 8
+experience 15
+speed 2
+SMART
+HUMANOID
+
+# Trolls
+monster troll
+desc Half again as tall as a tall Kafdaran, this lanky green-skinned humanoid is notorious for its indiscriminate appetite. Best do what you can to keep princess off the menu.
+ascii 'T'
+utf8 "T"
+colour green
+rarity 20
+power 12
+hp 80
+mhit 15
+mdam 15
+defence 13
+experience 150
+speed 1
+STUPID
+HUMANOID
+HUGE
+
+# Huge humanoids
+monster giant
+desc Half again as tall as a towering troll, and scarcely smarter.
+ascii 'H'
+utf8 "H"
+colour brown
+rarity 20
+power 21
+hp 80
+mhit 15
+mdam 25
+defence 20
+experience 500
+speed 1
+STUPID
+HUMANOID
+HUGE
+
+monster giant jarl
+desc A rarity among giants, this one has used its great strength and unusually sharp wits to persuade its dimmer brethren to obey.
+ascii 'H'
+utf8 "H"
+colour l_grey
+rarity 80
+power 25
+hp 160
+mhit 20
+mdam 30
+defence 22
+experience 1000
+speed 1
+HUMANOID
+HUGE
+
+# wizards
+monster wizard
+desc A practitioner of the arcane arts. Beware his command over the power of the storm!
+ascii 'w'
+utf8 "w"
+colour blue
+rarity 80
+power 12
+hp 40
+mhit 10
+rhit 20
+mdam 10
+rhit 10
+rdtyp ELEC
+shootverb casts
+defence 15
+experience 200
+speed 1
+SMART
+MAGICIAN
+HUMANOID
+
+monster archmage
+plural archmagi
+desc A grand master of the arcane arts, armed with spells of surpassing might.
+ascii 'w'
+utf8 "w"
+colour l_blue
+rarity 80
+power 24
+hp 80
+mhit 15
+rhit 30
+mdam 15
+rdam 15
+rdtyp ELEC
+shootverb casts
+defence 15
+experience 1500
+speed 1
+SMART
+MAGICIAN
+RESIST_ELEC
+HUMANOID
+
+# zombie
+monster zombie
+desc A half-rotted corpse, galvanized to a mockery of life by forbidden magic.
+ascii 'z'
+utf8 "z"
+colour l_grey
+rarity 25
+depth 3
+hp 30
+mhit 2
+mdam 10
+defence 1
+experience 7
+speed 0
+STUPID
+UNDEAD
+RESIST_COLD
+RESIST_POIS
+RESIST_NECR
+HUMANOID
+
+# Wraiths
+monster wraith
+desc The ethereal remnant of some long-dead unfortunate, wracked with eternal hunger for the light that shines within the living. No obstacle can bar its path.
+ascii 'W'
+utf8 "W"
+colour white
+rarity 25
+depth 12
+hp 40
+mhit 25
+mdam 5
+defence 5
+experience 100
+speed 0
+SMART
+UNDEAD
+ETHEREAL
+RESIST_COLD
+RESIST_POIS
+RESIST_NECR
+HUMANOID
+
+# Liches
+monster lich
+plural liches
+desc A long-dead practitioner of the magic of death, clad in mouldering finery and sustained by their forbidden arts.
+ascii 'L'
+utf8 "L"
+colour l_grey
+rarity 70
+power 15
+hp 70
+mhit 15
+rhit 25
+mdam 15
+rdam 15
+rdtyp NECRO
+shootverb casts
+defence 15
+experience 250
+speed 1
+SMART
+UNDEAD
+RESIST_COLD
+RESIST_POIS
+RESIST_NECR
+MAGICIAN
+HUMANOID
+
+monster master lich
+plural master liches
+desc A long-dead master of the deepest magics of death, clad in mouldering finery and sustained by their forbidden arts.
+ascii 'L'
+utf8 "L"
+colour purple
+rarity 60
+power 30
+hp 150
+mhit 30
+rhit 30
+mdam 20
+rdam 30
+rdtyp NECRO
+shootverb casts
+defence 30
+experience 3000
+speed 1
+SMART
+UNDEAD
+RESIST_COLD
+RESIST_POIS
+RESIST_NECR
+MAGICIAN
+HUMANOID
+
+# Vampires
+monster vampire
+desc A gaunt red-eyed figure with throat-tearing fangs, afflicted by an eternal hunger for the blood of the living.
+ascii 'V'
+utf8 "V"
+colour red
+rarity 55
+power 18
+hp 70
+mhit 25
+mdam 15
+defence 22
+experience 750
+speed 1
+SMART
+UNDEAD
+RESIST_COLD
+RESIST_POIS
+RESIST_NECR
+HUMANOID
+
+# 4th tier demons
+monster fire imp
+desc A small, bat-winged humanoid with a flame-tipped tail, spawned in some fiery corner of the Hells.
+ascii '4'
+utf8 "4"
+colour red
+rarity 60
+power 9
+hp 20
+mhit 10
+mdam 10
+defence 12
+experience 30
+speed 2
+DEMONIC
+RESIST_FIRE
+FLYING
+HUMANOID
+
+# 3rd tier demons
+monster demon
+desc A red-skinned and long-taloned horror spat up from some corner or other of the Hells. Presented with prey, it is quick to call its brethren into the world.
+ascii '3'
+utf8 "3"
+colour red
+rarity 60
+power 18
+hp 40
+mhit 25
+mdam 20
+defence 15
+experience 500
+speed 1
+SMART
+DEMONIC
+RESIST_FIRE
+HUMANOID
+
+# 2nd tier demons
+monster defiler
+desc Whatever shape this hellish horror has, it can scarcely be seen through the foetid green haze that surrounds it.
+ascii '2'
+utf8 "2"
+colour l_green
+rarity 65
+power 27
+hp 120
+mhit 30
+mdam 20
+defence 25
+experience 2000
+speed 1
+SMART
+DEMONIC
+MAGICIAN
+RESIST_POIS
+AMORPHOUS
+
+# 1st tier demons
+monster putrid emissary
+desc The stench of decay and pestilence hangs heavily around this misshapen figure in stained, half-rotted rags. It wields its scythe with an air of deadly purpose.
+ascii '1'
+utf8 "1"
+colour l_green
+rarity 90
+power 30
+hp 250
+mhit 20
+mdam 20
+defence 20
+experience 5000
+speed 1
+SMART
+DEMONIC
+RESIST_POIS
+HUMANOID
+
+monster tormentor
+desc The faceless violet-skinned figure before you wears a strangely cut garment of pale, supple leather which would be far beyond the limits of decency if there were any indecent parts to reveal. With one hand it beckons to you, while from the other it trails a long whip of the same leather as its garment.
+ascii '1'
+utf8 "1"
+colour purple
+rarity 90
+power 30
+hp 150
+mhit 50
+mdam 10
+defence 25
+experience 5000
+speed 2
+SMART
+DEMONIC
+RESIST_POIS
+HUMANOID
+
+monster iron lord
+desc This loyal and unwavering servant of Verant, Great Smith of the Hells, bears arms and armour from its master's forges. Fear the iron lord!
+ascii '1'
+utf8 "1"
+colour iron
+rarity 90
+power 30
+hp 300
+mhit 30
+mdam 30
+defence 30
+experience 5000
+speed 1
+SMART
+DEMONIC
+RESIST_POIS
+RESIST_FIRE
+RESIST_COLD
+RESIST_SLAM
+HUMANOID
+
+monster centaur
+desc A strange magical hybrid of horse and man.
+ascii 'C'
+utf8 "C"
+colour brown
+rarity 30
+power 9
+hp 40
+mhit 15
+mdam 10
+defence 10
+experience 50
+speed 2
+CENTAUROID
+
+monster ice monster
+desc A ponderous, shambling half-humanoid figure of ice and snow.
+ascii 'I'
+utf8 "I"
+colour white
+rarity 50
+power 6
+hp 40
+mhit 10
+rhit 20
+mdam 15
+rdam 15
+rdtyp COLD
+shootverb launches a blast of
+defence 10
+experience 35
+speed 0
+RESIST_COLD
+ARCHER
+HUMANOID
+
+monster dragon
+desc A bulky beast of scales and fangs and fumes, capable of spewing searing flames to roast its enemies and rumoured to have a preference for char-grilled princess.
+ascii 'D'
+utf8 "D"
+colour red
+rarity 50
+power 15
+hp 80
+mhit 20
+rhit 20
+mdam 20
+rdam 20
+rdtyp FIRE
+shootverb breathes
+defence 18
+experience 300
+speed 1
+RESIST_FIRE
+ARCHER
+QUADRUPED
+HUGE
+
+monster moondrake
+desc A bat-winged serpent with eyes that shimmer like quicksilver and a breath that chills the living to the bone.
+colour l_cyan
+ascii 'D'
+utf8 "D"
+rarity 60
+power 18
+hp 80
+mhit 20
+rhit 20
+mdam 20
+rdam 20
+rdtyp COLD
+shootverb breathes
+defence 24
+experience 500
+speed 2
+RESIST_FIRE
+RESIST_COLD
+RESIST_NECR
+FLYING
+ARCHER
+SERPENTINE
diff --git a/display-nc.cc b/display-nc.cc
new file mode 100644 (file)
index 0000000..e3ce020
--- /dev/null
@@ -0,0 +1,1753 @@
+/*! \file display-nc.cc
+ *  \brief ncurses display support
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define DISPLAY_NC_CC
+#include "obumbrata.hh"
+#include "monsters.hh"
+#include "objects.hh"
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <ncursesw/curses.h>
+#include <ncursesw/panel.h>
+
+#undef DEBUG_TO_STDERR
+//#define DEBUG_TO_STDERR
+#define DISP_NC_RADIUS (10)
+#define DISP_NC_SIDE (DISP_NC_RADIUS * 2 + 1)
+
+/* Prototypes for static funcs */
+static cchar_t const *object_char(int object_id);
+static cchar_t const *monster_char(int monster_id);
+static cchar_t const *terrain_char(Terrain terrain_type);
+static void draw_status_line(void);
+static void draw_world(void);
+static void draw_main_menu(void);
+static void run_main_menu(void);
+
+static void set_inventory_message(char const *s, bool allow_nil);
+static void reset_inventory_message(void);
+static void hide_inv(void);
+static void show_inv(void);
+static void update_inv(enum poclass_num filter);
+static int inv_select(enum poclass_num filter, char const *action, int accept_blank);
+static void farlook(void);
+
+/*! \brief For showing the player's status summary */
+static WINDOW *status_window;
+
+/*! \brief For showing the area around the player. */
+static WINDOW *world_window;
+
+/*! \brief For showing recent messages. */
+static WINDOW *message_window;
+
+/*! \brief For showing the player's inventory without smashing the messages */
+static WINDOW *inventory_window;
+
+/*! \brief 24x80 window for title screen etc. */
+static WINDOW *fullscreen_window;
+
+/*! \brief PANEL for the status window */
+static PANEL *status_panel;
+
+/*! \brief PANEL for the world window */
+static PANEL *world_panel;
+
+/*! \brief PANEL for the message window */
+static PANEL *message_panel;
+
+/*! \brief PANEL for the inventory window */
+static PANEL *inventory_panel;
+
+/*! \brief PANEL for the title screen etc. */
+static PANEL *fullscreen_panel;
+
+/*! \brief Should we coerce the tiles to ASCII? */
+static bool force_ascii;
+
+/*! \brief Flag indicating the player's status has changed */
+bool status_updated;
+
+/*! \brief Flag indicating the map has changed redrawn */
+bool map_updated;
+
+/*! \brief Flag indicating that the terrain should be drawn in preference to objects/monsters */
+bool show_terrain;
+
+/*! \brief Flag indicating a more comprehensive redraw of the map */
+bool hard_redraw;
+
+/*! \brief ncursesw attribute and colorpair for a game colour */
+struct Attr_wrapper 
+{
+    attr_t attr;
+    short cpair;
+};
+
+/*! \brief The list of attributes/colorpairs for the game's colours */
+Attr_wrapper colour_data[1 + LAST_COLOUR] =
+{
+    { 0, Gcol_l_grey },
+    { A_BOLD, Gcol_d_grey },
+    { 0, Gcol_red },
+    { 0, Gcol_blue },
+    { 0, Gcol_green },
+    { 0, Gcol_purple },
+    { 0, Gcol_brown },
+    { 0, Gcol_cyan },
+    { A_BOLD, Gcol_l_grey },
+    { A_BOLD, Gcol_red },
+    { A_BOLD, Gcol_blue },
+    { A_BOLD, Gcol_green },
+    { A_BOLD, Gcol_purple },
+    { A_BOLD, Gcol_brown },
+    { A_BOLD, Gcol_cyan },
+    /* Fancy colours */
+    { 0, Gcol_cyan }, /* Iron = dark cyan */
+    { A_BOLD, Gcol_brown }, /* Gold = yellow */
+    { A_BOLD, Gcol_l_grey }, /* Silver = white */
+    /* UI customizable colours */
+    { A_BOLD, Gcol_d_grey },
+    { 0, Gcol_l_grey },
+    { A_BOLD, Gcol_brown },
+    { A_BOLD, Gcol_red },
+    { A_BOLD, Gcol_blue }
+};
+
+/*! \brief ncursesw objects for drawing terrain */
+cchar_t terrain_tiles[NUM_TERRAINS];
+
+/*! \brief ncursesw objects for drawing items */
+cchar_t permobj_tiles[NUM_OF_PERMOBJS];
+
+#define DISP_HEIGHT (DISP_NC_SIDE)
+#define DISP_WIDTH (DISP_NC_SIDE)
+
+/*! \brief ncursesw objects for drawing monsters */
+cchar_t permon_tiles[NUM_OF_PERMONS];
+
+/*! \brief ncursesw object for drawing unexplored space */
+cchar_t blank_tile;
+
+/*! \brief ncursesw object for drawing player */
+cchar_t player_tile;
+
+/*! \brief Cached tile pointer for what we should draw next time we refresh the map */
+cchar_t const *back_buffer[DISP_HEIGHT][DISP_WIDTH];
+
+/*! \brief Cached tile pointer for what's currently on screen */
+cchar_t const *front_buffer[DISP_HEIGHT][DISP_WIDTH];
+
+/*! \brief Printable English-language names for damage types */
+char const *damtype_names[DT_COUNT] = {
+    "physical damage",
+    "cold",
+    "fire",
+    "necromantic force",
+    "electricity",
+    "hellfire",
+    "poison",
+    "knockback",
+    "drowning",
+};
+
+/*! \brief Draw the main menu. */
+static void draw_main_menu(void)
+{
+    wclear(fullscreen_window);
+    mvwprintw(fullscreen_window, 1, 20, "----====< Obumbrata et Velata >====----\n");
+    mvwprintw(fullscreen_window, 3, 25,  "A roguelike game by Martin Read\n");
+    mvwprintw(fullscreen_window, 10, 25, "S)tart new game\n");
+    mvwprintw(fullscreen_window, 11, 25, "R)esume existing game\n");
+    mvwprintw(fullscreen_window, 12, 25, "Q)uit\n");
+    mvwprintw(fullscreen_window, 23, 25, "Version %d.%d.%d\n", MAJVERS, MINVERS, PATCHVERS);
+    show_panel(fullscreen_panel);
+    update_panels();
+    doupdate();
+}
+
+/*! \brief Redraw the status line. */
+static void draw_status_line(void)
+{
+    mvwprintw(status_window, 0, 0, "%-16.16s", u.name);
+    mvwprintw(status_window, 0, 17, "HP: %3d/%03d", u.hpcur, u.hpmax);
+    mvwprintw(status_window, 0, 30, "BOD: %02d/%02d", u.body - u.bdam, u.body);
+    mvwprintw(status_window, 0, 62, "Exp: %d/%d", u.level, u.experience);
+    mvwprintw(status_window, 1, 0, "DEF: %2d", u.defence);
+    mvwprintw(status_window, 1, 15, "Food: %7d", u.food);
+    mvwprintw(status_window, 1, 30, "AGI: %02d/%02d", u.agility - u.adam, u.agility);
+    mvwprintw(status_window, 1, 60, "Depth: %d", depth);
+}
+
+/*! \brief Get pointer to cchar_t object for specified terrain
+ *
+ * \param terrain_type Specified terrain type
+ * \return pointer to the specified terrain type's tile
+ */
+static cchar_t const *terrain_char(Terrain terrain_type)
+{
+    return terrain_tiles + terrain_type;
+}
+
+/*! \brief Get pointer to cchar_t object for specified permon
+ *
+ * \param monster_id Specified permon
+ * \return pointer to the specified permon's tile
+ */
+static cchar_t const *monster_char(int monster_id)
+{
+    return permon_tiles + monster_id;
+}
+
+/*! \brief Get pointer to cchar_t object for specified permobj
+ *
+ * \param object_id Specified permobj
+ * \return pointer to the specified permobj's tile
+ */
+static cchar_t const *object_char(int object_id)
+{
+    return permobj_tiles + object_id;
+}
+
+/*! \brief Repopulate the back buffer and set the hard redraw flag */
+void touch_back_buffer(void)
+{
+    Coord c;
+    for (c.y = u.pos.y - DISP_NC_RADIUS; c.y <= u.pos.y + DISP_NC_RADIUS; ++c.y)
+    {
+        for (c.x = u.pos.x - DISP_NC_RADIUS; c.x <= u.pos.x + DISP_NC_RADIUS; ++c.x)
+        {
+            newsym(c);
+        }
+    }
+    map_updated = 1;
+    hard_redraw = 1;
+}
+
+/*! \brief Update symbol at specified location 
+ *
+ * newsym() notifies the display layer that the state of position 'c' has
+ * changed in a way that should change its graphical representation (e.g.
+ * there was a monster there and now there isn't).
+ *
+ * \param c Location to update appearance of
+ */
+void newsym(Coord c)
+{
+    Offset camoff = { DISP_NC_RADIUS + (c.y - u.pos.y), DISP_NC_RADIUS + (c.x - u.pos.x) };
+    cchar_t const *ch = back_buffer[camoff.y][camoff.x];
+    if ((camoff.y < 0) || (camoff.y >= DISP_HEIGHT) ||
+        (camoff.x < 0) || (camoff.x >= DISP_WIDTH))
+    {
+        /* out of frame */
+        return;
+    }
+    if ((c.y < lvl.min_y()) || (c.y > lvl.max_y()) ||
+        (c.x < lvl.min_x()) || (c.x > lvl.max_x()))
+    {
+        back_buffer[camoff.y][camoff.x] = &blank_tile;
+    }
+    else
+    {
+        int obj = lvl.obj_at(c);
+        int mon = lvl.mon_at(c);
+        Terrain terr = lvl.terrain_at(c);
+        uint32_t flags = lvl.flags_at(c);
+        if (c == u.pos)
+        {
+            back_buffer[camoff.y][camoff.x] = &player_tile;
+        }
+        else if ((!show_terrain) && (mon != NO_MON) && occupant_visible(c))
+        {
+            back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].mon_id);
+        }
+        else if (flags & MAPFLAG_EXPLORED)
+        {
+            if ((!show_terrain) && (obj != NO_OBJ))
+            {
+                back_buffer[camoff.y][camoff.x] = object_char(objects[obj].obj_id);
+            }
+            else
+            {
+                back_buffer[camoff.y][camoff.x] = terrain_char(terr);
+            }
+        }
+        else
+        {
+            back_buffer[camoff.y][camoff.x] = &blank_tile;
+        }
+    }
+    if (ch != back_buffer[camoff.y][camoff.x])
+    {
+        map_updated = 1;
+    }
+}
+
+/*! \brief Update the map window */
+static void draw_world(void)
+{
+    int i;
+    int j;
+    Coord c;
+    Coord d;
+
+    for ((i = 0), (c.y = u.pos.y - DISP_NC_RADIUS); i < DISP_HEIGHT; ++i, ++c.y)
+    {
+        for ((j = 0), (c.x = u.pos.x - DISP_NC_RADIUS); j < DISP_WIDTH; j++, ++c.x)
+        {
+            d.y = i - DISP_NC_RADIUS + MAX_FOV_RADIUS;
+            d.x = j - DISP_NC_RADIUS + MAX_FOV_RADIUS;
+            if (hard_redraw || (front_buffer[i][j] != back_buffer[i][j]))
+            {
+                mvwadd_wch(world_window, i, j, back_buffer[i][j]);
+                if (!player_fov.test(d))
+                {
+                    mvwchgat(world_window, i, j, 1, A_BOLD, Gcol_d_grey, nullptr);
+                }
+                front_buffer[i][j] = back_buffer[i][j];
+            }
+        }
+    }
+}
+
+/*! \brief Set up the tiles in unicode mode */
+static void load_unicode_tiles(void)
+{
+    int i;
+    int j;
+    {
+        wchar_t wch[2];
+        wch[0] = L'@';
+        wch[1] = 0;
+        setcchar(&player_tile, wch, 0, 0, nullptr);
+        wch[0] = L' ';
+        setcchar(&blank_tile, wch, 0, 0, nullptr);
+    }
+    for (i = 0; i < NUM_OF_PERMONS; ++i)
+    {
+        wchar_t wch[2];
+        wch[0] = permons[i].sym;
+        wch[1] = 0;
+        setcchar(permon_tiles + i, wch,
+                 colour_data[permons[i].colour].attr,
+                 colour_data[permons[i].colour].cpair, nullptr);
+    }
+    for (i = 0; i < NUM_TERRAINS; ++i)
+    {
+        wchar_t wch[2];
+        /* policy decision: for now we don't support use of combining
+         * characters for terrain. */
+        j = mbtowc(wch, terrain_props[i].unicode, 4);
+        if (j != 1)
+        {
+        }
+        wch[1] = 0;
+        setcchar(terrain_tiles + i, wch,
+                 colour_data[terrain_props[i].colour].attr,
+                 colour_data[terrain_props[i].colour].cpair, nullptr);
+    }
+    for (i = 0; i < NUM_OF_PERMOBJS; ++i)
+    {
+        wchar_t wch[2];
+        /* policy decision: for now we don't support use of combining
+         * characters for items. */
+        j = mbtowc(wch, permobjs[i].unicode, 4);
+        if (j != 1)
+        {
+        }
+        wch[1] = 0;
+        setcchar(permobj_tiles + i, wch,
+                 colour_data[permobjs[i].colour].attr,
+                 colour_data[permobjs[i].colour].cpair, nullptr);
+    }
+}
+
+/*! \brief Set up the tiles in ASCII mode */
+static void load_ascii_tiles(void)
+{
+    int i;
+    {
+        wchar_t wch[2];
+        wch[0] = L'@';
+        wch[1] = 0;
+        setcchar(&player_tile, wch, 0, 0, nullptr);
+        wch[0] = L' ';
+        setcchar(&blank_tile, wch, 0, 0, nullptr);
+    }
+    for (i = 0; i < NUM_OF_PERMONS; ++i)
+    {
+        wchar_t wch[2];
+        wch[0] = permons[i].sym;
+        wch[1] = 0;
+        setcchar(permon_tiles + i, wch,
+                 colour_data[permons[i].colour].attr,
+                 colour_data[permons[i].colour].cpair, nullptr);
+    }
+    for (i = 0; i < NUM_TERRAINS; ++i)
+    {
+        wchar_t wch[2];
+        wch[0] = terrain_props[i].ascii;
+        wch[1] = 0;
+        setcchar(terrain_tiles + i, wch,
+                 colour_data[terrain_props[i].colour].attr,
+                 colour_data[terrain_props[i].colour].cpair, nullptr);
+    }
+    for (i = 0; i < NUM_OF_PERMOBJS; ++i)
+    {
+        wchar_t wch[2];
+        wch[0] = permobjs[i].sym;
+        wch[1] = 0;
+        setcchar(permobj_tiles + i, wch,
+                 colour_data[permobjs[i].colour].attr,
+                 colour_data[permobjs[i].colour].cpair, nullptr);
+    }
+}
+
+/* extern funcs */
+
+/*! \brief Wait for the user to press RETURN */
+void press_enter(void)
+{
+    int ch;
+    print_msg("Press RETURN or SPACE to continue\n");
+    while (1)
+    {
+        ch = wgetch(message_window);
+        if ((ch == ' ') || (ch == '\n') || (ch == '\r'))
+        {
+            break;
+        }
+    }
+}
+
+/*! \brief Update any parts of the display that need it */
+void display_update(void)
+{
+    if (status_updated)
+    {
+        status_updated = 0;
+        draw_status_line();
+    }
+    if (map_updated)
+    {
+        map_updated = 0;
+        draw_world();
+    }
+    update_panels();
+    doupdate();
+}
+
+/*! \brief Initialize the display subsystem
+ *
+ * Yes, this architecture does mean we only support one display subsystem at
+ * compile time.
+ *
+ * \todo Rename this and put appropriate glue elsewhere to make it work
+ */
+int launch_user_interface(void)
+{
+    int i;
+    int j;
+    char const *lang;
+    char const *ct;
+    char const *all;
+    char const *encoding;
+
+    /*
+     * Yes, this code will misidentify the availability of meaningful wide
+     * characters on architectures where char is larger than 8 bits.
+     *
+     * It also treats all character encodings that are not UTF-8 as "ASCII".
+     *
+     * Contributions of additional display-[foo].cc modules that provide
+     * support for such architectures and/or character encodings will be
+     * considered.
+     */
+    if (sizeof(wchar_t) > 1)
+    {
+        lang = getenv("LANG");
+        ct = getenv("LC_CTYPE");
+        all = getenv("LC_ALL");
+        if (all || lang || ct)
+        {
+            setlocale(LC_ALL, "");
+        }
+        else
+        {
+            /* In the name of my narrowly defined concept of sanity, if the
+             * user has not configured any of the relevant locale envvars,
+             * force the C.UTF-8 locale. */
+            setlocale(LC_CTYPE, "C.UTF-8");
+        }
+        /* The only wire encodings I am willing to support are UTF-8 and 
+         * ASCII. If you want support for other wire encodings, you can
+         * maintain your own patch for it. */
+        encoding = nl_langinfo(CODESET);
+        force_ascii = strcmp(encoding, "UTF-8");
+    }
+    else
+    {
+        force_ascii = true;
+    }
+    {
+        char const *term = getenv("TERM");
+        if (term && (strstr(term, "xterm") || strstr (term, "rxvt")))
+        {
+            fputs("\033]0;Obumbrata et Velata\007", stdout);
+            fflush(stdout);
+        }
+    }
+    initscr();
+    noecho();
+    cbreak();
+    start_color();
+    init_pair(Gcol_brown, COLOR_YELLOW, COLOR_BLACK);
+    init_pair(Gcol_red, COLOR_RED, COLOR_BLACK);
+    init_pair(Gcol_green, COLOR_GREEN, COLOR_BLACK);
+    init_pair(Gcol_blue, COLOR_BLUE, COLOR_BLACK);
+    init_pair(Gcol_d_grey, COLOR_BLACK, COLOR_BLACK);
+    init_pair(Gcol_purple, COLOR_MAGENTA, COLOR_BLACK);
+    init_pair(Gcol_cyan, COLOR_CYAN, COLOR_BLACK);
+    if (force_ascii)
+    {
+        load_ascii_tiles();
+    }
+    else
+    {
+        load_unicode_tiles();
+    }
+    for (i = 0; i < DISP_HEIGHT; ++i)
+    {
+        for (j = 0; j < DISP_HEIGHT; ++j)
+        {
+            front_buffer[i][j] = back_buffer[i][j] = &blank_tile;
+        }
+    }
+    /* OK. We want a 21x21 viewport (player at centre), a 21x58 message
+     * window, and a 2x80 status line. */
+    status_window = newwin(2, 80, DISP_HEIGHT + 1, 0);
+    status_panel = new_panel(status_window);
+    world_window = newwin(DISP_HEIGHT, DISP_WIDTH, 0, 0);
+    world_panel = new_panel(world_window);
+    message_window = newwin(DISP_HEIGHT, 58, 0, DISP_WIDTH + 1);
+    message_panel = new_panel(message_window);
+    inventory_window = newwin(DISP_HEIGHT, 58, 0, DISP_WIDTH + 1);
+    inventory_panel = new_panel(inventory_window);
+    fullscreen_window = newwin(24, 80, 0, 0);
+    fullscreen_panel = new_panel(fullscreen_window);
+    wattr_set(inventory_window, colour_data[Gcol_l_grey].attr,
+              colour_data[Gcol_l_grey].cpair, nullptr);
+    hide_panel(inventory_panel);
+    clear();
+    curs_set(0);
+    wclear(status_window);
+    wclear(world_window);
+    wclear(message_window);
+    wclear(inventory_window);
+    wclear(fullscreen_window);
+    scrollok(status_window, FALSE);
+    scrollok(world_window, FALSE);
+    scrollok(message_window, TRUE);
+    scrollok(inventory_window, FALSE);
+    scrollok(fullscreen_window, FALSE);
+    idcok(status_window, FALSE);
+    idcok(world_window, FALSE);
+    idcok(message_window, FALSE);
+    idcok(inventory_window, FALSE);
+    idcok(fullscreen_window, FALSE);
+    wmove(message_window, 0, 0);
+    map_updated = FALSE;
+    status_updated = FALSE;
+    do
+    {
+        draw_main_menu();
+        run_main_menu();
+        if (!game_finished)
+        {
+            status_updated = 1;
+            map_updated = 1;
+            hard_redraw = 1;
+            display_update();
+            main_loop();
+        }
+    } while (1);
+    return 0;
+}
+
+/*! \brief Get string input from the player
+ *
+ * \param buffer destination for player input
+ * \param length maximum length of buffer
+ */
+int read_input(char *buffer, int length)
+{
+    echo();
+    curs_set(1);
+    display_update();
+    buffer[0] = '\0';
+    wgetnstr(message_window, buffer, length);
+    noecho();
+    curs_set(0);
+    return strlen(buffer);
+}
+
+/*! \brief Print some text in the message window
+ *
+ *  Calling print_msg() prints formatted text (printf-style) to the message
+ *  window. It also calls display_update(), so any change to the map or
+ *  the player should be flagged *before* print_msg() is called with the
+ *  message announcing the change.
+ *
+ *  \param fmt printf-style format string
+ *  \todo Handle the message window getting resized.
+ *  \todo Implement message scrollback.
+ *  \todo Put line breaks at word gaps in long messages.
+ *  \todo Handle large volumes of messages between player turns.
+ */
+void print_msg(char const *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vw_printw(message_window, fmt, ap);
+    va_end(ap);
+#ifdef DEBUG_TO_STDERR
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+#endif
+    display_update();
+}
+
+/*! \brief Print some text in the message window (priority variant)
+ *
+ *  Calling print_msg() prints formatted text (printf-style) to the message
+ *  window. It also calls display_update(), so any change to the map or
+ *  the player should be flagged *before* print_msg() is called with the
+ *  message announcing the change.
+ *
+ *  This version of print_msg() applies a message priority attribute to
+ *  the message being printed.
+ *
+ *  \param prio Message priority
+ *  \param fmt printf-style format string
+ *  \todo Handle the message window getting resized.
+ *  \todo Implement message scrollback.
+ *  \todo Put line breaks at word gaps in long messages.
+ *  \todo Handle large volumes of messages between player turns.
+ */
+void print_msg(Msg_prio prio, char const *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    switch (prio)
+    {
+    case Msg_prio::Low:
+        wattr_set(message_window, colour_data[Gcol_prio_low].attr, colour_data[Gcol_prio_low].cpair, nullptr);
+        break;
+    case Msg_prio::Normal:
+        wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
+        break;
+    case Msg_prio::Alert:
+        wattr_set(message_window, colour_data[Gcol_prio_alert].attr, colour_data[Gcol_prio_alert].cpair, nullptr);
+        break;
+    case Msg_prio::Warn:
+        wattr_set(message_window, colour_data[Gcol_prio_warn].attr, colour_data[Gcol_prio_warn].cpair, nullptr);
+        break;
+    case Msg_prio::Bug:
+        wattr_set(message_window, colour_data[Gcol_prio_bug].attr, colour_data[Gcol_prio_bug].cpair, nullptr);
+        break;
+    }
+    vw_printw(message_window, fmt, ap);
+    wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
+    va_end(ap);
+#ifdef DEBUG_TO_STDERR
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+#endif
+    display_update();
+}
+
+/*! \brief Set a message and whether 'nothing' should be listed in inventory
+ *
+ *  \param s Message to set.
+ *  \param allow_nil Whether the 'nothing' option should be listed.
+ */
+static void set_inventory_message(char const *s, bool allow_nil)
+{
+    mvwprintw(inventory_window, 0, 0, "%-57s\n", s);
+    if (!allow_nil)
+    {
+        mvwprintw(inventory_window, 20, 0, "          ");
+    }
+    else
+    {
+        mvwprintw(inventory_window, 20, 0, "-) nothing");
+    }
+}
+
+/*! \brief Clear any inventory message */
+static void reset_inventory_message(void)
+{
+    wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
+    mvwprintw(inventory_window, 0, 0, "                     === INVENTORY ===");
+    mvwprintw(inventory_window, 20, 0, "                 ");
+}
+
+/*! \brief Make the inventory visible */
+static void show_inv(void)
+{
+    show_panel(inventory_panel);
+    update_panels();
+    doupdate();
+}
+
+/*! \brief Hide the inventory if necessary on this display type */
+static void hide_inv(void)
+{
+    show_panel(message_panel);
+    hide_panel(inventory_panel);
+    update_panels();
+    doupdate();
+}
+
+/*! \brief Update the inventory window to reflect the filter in force
+ *
+ * \param filter Class of objects to highlight; (POCLASS_NONE to highlight all)
+ *  \todo Switch to using a function pointer + private argument for filtering
+ */
+static void update_inv(enum poclass_num filter)
+{
+    int i;
+    char inv_line[60];
+    wattr_set(inventory_window, colour_data[Gcol_d_grey].attr, colour_data[Gcol_d_grey].cpair, nullptr);
+    for (i = 0; i < 19; i++)
+    {
+        if (u.inventory[i] == NO_OBJ)
+        {
+            wattr_set(inventory_window, colour_data[Gcol_d_grey].attr, colour_data[Gcol_d_grey].cpair, nullptr);
+            mvwprintw(inventory_window, i + 1, 0, "%c) -----\n", 'a' + i);
+        }
+        else
+        {
+            if ((filter == POCLASS_NONE) ||
+                 (permobjs[objects[u.inventory[i]].obj_id].poclass == filter))
+            {
+                wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
+            }
+            else
+            {
+                wattr_set(inventory_window, colour_data[Gcol_d_grey].attr, colour_data[Gcol_d_grey].cpair, nullptr);
+            }
+            sprintf(inv_line, "%c) ", 'a' + i);
+            sprint_obj_name(inv_line + 3, u.inventory[i], 45);
+            if ((u.ring == u.inventory[i]) || (u.armour == u.inventory[i]))
+            {
+                strcat(inv_line, " (worn)");
+            }
+            else if (u.weapon == u.inventory[i])
+            {
+                strcat(inv_line, " (held)");
+            }
+            mvwprintw(inventory_window, i + 1, 0, "%s\n", inv_line);
+        }
+    }
+}
+
+/*! \brief Select an inventory item
+ *
+ * \param filter Type of item to accept
+ * \param action Verb to use in prompt
+ * \param accept_blank If true, the pseudo-item "nothing" can be selected
+ * \todo Take function pointer and private arg for smarter filtering
+ */
+static int inv_select(enum poclass_num filter, char const *action, int accept_blank)
+{
+    int selection;
+    int ch;
+    int i;
+    int items = 0;
+    char msg[60];
+
+    for (i = 0; i < 19; i++)
+    {
+        if ((u.inventory[i] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)))
+        {
+            items++;
+        }
+    }
+    if (items == 0)
+    {
+        print_msg("You have nothing to %s.\n", action);
+        return SLOT_CANCEL;
+    }
+    wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
+    snprintf(msg, 58, "What do you want to %s?\n", action);
+    set_inventory_message(msg, accept_blank);
+    update_inv(filter);
+    wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
+    mvwprintw(inventory_window, 21, 17, "[ESC or SPACE to cancel]");
+    show_inv();
+tryagain:
+    ch = wgetch(inventory_window);
+    switch (ch)
+    {
+    case '-':
+        if (accept_blank)
+        {
+            mvwprintw(inventory_window, 21, 0, "                        "); 
+            reset_inventory_message();
+            update_inv(POCLASS_NONE);
+            hide_inv();
+            return SLOT_NOTHING;
+        }
+    case 'x':
+    case '\x1b':
+    case ' ':
+        hide_inv();
+        print_msg("\nNever mind.\n");
+        return SLOT_CANCEL;
+    case 'a':
+    case 'b':
+    case 'c':
+    case 'd':
+    case 'e':
+    case 'f':
+    case 'g':
+    case 'h':
+    case 'i':
+    case 'j':
+    case 'k':
+    case 'l':
+    case 'm':
+    case 'n':
+    case 'o':
+    case 'p':
+    case 'q':
+    case 'r':
+    case 's':
+        /* I am assuming that we're in a place where the input character set is
+         * a strict superset of ASCII. IF we're not, the following code may
+         * break. */
+        selection = ch - 'a';
+        if ((u.inventory[selection] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].obj_id].poclass == filter)))
+        {
+            hide_inv();
+            return selection;
+        }
+        /* Fall through */
+    default:
+        goto tryagain;
+    }
+}
+
+/*! \brief Ask the player to choose a direction
+ *
+ * \param pstep Location to store direction's Offset value in
+ * \todo Consider making this accommodate up/down/self as well as compass directions
+ */
+Pass_fail select_dir(Offset *pstep)
+{
+    int ch;
+    bool done = false;
+    print_msg("Select a direction with movement keys.\n[ESC or space to cancel].\n");
+    while (!done)
+    {
+        ch = wgetch(message_window);
+        switch (ch)
+        {
+        case 'k':
+        case '8':
+            *pstep = North;
+            done = true;
+            break;
+        case 'j':
+        case '2':
+            *pstep = South;
+            done = true;
+            break;
+        case 'h':
+        case '4':
+            *pstep = West;
+            done = true;
+            break;
+        case 'l':
+        case '6':
+            *pstep = East;
+            done = true;
+            break;
+        case 'y':
+        case '7':
+            *pstep = Northwest;
+            done = true;
+            break;
+        case 'u':
+        case '9':
+            *pstep = Northeast;
+            done = true;
+            break;
+        case 'b':
+        case '1':
+            *pstep = Southwest;
+            done = true;
+            break;
+        case 'n':
+        case '3':
+            *pstep = Southeast;
+            done = true;
+            break;
+        case '\x1b':
+        case ' ':
+            return You_fail;  /* cancelled. */
+        default:
+            print_msg("Bad direction (use movement keys).\n");
+            print_msg("[Press ESC or space to cancel.]\n");
+            break;
+        }
+    }
+    return You_pass;
+}
+
+/*! \brief Read the player's next command
+ *
+ * \param act Location to store new command in
+ * \todo Consider redesigning in a way friendlier to non-terminal platforms
+ */
+void get_player_action(Action *act)
+{
+    int ch;
+    Pass_fail pf;
+    Offset step;
+    while (1)
+    {
+        ch = wgetch(message_window);
+        if (!panel_hidden(inventory_panel))
+        {
+            hide_inv();
+            if (ch == 'i')
+            {
+                continue;
+            }
+        }
+        switch (ch)
+        {
+        case 'a':
+            {
+                act->cmd = ATTACK;
+                pf = select_dir(&step);
+                if (pf != You_fail)
+                {
+                    act->details[0] = step.y;
+                    act->details[1] = step.x;
+                    return;
+                }
+            }
+            break;
+        case '0':
+        case ',':
+        case 'g':
+            act->cmd = GET_ITEM;
+            /*! \todo revisit if we ever implement multiple items/square */
+            return;
+        case 'd':
+            {
+                if (lvl.obj_at(u.pos) != NO_OBJ)
+                {
+                    print_msg(Msg_prio::Alert, "There is already an item here.\n");
+                }
+                else
+                {
+                    int slot = inv_select(POCLASS_NONE, "drop", 0);
+                    if (slot >= 0)
+                    {
+                        if ((u.inventory[slot] == u.ring) ||
+                            (u.inventory[slot] == u.armour))
+                        {
+                            print_msg(Msg_prio::Alert, "You cannot drop something you are wearing.\n");
+                        }
+                        else
+                        {
+                            act->cmd = DROP_ITEM;
+                            act->details[0] = slot;
+                            return;
+                        }
+                    }
+                }
+            }
+            break;
+        case '@':
+            write_char_dump();
+            break;
+        case 'S':
+            act->cmd = SAVE_GAME;
+            return;
+        case 'X':
+            {
+                int j = getYN("Really quit?\n");
+                if (j > 0)
+                {
+                    act->cmd = QUIT;
+                    return;
+                }
+                else
+                {
+                    print_msg("Never mind.\n");
+                }
+            }
+            break;
+        case 'i':
+            if (panel_hidden(inventory_panel))
+            {
+                reset_inventory_message();
+                update_inv(POCLASS_NONE);
+                show_inv();
+            }
+            else
+            {
+                hide_inv();
+            }
+            break;
+        case 'I':
+            {
+                int slot = inv_select(POCLASS_NONE, "inspect", 0);
+                if ((slot != SLOT_CANCEL) && (u.inventory[slot] != NO_OBJ))
+                {
+                    describe_object(u.inventory[slot]);
+                }
+            }
+            break;
+        case ';':
+            farlook();
+            break;
+        case '#':
+            show_terrain = 1;
+            touch_back_buffer();
+            display_update();
+            print_msg("Display of monsters and objects suppressed.\n");
+            press_enter();
+            show_terrain = 0;
+            touch_back_buffer();
+            display_update();
+            break;
+        case '4':
+        case 'h':
+            {
+                act->cmd = WALK;
+                act->details[0] = West.y;
+                act->details[1] = West.x;
+            }
+            return;
+        case '2':
+        case 'j':
+            {
+                act->cmd = WALK;
+                act->details[0] = South.y;
+                act->details[1] = South.x;
+            }
+            return;
+        case '8':
+        case 'k':
+            {
+                act->cmd = WALK;
+                act->details[0] = North.y;
+                act->details[1] = North.x;
+            }
+            return;
+        case '6':
+        case 'l':
+            {
+                act->cmd = WALK;
+                act->details[0] = East.y;
+                act->details[1] = East.x;
+            }
+            return;
+        case '7':
+        case 'y':
+            {
+                act->cmd = WALK;
+                act->details[0] = Northwest.y;
+                act->details[1] = Northwest.x;
+            }
+            return;
+        case '9':
+        case 'u':
+            {
+                act->cmd = WALK;
+                act->details[0] = Northeast.y;
+                act->details[1] = Northeast.x;
+            }
+            return;
+        case '1':
+        case 'b':
+            {
+                act->cmd = WALK;
+                act->details[0] = Southwest.y;
+                act->details[1] = Southwest.x;
+            }
+            return;
+        case '3':
+        case 'n':
+            {
+                act->cmd = WALK;
+                act->details[0] = Southeast.y;
+                act->details[1] = Southeast.x;
+            }
+            return;
+        case 'q':
+            {
+                int slot = inv_select(POCLASS_POTION, "quaff", 0);
+                if (slot != SLOT_CANCEL)
+                {
+                    act->cmd = QUAFF_POTION;
+                    act->details[0] = slot;
+                    return;
+                }
+            }
+            break;
+        case 'r':
+            {
+                int slot = inv_select(POCLASS_SCROLL, "read", 0);
+                if (slot != SLOT_CANCEL)
+                {
+                    act->cmd = READ_SCROLL;
+                    act->details[0] = slot;
+                    return;
+                }
+            }
+            break;
+        case 'e':
+            {
+                int slot = inv_select(POCLASS_FOOD, "eat", 0);
+                if (slot != SLOT_CANCEL)
+                {
+                    act->cmd = EAT_FOOD;
+                    act->details[0] = slot;
+                    return;
+                }
+            }
+            break;
+        case 'w':
+            {
+                int slot = inv_select(POCLASS_WEAPON, "wield", 1);
+                if (slot != SLOT_CANCEL)
+                {
+                    if (slot == SLOT_NOTHING && u.weapon == NO_OBJ)
+                    {
+                        print_msg("Already empty-handed.\n");
+                    }
+                    else
+                    {
+                        act->cmd = WIELD_WEAPON;
+                        act->details[0] = slot;
+                        return;
+                    }
+                }
+            }
+            break;
+        case 'z':
+            if (u.weapon == NO_OBJ)
+            {
+                print_msg("You cannot zap your weapon when you aren't wielding one.\n");
+            }
+            else
+            {
+                act->cmd = ZAP_WEAPON;
+                return;
+            }
+            break;
+        case 'W':
+            {
+                if (u.armour != NO_OBJ)
+                {
+                    print_msg("You are already wearing armour.\n");
+                }
+                else
+                {
+                    int slot = inv_select(POCLASS_ARMOUR, "wear", 1);
+                    if (slot != SLOT_CANCEL)
+                    {
+                        act->cmd = WEAR_ARMOUR;
+                        act->details[0] = slot;
+                        return;
+                    }
+                }
+            }
+            break;
+        case 'T':
+            act->cmd = TAKE_OFF_ARMOUR;
+            return;
+        case 'E':
+            act->cmd = EMANATE_ARMOUR;
+            return;
+        case 'P':
+            if (u.ring != NO_OBJ)
+            {
+                print_msg("You are already wearing a magic ring. Remove it first.\n");
+            }
+            else
+            {
+                int slot = inv_select(POCLASS_RING, "put on", 0);
+                if (slot != SLOT_CANCEL)
+                {
+                    act->cmd = PUT_ON_RING;
+                    act->details[0] = slot;
+                    return;
+                }
+            }
+            break;
+        case 'R':
+            if (u.ring == NO_OBJ)
+            {
+                print_msg("You are not wearing a ring.\n");
+            }
+            else if (ring_removal_unsafe(Noise_std) == You_pass)
+            {
+                act->cmd = REMOVE_RING;
+                return;
+            }
+            break;
+        case 'm':
+            if (u.ring == NO_OBJ)
+            {
+                print_msg("You are not wearing a ring.\n");
+            }
+            else
+            {
+                act->cmd = MAGIC_RING;
+                return;
+            }
+            break;
+        case '?':
+            print_help();
+            break;
+        case '<':
+            {
+                Terrain t = lvl.terrain_at(u.pos);
+                if (terrain_props[t].flags & TFLAG_ascend)
+                {
+                    act->cmd = GO_UP_STAIRS;
+                    return;
+                }
+                else
+                {
+                    print_msg("There are no stairs here.\n");
+                }
+                // TODO accept this keystroke as "enter portal" too
+            }
+            break;
+        case '>':
+            {
+                Terrain t = lvl.terrain_at(u.pos);
+                if (terrain_props[t].flags & TFLAG_descend)
+                {
+                    act->cmd = GO_DOWN_STAIRS;
+                    return;
+                }
+                else
+                {
+                    print_msg("There are no stairs here.\n");
+                }
+                // TODO accept this keystroke as "enter portal" too
+            }
+            break;
+        case '5':
+        case '.':
+            act->cmd = STAND_STILL;
+            return;
+        case '\x04':
+            if (wizard_mode)
+            {
+                act->cmd = WIZARD_DESCEND;
+                return;
+            }
+            break;
+        case '\x05':
+            if (wizard_mode)
+            {
+                act->cmd = WIZARD_LEVELUP;
+                return;
+            }
+            break;
+#ifdef DEBUG_MAPDUMP
+        case 'M':
+            {
+                Coord c;
+                for (c.y = lvl.min_y(); c.y <= lvl.max_y(); ++c.y)
+                {
+                    for (c.x = lvl.min_x(); c.x <= lvl.max_x(); ++c.x)
+                    {
+                        lvl.set_flags_at(c, MAPFLAG_EXPLORED);
+                    }
+                }
+                touch_back_buffer();
+                display_update();
+            }
+            break;
+#endif
+        default:
+            if ((ch > 127))
+            {
+                print_msg("Unrecognized non-ASCII character %#x.\n", ch);
+            }
+            else if (iscntrl(ch))
+            {
+                print_msg("Unrecognized control character %#x.\n", ch);
+            }
+            else
+            {
+                print_msg("Unrecognized command '%c'.\n", ch);
+            }
+            break;
+        }
+    }
+    print_msg(Msg_prio::Bug, "BUG: broke out of input loop!\n");
+    act->cmd = QUIT;
+    return;
+}
+
+/*! \brief Shut down the display subsystem
+ *
+ * \return 0 normally, -1 if error.
+ */
+int display_shutdown(void)
+{
+    clear();
+    refresh();
+    endwin();
+    return 0;
+}
+
+/*! \brief Prompt the player to press any key */
+void pressanykey(void)
+{
+    print_msg("Press any key to continue.\n");
+    wgetch(message_window);
+}
+
+/*! \brief Prompt the player for a relatively paranoid 'Y'
+ *
+ * \param msg Question to ask the player
+ */
+int getYN(char const *msg)
+{
+    int ch;
+    print_msg("%s", msg);
+    print_msg("Press capital Y to confirm, any other key to cancel\n");
+    ch = wgetch(message_window);
+    if (ch == 'Y')
+    {
+        return 1;
+    }
+    return 0;
+}
+
+/*! \brief Get a yes/no response from the player
+ *
+ * \param msg Question to ask the player
+ */
+int getyn(char const *msg)
+{
+    int ch;
+    print_msg("%s", msg);
+    while (1)
+    {
+        ch = wgetch(message_window);
+        switch (ch)
+        {
+        case 'y':
+        case 'Y':
+            return 1;
+        case 'n':
+        case 'N':
+            return 0;
+        case '\x1b':
+        case ' ':
+            return -1;
+        default:
+            print_msg("Invalid response. Press y or n (ESC or space to cancel)\n");
+        }
+    }
+}
+
+/*! \brief Print some basic help for the player
+ *
+ * \todo Consider providing a more capable help system.
+ */
+void print_help(void)
+{
+    print_msg("MOVEMENT\n");
+    print_msg("y  k  u  7  8  9\n");
+    print_msg(" \\ | /    \\ | / \n");
+    print_msg("  \\|/      \\|/  \n");
+    print_msg("h--*--l  4--*--6\n");
+    print_msg("  /|\\      /|\\  \n");
+    print_msg(" / | \\    / | \\ \n");
+    print_msg("b  j  n  1  2  3\n");
+    print_msg("Attack monsters in melee by bumping into them.\n");
+    print_msg("Doors do not have to be opened before you go through.\n");
+    print_msg("Turn on NUM LOCK to use the numeric keypad for movement.\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("ACTIONS\n");
+    print_msg("a   make an attack (used to fire bows)\n");
+    print_msg("P   put on a ring\n");
+    print_msg("R   remove a ring\n");
+    print_msg("W   wear armour\n");
+    print_msg("T   take off armour\n");
+    print_msg("w   wield a weapon\n");
+    print_msg("r   read a scroll\n");
+    print_msg("q   quaff a potion\n");
+    print_msg("e   eat something edible\n");
+    print_msg("g   pick up an item (also 0 or comma)\n");
+    print_msg("d   drop an item\n");
+    print_msg(">   go down stairs\n");
+    print_msg("5   do nothing (wait until next action)\n");
+    print_msg(".   do nothing (wait until next action)\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("ACTIONS (continued)\n");
+    print_msg("z   activate your weapon's magical power (if any)\n");
+    print_msg("m   activate your ring's magical power (if any)\n");
+    print_msg("E   activate your armour's magical power (if any)\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("OTHER COMMANDS\n");
+    print_msg("S   save and exit\n");
+    print_msg("X   quit without saving\n");
+    print_msg("i   show your inventory\n");
+    print_msg("I   examine an item you are carrying\n");
+    print_msg("#   show underlying terrain of occupied squares\n");
+    print_msg("\\   list all recognised items\n");
+    print_msg("@   dump your character's details to <name>.dump\n");
+    print_msg("?   print this message\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("SYMBOLS\n");
+    print_msg("@   you\n");
+    print_msg(".   floor\n");
+    print_msg(">   stairs down\n");
+    print_msg("#   wall\n");
+    print_msg("+   a door\n");
+    print_msg(")   a weapon\n");
+    print_msg("(   a missile weapon\n");
+    print_msg("[   a suit of armour\n");
+    print_msg("=   a ring\n");
+    print_msg("?   a scroll\n");
+    print_msg("!   a potion\n");
+    print_msg("%%   some food\n");
+    print_msg("1-4 demons (smaller numbers are stronger)\n");
+    print_msg("\nMost other monsters are shown as letters.\n");
+    print_msg("\nThis is all the help you get. Good luck!\n");
+}
+
+/*! \brief Call newsym() for all locations in camera range of a position
+ *
+ * \param c Position to update around
+ */
+void touch_one_screen(Coord c)
+{
+    Coord c2;
+    for (c2.y = c.y - DISP_NC_RADIUS; c2.y <= c.y + DISP_NC_RADIUS; c2.y++)
+    {
+        for (c2.x = c.x - DISP_NC_RADIUS; c2.x <= c.x + DISP_NC_RADIUS; c2.x++)
+        {
+            newsym(c2);
+        }
+    }
+}
+
+bool query_wizmode_death(void)
+{
+    int yn = getyn("Really die?");
+    if (yn != 1)
+    {
+        print_msg("You survived that attempt on your life.");
+        return false;
+    }
+    return true;
+}
+
+static void show_game_view(void)
+{
+    hide_panel(fullscreen_panel);
+    update_panels();
+    doupdate();
+    display_update();
+}
+
+static void run_main_menu(void)
+{
+    bool done = false;
+    char name[16];
+    do
+    {
+        int ch = wgetch(fullscreen_window);
+        switch (ch)
+        {
+        case 's':
+        case 'S':
+            mvwprintw(fullscreen_window, 10, 1, "\n");
+            mvwprintw(fullscreen_window, 11, 1, "\n");
+            mvwprintw(fullscreen_window, 12, 1, "\n");
+            mvwprintw(fullscreen_window, 13, 19, "Welcome, Princess. Remind me of your name?\n");
+            mvwprintw(fullscreen_window, 14, 1, "\n");
+            wmove(fullscreen_window, 14, 30);
+            update_panels();
+            doupdate();
+            echo();
+            curs_set(1);
+            mvwgetnstr(fullscreen_window, 14, 30, name, 16);
+            curs_set(0);
+            noecho();
+            new_game(name);
+            done = true;
+            wclear(message_window);
+            wmove(message_window, 0, 0);
+            show_game_view();
+            break;
+        case 'c':
+        case 'C':
+            // TODO implement configuration management
+            break;
+        case 'r':
+        case 'R':
+            // TODO implement loading properly.
+            {
+                int i;
+                wclear(message_window);
+                wmove(message_window, 0, 0);
+                i = load_game();
+                if (i != -1)
+                {
+                    done = true;
+                }
+                else
+                {
+                    mvwprintw(fullscreen_window, 14, 30, "No saved game found.\n");
+                    update_panels();
+                    doupdate();
+                }
+            }
+            break;
+        case 'q':
+        case 'Q':
+            endwin();
+            exit(0);
+        }
+    } while (!done);
+    if (!game_finished)
+    {
+        show_game_view();
+    }
+}
+
+bool is_movekey(chtype ch)
+{
+    switch (ch)
+    {
+    case 'h': case 'H': case '4':
+    case 'j': case 'J': case '2':
+    case 'k': case 'K': case '8':
+    case 'l': case 'L': case '6':
+    case 'y': case 'Y': case '7':
+    case 'u': case 'U': case '9':
+    case 'b': case 'B': case '1':
+    case 'n': case 'N': case '3':
+    case '.': case '5': return true;
+    default: return false;
+    }
+}
+
+Offset movekey_to_direction(chtype ch)
+{
+    switch (ch)
+    {
+    case 'h': case 'H': case '4': return West;
+    case 'j': case 'J': case '2': return South;
+    case 'k': case 'K': case '8': return North;
+    case 'l': case 'L': case '6': return East;
+    case 'y': case 'Y': case '7': return Northwest;
+    case 'u': case 'U': case '9': return Northeast;
+    case 'b': case 'B': case '1': return Southwest;
+    case 'n': case 'N': case '3': return Southeast;
+    case '.': case '5': return Stationary;
+    default: return Stationary;
+    }
+}
+
+static void examine_square(Offset o)
+{
+    Coord c = u.pos + o;
+    uint32_t flags;
+    int mon;
+    int obj;
+    Terrain terr;
+    if ((c.y < 0) || (c.x < 0) || (c.y >= (lvl.chunks_high << CHUNK_SHIFT)) ||
+        (c.x >= (lvl.chunks_wide << CHUNK_SHIFT)))
+    {
+        print_msg("That square is beyond the bounds of the level.\n");
+        return;
+    }
+    flags = lvl.flags_at(c);
+    if (!flags & MAPFLAG_EXPLORED)
+    {
+        print_msg("Unexplored territory.\n");
+        return;
+    }
+    mon = lvl.mon_at(c);
+    obj = lvl.obj_at(c);
+    terr = lvl.terrain_at(c);
+    print_msg("%s\n", terrain_props[terr]);
+    if ((mon != NO_MON) && mon_visible(mon))
+    {
+        print_msg("%s\n%s\n", permons[monsters[mon].mon_id].name, permons[monsters[mon].mon_id].description);
+    }
+    if (obj != NO_OBJ)
+    {
+        print_msg("%s\n%s\n", permobjs[objects[obj].obj_id].name, permobjs[objects[obj].obj_id].description);
+    }
+}
+
+static void farlook(void)
+{
+    bool done = false;
+    chtype ch;
+    Offset screendelta = { 0, 0 };
+    print_msg("Use movement keys to move the cursor. Use '5' or '.' to examine the selected square. Press ESC or space when done.\n");
+    wmove(world_window, 10, 10);
+    curs_set(1);
+    update_panels();
+    doupdate();
+    do
+    {
+        Offset o;
+        ch = mvwgetch(world_window, 10 + screendelta.y, 10 + screendelta.x);
+        switch (ch)
+        {
+        case ' ':
+        case '\x1b':
+            done = true;
+            break;
+        case '5':
+        case '.':
+            examine_square(screendelta);
+            wmove(world_window, 10 + screendelta.y, 10 + screendelta.x);
+            update_panels();
+            doupdate();
+            break;
+        default:
+            o = movekey_to_direction(ch);
+            if (o != Stationary)
+            {
+                screendelta += o;
+                screendelta.clamp(-10, -10, 10, 10);
+                wmove(world_window, 10 + screendelta.y, 10 + screendelta.x);
+                update_panels();
+                doupdate();
+            }
+            break;
+        }
+    } while (!done);
+    wmove(world_window, 10, 10);
+    curs_set(0);
+    update_panels();
+    doupdate();
+    print_msg("Done.\n");
+}
+
+void welcome(void)
+{
+    print_msg("Welcome to the Abyss, Princess %s.\n", u.name);
+    print_msg("Press '?' for help.\n");
+}
+
+void describe_object(int obj)
+{
+    Obj *optr;
+    Permobj *poptr;
+    print_obj_name(obj);
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    print_msg("\n%s\n", poptr->description);
+}
+
+void print_obj_name(int obj)
+{
+    Obj *optr;
+    Permobj *poptr;
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (optr->quan > 1)
+    {
+        print_msg("%d %s", optr->quan, poptr->plural);
+    }
+    else if (po_is_stackable(optr->obj_id))
+    {
+        print_msg("1 %s", poptr->name);
+    }
+    else if ((poptr->poclass == POCLASS_WEAPON) ||
+             (poptr->poclass == POCLASS_ARMOUR))
+    {
+        print_msg("a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+    }
+    else
+    {
+        print_msg("a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+    }
+}
+
+void print_mon_name(int mon, int article)
+{
+    if (permons[monsters[mon].mon_id].name[0] == '\0')
+    {
+        print_msg("GROB THE VOID (%d)", monsters[mon].mon_id);
+        return;
+    }
+    switch (article)
+    {
+    case 0:     /* a */
+        print_msg("a%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
+        break;
+    case 1: /* the */
+        print_msg("the %s", permons[monsters[mon].mon_id].name);
+        break;
+    case 2: /* A */
+        print_msg("A%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
+        break;
+    case 3: /* The */
+        print_msg("The %s", permons[monsters[mon].mon_id].name);
+        break;
+    }
+}
+
+/* display-nc.cc */
+// vim:cindent
diff --git a/display.hh b/display.hh
new file mode 100644 (file)
index 0000000..0a92378
--- /dev/null
@@ -0,0 +1,70 @@
+/*! \file display.hh
+ *  \brief Display-related definitions for Obumbrata et Velata
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DISPLAY_HH
+#define DISPLAY_HH
+
+enum class Msg_prio
+{
+    Low,
+    Normal,
+    Alert,
+    Warn,
+    Bug
+};
+
+extern void print_msg(char const *fmt, ...);
+extern void print_msg(Msg_prio prio, char const *fmt, ...);
+extern int read_input(char *buffer, int length);
+extern void print_help(void);
+extern int launch_user_interface(void);
+extern void display_update(void);
+extern int display_shutdown(void);
+extern void newsym(Coord c);
+extern void touch_back_buffer(void);
+extern void get_player_action(Action *act);
+extern Pass_fail select_dir(Offset *pstep);
+extern int getYN(char const *msg);
+extern int getyn(char const *msg);
+extern void press_enter(void);
+extern void pressanykey(void);
+extern void show_discoveries(void);
+extern void touch_one_screen(Coord c);
+extern void welcome(void);
+
+/* "I've changed things that need to be redisplayed" flags. */
+extern bool hard_redraw;
+extern bool status_updated;
+extern bool map_updated;
+/* "Show the player the terrain only" flag. */
+extern bool show_terrain;
+
+#endif
+
+/* display.hh */
+// vim:cindent
diff --git a/fov.cc b/fov.cc
new file mode 100644 (file)
index 0000000..f1d6d68
--- /dev/null
+++ b/fov.cc
@@ -0,0 +1,443 @@
+/*! \file fov.cc
+ *  \brief field-of-view computation (recursive shadowcasting)
+ *
+ *  A recursive shadowcasting implementation using diamond (rather than
+ *  whole-cell) occlusion rules.
+ */
+
+/* Copyright 2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "fov.hh"
+#include <string.h>
+#include <math.h>
+
+/*! \brief Transverse-step delta-y for each octant
+ */
+int trn_dy[8] = {
+    0, -1, 1, 0, 0, 1, -1, 0
+};
+
+/*! \brief Transverse-step delta-x for each octant
+ */
+int trn_dx[8] = {
+    1, 0, 0, 1, -1, 0, 0, -1
+};
+
+/*! \brief Radial-step delta-y for each octant
+ */
+int rdl_dy[8] = {
+    -1, 0, 0, 1, 1, 0, 0, -1
+};
+
+/*! \brief Radial-step delta-x for each octant
+ */
+int rdl_dx[8] = {
+    0, 1, 1, 0, 0, -1, -1, 0
+};
+
+static void compute_row(Radiance *rad, int octant, int radius, double inmost_slope, double outmost_slope);
+
+/*! \brief Default blocking function
+ *
+ *  This function returns true if the specified coordinates are out of bounds
+ *  or the terrain at the specified position is opaque.
+ */
+static bool dflt_blk(Coord c)
+{
+    return ((c.y <= lvl.min_y()) || (c.x <= lvl.min_x()) || (c.y >= lvl.max_y()) || (c.x >= lvl.max_x()) || (terrain_is_opaque(lvl.terrain_at(c))));
+}
+
+/*! \brief Function for marking affected cells as explored
+ *
+ *  This function marks the specified tile as explored, triggers a symbol
+ *  update, and returns true.
+ */
+static bool mark_explored(Coord c, void *pvt)
+{
+    lvl.set_flags_at(c, MAPFLAG_EXPLORED);
+    return true;
+}
+
+/*! \brief Return the inner (cardinal-wards) blocking limit of an octant-relative coordinate
+ */
+static inline double inner_slope(int rdl, int trn)
+{
+    return trn ? ((trn - 0.5) / ((double) rdl)) : 0.0;
+}
+
+/*! \brief Return the innermost (cardinal-wards) visible slope of an octant-relative coordinate
+ */
+static inline double inner_visible_slope(int rdl, int trn)
+{
+    return trn ? ((trn - 0.5) / (rdl + 0.5)) : 0.0;
+}
+
+/*! \brief Return the outer (diagonal-wards) blocking limit of an octant-relative coordinate
+ */
+
+static inline double outer_slope(int rdl, int trn)
+{
+    return (rdl == trn) ? 1.0 : ((trn + 0.5) / (double) rdl);
+}
+
+/*! \brief Return the outermost (diagonal-wards) visible slope of an octant-relative coordinate
+ */
+
+static inline double outer_visible_slope(int rdl, int trn)
+{
+    return (rdl == trn) ? 1.0 : ((trn + 0.5) / (rdl - 0.5));
+}
+
+static inline double centre_slope(int rdl, int trn)
+{
+    return ((double) trn) / ((double) rdl);
+}
+
+/*! \brief Reset the affected flags of a Radiance object
+ */
+void clear_radiance(Radiance *rad)
+{
+    memset(&(rad->affected), '\0', sizeof rad->affected);
+}
+
+/*! \brief Start computing one octant of a Radiance
+ */
+static inline void compute_octant(Radiance *rad, int octant)
+{
+    compute_row(rad, octant, 1, 0.0, 1.0);
+}
+
+/*! \brief Compute one (partial) row of a Radiance and recurse.
+ */
+static void compute_row(Radiance *rad, int octant, int radius, double inmost_slope, double outmost_slope)
+{
+    int trn;
+    Offset delta;
+    Coord tst;
+    bool block_flag = false;
+    int outer_idx;
+    int inner_idx;
+    double isl;
+    double csl;
+
+    /* 
+     * We should never get the slopes mismatched like this, but if we do,
+     * return immediately.
+     */
+    if (inmost_slope >= outmost_slope)
+    {
+        return;
+    }
+    /* 
+     * The inner_idx and outer_idx values are used to control which tiles of
+     * the current row we actually examine.
+     */
+    inner_idx = 0.5 + (inmost_slope * radius);
+    outer_idx = 0.5 + (outmost_slope * radius);
+    /*
+     * dx and dy - the position of the "cursor" relative to the centre of
+     * the potentially affected area - are calculated using the rdl_* and trn_*
+     * arrays.
+     */
+    delta.x = radius * rdl_dx[octant] + outer_idx * trn_dx[octant];
+    delta.y = radius * rdl_dy[octant] + outer_idx * trn_dy[octant];
+    /*
+     * Iterate over the range of transverse deflections from outer_idx to
+     * inner_idx.
+     */
+    for (trn = outer_idx; trn >= inner_idx; --trn)
+    {
+        /* Get the inner "grazing" slope of the diamond inside the tile. */
+        isl = inner_slope(radius, trn);
+        csl = centre_slope(radius, trn);
+        /* Mark the tile being examined as affected. */
+        rad->affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x] |= Visflag_partial;
+        tst = rad->centre + delta;
+        if ((csl >= inmost_slope) && (csl <= outmost_slope))
+        {
+            rad->affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x] |= Visflag_central;
+        }
+        if (block_flag)
+        {
+            /* If the previous tile was opaque... */
+            if (rad->opaque_fun(tst))
+            {
+                /* ... then if this one is too, update the slope */
+                if (outmost_slope > isl)
+                {
+                    outmost_slope = isl;
+                }
+            }
+            else
+            {
+                /* ... and if not, clear the blocking flag. */
+                block_flag = false;
+            }
+        }
+        else
+        {
+            /* Otherwise... */
+            if (rad->opaque_fun(tst))
+            {
+                /* ... if the current tile is opaque, set the blocking flag. */
+                block_flag = true;
+                /* 
+                 * and if we're not examining the first tile of this strip,
+                 * and we're not examining the last row of the octant,
+                 * recursively examine the diagonalwards portion of the
+                 * next row.
+                 */
+                if ((trn < outer_idx) && (radius < rad->radius))
+                {
+                    compute_row(rad, octant, radius + 1, outer_slope(radius, trn), outmost_slope);
+                }
+                /*
+                 * Update the outermost slope to reflect what is occluded by
+                 * this cell.
+                 */
+                if (outmost_slope > isl)
+                {
+                    outmost_slope = isl;
+                }
+            }
+        }
+        /* 
+         * Advance whichever of dx or dy is the transverse coordinate for this
+         * octant. 
+         */
+        delta.x -= trn_dx[octant];
+        delta.y -= trn_dy[octant];
+    }
+    if ((radius < rad->radius) && !block_flag)
+    {
+        /*
+         * If we haven't reached the last row, and the last tile we examined
+         * in this row wasn't opaque, recursively compute the next row.
+         */
+        compute_row(rad, octant, radius + 1, inmost_slope, outmost_slope);
+    }
+}
+
+void compute_radiance(Radiance *rad)
+{
+    int oct;
+    /* Compute the eight octants in order. */
+    for (oct = 0; oct < 8; ++oct)
+    {
+        compute_octant(rad, oct);
+    }
+    /* Mark the centre as (un)affected according to the Radiance's setup. */
+    if (rad->exclude_centre)
+    {
+        rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = false;
+    }
+    else
+    {
+        rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = true;
+    }
+}
+
+void resolve_radiance(Radiance *rad)
+{
+    Coord c;
+    int i;
+    int j;
+    int k;
+
+    switch (rad->order)
+    {
+    case Reo_ascending:
+        for ((i = MAX_FOV_RADIUS - rad->radius), (c.y = rad->centre.y - rad->radius); c.y <= rad->centre.y + rad->radius; ++c.y, ++i)
+        {
+            for ((j = MAX_FOV_RADIUS - rad->radius), (c.x = rad->centre.x - rad->radius); c.x <= rad->centre.x + rad->radius; ++c.x, ++j)
+            {
+                if (rad->affected[i][j])
+                {
+                    rad->effect_fun(c, rad->pvt);
+                }
+            }
+        }
+        break;
+    case Reo_spiral_out:
+        for (i = 0, j = 0, k = 0; i >= -(rad->radius); )
+        {
+            c.y = rad->centre.y + i;
+            c.x = rad->centre.x + j;
+            if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+            {
+                rad->effect_fun(c, rad->pvt);
+            }
+            if ((i == 0) && (j == 0))
+            {
+                i = j = -1;
+                k = 1;
+            }
+            else if (i == -k)
+            {
+                if (j == k)
+                {
+                    ++i;
+                }
+                else
+                {
+                    ++j;
+                }
+            }
+            else if (j == k)
+            {
+                if (i == k)
+                {
+                    --j;
+                }
+                else
+                {
+                    ++i;
+                }
+            }
+            else if (i == k)
+            {
+                if (j == -k)
+                {
+                    --i;
+                }
+                else
+                {
+                    --j;
+                }
+            }
+            else if (j == -k)
+            {
+                if (i == 1 - k)
+                {
+                    ++k;
+                    i = -k;
+                    j = -k;
+                }
+                else
+                {
+                    --i;
+                }
+            }
+        }
+        break;
+    case Reo_spiral_in:
+        for (i = -(rad->radius), j = -(rad->radius), k = rad->radius; k >= 0; )
+        {
+            c.y = rad->centre.y + i;
+            c.x = rad->centre.x + j;
+            if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+            {
+                rad->effect_fun(c, rad->pvt);
+            }
+            if ((i == 0) && (j == 0))
+            {
+                k = -1;
+            }
+            else if (i == -k)
+            {
+                if (j == k)
+                {
+                    ++i;
+                }
+                else
+                {
+                    ++j;
+                }
+            }
+            else if (j == k)
+            {
+                if (i == k)
+                {
+                    --j;
+                }
+                else
+                {
+                    ++i;
+                }
+            }
+            else if (i == k)
+            {
+                if (j == -k)
+                {
+                    --i;
+                }
+                else
+                {
+                    --j;
+                }
+            }
+            else if (j == -k)
+            {
+                if (i == 1 - k)
+                {
+                    --k;
+                    i = -k;
+                    j = -k;
+                }
+                else
+                {
+                    --i;
+                }
+            }
+        }
+        break;
+
+    default:
+        debug_unimplemented_radiance_order(rad->order);
+        abort();
+    }
+}
+
+Radiance player_fov;
+
+void compute_fov(void)
+{
+    clear_radiance(&player_fov);
+    player_fov.centre = u.pos;
+    player_fov.radius = MAX_FOV_RADIUS;
+    player_fov.order = Reo_ascending;
+    player_fov.exclude_centre = false;
+    player_fov.opaque_fun = dflt_blk;
+    player_fov.effect_fun = mark_explored;
+    player_fov.pvt = nullptr;
+    compute_radiance(&player_fov);
+    resolve_radiance(&player_fov);
+}
+
+bool tile_visible(Coord c)
+{
+    Offset delta = c.delta(u.pos);
+    return (delta.len_cheb() <= MAX_FOV_RADIUS) ? player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x] & (Visflag_central | Visflag_partial) : false;
+}
+
+bool occupant_visible(Coord c)
+{
+    Offset delta = c.delta(u.pos);
+    return (delta.len_cheb() <= MAX_FOV_RADIUS) ? player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x] & Visflag_central : false;
+}
+
+/* fov.c */
+// vim:cindent
diff --git a/fov.hh b/fov.hh
new file mode 100644 (file)
index 0000000..15f40ad
--- /dev/null
+++ b/fov.hh
@@ -0,0 +1,88 @@
+/*! \file fov.hh
+ *  \brief field-of-view header
+ */
+
+/* Copyright 2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FOV_HH
+#define FOV_HH
+
+#define MAX_FOV_RADIUS 10
+#define FOV_SIDE (2 * MAX_FOV_RADIUS + 1)
+
+/*! \brief Specifies order in which to affect tiles
+ *  
+ *  Reo_ascending starts from top left and cycles LTR TTB.
+ *
+ *  Reo_spiral_out starts from centre and spirals outward.
+ *
+ *  Reo_spiral_in starts from top left and spirals inward.
+ */ 
+enum rad_eval_order {
+    Reo_ascending,
+    Reo_spiral_out,
+    Reo_spiral_in
+};
+
+typedef enum rad_eval_order Rad_eval_order;
+
+#define Visflag_partial 0x01u
+#define Visflag_central 0x02u
+
+typedef uint8_t Vision_flags; /* 8 bits should do for now */
+class radiance_data
+{
+public:
+    Vision_flags affected[FOV_SIDE][FOV_SIDE];
+    Coord centre;
+    int radius;
+    Rad_eval_order order; /*!< What order to iterate through affected squares */
+    bool exclude_centre; /*!< Exclude the centre from being affected. */
+    void *pvt;
+    bool (*opaque_fun)(Coord c);
+    bool (*effect_fun)(Coord c, void *pvt);
+    bool test(Coord c) const {
+        if ((c.y >= 0) && (c.x >= 0) && (c.y < FOV_SIDE) && (c.x < FOV_SIDE))
+        {
+            return affected[c.y][c.x];
+        }
+        return false;
+    }
+};
+
+typedef struct radiance_data Radiance;
+
+extern void clear_radiance(Radiance *rad);
+extern void compute_radiance(Radiance *rad);
+extern void resolve_radiance(Radiance *rad);
+extern void compute_fov(void);
+extern bool tile_visible(Coord c);
+extern bool occupant_visible(Coord c);
+extern Radiance player_fov;
+
+#endif
+
+/* fov.h */
+// vim:cindent
diff --git a/log.cc b/log.cc
new file mode 100644 (file)
index 0000000..b2bc2c0
--- /dev/null
+++ b/log.cc
@@ -0,0 +1,302 @@
+/* \file log.cc
+ * \brief File I/O functions for Obumbrata et Velata
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "player.hh"
+#include "map.hh"
+#include "util.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <limits.h>
+#include <map>
+
+static void rebuild_mapobjs(void);
+static void rebuild_mapmons(void);
+
+int datadir_fd;
+int confdir_fd;
+
+/*! \brief Read configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+void load_config(void)
+{
+    int i;
+    i = setup_dirs("com.blackswordsonics", "obumbrata", &datadir_fd, &confdir_fd);
+    if (i < 0)
+    {
+        perror("obumbrata: couldn't access configuration directories");
+        exit(1);
+    }
+    // TODO write this function
+}
+
+/*! \brief Rewrite configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+int rewrite_config(void)
+{
+    // TODO write this function
+    return 0;
+}
+
+/*! \brief Get a list of extant saved games and character names
+ *
+ * Fill in the map<string,string> container *dest using character names
+ * as keys and filenames as values.
+ *
+ * \param dest Pointer to map<string,string> container to fill with data
+ * \return Number of charname/filename pairs written to container
+ * \todo Basic implementation
+ * \todo Deal gracefully with a savefile going away before we open it
+ */
+int get_savefile_list(std::map<std::string, std::string> *dest)
+{
+    // TODO write this function
+    return 0;
+}
+
+/*! \brief Record the details of the Princess's death to the log
+ */
+void log_death(Death d, char const *what)
+{
+    FILE *fp;
+    int fd;
+    fd = openat(datadir_fd, "obumbrata.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        return;
+    }
+    fp = fdopen(fd, "a");
+    if (fp)
+    {
+        switch (d)
+        {
+        case DEATH_KILLED:
+            fprintf(fp, "%s was killed by %s.\n", u.name, what);
+            break;
+        case DEATH_KILLED_MON:
+            fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what);
+            break;
+        case DEATH_BODY:
+            fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what);
+            break;
+        case DEATH_AGILITY:
+            fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); 
+            break;
+        case DEATH_LASH:
+            fprintf(fp, "%s tasted the lash one time too many.\n", u.name);
+            break;
+        case DEATH_RIBBONS:
+            fprintf(fp, "%s looked good in ribbons.\n", u.name);
+            break;
+        }
+        fprintf(fp, "    %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, depth);
+        fflush(fp);
+        fsync(fd);
+        fclose(fp);
+    }
+    else
+    {
+        close(fd);
+    }
+}
+
+void wrapped_system(char const *cmd)
+{
+    int i = system(cmd);
+    if (i != 0)
+    {
+        throw(i);
+    }
+}
+
+void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
+{
+    size_t i = fread(buf, size, nmemb, fp);
+    if (i != nmemb)
+    {
+        throw(i);
+    }
+}
+
+void save_game(void)
+{
+    FILE *fp;
+    int fd;
+    uint32_t tmp;
+    fd = openat(datadir_fd, "obumbrata.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        return;
+    }
+    fp = fdopen(fd, "wb");
+    if (!fp)
+    {
+        close(fd);
+        return;
+    }
+    /* Write out the player. */
+    fwrite(&u, 1, sizeof u, fp);
+    tmp = htonl(depth);
+    fwrite(&tmp, 1, sizeof tmp, fp);
+    tmp = htonl(game_tick);
+    fwrite(&tmp, 1, sizeof tmp, fp);
+    serialize_level(fp, &lvl);
+    fwrite(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
+    fwrite(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
+    /* Clean up */
+    fflush(fp);
+    fsync(fd);
+    fclose(fp);
+    game_finished = 1;
+    return;
+}
+
+/*! \brief Monster map reinitialization after reload
+ */
+static void rebuild_mapmons(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+        if (monsters[i].used)
+        {
+            lvl.set_mon_at(monsters[i].pos, i);
+        }
+    }
+}
+
+/*! \brief Object map reinitialization after reload
+ */
+static void rebuild_mapobjs(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+        if (objects[i].used && !objects[i].with_you)
+        {
+            lvl.set_obj_at(objects[i].pos, i);
+        }
+    }
+}
+
+int load_game(void)
+{
+    int fd;
+    FILE *fp;
+    fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0);
+    if (fd == -1)
+    {
+        return -1;
+    }
+    fp = fdopen(fd, "rb");
+    if (!fp)
+    {
+        return -1;
+    }
+    wrapped_fread(&u, 1, sizeof u, fp);
+    wrapped_fread(&depth, 1, sizeof depth, fp);
+    depth = ntohl(depth);
+    wrapped_fread(&game_tick, 1, sizeof game_tick, fp);
+    game_tick = ntohl(game_tick);
+    deserialize_level(fp, &lvl);
+    wrapped_fread(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
+    wrapped_fread(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
+    rebuild_mapobjs();
+    rebuild_mapmons();
+    fclose(fp);
+    game_finished = false;
+    look_around_you();
+    notify_load_complete();
+    int i = unlinkat(datadir_fd, "obumbrata.sav", 0);
+    if (i == -1)
+    {
+        debug_save_unlink_failed();
+    }
+    return 0;
+}
+
+void write_char_dump(void)
+{
+    FILE *fp;
+    char filename[64];
+    int i;
+    Coord c;
+    snprintf(filename, 63, "%s-%llx.dump", u.name, (unsigned long long) time(NULL));
+    fp = fopen(filename, "w");
+    if (fp == nullptr)
+    {
+        debug_dump_write_failed();
+        return;
+    }
+    fprintf(fp, "%s, level %d princess (%d XP)\n", u.name, u.level, u.experience);
+    fprintf(fp, "%d of %d hit points.\n", u.hpcur, u.hpmax);
+    fprintf(fp, "Body %d (%d damage).\n", u.body, u.bdam);
+    fprintf(fp, "Agility %d (%d damage).\n", u.agility, u.adam);
+    fprintf(fp, "Defence %d.\n", u.defence);
+    fprintf(fp, "Inventory:\n");
+    for (i = 0; i < 19; i++)
+    {
+        if (u.inventory[i] != NO_OBJ)
+        {
+            fprint_obj_name(fp, u.inventory[i]);
+            fputc('\n', fp);
+        }
+    }
+    fprintf(fp, "=== MAP ===\n");
+    for (c.y = lvl.min_y(); c.y <= lvl.max_y(); ++c.y)
+    {
+        for (c.x = lvl.min_x(); c.x <= lvl.max_x(); ++c.x)
+        {
+            if (lvl.flags_at(c) & MAPFLAG_EXPLORED)
+            {
+                fputc(terrain_props[lvl.terrain_at(c)].ascii, fp);
+            }
+            else
+            {
+                fputc(' ', fp);
+            }
+        }
+        fputc('\n', fp);
+    }
+    fflush(fp);
+    fclose(fp);
+}
+
+/* log.cc */
+// vim:cindent:expandtab
diff --git a/main.cc b/main.cc
new file mode 100644 (file)
index 0000000..411c02d
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,147 @@
+/* \file main.cc
+ * \brief main core of Obumbrata et Velata
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "map.hh"
+#include "player.hh"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <limits.h>
+#include <deque>
+#include <basedir.h>
+
+bool game_finished;
+int32_t game_tick;
+bool wizard_mode = WIZARD_MODE;
+
+void game_cleanup(void)
+{
+    depth = 0;
+    game_tick = 0;
+    level_cleanup();
+    player_cleanup();
+}
+
+void new_game(char const *name)
+{
+    game_finished = false;
+    depth = 1;
+    game_tick = 0;
+    u_init(name);
+    make_new_level();
+}
+
+void main_loop(void)
+{
+    int i;
+    int action_speed = 0;
+    welcome();
+    while (!game_finished)
+    {
+        switch (game_tick & 3)
+        {
+        case 0:
+        case 2:
+            action_speed = 0;
+            break;
+        case 1:
+            action_speed = 1;
+            break;
+        case 3:
+            action_speed = 2;
+            break;
+        }
+        /* Player is always speed 1, for now. */
+        if (action_speed <= u.speed)
+        {
+            Action act;
+            Action_cost cost = Cost_none;
+            do
+            {
+                get_player_action(&act);
+                cost = do_player_action(&act);
+                if (game_finished)
+                {
+                    break;
+                }
+            } while (cost == Cost_none);
+        }
+        for (i = 0; i < 100; i++)
+        {
+            if (!monsters[i].used)
+            {
+                /* Unused monster */
+                continue;
+            }
+            /* Update the monster's status. */
+            update_mon(i);
+            if (action_speed <= permons[monsters[i].mon_id].speed)
+            {
+                mon_acts(i);
+            }
+            if (game_finished)
+            {
+                break;
+            }
+        }
+        if (game_finished)
+        {
+            break;
+        }
+        update_player();
+        game_tick++;
+        notify_tick();
+    }
+    game_cleanup();
+}
+
+/*! \brief main()
+ */
+int main(void)
+{
+    load_config();
+    rng_init();
+    launch_user_interface();
+    if (!game_finished)
+    {
+        main_loop();
+    }
+    display_shutdown();
+    return 0;
+}
+
+/* main.cc */
+// vim:cindent:expandtab
diff --git a/map.cc b/map.cc
new file mode 100644 (file)
index 0000000..ab8069d
--- /dev/null
+++ b/map.cc
@@ -0,0 +1,835 @@
+/*! \file map.cc
+ *  \brief Map generation and population
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#include <string.h>
+Level lvl;
+int depth;
+
+void drop_all_chunks(Level *l)
+{
+    int i;
+    int j;
+    if (l->chunks)
+    {
+        for (i = 0; i < l->chunks_high; ++i)
+        {
+            for (j = 0; j < l->chunks_wide; ++j)
+            {
+                if (l->chunks[i][j])
+                {
+                    delete l->chunks[i][j];
+                }
+                l->chunks[i][j] = nullptr;
+            }
+            delete[] l->chunks[i];
+            l->chunks[i] = nullptr;
+        }
+        delete[] l->chunks;
+        l->chunks = nullptr;
+    }
+}
+
+Chunk::Chunk(Terrain t)
+{
+    int k, m;
+    for (k = 0; k < CHUNK_EDGE; ++k)
+    {
+        for (m = 0; m < CHUNK_EDGE; ++m)
+        {
+            objs[k][m] = NO_OBJ;
+            mons[k][m] = NO_MON;
+            terrain[k][m] = t;
+            flags[k][m] = 0;
+            region_number[k][m] = NO_REGION;
+        }
+    }
+}
+
+void initialize_chunks(Level *l, int height, int width, bool dense)
+{
+    Chunk ***new_chunk_grid;
+    Chunk **new_chunk_row;
+    Chunk *new_chunk;
+    int i;
+    int j;
+    drop_all_chunks(l);
+    l->origin_off = Stationary;
+    l->chunks_high = height;
+    l->chunks_wide = width;
+    new_chunk_grid = new Chunk ** [height];
+    for (i = 0; i < height; ++i)
+    {
+        new_chunk_row = new Chunk * [width];
+        for (j = 0; j < width; ++j)
+        {
+            new_chunk_row[j] = new_chunk = (dense ? new Chunk(l->dead_space) : nullptr);
+        }
+        new_chunk_grid[i] = new_chunk_row;
+    }
+    l->chunks = new_chunk_grid;
+}
+
+void Level::vivify(Coord c)
+{
+    Coord rc = c + origin_off;
+    while (rc.y < 0)
+    {
+        grow(North);
+        rc = c + origin_off;
+    }
+    while (rc.y > (chunks_high << CHUNK_SHIFT))
+    {
+        grow(South);
+    }
+    while (rc.x < 0)
+    {
+        grow(West);
+        rc = c + origin_off;
+    }
+    while (rc.x > (chunks_wide << CHUNK_SHIFT))
+    {
+        grow(East);
+    }
+    if (!chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT])
+    {
+        chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT] = new Chunk(dead_space);
+    }
+}
+
+void Level::grow(Offset o, bool dense)
+{
+    Chunk ***new_chunk_grid;
+    Chunk **new_chunk_row;
+    int i;
+    int j;
+    if (o.y < 0)
+    {
+        int i;
+        origin_off.y += CHUNK_EDGE;
+        ++chunks_high;
+        new_chunk_grid = new Chunk **[chunks_high];
+        for (i = 0; i < (chunks_high - 1); ++i)
+        {
+            new_chunk_grid[i + 1] = chunks[i];
+        }
+        new_chunk_grid[0] = new_chunk_row = new Chunk *[chunks_wide];
+        for (i = 0; i < chunks_wide; ++i)
+        {
+            new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+        }
+        delete[] chunks;
+        chunks = new_chunk_grid;
+    }
+    else if (o.y > 0)
+    {
+        ++chunks_high;
+        new_chunk_grid = new Chunk **[chunks_high];
+        for (i = 0; i < (chunks_high - 1); ++i)
+        {
+            new_chunk_grid[i] = chunks[i];
+        }
+        new_chunk_grid[chunks_high - 1] = new_chunk_row = new Chunk *[chunks_wide];
+        for (i = 0; i < chunks_wide; ++i)
+        {
+            new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+        }
+        delete[] chunks;
+        chunks = new_chunk_grid;
+    }
+    if (o.x < 0)
+    {
+        ++chunks_wide;
+        origin_off.x += CHUNK_EDGE;
+        for (i = 0; i < chunks_high; ++i)
+        {
+            new_chunk_row = new Chunk *[chunks_wide];
+            for (j = 0; j < chunks_wide - 1; ++j)
+            {
+                new_chunk_row[j + 1] = chunks[i][j];
+            }
+            new_chunk_row[0] = dense ? new Chunk(dead_space) : nullptr;
+            delete[] chunks[i];
+            chunks[i] = new_chunk_row;
+        }
+    }
+    else if (o.x > 0)
+    {
+        ++chunks_wide;
+        for (i = 0; i < chunks_high; ++i)
+        {
+            new_chunk_row = new Chunk *[chunks_wide];
+            for (j = 0; j < chunks_wide - 1; ++j)
+            {
+                new_chunk_row[j] = chunks[i][j];
+            }
+            new_chunk_row[chunks_wide - 1] = dense ? new Chunk(dead_space) : nullptr;
+            delete[] chunks[i];
+            chunks[i] = new_chunk_row;
+        }
+    }
+}
+
+/*! \brief Find a random point on the level
+ *
+ * \param margin Width of the "dead" space along each edge in which the point cannot appear
+ */
+Coord Level::random_point(int margin) const
+{
+    Coord tl = { margin, margin };
+    Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) };
+    return inc_boxed(tl, br);
+}
+
+int Level::add_stairs_at(Coord c, Terrain t, Level_key l)
+{
+    Stair_detail sd = { c, t, l };
+    if (!(terrain_props[t].flags & 
+          (TFLAG_ascend | TFLAG_portal | TFLAG_descend)))
+    {
+        return NO_STAIRS;
+    }
+    set_terrain_at(c, t);
+    stairs.push_back(sd);
+    return stairs.size() - 1;
+}
+
+Level_key const No_level = { 65535, -32768 };
+
+Stair_detail const Bad_stairs = { Nowhere, FLOOR, No_level };
+Stair_detail Level::find_stairs(Coord pos) const
+{
+    for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+    {
+        if (iter->pos == pos)
+        {
+            return *iter;
+        }
+    }
+    return Bad_stairs;
+}
+
+int Level::find_stairs(Terrain t, std::vector<Stair_detail> *dest) const
+{
+    int count = 0;
+    for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+    {
+        if (iter->t == t)
+        {
+            dest->push_back(*iter);
+            ++count;
+        }
+    }
+    return count;
+}
+
+int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
+{
+    int count = 0;
+    for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter)
+    {
+        if (iter->destination == from)
+        {
+            dest->push_back(*iter);
+            ++count;
+        }
+    }
+    return count;
+}
+
+void leave_level(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+        /* Throw away each monster */
+        monsters[i].used = false;
+        /* and each object not carried by the player */
+        if (!objects[i].with_you)
+        {
+            objects[i].used = false;
+        }
+    }
+    depth++;
+}
+
+void make_new_level(void)
+{
+    build_level();
+    populate_level();
+    inject_player(lvl.self.naive_prev());
+    look_around_you();
+    notify_change_of_depth();
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+                      void const *priv_ptr, int cells)
+{
+    int i;
+    Coord c = oc;
+    int bailout = 10000;
+
+    func(c, priv_ptr);
+    for (i = 0; (i < cells) && (bailout > 0); --bailout)
+    {
+        oc = c;
+        if (zero_die(2))
+        {
+            c.y += (zero_die(2) ? -1 : 1);
+            c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2));
+        }
+        else
+        {
+            c.x += (zero_die(2) ? -1 : 1);
+            c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2));
+        }
+        switch (func(c, priv_ptr))
+        {
+        case 0:
+            /* nothing changed */
+            break;
+        case 1:
+            /* changed normally */
+            ++i;
+            break;
+        case 2:
+            /* reject! */
+            c = oc;
+            break;
+        }
+    }
+    if (bailout < 1)
+    {
+        debug_excavation_bailout();
+    }
+    return c;
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+                                void const *priv_ptr, int cells)
+{
+    int i;
+    Coord c = oc;
+    int bailout = 10000;
+
+    func(c, priv_ptr);
+    for (i = 0; (i < cells) && (bailout > 0); --bailout)
+    {
+        oc = c;
+        if (zero_die(2))
+        {
+            c.y += (zero_die(2) ? -1 : 1);
+        }
+        else
+        {
+            c.x += (zero_die(2) ? -1 : 1);
+        }
+        if (c.y <= lvl.min_y())
+        {
+            lvl.grow(North, true);
+        }
+        if (c.y >= lvl.max_y())
+        {
+            lvl.grow(South, true);
+        }
+        if (c.x <= lvl.min_x())
+        {
+            lvl.grow(West, true);
+        }
+        if (c.x >= lvl.max_x())
+        {
+            lvl.grow(East, true);
+        }
+        switch (func(c, priv_ptr))
+        {
+        case 0:
+            /* nothing changed */
+            break;
+        case 1:
+            /* changed normally */
+            ++i;
+            break;
+        case 2:
+            /* reject! */
+            c = oc;
+            break;
+        }
+    }
+    if (bailout < 1)
+    {
+        debug_excavation_bailout();
+    }
+    return c;
+}
+
+/*! \brief Entry point for level generation.
+ *
+ *  \todo Maybe implement support for Lua-based level generators.
+ */
+void build_level(void)
+{
+    int theme_roll;
+    lvl.self.depth = (int16_t) depth;
+    lvl.self.dungeon = 0;
+    lvl.stairs.clear();
+    rng.extract_serialization(saved_state_buffer, saved_state_size);
+    theme_roll = zero_die(depth + 50);
+    if (!zero_die(4))
+    {
+        lvl.layout = LAYOUT_CAVE_INTRUSIONS;
+    }
+    else if ((depth > 1) && !zero_die(6))
+    {
+        lvl.layout = LAYOUT_CAVE_SHRINE;
+    }
+    else
+    {
+        lvl.layout = LAYOUT_CLASSIC_CAVE;
+    }
+    if ((theme_roll < 50) || (depth < 10))
+    {
+        lvl.theme = THEME_NORMAL; /* no restrictions */
+    }
+    else if (theme_roll < 60)
+    {
+        lvl.theme = THEME_UNDEAD;
+    }
+    else if (theme_roll < 80)
+    {
+        lvl.theme = THEME_DRAGONS;
+    }
+    else if (theme_roll < 90)
+    {
+        lvl.theme = THEME_DEMONS;
+    }
+    switch (lvl.layout)
+    {
+    case LAYOUT_CAVE_SHRINE:
+        build_level_shrine();
+        break;
+    case LAYOUT_CAVE_INTRUSIONS:
+        build_level_intrusions();
+        break;
+    case LAYOUT_DUNGEONBASH:
+        build_level_dungeonbash();
+        break;
+    case LAYOUT_CLASSIC_CAVE:
+        build_level_cave();
+        break;
+    }
+}
+
+/*! \brief Build a dungeonbash-style rooms-and-corridors level */
+void build_level_dungeonbash()
+{
+    int chy;
+    int chx;
+    /* We know we're going to use all nine chunks, so create them
+     * immediately. */
+    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    /* One room per chunk. */
+    for (chy = 0; chy < lvl.chunks_high; ++chy)
+    {
+        for (chx = 0; chx < lvl.chunks_wide; ++ chx)
+        {
+            /* Smallest allowed room has a 2x2 interior. */
+            Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) };
+            /* Each dimension has a 1-in-3 chance to get another boost */
+            if (!zero_die(3))
+            {
+                room_size.y += zero_die(5);
+            }
+            if (!zero_die(3))
+            {
+                room_size.x += zero_die(5);
+            }
+        }
+    }
+}
+
+/*! \brief Excavation function for use with random walks */
+int excavation_write(Coord c, void const *data)
+{
+    int const *data_as_ints = (int const *) data;
+    int const *overwrite = data_as_ints + 2;
+    int newterr = data_as_ints[1];
+    int j;
+    if (lvl.flags_at(c) & MAPFLAG_HARDWALL)
+    {
+        /* Don't bite into hardened walls, but don't waste a step on
+         * them either. */
+        return 2;
+    }
+    for (j = 0; j < data_as_ints[0]; ++j)
+    {
+        if (lvl.terrain_at(c) == overwrite[j])
+        {
+            lvl.set_terrain_at(c, (Terrain) newterr);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+/*! \brief "Intrusion" function for use with random walks */
+int intrusion_write(Coord c, void const *data)
+{
+    Terrain const *tptr = (Terrain const *) data;
+    if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL))
+    {
+        return 0;
+    }
+    /* Don't intrude too closely on the centre of the level */
+    if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4)))
+    {
+        return 2;
+    }
+    if ((c.x > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.x < ((GUIDE_EDGE_SIZE / 2) - 4)))
+    {
+        return 2;
+    }
+    if (tptr)
+    {
+        lvl.set_terrain_at(c, *tptr);
+    }
+    lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+    return 1;
+}
+
+/*! \brief Do a random walk laying down an exclusion area */
+void place_random_intrusion(Terrain new_wall)
+{
+    Coord c;
+    int intrusion_size = inc_flat(27, 54);
+    do
+    {
+        c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+        c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+    } while (lvl.flags_at(c) & MAPFLAG_HARDWALL);
+    run_random_walk(c, intrusion_write, &new_wall, intrusion_size);
+}
+
+/*! \brief Get a valid square to generate a monster on */
+Pass_fail get_levgen_mon_floor(Coord *c)
+{
+    int cell_try;
+    Coord t;
+    for (cell_try = 0; cell_try < 200; cell_try++)
+    {
+        t = lvl.random_point(1);
+        if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON))
+        {
+            t = Nowhere;
+            continue;
+        }
+        break;
+    }
+    if (t == Nowhere)
+    {
+        return You_fail;
+    }
+    *c = t;
+    return You_pass;
+}
+
+/*! \brief Populate the level! */
+void populate_level(void)
+{
+    int i;
+    Pass_fail pf;
+    Coord c;
+    int ic;
+    /* Generate some random monsters */
+    for (i = 0; i < 10; i++)
+    {
+        pf = get_levgen_mon_floor(&c);
+        if (pf == You_fail)
+        {
+            continue;
+        }
+        create_mon(NO_PMON, c);
+    }
+    ic = 3 + depth;
+    if (ic > 40)
+    {
+        /* Never create more than 40 items. */
+        ic = 40;
+    }
+    /* Generate some random treasure */
+    for (i = 0; i < ic; i++)
+    {
+        pf = get_levgen_mon_floor(&c);
+        if (pf == You_fail)
+        {
+            continue;
+        }
+        create_obj(NO_POBJ, 1, 0, c);
+    }
+}
+
+/*! \brief Inject the player into the level based on where they arrived from */
+void inject_player(Level_key from)
+{
+    /* For now we are allowing only one linkage between levels */ 
+    Coord c;
+    std::vector<Stair_detail> stair_list;
+    int i = lvl.find_stairs(from, &stair_list);
+    if (i != 0)
+    {
+        c = stair_list[0].pos;
+    }
+    else
+    {
+        fprintf(stderr, "Couldn't find any stairs!\n");
+        abort();
+    }
+    u.pos = c;
+    reloc_player(u.pos);
+}
+
+/*! \brief Look around the player */
+void look_around_you(void)
+{
+    compute_fov();
+    notify_fov();
+}
+
+/*! \brief Description of terrain types */
+Terrain_props terrain_props[NUM_TERRAINS] = 
+{
+    { "cave wall", '#', "â–ˆ", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "masonry wall", '#', "â–ˆ", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "amethyst wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "iron wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "skin wall", '#', "â–ˆ", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "bone wall", '#', "â–ˆ", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+    { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile },
+    { "floor", '.', "·", Gcol_l_grey, TFLAG_floor },
+    { "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor },
+    { "iron floor", '.', "·", Gcol_iron, TFLAG_floor },
+    { "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor },
+    { "bone floor", '.', "·", Gcol_white, TFLAG_floor },
+    { "altar", '_', "_", Gcol_l_grey, 0 },
+    { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend },
+    { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend },
+    { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard },
+    { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard },
+};
+
+/*! \brief self-explanatory */
+bool terrain_is_opaque(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_opaque;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_beings(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_beings;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_missiles(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_missile;
+}
+
+/*! \brief Read a Chunk from a FILE.
+ *
+ *  Yes, I know "throw(errno)" is tacky and stupid, but it achieves the
+ *  desired result: the save is obviously garbage, so there's no point
+ *  in finishing loading it.
+ */
+void deserialize_chunk(FILE *fp, Chunk *c)
+{
+    static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    for (i = 0; i < CHUNK_EDGE; ++i)
+    {
+        for (j = 0; j < CHUNK_EDGE; ++j)
+        {
+            c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
+            c->flags[i][j] = ntohl(deserializable_flags[i][j]);
+            c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
+            /* objs and mons will get accurately set once we've loaded the
+             * objs and mons. */
+            c->objs[i][j] = NO_OBJ;
+            c->mons[i][j] = NO_MON;
+        }
+    }
+}
+
+/*! \brief Write a Chunk out to a FILE.  */
+void serialize_chunk(FILE *fp, Chunk const *c)
+{
+    static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    for (i = 0; i < CHUNK_EDGE; ++i)
+    {
+        for (j = 0; j < CHUNK_EDGE; ++j)
+        {
+            serializable_terrain[i][j] = htonl(c->terrain[i][j]);
+            serializable_flags[i][j] = htonl(c->flags[i][j]);
+            serializable_regions[i][j] = htonl(c->region_number[i][j]);
+        }
+    }
+    fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+}
+
+/*! \brief Serialize a Level */
+void serialize_level(FILE *fp, Level const *l)
+{
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    int i;
+    int j;
+    tmp_shorts[0] = htons(l->self.dungeon);
+    tmp_shorts[1] = htons(l->self.depth);
+    fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    tmp_pair[0] = htonl(l->origin_off.y);
+    tmp_pair[1] = htonl(l->origin_off.x);
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    tmp = htonl(l->dead_space);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->theme);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->layout);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_high);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_wide);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    for (i = 0; i < l->chunks_high; ++i)
+    {
+        tmp_pair[0] = htonl(i);
+        for (j = 0; j < l->chunks_wide; ++j)
+        {
+            if (l->chunks[i][j])
+            {
+                tmp_pair[1] = htonl(j);
+                fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+                serialize_chunk(fp, l->chunks[i][j]);
+            }
+        }
+    }
+    tmp_pair[0] = tmp_pair[1] = ~0u;
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+}
+
+/*! \brief Deserialize a Level
+ *
+ * \todo Throw an exception if the level is malformed e.g. OOB chunk indices.
+ */
+void deserialize_level(FILE *fp, Level *l)
+{
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    uint32_t i;
+    uint32_t j;
+    wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    l->self.dungeon = ntohs(tmp_shorts[0]);
+    l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
+    wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    l->origin_off.y = (int) ntohl(tmp_pair[0]);
+    l->origin_off.x = (int) ntohl(tmp_pair[1]);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->dead_space = Terrain(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->theme = level_theme(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->layout = level_layout(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_high = ntohl(tmp);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_wide = ntohl(tmp);
+    initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
+    do
+    {
+        wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
+        i = ntohl(tmp_pair[0]);
+        j = ntohl(tmp_pair[1]);
+        if (i == ~0u)
+        {
+            break;
+        }
+        l->chunks[i][j] = new Chunk(l->dead_space);
+        deserialize_chunk(fp, l->chunks[i][j]);
+    }
+    while (1);
+}
+
+uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
+{
+    clockwise_steps &= 0x3;
+    return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf;
+}
+
+void level_cleanup(void)
+{
+    int i;
+    for (i = 0; i < MONSTERS_IN_PLAY; ++i)
+    {
+        monsters[i].used = false;
+    }
+    for (i = 0; i < OBJECTS_IN_PLAY; ++i)
+    {
+        objects[i].used = false;
+        objects[i].with_you = false;
+    }
+    drop_all_chunks(&lvl);
+}
+
+/* map.cc */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/map.hh b/map.hh
new file mode 100644 (file)
index 0000000..3f5bbcf
--- /dev/null
+++ b/map.hh
@@ -0,0 +1,308 @@
+/*! \file map.hh
+ *  \brief Map-related header
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MAP_HH
+#define MAP_HH
+
+#ifndef OBUMBRATA_HH
+#include "obumbrata.hh"
+#endif
+
+#include <deque>
+/* XXX enum terrain_num */
+enum Terrain {
+    WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, LAVA, WATER
+};
+#define MAX_TERRAIN (WATER)
+#define NUM_TERRAINS (1 + MAX_TERRAIN)
+
+#define MAPFLAG_EXPLORED 0x00000001
+#define MAPFLAG_HARDWALL 0x00000002
+enum level_theme {
+    THEME_NORMAL = 0, THEME_DRAGONS, THEME_DEMONS, THEME_UNDEAD
+};
+
+enum level_layout
+{
+    LAYOUT_CLASSIC_CAVE = 0,
+    LAYOUT_CAVE_INTRUSIONS, /* the cave has hardened intrusions */
+    LAYOUT_CAVE_SHRINE, /* the cave contains a shrine */
+    LAYOUT_DUNGEONBASH /* maybe not for this version: dungeonbash-style room grid */
+};
+
+#define NO_REGION 0xffffffffu
+
+#define SHRINE_HEIGHT 11
+#define SHRINE_WIDTH 11
+
+//! Layout data for "shrines"
+struct shrine
+{
+    bool used;
+    uint32_t connection_mask;
+    char const *grid[SHRINE_HEIGHT];
+};
+
+//! Property structure for terrain types
+struct Terrain_props
+{
+    char const *name; //!< UTF-8 encoded English name
+    char ascii; //!< ASCII symbol
+    char const * unicode; //!< UTF-8 encoded Unicode symbol
+    Gamecolour colour; //!< colour to use in terminal-like environments
+    uint32_t flags; //!< Bitmask composed using TFLAG_* macros.
+};
+
+//! Array of terrain properties
+extern Terrain_props terrain_props[NUM_TERRAINS];
+
+#define NO_STAIRS (-1)
+
+#define TFLAG_opaque        0x00000001u
+#define TFLAG_block_beings  0x00000002u
+#define TFLAG_block_ether   0x00000004u
+#define TFLAG_block_missile 0x00000008u
+#define TFLAG_portal        0x00000010u
+#define TFLAG_ascend        0x00000020u
+#define TFLAG_descend       0x00000040u
+#define TFLAG_floor         0x00000080u
+#define TFLAG_fire_hazard   0x00010000u
+#define TFLAG_fall_hazard   0x00020000u
+#define TFLAG_drown_hazard  0x00040000u
+#define TFLAG_flammable     0x10000000u
+
+#define CHUNK_EDGE 16
+#define CHUNK_SHIFT 4
+#define CHUNK_MASK 0xf
+#define CHUNK_AREA (CHUNK_EDGE * CHUNK_EDGE)
+
+//! The fundamental object-like unit of cartography 
+class Chunk
+{
+public:
+    int objs[CHUNK_EDGE][CHUNK_EDGE];
+    int mons[CHUNK_EDGE][CHUNK_EDGE];
+    Terrain terrain[CHUNK_EDGE][CHUNK_EDGE];
+    uint32_t flags[CHUNK_EDGE][CHUNK_EDGE];
+    uint32_t region_number[CHUNK_EDGE][CHUNK_EDGE];
+    Terrain terrain_at(Coord c) const { return terrain[c.y][c.x]; }
+    uint32_t flags_at(Coord c) const { return flags[c.y][c.x]; }
+    uint32_t region_at(Coord c) const { return region_number[c.y][c.x]; }
+    uint32_t obj_at(Coord c) const { return objs[c.y][c.x]; }
+    uint32_t mon_at(Coord c) const { return mons[c.y][c.x]; }
+    void set_terrain_at(Coord c, Terrain t) { terrain[c.y][c.x] = t; }
+    void overwrite_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] = f; }
+    void set_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] |= f; }
+    void clear_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] &= ~f; }
+    void set_region_at(Coord c, uint32_t r) { region_number[c.y][c.x] = r; }
+    void set_obj_at(Coord c, int o) { objs[c.y][c.x] = o; }
+    void set_mon_at(Coord c, int m) { mons[c.y][c.x] = m; }
+    Chunk(Terrain t = WALL);
+};
+
+#define MIN_ROOM_EDGE 4
+
+class Level_key
+{
+public:
+    uint16_t dungeon;
+    int16_t depth;
+    /*
+     * g++ 4.7's brain-dead dogmatic narrowing detection makes the casts below
+     * compulsory. And no, don't tell me to use 32-bit integers for these
+     * fields, because I AM NEVER GOING TO SUPPORT DUNGEONS LARGER THAN 64
+     * KIBILEVELS, OR HAVING MORE THAN 64 KIBIDUNGEONS.
+     */
+    Level_key naive_next() const { Level_key l = { dungeon, (int16_t) (depth + 1) }; return l; }
+    Level_key naive_prev() const { Level_key l = { dungeon, (int16_t) (depth - 1) }; return l; }
+    bool operator ==(Level_key right) const { return (dungeon == right.dungeon) && (depth == right.depth); }
+    bool operator <(Level_key right) const { return (dungeon < right.dungeon) || ((dungeon == right.dungeon) && (depth < right.depth)); }
+};
+
+extern Level_key const No_level;
+
+/*! \brief Description of a staircase leading away from the level
+ */
+class Stair_detail
+{
+public:
+    Coord pos;
+    Terrain t;
+    Level_key destination;
+};
+
+extern Stair_detail const Bad_stairs;
+
+//! The top-tier object for describing everything about a level
+class Level
+{
+public:
+    Chunk ***chunks; //!< 16x16 subsections of the level, not necessarily dense
+    Offset origin_off; //!< Don't force a map size change to recalculate all Coords
+    int chunks_high; //!< Chunkwise size of level in the y-direction
+    int chunks_wide; //!< Chunkwise size of level in the x-direction
+    Terrain dead_space; //!< Terrain to fill new chunks with and return for Coords in unpopulated chunks
+    level_theme theme; //!< Will affect monster and maybe item generation
+    level_layout layout; //!< Determines generation algorithm
+    Level_key self;
+    std::deque<Stair_detail> stairs;
+    Terrain terrain_at(Coord c) const
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        return ch ? ch->terrain_at(c2) : dead_space;
+    }
+    void set_terrain_at(Coord c, Terrain t)
+    {
+        /* Algorithms that want to potentially stretch the level are
+         * responsible for telling the level to stretch. */
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        if (ch)
+        {
+            ch->set_terrain_at(c2, t);
+        }
+    }
+    uint32_t flags_at(Coord c) const
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        return ch ? ch->flags_at(c2) : 0;
+    }
+    void set_flags_at(Coord c, uint32_t to_set) 
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        if (ch)
+        {
+            ch->set_flags_at(c2, to_set);
+        }
+    }
+    void clear_flags_at(Coord c, uint32_t to_clear)
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        if (ch)
+        {
+            ch->clear_flags_at(c2, to_clear);
+        }
+    }
+    int mon_at(Coord c) const
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        return ch ? ch->mon_at(c2) : NO_MON;
+    }
+    void set_mon_at(Coord c, int mon)
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        if (ch)
+        {
+            ch->set_mon_at(c2, mon);
+        }
+    }
+    int obj_at(Coord c) const
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        return ch ? ch->obj_at(c2) : NO_OBJ;
+    }
+    void set_obj_at(Coord c, int obj)
+    {
+        Coord rc = c + origin_off;
+        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+        if (ch)
+        {
+            ch->set_obj_at(c2, obj);
+        }
+    }
+    Coord random_point(int margin) const;
+    bool in_bounds(Coord c) const
+    {
+        c += origin_off;
+        return !((c.y < 0) || (c.x < 0) || (c.y >= (chunks_high << CHUNK_SHIFT)) ||
+            (c.x >= (chunks_wide << CHUNK_SHIFT)));
+    }
+    int min_y() const
+    {
+        return -(origin_off.y);
+    }
+    int max_y() const
+    {
+        return (chunks_high << CHUNK_SHIFT) - origin_off.y - 1;
+    }
+    int min_x() const
+    {
+        return -(origin_off.x);
+    }
+    int max_x() const
+    {
+        return (chunks_wide << CHUNK_SHIFT) - origin_off.x - 1;
+    }
+    int add_stairs_at(Coord c, Terrain t, Level_key l);
+    int find_stairs(Level_key from, std::vector<Stair_detail> *result) const;
+    Stair_detail find_stairs(Coord pos) const;
+    int find_stairs(Terrain t, std::vector<Stair_detail> *result) const;
+    int find_stairs(Level_key from, Terrain t, std::vector<Stair_detail> *result) const;
+    void grow(Offset o, bool dense = true);
+    void vivify(Coord c);
+};
+
+extern Level lvl;
+
+extern int depth;
+extern enum level_theme current_theme;
+extern enum level_layout current_layout;
+
+void leave_level(void);
+void make_new_level(void);
+void build_level(void);
+void populate_level(void);
+void inject_player(Level_key from);
+void look_around_you(void);
+bool terrain_is_opaque(Terrain terr);
+bool terrain_blocks_beings(Terrain terr);
+bool terrain_blocks_missiles(Terrain terr);
+void serialize_level(FILE *fp, Level const *l);
+void deserialize_level(FILE *fp, Level *l);
+
+void level_cleanup(void);
+#endif
+
+/* map.h */
+// vim:cindent:expandtab
diff --git a/mapgen.hh b/mapgen.hh
new file mode 100644 (file)
index 0000000..4936e8e
--- /dev/null
+++ b/mapgen.hh
@@ -0,0 +1,73 @@
+/*! \file mapgen.hh
+ *  \brief This is a map-generation header for map-generation modules.
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MAPGEN_HH
+#define MAPGEN_HH
+
+#include "map.hh"
+
+#define LEVGEN_WALK_CELLS 300
+#define ROOM_HT_DELTA 4
+#define ROOM_WD_DELTA 4
+#define MAX_ROOMS 9
+#define GUIDE_EDGE_CHUNKS 3
+#define GUIDE_EDGE_SIZE (GUIDE_EDGE_CHUNKS << CHUNK_SHIFT)
+
+extern int depth;
+extern enum level_theme current_theme;
+extern enum level_layout current_layout;
+
+void build_level_shrine(void);
+void build_level_intrusions(void);
+void build_level_cave(void);
+void build_level_dungeonbash(void);
+Pass_fail get_levgen_mon_floor(Coord *c);
+int excavation_write(Coord c, void const *data);
+int intrusion_write(Coord c, void const *data);
+void initialize_chunks(Level *l, int height, int width, bool dense);
+void drop_all_chunks(Level *l);
+void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false);
+void place_random_intrusion(Terrain new_wall = WALL);
+void place_cave_stairs(void);
+
+#define ROOMCONN_NORTH 0x00000001u
+#define ROOMCONN_EAST 0x00000002u
+#define ROOMCONN_SOUTH 0x00000004u
+#define ROOMCONN_WEST 0x00000008u
+uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps);
+
+typedef int (*rwalk_mod_funcptr)(Coord c, void const *data);
+Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+                      void const *priv_ptr, int cells);
+Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+                                void const *priv_ptr, int cells);
+
+#endif
+
+/* mapgen.hh */
+// vim:cindent:expandtab
diff --git a/mon2.cc b/mon2.cc
new file mode 100644 (file)
index 0000000..9f216bf
--- /dev/null
+++ b/mon2.cc
@@ -0,0 +1,729 @@
+/* \file mon2.cc
+ * \brief More monster functions
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* TODO: Convert missile AI to a new-style AI function. */
+#define MON2_CC
+#include "obumbrata.hh"
+#include "sorcery.hh"
+#include "monsters.hh"
+#include "combat.hh"
+
+#define AI_REALLY_HATE (-10000)
+/* AI map cell descriptor. */
+struct ai_cell {
+    Coord pos;
+    Offset delta;
+    int score;
+};
+
+/* prototypes for AI preference functions. */
+static void get_naive_prefs(Coord loc, Offset delta, Coord *pref_pos);
+static void get_seeking_prefs(Coord loc, Offset delta, Coord *pref_pos);
+static void get_drunk_prefs(Coord loc, Offset delta, Coord *pref_pos);
+static void build_ai_cells(struct ai_cell *cells, Coord loc);
+static Comparison ai_cell_compare(struct ai_cell *cell, Offset delta);
+static void get_dodger_prefs(Coord loc, Offset delta, Coord *pref_pos);
+static void get_chase_prefs(int mon, Coord *pref_pos);
+
+/* get_drunk_prefs()
+ *
+ * Fills the three-entry preference arrays with three randomly-selected
+ * adjacent squares.
+ */
+
+static void get_drunk_prefs(Coord loc, Offset delta, Coord *pref_pos)
+{
+    Coord trypos;
+    Offset step;
+    int tryct;
+    int pref_idx;
+    int idx2;
+    int retry;
+    pref_pos[0] = loc;
+    pref_pos[1] = loc;
+    pref_pos[2] = loc;
+    for (pref_idx = 0; pref_idx < 3; pref_idx++)
+    {
+        for (tryct = 0; tryct < 40; tryct++)
+        {
+            retry = 0;
+            step = random_step();
+            trypos = loc + step;
+            for (idx2 = 0; idx2 < pref_idx; idx2++)
+            {
+                if (pref_pos[idx2] == trypos)
+                {
+                    retry = 1;
+                    break;
+                }
+            }
+            if (retry) 
+            {
+                continue;
+            }
+            pref_pos[pref_idx] = trypos;
+            break;
+        }
+    }
+}
+
+/* get_chase_prefs()
+ *
+ * The naive "chase" AI is used by non-stupid non-smart monsters to chase your
+ * last known position. If after moving towards it once they can't see you,
+ * they will give up and revert to "drunk" AI. (Contrast stupid monsters,
+ * who always use "drunk" AI if they can't see you, and smart monsters, who
+ * always use "seeking" AI if they can't see you.)
+ *
+ * This function takes different parameters to the other AI preference
+ * functions because it has to have access to the monster's lasty/lastx
+ * details.
+ */
+
+static void get_chase_prefs(int mon, Coord *pref_pos)
+{
+    Mon const *mptr = monsters + mon;
+    Offset delta = mptr->ai_lastpos.delta(mptr->pos);
+    Offset step = mysign(delta);
+    Offset absdelta = myabs(delta);
+    Coord testpos = mptr->pos + step;
+    if (monsters[mon].can_pass(testpos))
+    {
+        *pref_pos = testpos;
+    }
+    else
+    {
+        if (!step.y)
+        {
+            /* We're on the horizontal; check the horizontally adjacent
+             * square, then the squares one square north or south in a
+             * random order. */
+            if (zero_die(2))
+            {
+                pref_pos[1] = testpos + North;
+                pref_pos[2] = testpos + South;
+            }
+            else
+            {
+                pref_pos[2] = testpos + North;
+                pref_pos[1] = testpos + South;
+            }
+        }
+        else if (!step.x)
+        {
+            /* We're on the horizontal; check the horizontally adjacent
+             * square, then the squares one square north or south in a
+             * random order. */
+            if (zero_die(2))
+            {
+                pref_pos[1] = testpos + West;
+                pref_pos[2] = testpos + East;
+            }
+            else
+            {
+                pref_pos[2] = testpos + West;
+                pref_pos[1] = testpos + East;
+            }
+        }
+        else
+        {
+            pref_pos[1] = testpos;
+            pref_pos[2] = testpos;
+            if (zero_die(2))
+            {
+                pref_pos[1].x = mptr->pos.x;
+                pref_pos[2].y = mptr->pos.y;
+            }
+            else
+            {
+                pref_pos[2].x = mptr->pos.x;
+                pref_pos[1].y = mptr->pos.y;
+            }
+        }
+        if (mptr->can_pass(pref_pos[1]))
+        {
+            pref_pos[0] = pref_pos[1];
+        }
+        else if (mptr->can_pass(pref_pos[2]))
+        {
+            pref_pos[0] = pref_pos[2];
+        }
+        else
+        {
+            pref_pos[0] = mptr->pos;
+        }
+    }
+}
+
+/* get_seeking_prefs()
+ *
+ * Does all the work of finding the best (or least-bad) square for a seeking
+ * AI monster to move to.
+ */
+
+static void get_seeking_prefs(Coord loc, Offset delta, Coord *pref_pos)
+{
+    struct ai_cell ai_cells[8];
+    int i;
+    int highest_score = AI_REALLY_HATE;
+    int tryct;
+    Mon *mptr = monsters + lvl.mon_at(loc);
+    *pref_pos = loc;
+    build_ai_cells(ai_cells, loc);
+    for (i = 0; i < 8; i++)
+    {
+        ai_cells[i].delta = u.pos.delta(ai_cells[i].pos);
+        /* Scoring factors:
+         *   Square closer to player: +1
+         *   Square further from player: -1
+         */
+        if (!mptr->can_pass(ai_cells[i].pos))
+        {
+            /* Square impassable to this monster. Set score WAY
+             * out of bounds and continue. */
+            ai_cells[i].score = AI_REALLY_HATE;
+            continue;
+        }
+        if (ai_cells[i].delta.rcardinal())
+        {
+            ai_cells[i].score += 1;
+        }
+        switch (ai_cell_compare(ai_cells + i, delta))
+        {
+        case Greater:
+            ai_cells[i].score -= 1;
+            break;
+        case Lesser:
+            ai_cells[i].score += 1;
+            break;
+        default:
+            break;
+        }
+        if (ai_cells[i].score > highest_score)
+        {
+            highest_score = ai_cells[i].score;
+        }
+    }
+    if (highest_score == AI_REALLY_HATE)
+    {
+        /* No good targets. */
+        return;
+    }
+    for (tryct = 0; tryct < 32; tryct++)
+    {
+        i = zero_die(8);
+        if (ai_cells[i].score == highest_score)
+        {
+            *pref_pos = ai_cells[i].pos;
+            break;
+        }
+    }
+    return;
+}
+
+/* get_naive_prefs()
+ *
+ * Fills the three-entry preference arrays with three best choices for closing
+ * with the player - optimal first, then secondaries in random order as #2 and
+ * #3.
+ */
+
+static void get_naive_prefs(Coord loc, Offset delta, Coord *pref_pos)
+{
+    Offset step = mysign(delta);
+    Offset absdelta = myabs(delta);
+    pref_pos[0] = loc + step;
+    if (!step.y)
+    {
+        /* We're on the horizontal; check the horizontally adjacent
+         * square, then the squares one square north or south in a
+         * random order. */
+        if (zero_die(2))
+        {
+            pref_pos[1] = pref_pos[0] + North;
+            pref_pos[2] = pref_pos[0] + South;
+        }
+        else
+        {
+            pref_pos[2] = pref_pos[0] + North;
+            pref_pos[1] = pref_pos[0] + South;
+        }
+    }
+    else if (!step.x)
+    {
+        if (zero_die(2))
+        {
+            pref_pos[1] = pref_pos[0] + West;
+            pref_pos[2] = pref_pos[0] + East;
+        }
+        else
+        {
+            pref_pos[2] = pref_pos[0] + West;
+            pref_pos[1] = pref_pos[0] + East;
+        }
+    }
+    else
+    {
+        pref_pos[1] = pref_pos[0];
+        pref_pos[2] = pref_pos[0];
+        if (zero_die(2))
+        {
+            pref_pos[1].x = loc.x;
+            pref_pos[2].y = loc.y;
+        }
+        else
+        {
+            pref_pos[2].x = loc.x;
+            pref_pos[1].y = loc.y;
+        }
+    }
+}
+
+/* XXX build_ai_cells()
+ *
+ * Populate array of eight AI cell descriptors.
+ */
+
+static void build_ai_cells(struct ai_cell *cells, Coord loc)
+{
+    cells[0].score = 0;
+    cells[1].score = 0;
+    cells[2].score = 0;
+    cells[3].score = 0;
+    cells[4].score = 0;
+    cells[5].score = 0;
+    cells[6].score = 0;
+    cells[7].score = 0;
+    cells[0].pos = loc + Northwest;
+    cells[1].pos = loc + North;
+    cells[2].pos = loc + Northeast;
+    cells[3].pos = loc + East;
+    cells[4].pos = loc + West;
+    cells[5].pos = loc + Southwest;
+    cells[6].pos = loc + South;
+    cells[7].pos = loc + Southeast;
+}
+
+/* XXX ai_cell_compare()
+ *
+ * Find relative range of cell compared to monster's current range.
+ */
+static Comparison ai_cell_compare(struct ai_cell *cell, Offset delta)
+{
+    int pointrange = delta.len_cheb();
+    int cellrange = cell->delta.len_cheb();
+    if (cellrange < pointrange)
+    {
+        return Lesser;
+    }
+    else if (cellrange > pointrange)
+    {
+        return Greater;
+    }
+    return Equal;
+}
+
+/* XXX get_dodger_prefs()
+ *
+ * Get preferences for "smart" monsters without ranged attacks.
+ */
+static void get_dodger_prefs(Coord loc, Offset delta, Coord *pref_pos)
+{
+    /* "Dodgers" are smart melee-only monsters. They will try to avoid
+     * the cardinals as they close, and will even flow around other
+     * monsters to try to get to the player. 
+     *
+     * This function does *all* the work of selecting a destination square
+     * for a smart melee-only monster; accordingly, only pref_y[0] and
+     * pref_x[0] get set.
+     */
+    struct ai_cell ai_cells[8];
+    int i;
+    Offset absdelta = myabs(delta);
+    int highest_score = AI_REALLY_HATE;
+    int tryct;
+    Mon *mptr = monsters + lvl.mon_at(loc);
+    *pref_pos = loc;
+    build_ai_cells(ai_cells, loc);
+    /* Build the local dx/dy arrays. */
+    for (i = 0; i < 8; i++)
+    {
+        ai_cells[i].delta = u.pos.delta(ai_cells[i].pos);
+        /* Scoring factors:
+         * Square on cardinal: -2.
+         * Square closer to player: +1.
+         * Square further from player: -3.
+         * Square next to player: +10.
+         *
+         * Yes, this monster prizes not opening the range more than
+         * it prizes staying off the cardinal; this is intentional.
+         * It also prizes staying off the cardinal more than actually
+         * closing. When I add more AI state to the monster structure,
+         * this will change.
+         */
+        if (!mptr->can_pass(ai_cells[i].pos))
+        {
+            /* Square impassable. Set score WAY out of bounds
+             * and continue. */
+            ai_cells[i].score = AI_REALLY_HATE;
+            continue;
+        }
+        /* Cardinality */
+        if (ai_cells[i].delta.rcardinal())
+        {
+            /* Score this square down for being on a cardinal. */
+            ai_cells[i].score -= 2;
+        }
+        /* Range */
+        if (ai_cells[i].delta.len_cheb() < 2)
+        {
+            /* Score upward a *lot* for being adjacent to player */
+            ai_cells[i].score += 10;
+        }
+        switch (ai_cell_compare(ai_cells + i, delta))
+        {
+        case Greater:
+            ai_cells[i].score -= 3;
+            break;
+        case Lesser:
+            ai_cells[i].score += 1;
+            break;
+        default:
+            break;
+        }
+        if (ai_cells[i].score > highest_score)
+        {
+            highest_score = ai_cells[i].score;
+        }
+    }
+    if (highest_score == AI_REALLY_HATE)
+    {
+        /* No good targets. */
+        return;
+    }
+    for (tryct = 0; tryct < 32; tryct++)
+    {
+        i = zero_die(8);
+        if (ai_cells[i].score == highest_score)
+        {
+            *pref_pos = ai_cells[i].pos;
+            break;
+        }
+    }
+    return;
+}
+
+void select_space(Coord *pc, Offset delta, int selection_mode)
+{
+    Coord ai_pos[3];
+    Offset absdelta = myabs(delta);
+    Coord c;
+    Offset step = mysign(delta);
+    Mon *mptr = monsters + lvl.mon_at(*pc);
+    switch (selection_mode)
+    {
+    case AI_charger:
+        /* Simple convergence */
+        get_naive_prefs(*pc, delta, ai_pos);
+        if (mptr->can_pass(ai_pos[0]))
+        {
+            c = ai_pos[0];
+        }
+        else if (mptr->can_pass(ai_pos[1]))
+        {
+            c = ai_pos[1];
+        }
+        else if (mptr->can_pass(ai_pos[2]))
+        {
+            c = ai_pos[2];
+        }
+        else
+        {
+            c = *pc;
+        }
+        break;
+    case AI_archer:
+        /* Converge to cardinal */
+        if (delta.rcardinal())
+        {
+            /* On cardinal. Stay there if we can. But close anyway. */
+            c = *pc + step;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.x = pc->x;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.y = pc->y;
+            c.x = pc->x + step.x;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+        }
+        else if ((absdelta.y == 1) || ((absdelta.x > 1) && (absdelta.y > absdelta.x)))
+        {
+            /* One step in ydir off an EW cardinal, or further
+             * off cardinal in y than in x */
+            c.y = pc->y + step.y;
+            c.x = pc->x;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.x = pc->x + step.x;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.y = pc->y;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+        }
+        else if ((absdelta.x == 1) || ((absdelta.y > 1) && (absdelta.x > absdelta.y)))
+        {
+            /* One step in xdir off an NS cardinal, with adx > ady */
+            c.x = pc->x + step.x;
+            c.y = pc->y;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.y = pc->y + step.y;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+            c.x = pc->x;
+            if (mptr->can_pass(c))
+            {
+                break;
+            }
+        }
+        c = *pc;
+        break;
+    case AI_dodger:
+        get_dodger_prefs(*pc, delta, ai_pos);
+        c = ai_pos[0];
+        break;
+    case AI_drunk:
+        /* "Drunk" monster i.e. monster moving while it doesn't know
+         * how to find you. */
+        get_drunk_prefs(*pc, delta, ai_pos);
+        if (mptr->can_pass(ai_pos[0]))
+        {
+            c = ai_pos[0];
+        }
+        else if (mptr->can_pass(ai_pos[1]))
+        {
+            c = ai_pos[1];
+        }
+        else if (mptr->can_pass(ai_pos[2]))
+        {
+            c = ai_pos[2];
+        }
+        else
+        {
+            c = *pc;
+        }
+        break;
+    case AI_seeker:
+        /* "Seeking" monster i.e. monster moving while it can't see
+         * you, but thinks it knows where you are. This AI isn't
+         * great, but it'll do for now. */
+        get_seeking_prefs(*pc, delta, ai_pos);
+        c = ai_pos[0];
+        break;
+    case AI_chaser:
+        /* "chase" AI i.e. pursue your last known position. */
+        get_chase_prefs(mptr - monsters, ai_pos);
+        c = ai_pos[0];
+        break;
+    }
+    *pc = c;
+}
+
+void mon_acts(int mon)
+{
+    Mon *mptr;
+    Offset delta;
+    Coord c;
+    Offset step;
+    bool meleerange;
+    bool oncardinal;
+    bool special_used = false;
+    mptr = monsters + mon;
+    /* dy,dx == direction monster must go to reach you. */
+    c = mptr->pos;
+    delta = u.pos.delta(c);
+    step = mysign(delta);
+    meleerange = delta.len_cheb() < 2;
+    oncardinal = delta.rcardinal();
+    if (delta.len_cheb() == 0)
+    {
+        debug_misplaced_monster();
+        mptr->used = false;
+        lvl.set_mon_at(c, NO_MON);
+        return;
+    }
+    if (lvl.mon_at(c) != mon)
+    {
+        debug_misplaced_monster();
+        mptr->used = false;
+        if (lvl.mon_at(c) != NO_MON)
+        {
+            monsters[lvl.mon_at(c)].used = false;
+            lvl.set_mon_at(c, NO_MON);
+        }
+        return;
+    }
+    if (mon_visible(mon))
+    {
+        mptr->awake = true;
+    }
+    if (!mptr->awake)
+    {
+        return;
+    }
+    else if (meleerange)
+    {
+        /* Adjacent! Attack you.  Demons have a 1 in 10 chance of attempting to
+         * summon another demon instead of attacking you, if that individual
+         * demon has not summoned in the last 100 ticks. */
+        if ((mptr->mon_id == PM_DEMON) && (mptr->next_summon < game_tick) &&
+            !zero_die(10))
+        {
+            summon_demon_near(c);
+            mptr->next_summon = game_tick + 100;
+            special_used = true;
+        }
+        else if (pmon_is_magician(mptr->mon_id))
+        {
+            special_used = mon_use_sorcery(mon);
+        }
+        if (!special_used)
+        {
+            mhitu(mon, DT_PHYS);
+        }
+    }
+    else if (mon_visible(mon))
+    {
+        /* In sight. */
+        if (pmon_is_magician(mptr->mon_id))
+        {
+            /* Two-thirds of the time, try to use sorcery. */
+            if (zero_die(6) < 4)
+            {
+                special_used = mon_use_sorcery(mon);
+            }
+            if (special_used)
+            {
+                return;
+            }
+            /* Didn't, or couldn't, use sorcery; converge
+             * as if an archer. */
+            select_space(&c, delta, AI_archer);
+        }
+        else if (pmon_is_archer(mptr->mon_id))
+        {
+            if (oncardinal && (zero_die(6) < 3))
+            {
+                special_used = 1;
+                mshootu(mon);
+            }
+            if (special_used)
+            {
+                return;
+            }
+            select_space(&c, delta, AI_archer);
+        }
+        else if (pmon_is_smart(mptr->mon_id))
+        {
+            select_space(&c, delta, AI_dodger);
+        }
+        else /* pmon_is_stupid() */
+        {
+            select_space(&c, delta, AI_charger);
+        }
+        if (c != mptr->pos)
+        {
+            /* We decided to move; move! */
+            move_mon(mon, c);
+        }
+    }
+    else
+    {
+        /* Out of LOS, but awake. Stupid monsters move "drunkenly"; smart
+         * monsters (may) seek you out. */
+        if (pmon_is_magician(mptr->mon_id))
+        {
+            /* Magicians may have spells that are used when
+             * you are out of sight.  For example, some magicians
+             * may teleport themselves to your vicinity. */
+            special_used = mon_use_sorcery(mon);
+        }
+        if (special_used)
+        {
+            return;
+        }
+        if (pmon_is_smart(mptr->mon_id))
+        {
+            select_space(&c, delta, AI_seeker);
+        }
+        else if (pmon_is_stupid(mptr->mon_id) || (mptr->ai_lastpos == Nowhere))
+        {
+            select_space(&c, delta, AI_drunk);
+        }
+        else
+        {
+            select_space(&c, delta, AI_chaser);
+        }
+        if (c != mptr->pos)
+        {
+            /* We decided to move; move! */
+            move_mon(mon, c);
+        }
+    }
+    /* Let's get the data again. */
+    if (u.pos.dist_cheb(c) <= MAX_FOV_RADIUS)
+    {
+        mptr->ai_lastpos = u.pos;
+    }
+    else
+    {
+        mptr->ai_lastpos = Nowhere;
+    }
+}
+
+/* mon2.c */
+// vim:cindent
diff --git a/monsters.cc b/monsters.cc
new file mode 100644 (file)
index 0000000..a3d8ed4
--- /dev/null
@@ -0,0 +1,600 @@
+/*! \file monsters.cc
+ *  \brief Monster-related functions
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define MONSTERS_CC
+#include "obumbrata.hh"
+#include "monsters.hh"
+#include "objects.hh"
+
+Mon monsters[100];
+static int reject_mon(int pm);
+
+/*! \brief Summon some monsters
+ *
+ *  \return Number of monsters summoned
+ *  \param how_many Maximum number of monsters to summon
+ */
+int summoning(Coord c, int how_many)
+{
+    int i;
+    Offset delta;
+    Coord testpos;
+    int tryct;
+    int mon;
+    int created = 0;
+    int pmon;
+    for (i = 0; i < how_many; i++)
+    {
+        for (tryct = 0; tryct < 20; tryct++)
+        {
+            delta = random_step();
+            testpos = c + delta;
+            if ((lvl.terrain_at(testpos) == FLOOR) &&
+                (lvl.mon_at(testpos) == NO_MON) &&
+                (testpos != u.pos))
+            {
+                pmon = get_random_pmon();
+                if (pmon_is_magician(pmon))
+                {
+                    /* Never summon magicians! */
+                    continue;
+                }
+                mon = create_mon(NO_PMON, testpos);
+                if (mon != NO_MON)
+                {
+                    created++;
+                    break;
+                }
+            }
+        }
+    }
+    return created;
+}
+
+int ood(int power, int ratio)
+{
+    return (depth - power + ratio - 1) / ratio;
+}
+
+int get_random_pmon(void)
+{
+    int tryct;
+    int pm;
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+        pm = zero_die(NUM_OF_PERMONS);
+        if (reject_mon(pm))
+        {
+            pm = NO_PMON;
+            continue;
+        }
+        break;
+    }
+    return pm;
+}
+
+int create_mon(int pm_idx, Coord c)
+{
+    int mon;
+    if (lvl.mon_at(c) != NO_MON)
+    {
+        debug_create_mon_occupied(c);
+        return NO_MON;
+    }
+    if (pm_idx == NO_PMON)
+    {
+        pm_idx = get_random_pmon();
+        if (pm_idx == NO_PMON)
+        {
+            debug_pmon_select_failed();
+            return NO_MON;
+        }
+    }
+    for (mon = 0; mon < 100; mon++)
+    {
+        if (!monsters[mon].used)
+        {
+            monsters[mon].mon_id = pm_idx;
+            monsters[mon].used = true;
+            monsters[mon].pos = c;
+            monsters[mon].ai_lastpos = Nowhere;
+            monsters[mon].hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
+            monsters[mon].hpcur = monsters[mon].hpmax;
+            monsters[mon].mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
+            monsters[mon].defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
+            monsters[mon].mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
+            if (permons[pm_idx].rdam != NO_ATK)
+            {
+                monsters[mon].rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
+                monsters[mon].rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
+            }
+            else
+            {
+                monsters[mon].rtohit = NO_ATK;
+                monsters[mon].rdam = NO_ATK;
+            }
+            monsters[mon].awake = false;
+            lvl.set_mon_at(c, mon);
+            if (mon_visible(mon))
+            {
+                notify_new_mon_at(c, mon);
+            }
+            return mon;
+        }
+    }
+    return NO_MON;
+}
+
+void death_drop(int mon)
+{
+    Mon *mptr = monsters + mon;
+    int pm = mptr->mon_id;
+    Coord c = mptr->pos;
+    Offset delta;
+    int tryct = 0;
+    while (((lvl.obj_at(c) != NO_OBJ) || (lvl.terrain_at(c) != FLOOR)) &&
+           (tryct < 100))
+    {
+        delta = random_step();
+        tryct++;
+        c += delta;
+    }
+    if (tryct >= 100)
+    {
+        return;
+    }
+    switch (pm)
+    {
+    case PM_GOBLIN:
+        if (!zero_die(4))
+        {
+            create_obj(PO_DAGGER, 1, 0, c);
+        }
+        break;
+    case PM_THUG:
+    case PM_GOON:
+        if (!zero_die(4))
+        {
+            create_obj(PO_MACE, 1, 0, c);
+        }
+        else if (!zero_die(3))
+        {
+            create_obj(PO_LEATHER_ARMOUR, 1, 0, c);
+        }
+        break;
+    case PM_HUNTER:
+        if (!zero_die(6))
+        {
+            create_obj(PO_BOW, 1, 0, c);
+        }
+        break;
+    case PM_DUELLIST:
+        if (!zero_die(6))
+        {
+            create_obj(PO_LONG_SWORD, 1, 0, c);
+        }
+        break;
+    case PM_WIZARD:
+        if (!zero_die(4))
+        {
+            create_obj_class(POCLASS_SCROLL, 1, 0, c);
+        }
+        else if (!zero_die(3))
+        {
+            create_obj_class(POCLASS_POTION, 1, 0, c);
+        }
+        break;
+    case PM_WARLORD:
+        if (!zero_die(3))
+        {
+            create_obj(PO_RUNESWORD, 1, 0, c);
+        }
+        break;
+    case PM_DEMON:
+        if (!zero_die(100))
+        {
+            create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
+        }
+        break;
+    case PM_DEFILER:
+        if (!zero_die(50))
+        {
+            create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+bool Mon::can_pass(Coord c) const
+{
+    Terrain terr;
+    if (!lvl.in_bounds(c))
+    {
+        return false;
+    }
+    if (lvl.mon_at(c) != NO_MON)
+    {
+        return false;
+    }
+    if (c == u.pos)
+    {
+        /* Sanity check! */
+        return false;
+    }
+    if (is_ethereal())
+    {
+        return true;
+    }
+    terr = lvl.terrain_at(c);
+    if (terrain_blocks_beings(terr))
+    {
+        /* Let's *not* stuff all the wall types into a switch, eh? */
+        return false;
+    }
+    /* Keep the switch, so that we can maintain a convenient distinction
+     * between floor hazards and volumetric hazards. */
+    switch (terr)
+    {
+    case LAVA:
+        if (!can_fly() && !resists(DT_FIRE))
+        {
+            return false;
+        }
+        break;
+    case WATER:
+        if (!can_fly() && !resists(DT_DROWNING))
+        {
+            return false;
+        }
+    default:
+        break;
+    }
+    return true;
+}
+
+void heal_mon(int mon, int amount, int cansee)
+{
+    if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
+    {
+        amount = monsters[mon].hpmax - monsters[mon].hpcur;
+    }
+    if (amount > 0)
+    {
+        if (cansee)
+        {
+            notify_mon_healed(mon);
+        }
+        monsters[mon].hpcur += amount;
+    }
+}
+
+void unplace_mon(int mon)
+{
+    lvl.set_mon_at(monsters[mon].pos, NO_MON);
+    monsters[mon].used = false;
+}
+
+/*! \brief Handle the death of a monster
+ *
+ * \todo Support special effects on monster death
+ */
+void kill_mon(int mon, bool by_you)
+{
+    death_drop(mon); // phat lewt!
+    monsters[mon].hpcur = -1; // Set HP to -1 so nothing will think it's alive
+    unplace_mon(mon); // cleanup
+    if (by_you)
+    {
+        notify_player_killed_mon(mon);
+        gain_experience(permons[monsters[mon].mon_id].exp);
+    }
+    else if (mon_visible(mon))
+    {
+        notify_mon_dies(mon);
+    }
+}
+
+void damage_mon(int mon, int amount, bool by_you)
+{
+    Mon *mptr;
+    mptr = monsters + mon;
+    if (amount >= mptr->hpcur)
+    {
+        kill_mon(mon, by_you);
+    }
+    else
+    {
+        mptr->hpcur -= amount;
+    }
+}
+
+int reject_mon(int pm)
+{
+    if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
+    {
+        return 1;
+    }
+    return 0;
+}
+
+Pass_fail teleport_mon_to_you(int mon)
+{
+    int tryct;
+    Offset delta;
+    Coord c;
+    int success = 0;
+    Mon *mptr = monsters + mon;
+    Coord oldpos = mptr->pos;
+    for (tryct = 0; tryct < 40; tryct++)
+    {
+        delta = random_step();
+        c = u.pos + delta;
+        if (mptr->can_pass(c))
+        {
+            success = 1;
+            break;
+        }
+    }
+    if (success)
+    {
+        reloc_mon(mon, c);
+        notify_mon_appears(mon);
+        return You_pass;
+    }
+    return You_fail;
+}
+
+Pass_fail teleport_mon(int mon)
+{
+    Pass_fail rval = You_fail;
+    int cell_try;
+    Coord c;
+    for (cell_try = 0; cell_try < 200; cell_try++)
+    {
+        c = lvl.random_point(1);
+        if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos))
+        {
+            reloc_mon(mon, c);
+            rval = You_pass;
+            break;
+        }
+    }
+    return rval;
+}
+
+int knockback_mon(int mon, Offset step, bool cansee, bool by_you)
+{
+    /* 0 = blocked, 1 = knocked, 2 = killed */
+    Mon *mptr = monsters + mon;
+    Coord c = mptr->pos + step;
+    Coord savedpos = mptr->pos;
+    Terrain terr = lvl.terrain_at(c);
+
+    if (mptr->resists(DT_KNOCKBACK))
+    {
+        if (cansee)
+        {
+            notify_knockback_mon_resisted(mon);
+        }
+        return 0;
+    }
+    if (terrain_blocks_beings(terr))
+    {
+        if (cansee)
+        {
+            notify_knockback_mon_blocked(mon);
+        }
+        return 0;
+    }
+    reloc_mon(mon, c);
+    switch (terr)
+    {
+    case LAVA:
+        if (mptr->can_fly())
+        {
+            if (cansee)
+            {
+                notify_knockback_mon_hover_lava(mon);
+            }
+        }
+        else
+        {
+            if (cansee)
+            {
+                notify_knockback_mon_immersed_lava(mon);
+            }
+            else
+            {
+                notify_knockback_mon_lava_offscreen();
+            }
+            if (!mptr->resists(DT_FIRE))
+            {
+                kill_mon(mon, by_you);
+                return 2;
+            }
+        }
+        break;
+    case WATER:
+        if (mptr->can_fly())
+        {
+            if (cansee)
+            {
+                notify_knockback_mon_hover_water(mon);
+            }
+        }
+        else
+        {
+            if (cansee)
+            {
+                notify_knockback_mon_immersed_water(mon);
+            }
+            else
+            {
+                notify_knockback_mon_water_offscreen();
+            }
+            if (!mptr->resists(DT_DROWNING))
+            {
+                kill_mon(mon, by_you);
+                return 2;
+            }
+        }
+        break;
+    default:
+        break;
+    }
+    return 1;
+}
+
+void reloc_mon(int mon, Coord newpos)
+{
+    Mon *mptr = monsters + mon;
+    lvl.set_mon_at(mptr->pos, NO_MON);
+    notify_new_mon_at(mptr->pos, NO_MON);
+    mptr->pos = newpos;
+    lvl.set_mon_at(mptr->pos, mon);
+    notify_new_mon_at(mptr->pos, mon);
+}
+
+void move_mon(int mon, Coord c)
+{
+    Mon *mptr;
+    mptr = monsters + mon;
+    if (!mptr->can_pass(c))
+    {
+        debug_mon_invalid_move(mon, c);
+        return;
+    }
+    if (lvl.mon_at(mptr->pos) != mon)
+    {
+        debug_misplaced_monster();
+        return;
+    }
+    reloc_mon(mon, c);
+}
+
+void summon_demon_near(Coord c)
+{
+    Coord c2 = c + random_step();
+    int mon;
+    if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
+        (c2 != u.pos))
+    {
+        mon = create_mon(PM_DEMON, c2);
+        notify_summon_demon(mon);
+    }
+}
+
+bool mon_visible(int mon)
+{
+    Offset delta;
+    if (monsters[mon].used == 0)
+    {
+        return false;
+    }
+    delta = monsters[mon].pos.delta(u.pos);
+    if (delta.len_cheb() <= MAX_FOV_RADIUS)
+    {
+        return (player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x]) & Visflag_central;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+void update_mon(int mon)
+{
+    int cansee;
+    if (monsters[mon].hpcur < monsters[mon].hpmax)
+    {
+        cansee = mon_visible(mon);
+        // TODO modify regen handling to use flags/fields instead of switching on mon_id
+        switch (monsters[mon].mon_id)
+        {
+        case PM_TROLL:
+            if (!(game_tick % 10))
+            {
+                if (cansee)
+                {
+                    notify_mon_regenerates(mon);
+                }
+                heal_mon(mon, one_die(3) + 3, 0);
+            }
+            break;
+
+        case PM_ZOMBIE:
+            /* Zombies don't recover from their injuries. */
+            break;
+
+        default:
+            if (!(game_tick % 20))
+            {
+                heal_mon(mon, 1, cansee);
+            }
+            break;
+        }
+    }
+}
+
+bool Mon::resists(Damtyp dt) const
+{
+    switch (dt)
+    {
+    case DT_COLD:
+        return pmon_resists_cold(mon_id);
+    case DT_FIRE:
+        return pmon_resists_fire(mon_id);
+    case DT_POISON:
+        return pmon_resists_poison(mon_id);
+    case DT_NECRO:
+        return pmon_resists_necro(mon_id);
+    case DT_ELEC:
+        return pmon_resists_elec(mon_id);
+    case DT_KNOCKBACK:
+        return pmon_resists_knockback(mon_id);
+    case DT_DROWNING:
+        return pmon_resists_drowning(mon_id);
+    default:
+        return false;
+    }
+}
+
+bool Mon::is_ethereal(void) const
+{
+    return pmon_is_ethereal(mon_id);
+}
+
+bool Mon::can_fly(void) const
+{
+    return pmon_can_fly(mon_id);
+}
+
+/* monsters.c */
+// vim:cindent
diff --git a/monsters.hh b/monsters.hh
new file mode 100644 (file)
index 0000000..fc8fd38
--- /dev/null
@@ -0,0 +1,94 @@
+/*! \file monsters.hh
+ *  \brief Monster-related header
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MONSTERS_HH
+#define MONSTERS_HH
+
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#ifndef PERMON_HH
+#include "permon.hh"
+#endif
+
+/* XXX struct mon */
+#define MONSTERS_IN_PLAY 100
+class Mon {
+public:
+    int mon_id;
+    Coord pos;
+    Coord ai_lastpos;
+    bool used;
+    int hpmax;  /* Improved by OOD rating at 1:1. */
+    int hpcur;  /* <= 0 is dead. */
+    int mtohit; /* Improved by OOD rating at 1:3. */
+    int rtohit; /* Improved by OOD rating at 1:3. */
+    int defence;        /* Improved by OOD rating at 1:3. */
+    int mdam;   /* Improved by OOD rating at 1:5. */
+    int rdam;   /* Improved by OOD rating at 1:5. */
+    bool awake;
+    int next_summon;
+    bool resists(Damtyp dt) const;
+    bool can_pass(Coord c) const;
+    bool can_fly(void) const;
+    bool is_ethereal(void) const;
+};
+extern Mon monsters[MONSTERS_IN_PLAY];
+
+#define NO_MON (-1)
+#define PLAYER_MON (-2)
+
+/* XXX monsters.c data and funcs */
+extern void update_mon(int mon);
+extern void mon_acts(int mon);
+extern void death_drop(int mon);
+extern void print_mon_name(int mon, int article);
+extern void summon_demon_near(Coord  c);
+extern int create_mon(int pm_idx, Coord c);
+extern int summoning(Coord c, int how_many);
+extern int ood(int power, int ratio);
+extern int get_random_pmon(void);
+extern void damage_mon(int mon, int amount, bool by_you);
+extern bool mon_visible(int mon);
+extern int knockback_mon(int mon, Offset step, bool cansee, bool by_you);
+extern void move_mon(int mon, Coord c);
+extern void reloc_mon(int mon, Coord c);
+extern Pass_fail teleport_mon(int mon);       /* Randomly relocate monster. */
+extern Pass_fail teleport_mon_to_you(int mon);        /* Relocate monster to your vicinity. */
+extern void heal_mon(int mon, int amount, int cansee);
+extern void kill_mon(int mon, bool by_you);
+extern void unplace_mon(int mon);
+
+/* XXX mon2.c data and funcs */
+extern void select_space(int *py, int *px, int dy, int dx, int selection_mode);
+
+#endif
+
+/* monsters.hh */
+// vim:cindent:expandtab
diff --git a/notes.txt b/notes.txt
new file mode 100644 (file)
index 0000000..be90bf1
--- /dev/null
+++ b/notes.txt
@@ -0,0 +1,56 @@
+OBUMBRATA ET VELATA 
+===================
+Copyright 2014 Martin Read.
+
+This software is released under the terms of the BSD-style licence as provided
+in the file /usr/share/doc/apps/LICENSES/BSD on a Debian GNU/Linux system and
+delivered as "COPYING" in this source archive.
+
+Obumbrata et Velata (aka "obumbrata") is copyright 2014 Martin Read.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+INSTALLATION INSTRUCTIONS
+-------------------------
+On Linux (and other Unix-like systems, though I don't guarantee it working
+on any given Unix):
+   tar xzf obumbrata_1.0.0.tar.gz
+   cd obumbrata-1.0.0
+   make all
+   cp obumbrata /somewhere/in/your/PATH
+
+The obumbrata binary does not require any setgid or setuid privileges, and
+does not require root privilege to install unless you want to put it in a
+system directory (e.g. /usr/local/games), as it has no shared data and no
+shared playground.  Players are trusted to not savescum, because savescumming
+is cheating, and cheating sucks.
+
+ABOUT THE GAME
+--------------
+Vana salus.
+
+Obumbrata et Velata is Martin Read's entry in the 2014 Seven Day Roguelike
+Challenge.
+
+REPORTING BUGS
+--------------
+Report bugs by e-mail to martin (at) blackswordsonics dot com.
diff --git a/notify-local-tty.cc b/notify-local-tty.cc
new file mode 100644 (file)
index 0000000..0497681
--- /dev/null
@@ -0,0 +1,1171 @@
+/*! \file notify-local-tty.cc
+ *  \brief Notification backend 
+ *
+ *  This module provides an implementation of the API defined in notify.hh
+ *  which emits messages in English prose. It should, at some point, be
+ *  replaced with something that is more internationalized.
+ */
+
+/* Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define NOTIFY_LOCAL_TTY_CC
+#include "obumbrata.hh"
+#include "monsters.hh"
+#include "objects.hh"
+#include "player.hh"
+#include "map.hh"
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <langinfo.h>
+
+void notify_player_heal(int amount, int boost, bool loud)
+{
+    status_updated = true;
+    if (loud)
+    {
+        /* Tell the player how much better they feel. */
+        if (u.hpcur == u.hpmax)
+        {
+            print_msg("You feel great.\n");
+        }
+        else
+        {
+            print_msg("You feel %sbetter.\n", amount > 10 ? "much " : "");
+        }
+    }
+    else
+    {
+        /* Update the display. */
+        display_update();
+    }
+}
+
+void notify_death(Death d, char const *what)
+{
+    print_msg(Msg_prio::Warn, "\n\nTHOU ART SLAIN!\n\n");
+    switch (d)
+    {
+    case DEATH_KILLED:
+        print_msg("You were killed by %s.\n", what);
+        break;
+    case DEATH_KILLED_MON:
+        print_msg("You were killed by a nasty %s.\n", what);
+        break;
+    case DEATH_BODY:
+        print_msg("Your heart was stopped by %s.\n", what);
+        break;
+    case DEATH_AGILITY:
+        print_msg("Your nerves were destroyed by %s.\n", what); 
+        break;
+    case DEATH_LASH:
+        print_msg("You tasted the lash one time too many.\n");
+        break;
+    case DEATH_RIBBONS:
+        print_msg("You looked good in ribbons.\n");
+        break;
+    }
+    print_msg("Your game lasted %d ticks.\n", game_tick);
+    print_msg("You killed monsters worth %d experience.\n", u.experience);
+    press_enter();
+}
+
+/*! \brief The player has gained a level
+ */
+void notify_level_gain(void)
+{
+    status_updated = true;
+    print_msg("You gained a level!\n");
+}
+
+/*! \brief The player has gained experience points
+ */
+void notify_exp_gain(int amount)
+{
+    status_updated = true;
+    display_update();
+}
+
+/*! \brief The player has gained some Agility
+ *
+ *  \param Amount of Agility gained
+ */
+void notify_agility_gain(int amount)
+{
+    status_updated = true;
+    print_msg("You gained %d Agility.\n", amount);
+}
+
+/*! \brief The player has gained some Body.
+ *
+ *  \param Amount of Body gained.
+ */
+void notify_body_gain(int amount)
+{
+    status_updated = true;
+    print_msg("You gained %d Body.\n", amount);
+}
+
+void notify_hp_gain(int amount)
+{
+    status_updated = true;
+    print_msg("You gained %d hit points.\n", amount);
+}
+
+void notify_body_restore(void)
+{
+    status_updated = true;
+    print_msg("You feel less feeble.\n");
+}
+
+void notify_agility_restore(void)
+{
+    status_updated = true;
+    print_msg("You feel less clumsy.\n");
+}
+
+void notify_agility_drain(int amount)
+{
+    status_updated = true;
+    print_msg(Msg_prio::Alert, "You feel clumsy!\n");
+}
+
+void notify_body_drain(int amount)
+{
+    status_updated = true;
+    print_msg(Msg_prio::Alert, "You feel weaker!\n");
+}
+
+void notify_defence_recalc(void)
+{
+    status_updated = true;
+    display_update();
+}
+
+void notify_player_teleport(void)
+{
+    print_msg("You are whisked away!\n");
+}
+
+void notify_player_telefail(void)
+{
+    print_msg("You feel briefly dislocated.\n");
+}
+
+void notify_player_regen(void)
+{
+    print_msg("Your ring pulses soothingly.\n");
+}
+
+void notify_food_use(int amount, int hunger_severity)
+{
+    if (amount != 0)
+    {
+        status_updated = true;
+        display_update();
+    }
+    switch (hunger_severity)
+    {
+    case 0:
+    default:
+        break;
+    case 1:
+        print_msg("You are getting quite hungry.\n");
+        break;
+    case 2:
+        print_msg(Msg_prio::Warn, "You are feeling hunger pangs, and will recover more slowly from your injuries.\n");
+        break;
+    }
+}
+
+void notify_leadfoot_recovered(void)
+{
+    print_msg("You shed your feet of lead.\n");
+}
+
+void notify_armourmelt_recovered(void)
+{
+    print_msg("Your armour seems solid once more.\n");
+}
+
+void notify_wither_recovered(void)
+{
+    print_msg("Your limbs straighten.\n");
+}
+
+void notify_protection_lost(void)
+{
+    print_msg("You feel like you are no longer being helped.\n");
+}
+
+void notify_start_lavawalk(void)
+{
+    print_msg("You walk on the lava.\n");
+}
+
+void notify_blocked_lava(void)
+{
+    print_msg("The fierce heat of the molten rock repels you.\n");
+}
+
+void notify_start_waterwalk(void)
+{
+    print_msg("You walk on the water.\n");
+}
+
+void notify_blocked_water(void)
+{
+    print_msg("The idiot who raised you never taught you to swim.\n");
+}
+
+void notify_obj_at(Coord c)
+{
+    print_msg("You see here ");
+    print_obj_name(lvl.obj_at(c));
+    print_msg(".\n");
+}
+
+void debug_move_oob(Coord c)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempted move out of bounds (dest %d, %d)\n", c.y, c.x);
+}
+
+void notify_swing_bow(void)
+{
+    print_msg("You can't use that weapon in melee!\n");
+}
+
+void notify_cant_go(void)
+{
+    print_msg("You cannot go there.\n");
+}
+
+void notify_ascent_blocked(void)
+{
+    print_msg("A mysterious force prevents you climbing the stairs.\n");
+}
+
+void notify_wasted_gain(void)
+{
+    print_msg("You feel disappointed.\n");
+}
+
+void notify_summon_help(int mon, bool success)
+{
+    /* Do the summoning... */
+    print_mon_name(mon, 3);
+    print_msg(" calls for help...\n");
+    if (success)
+    {
+        print_msg("... and gets it.\n");
+    }
+    else
+    {
+        print_msg("... luckily for you, help wasn't listening.\n");
+    }
+}
+
+void notify_summon_demon(int mon)
+{
+    if (mon != NO_MON)
+    {
+        print_msg("Another demon appears!\n");
+    }
+    else
+    {
+        print_msg("You smell sulphurous fumes.\n");
+    }
+}
+
+void notify_mon_disappear(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" vanishes in a puff of smoke.\n");
+}
+
+void notify_start_armourmelt(void)
+{
+    status_updated = true;
+    print_msg("Your armour seems suddenly no stronger than dust!\n");
+}
+
+void notify_start_leadfoot(void)
+{
+    status_updated = true;
+    print_msg("Your feet feel like lead!\n");
+}
+
+void notify_start_withering(void)
+{
+    status_updated = true;
+    print_msg("Your limbs twist and wither!\n");
+}
+
+void notify_necrosmite_fail(void)
+{
+    print_msg("Darkness reaches towards you, but dissolves.\n");
+}
+
+void notify_necrosmite_hit(void)
+{
+    print_msg("Soul-chilling darkness engulfs you!\n");
+}
+
+void notify_moncurse_fail(void)
+{
+    print_msg("A malignant aura surrounds you briefly.\n");
+}
+
+void notify_hellfire_hit(bool resisted)
+{
+    print_msg("The fires of hell %s\n", resisted ? "lightly singe you." : "burn you!");
+}
+
+void notify_monster_cursing(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" points at you and curses horribly.\n");
+}
+
+void notify_no_attackee(void)
+{
+    print_msg("Nothing to attack.\n");
+}
+
+void notify_knockback_mon_pass(void)
+{
+    print_msg("Your foe is knocked backwards by the force of the shot.\n");
+}
+
+void notify_player_miss(int mon)
+{
+    print_msg("You miss ");
+    print_mon_name(mon, 1);
+    print_msg(".\n");
+}
+
+void notify_player_hit_mon(int mon)
+{
+    print_msg("You hit ");
+    print_mon_name(mon, 1);
+    print_msg(".\n");
+}
+
+void notify_player_combo_powatk(int mon)
+{
+    print_msg("You deal a powerful blow to ");
+    print_mon_name(mon, 1);
+    print_msg(".\n");
+}
+
+void notify_point_blank_warning(void)
+{
+    print_msg(Msg_prio::Alert, "Using a bow at such close quarters is awkward, and you are unlikely to hit your target.\n");
+}
+
+void notify_player_shot_terrain(int obj, Coord c)
+{
+    print_msg("Your %s hits the %s.\n", (objects[obj].obj_id == PO_CROSSBOW) ? "bolt" : "arrow", terrain_props[lvl.terrain_at(c)].name);
+}
+
+void notify_ring_boost(int mon, int pobj)
+{
+    switch (pobj)
+    {
+    case PO_FIRE_RING:
+        print_msg("Your ring burns ");
+        print_mon_name(mon, 1);
+        print_msg("!\n");
+        break;
+    case PO_VAMPIRE_RING:
+        print_msg("Your ring drains ");
+        print_mon_name(mon, 1);
+        print_msg("!\n");
+        break;
+    case PO_FROST_RING:
+        print_msg("Your ring freezes ");
+        print_mon_name(mon, 1);
+        print_msg("!\n");
+        break;
+    }
+}
+
+void notify_player_hurt_mon(int mon, int damage)
+{
+    print_msg("You do %d damage.\n", damage);
+}
+
+void notify_player_killed_mon(int mon)
+{
+    newsym(monsters[mon].pos);
+    print_msg("You kill ");
+    if (occupant_visible(monsters[mon].pos))
+    {
+        print_mon_name(mon, 1);
+    }
+    else
+    {
+        print_msg("something");
+    }
+    print_msg("!\n");
+}
+
+void notify_mon_dies(int mon)
+{
+    newsym(monsters[mon].pos);
+    if (occupant_visible(monsters[mon].pos))
+    {
+        print_mon_name(mon, 2);
+        print_msg(" dies.\n");
+    }
+}
+
+void notify_knockback_mon_resisted(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" wobbles slightly.\n");
+}
+
+void notify_knockback_mon_blocked(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" is slammed against the wall.\n");
+}
+
+void notify_knockback_mon_hover_lava(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" is hurled out over the lava.\n");
+}
+
+void notify_knockback_mon_hover_water(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" is hurled out over the water.\n");
+}
+
+void notify_knockback_mon_immersed_lava(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" tumbles into a pool of molten rock.\n");
+}
+
+void notify_knockback_mon_immersed_water(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" tumbles into the water.\n");
+}
+
+void notify_knockback_mon_lava_offscreen(void)
+{
+    print_msg("Splut!\n");
+}
+
+void notify_knockback_mon_water_offscreen(void)
+{
+    print_msg("Splash!\n");
+}
+
+void notify_mon_disappears(int mon, Coord oldpos)
+{
+}
+
+void notify_mon_appears(int mon)
+{
+    print_mon_name(mon, 2);
+    print_msg(" appears in a puff of smoke.\n");
+}
+
+void notify_mon_hit_armour(int mon)
+{
+    print_msg("Your armour deflects ");
+    print_mon_name(mon, 1);
+    print_msg("'s blow.\n");
+}
+
+void notify_mon_missed_player(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" misses you.\n");
+}
+
+void notify_mon_hit_player(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" hits you.\n");
+}
+
+void notify_mon_ranged_attack(int mon)
+{
+    int pm = monsters[mon].mon_id;
+    Damtyp dt = permons[pm].rdtyp;
+    print_mon_name(mon, 3);
+    if (dt == DT_PHYS)
+    {
+        print_msg(" %s at you!\n", permons[pm].shootverb);
+    }
+    else
+    {
+        print_msg(" %s %s at you!\n", permons[pm].shootverb, damtype_names[dt]);
+    }
+}
+
+void notify_mon_ranged_hit_mon(int er, int ee)
+{
+    // TODO make this message specific and non-sucky.
+    print_msg("It hits a bystander.\n");
+}
+
+void notify_mon_ranged_missed_player(int mon)
+{
+    print_msg("It misses you.\n");
+}
+
+void notify_mon_ranged_hit_player(int mon)
+{
+    print_msg("It hits you!\n");
+}
+
+
+void notify_no_flask_target(void)
+{
+    print_msg("That would be a waste; there's nobody to throw it at.\n");
+}
+
+void notify_magic_no_ring(void)
+{
+    print_msg("You are not wearing a ring.\n");
+}
+
+void notify_emanate_no_armour(void)
+{
+    print_msg("You are not wearing any armour.\n");
+}
+
+void notify_zap_no_weapon(void)
+{
+    print_msg("You have no weapon in hand.\n");
+}
+
+void notify_magic_powerless_ring(void)
+{
+    print_msg("Your current ring seems to have no magic powers to activate.\n");
+}
+
+void notify_emanate_powerless_armour(void)
+{
+    print_msg("Your current attire seems to have no magic powers to activate.\n");
+}
+
+void notify_zap_powerless_weapon(void)
+{
+    print_msg("Your current weapon seems to have no magic powers to activate.\n");
+}
+
+void notify_armour_equip(int obj)
+{
+    Permobj *pobj = permobjs + objects[obj].obj_id;
+    if (pobj->flags[0] & POF_NOTIFY_EQUIP)
+    {
+        switch (objects[u.armour].obj_id)
+        {
+        case PO_FOETID_VESTMENTS:
+            if (u.rotten())
+            {
+                print_msg("You garb yourself in the proper raiment of one devoted to decay.\n");
+            }
+            else
+            {
+                print_msg("Forcing down a wave of nausea, you dress yourself in the stinking rags.\n");
+            }
+            break;
+        case PO_LICHS_ROBE:
+            print_msg("A supernatural chill settles in your bones as you don your ancient finery.\n");
+            break;
+        case PO_INFERNITE_ARMOUR:
+            print_msg("The %s of clashing steel fills your ears as you armour yourself in Verant's handiwork.\n", u.martial() ? "glorious song" : "harsh cacophony");
+            break;
+        default:
+            print_msg(Msg_prio::Bug, "BUG: object '%s' has POF_NOTIFY_EQUIP defined but no special message written.\n", pobj->name);
+            break;
+        }
+    }
+    else
+    {
+        print_msg("Wearing ");
+        print_obj_name(u.armour);
+        print_msg(".\n");
+    }
+}
+
+void notify_armour_unequip(int obj)
+{
+    Permobj *pobj = permobjs + objects[obj].obj_id;
+    // TODO add unequip messages for NOTIFY_EQUIP armours.
+    if (pobj->flags[0] & POF_NOTIFY_EQUIP)
+    {
+        switch (objects[obj].obj_id)
+        {
+        case PO_LICHS_ROBE:
+            print_msg("The supernatural chill of the robes lingers in your bones even after you put them aside.\n");
+            break;
+        case PO_FOETID_VESTMENTS:
+            if (u.rotten())
+            {
+                print_msg("Reluctantly you put aside the proper raiment of one devoted to decay.\n");
+            }
+            else
+            {
+                print_msg("Even after putting the stinking rags aside, ou're not sure you'll ever feel clean again.\n");
+            }
+            break;
+        case PO_INFERNITE_ARMOUR:
+            print_msg("The sounds of clashing steel fade away as you put aside Verant's handiwork.\n");
+            break;
+        default:
+            print_msg(Msg_prio::Bug, "BUG: object '%s' has POF_NOTIFY_EQUIP defined but no special message written.\n", pobj->name);
+            break;
+        }
+    }
+    else
+    {
+        print_msg("You take off your %s.\n", (pobj->flags[0] & POF_DRESS) ? "dress" : "armour");
+    }
+}
+
+void notify_ring_equip(int obj)
+{
+    print_msg("You put on ");
+    print_obj_name(obj);
+    print_msg(".\n");
+}
+
+void notify_ring_unequip(int obj)
+{
+    print_msg("You remove your ring.\n");
+}
+
+void notify_lava_blocks_unequip(void)
+{
+    print_msg(Msg_prio::Warn, "That item is your only current source of fire resistance; setting it aside here would incinerate you.\n");
+}
+
+void notify_water_blocks_unequip(void)
+{
+    print_msg(Msg_prio::Warn, "Setting that item aside here would cause your death by drowning.\n");
+}
+
+void notify_player_touch_effect(Damtyp dt)
+{
+    switch (dt)
+    {
+    default:
+    case DT_PHYS:
+        break;
+    case DT_FIRE:
+        print_msg("You are engulfed in flames.\n");
+        break;
+    case DT_COLD:
+        print_msg("You are covered in frost.\n");
+        break;
+    case DT_NECRO:
+        print_msg("You feel your life force slipping away.\n");
+        break;
+    case DT_ELEC:
+        print_msg("Your muscles spasm agonizingly.\n");
+        break;
+    }
+}
+
+void notify_player_damage_taken(int amount)
+{
+    status_updated = true;
+    print_msg("You take %d damage.\n", amount);
+}
+
+void notify_player_ignore_damage(Damtyp dt)
+{
+    switch (dt)
+    {
+    case DT_FIRE:
+        print_msg("The feel a pleasant warmth.\n");
+        break;
+    case DT_COLD:
+        print_msg("You feel a pleasant chill.\n");
+        break;
+    case DT_ELEC:
+        print_msg("You feel a pleasant tingle.\n");
+        break;
+    case DT_NECRO:
+        print_msg("You feel no deader than before.\n");
+        break;
+    case DT_DROWNING:
+        print_msg("TODO: Find a good way to express not-drowning.\n");
+        break;
+    case DT_POISON:
+        print_msg("You feel vaguely queasy for a moment.\n");
+        break;
+    default:
+        print_msg(Msg_prio::Bug, "BUG: You shouldn't be resisting damage type %d\n", (int) dt);
+        break;
+    }
+}
+
+void notify_item_explodes_flames(int obj)
+{
+    print_msg("The %s explodes in flames!\n", permobjs[objects[obj].obj_id].name);
+}
+
+void notify_read_scroll_protection(void)
+{
+    print_msg("You feel like something is helping you.\n");
+}
+
+void notify_telering_activation(int specific)
+{
+    switch (specific)
+    {
+    case 0:
+        print_msg("You are too hungry to activate your ring's power.\n");
+        break;
+    case 1:
+        print_msg("You activate your ring's power of teleportation.\n");
+        break;
+    case 2:
+        print_msg(Msg_prio::Bug, "BUG: Some clown thinks teleport rings have a 'item isn't hungry' message.\n");
+        break;
+    case 3:
+        print_msg(Msg_prio::Bug, "BUG: Some clown thinks teleport rings have a 'item force-activates itself' message.\n");
+        break;
+    default:
+        print_msg(Msg_prio::Bug, "BUG: notify_telering_activation called with invalid subcode %d\n", specific);
+        break;
+    }
+}
+
+void notify_firestaff_activation(int specific)
+{
+    switch (specific)
+    {
+    case 0:
+        print_msg("You are too hungry to activate your staff's power.\n");
+        break;
+    case 1:
+        print_msg("You unleash the fiery powers of your staff!\n");
+        break;
+    case 2:
+        print_msg(Msg_prio::Bug, "BUG: Some clown thinks staves of fire have a 'item isn't hungry' message.\n");
+        break;
+    case 3:
+        print_msg(Msg_prio::Bug, "BUG: Some clown thinks staves of fire have a 'item force-activates itself' message.\n");
+        break;
+    default:
+        print_msg(Msg_prio::Bug, "BUG: notify_firestaff_activation called with invalid subcode %d\n", specific);
+        break;
+    }
+}
+
+void notify_fireitem_hit(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" is engulfed in roaring flames.\n");
+}
+
+void notify_lash_activation(int specific)
+{
+    switch (specific)
+    {
+    case 0:
+        print_msg("You are too hungry to willingly let the lash draw on your strength.\n");
+        break;
+    case 1:
+        print_msg("%s runs up your arm as the lash draws on your strength to repair itself.\n", u.sybaritic() ? "A delightful tingle" : "A pins-and-needles sensation");
+        break;
+    case 2:
+        print_msg("The lash is undamaged; it hungers not for your strength.\n");
+        break;
+    case 3:
+        print_msg("Pain explodes%s through your arm as the lash restores itself!\n", u.sybaritic() ? " delightfully" : "");
+        break;
+    default:
+        print_msg(Msg_prio::Bug, "BUG: notify_ribbon_activation called with invalid subcode %d\n", specific);
+        break;
+    }
+}
+
+void notify_ribbon_activation(int specific)
+{
+    switch (specific)
+    {
+    case 0:
+        print_msg("You are too hungry to willingly let the ribbons draw on your strength.\n");
+        break;
+    case 1:
+        print_msg("You gasp%s as the ribbons draw on your strength to repair themselves.\n", u.sybaritic() ? " in delight" : "");
+        break;
+    case 2:
+        print_msg("The ribbons are undamaged; they hunger not for your strength.\n");
+        break;
+    case 3:
+        print_msg("Pain explodes%s through your body as the lash restores itself!\n", u.sybaritic() ? " delightfully" : "");
+        break;
+    default:
+        print_msg(Msg_prio::Bug, "BUG: notify_ribbon_activation called with invalid subcode %d\n", specific);
+        break;
+    }
+}
+
+void notify_dress_shredded(void)
+{
+    print_msg("Your dress has been reduced to a tattered wreck.\n");
+}
+
+void notify_eat_food(bool ravenous)
+{
+    status_updated = true;
+    print_msg(ravenous ? "You ravenously devour your food!\n" : "You eat some food.\n");
+}
+
+void notify_ingest_spleen(void)
+{
+    status_updated = true;
+    print_msg("%s power suffuses your body as you devour the devil spleen.\n", u.corrupt() ? "Delicious" : "Vile");
+}
+
+void notify_quaff_potion_restoration(void)
+{
+    print_msg("This potion makes you feel warm all over.\n");
+}
+
+void notify_nothing_to_get(void)
+{
+    print_msg("Nothing to get.\n");
+}
+
+void notify_mon_healed(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" looks healthier.\n");
+}
+
+void notify_mon_regenerates(int mon)
+{
+    // TODO allow things that aren't trolls to be regenerators
+    print_msg("The troll regenerates.\n");
+}
+
+void notify_unwield(int obj, Noisiness noisy)
+{
+    if (noisy == Noise_std)
+    {
+        print_msg("Weapon unwielded.\n");
+    }
+}
+
+void notify_wield_weapon(int obj)
+{
+    print_msg("Wielding ");
+    print_obj_name(obj);
+    print_msg(".\n");
+}
+
+void notify_weapon_broke(int obj)
+{
+    print_msg(Msg_prio::Warn, "Your weapon breaks!\n");
+}
+
+void notify_armour_broke(int obj)
+{
+    print_msg(Msg_prio::Warn, "Your armour is ruined!\n");
+}
+
+void notify_drop_item(int obj)
+{
+    print_msg("You drop ");
+    print_obj_name(obj);
+    print_msg(".\n");
+}
+
+void notify_drop_blocked(void)
+{
+    print_msg(Msg_prio::Alert, "There is no room to drop that here.\n");
+}
+
+void notify_get_item(int from, int slot)
+{
+    if (from != NO_OBJ)
+    {
+        print_msg("You get ");
+        print_obj_name(from);
+        print_msg(".\nYou now have\n");
+        print_msg("%c) ", 'a' + slot);
+        print_obj_name(u.inventory[slot]);
+        print_msg("\n");
+    }
+    else
+    {
+        print_msg("You now have\n");
+        print_msg("%c) ", 'a' + slot);
+        print_obj_name(u.inventory[slot]);
+        print_msg("\n");
+    }
+}
+
+void notify_pack_full(void)
+{
+    print_msg(Msg_prio::Alert, "Your pack is full.\n");
+}
+
+/*! \brief Inform client of monster at specified position
+ *
+ * For local tty versions, this is just a call to newsym() and
+ * display_update().
+ *
+ * \param c Affected location
+ * \param mon Monster at that location.
+ */
+void notify_new_mon_at(Coord c, int mon)
+{
+    newsym(c);
+    display_update();
+}
+
+void notify_fov(void)
+{
+    touch_back_buffer();
+    display_update();
+}
+
+/*! \brief Inform the client that the game tick has advanced.
+ *
+ * For local tty versions, this is done by flushing any pending
+ * display updates.
+ */
+void notify_tick(void)
+{
+    display_update();
+}
+
+void notify_change_of_depth(void)
+{
+    status_updated = true;
+    print_msg("Welcome to level %d of the Abyss.\n", depth);
+}
+
+void notify_load_complete(void)
+{
+    status_updated = 1;
+    map_updated = 1;
+    hard_redraw = 1;
+    display_update();
+    print_msg("Game successfully restored.\n");
+}
+
+/* Debugging notifications */
+
+void debug_bad_monspell(int spell)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt by monster to cast bogus/unimplemented spell %d!\n", spell);
+}
+
+void debug_agility_gain(int amount)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt to cause negative agility gain %d\n", amount);
+}
+
+void debug_body_gain(int amount)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt to cause negative body gain %d\n", amount);
+}
+
+void debug_player_resists_phys(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Player resisting physical damage\n");
+}
+
+void debug_bad_damage_type(int dt)
+{
+    print_msg(Msg_prio::Bug, "BUG: bogus damage type %d.\n", dt);
+}
+
+void debug_throw_non_flask(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Throwing non-flask.\n");
+}
+
+void debug_unimplemented(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt to activate unimplemented feature\n");
+}
+
+void debug_wear_while_wearing(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Got to wear_armour while wearing armour\n");
+}
+
+void debug_wear_uncarried_armour(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt to wear uncarried armour\n");
+}
+
+void debug_remove_no_ring(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: calling remove_ring with no ring equipped.\n");
+}
+
+void debug_put_on_second_ring(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: calling put_on_ring with ring already equipped.\n");
+}
+
+void debug_put_on_uncarried_ring(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: attempting to put on uncarried ring.\n");
+}
+
+void debug_read_non_scroll(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: reading non-scroll\n");
+}
+
+void debug_eat_non_food(int obj)
+{
+    print_msg(Msg_prio::Bug, "BUG: attempt to eat non-food (%d)!\n", objects[obj].obj_id);
+}
+
+void debug_quaff_non_potion(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: quaffing non-potion\n");
+}
+
+void debug_ascend_non_stairs(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Received GO_UP_STAIRS while not on up stairs.\n");
+}
+
+void debug_descend_non_stairs(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Received GO_DOWN_STAIRS while not on down stairs.\n");
+}
+
+void debug_wizmode_violation(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Received wizmode command when not in wizmode.\n");
+}
+
+void debug_take_off_no_armour(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Taking off armour when no armour equipped.\n");
+}
+
+void debug_excavation_bailout(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Bailed out while excavating level!\n");
+}
+
+void debug_object_pool_exhausted(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Ran out of objects[].\n");
+}
+
+void debug_pobj_select_failed(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Failed to choose a permobj.\n");
+}
+
+void debug_misplaced_monster(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Misplaced monster detected.\n");
+}
+
+void debug_create_mon_occupied(Coord c)
+{
+    print_msg(Msg_prio::Bug, "BUG: Attempt to create mon at occupied space y=%d x=%d\n", c.y, c.x);
+}
+
+void debug_monster_pool_exhauseted(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Ran out of monsters[].\n");
+}
+
+void debug_pmon_select_failed(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: Failed to choose a permon.\n");
+}
+
+void debug_dump_write_failed(void)
+{
+    print_msg(Msg_prio::Alert, "NOTICE: Couldn't create dump file. Dump failed.\n");
+}
+
+void debug_unwield_nothing(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: got to player_unwield() with no weapon wielded - have a quiet word with your HCI module's author, please.\n");
+}
+
+void debug_unimplemented_activation(int po)
+{
+    print_msg(Msg_prio::Bug, "BUG: permobj %d should be activatable but has no activation handler.\n");
+}
+
+void debug_unimplemented_break_reaction(int po)
+{
+    print_msg(Msg_prio::Bug, "BUG: permobj %d should react to hitting durability 0 but has no break reaction handler.\n");
+}
+
+void debug_mon_invalid_move(int mon, Coord c)
+{
+    print_msg(Msg_prio::Bug, "BUG: monster %d attempted move to impassable location y=%d x=%d\n", mon, c.y, c.x);
+}
+
+void debug_unimplemented_radiance_order(int order)
+{
+    print_msg(Msg_prio::Bug, "FATAL: attempt to use unimplemented radiance evaluation order %d\n", order);
+}
+
+void debug_save_unlink_failed(void)
+{
+    print_msg(Msg_prio::Alert, "NOTICE: savefile unlink() failed - are you trying to savescum?\n");
+}
+
+void debug_attacking_with_wielded_nonweapon(void)
+{
+    print_msg(Msg_prio::Bug, "BUG: attacking with wielded nonweapon\n");
+}
+
+void debug_control_flow(char const *func, int line)
+{
+    print_msg(Msg_prio::Bug, "BUG: control flow passed through unexpected line %d in function %s\n", line, func);
+}
+
+void debug_arbitrary_string(char const *func, int line, char const *s)
+{
+    print_msg(Msg_prio::Bug, "DEBUG:%s:%d:%s\n", func, line, s);
+}
+
+/* notify-local-tty.cc */
+// vim:cindent
diff --git a/notify.hh b/notify.hh
new file mode 100644 (file)
index 0000000..c6778f5
--- /dev/null
+++ b/notify.hh
@@ -0,0 +1,224 @@
+/*! \file notify.hh
+ *  \brief Notification-related definitions for Obumbrata et Velata
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef NOTIFY_HH
+#define NOTIFY_HH
+
+// Player status changes
+void notify_death(Death d, char const *what);
+void notify_player_heal(int amount, int boost, bool loud);
+void notify_level_gain(void);
+void notify_exp_gain(int amount);
+void notify_agility_gain(int amount);
+void notify_body_gain(int amount);
+void notify_agility_restore(void);
+void notify_body_restore(void);
+void notify_agility_drain(int amount);
+void notify_body_drain(int amount);
+void notify_hp_gain(int amount);
+void notify_player_teleport(void);
+void notify_player_telefail(void);
+void notify_player_regen(void);
+void notify_leadfoot_recovered(void);
+void notify_armourmelt_recovered(void);
+void notify_wither_recovered(void);
+void notify_protection_lost(void);
+void notify_wasted_gain(void);
+void notify_defence_recalc(void);
+void notify_food_use(int food_use, int hunger_severity);
+
+// Resistance notifications
+
+// Player movement notifications
+void notify_start_lavawalk(void);
+void notify_blocked_lava(void);
+void notify_start_waterwalk(void);
+void notify_blocked_water(void);
+void notify_cant_go(void);
+
+// Unsorted notifications
+void notify_obj_at(Coord c);
+void notify_dress_shredded(void);
+void notify_mon_healed(int mon);
+void notify_mon_regenerates(int mon);
+void notify_mon_disappears(int mon, Coord oldpos);
+void notify_mon_appears(int mon);
+
+// Item manipulation notifications
+void notify_magic_no_ring(void);
+void notify_emanate_no_armour(void);
+void notify_zap_no_weapon(void);
+void notify_magic_powerless_ring(void);
+void notify_emanate_powerless_armour(void);
+void notify_zap_powerless_weapon(void);
+void notify_ring_equip(int obj);
+void notify_ring_unequip(int obj);
+void notify_armour_equip(int obj);
+void notify_armour_unequip(int obj);
+void notify_lava_blocks_unequip(void);
+void notify_water_blocks_unequip(void);
+void notify_nothing_to_get(void);
+void notify_unwield(int obj, Noisiness noisy);
+void notify_wield_weapon(int obj);
+void notify_weapon_broke(int obj);
+void notify_armour_broke(int obj);
+void notify_drop_item(int obj);
+void notify_drop_blocked(void);
+void notify_get_item(int from, int slot);
+void notify_pack_full(void);
+
+// Magic item use releated notifications
+void notify_read_scroll_protection(void);
+
+void notify_quaff_potion_restoration(void);
+
+void notify_ribbon_activation(int specific);
+void notify_lash_activation(int specific);
+void notify_firestaff_activation(int specific);
+void notify_fireitem_hit(int mon);
+void notify_telering_activation(int specific);
+
+void notify_item_explodes_flames(int obj);
+void notify_eat_food(bool ravenous);
+void notify_ingest_spleen(void);
+
+// combat notifications
+void notify_swing_bow(void);
+void notify_no_attackee(void);
+void notify_no_flask_target(void);
+void notify_player_miss(int mon);
+void notify_ring_boost(int mon, int pobj);
+void notify_player_hit_mon(int mon);
+void notify_player_hurt_mon(int mon, int damage);
+void notify_player_killed_mon(int mon);
+void notify_player_combo_powatk(int mon);
+void notify_knockback_mon_resisted(int mon);
+void notify_knockback_mon_blocked(int mon);
+void notify_knockback_mon_hover_lava(int mon);
+void notify_knockback_mon_hover_water(int mon);
+void notify_knockback_mon_immersed_lava(int mon);
+void notify_knockback_mon_immersed_water(int mon);
+void notify_knockback_mon_water_offscreen(void);
+void notify_knockback_mon_lava_offscreen(void);
+
+void notify_point_blank_warning(void);
+void notify_player_shot_terrain(int obj, Coord c);
+
+void notify_mon_hit_armour(int mon);
+void notify_mon_missed_player(int mon);
+void notify_mon_hit_player(int mon);
+void notify_mon_ranged_attack(int mon);
+void notify_mon_ranged_hit_player(int mon);
+void notify_mon_ranged_missed_player(int mon);
+void notify_mon_ranged_hit_mon(int er, int ee);
+void notify_mon_dies(int mon);
+void notify_new_mon_at(Coord c, int mon);
+
+void notify_player_damage_taken(int amount);
+void notify_player_touch_effect(Damtyp dt);
+void notify_player_ignore_damage(Damtyp dt);
+
+// Sorcery notifications
+void notify_summon_help(int mon, bool success);
+void notify_summon_demon(int mon);
+void notify_monster_cursing(int mon);
+void notify_mon_disappear(int mon);
+void notify_moncurse_fail(void);
+void notify_start_armourmelt(void);
+void notify_start_withering(void);
+void notify_start_leadfoot(void);
+void notify_necrosmite_fail(void);
+void notify_necrosmite_hit(void);
+void notify_hellfire_hit(bool resisted);
+
+// Miscellaneous notifications
+void notify_fov(void);
+void notify_tick(void);
+void notify_change_of_depth(void);
+void notify_load_complete(void);
+void notify_ascent_blocked(void);
+
+// Debugging notifications
+void debug_move_oob(Coord c);
+void debug_body_gain(int amount);
+void debug_agility_gain(int amount);
+void debug_bad_monspell(int spell);
+void debug_player_resists_phys(void);
+void debug_bad_damage_type(int dt);
+void debug_unimplemented(void);
+void debug_wear_while_wearing(void);
+void debug_wear_uncarried_armour(void);
+void debug_unwield_nothing(void);
+void debug_take_off_no_armour(void);
+void debug_remove_no_ring(void);
+void debug_put_on_second_ring(void);
+void debug_put_on_uncarried_ring(void);
+void debug_eat_non_food(int obj);
+void debug_read_non_scroll(void);
+void debug_quaff_non_potion(void);
+void debug_ascend_non_stairs(void);
+void debug_descend_non_stairs(void);
+void debug_wizmode_violation(void);
+void debug_excavation_bailout(void);
+void debug_object_pool_exhausted(void);
+void debug_pobj_select_failed(void);
+void debug_misplaced_monster(void);
+void debug_create_mon_occupied(Coord c);
+void debug_monster_pool_exhausted(void);
+void debug_mon_invalid_move(int mon, Coord c);
+void debug_pmon_select_failed(void);
+void debug_dump_write_failed(void);
+void debug_unimplemented_activation(int po);
+void debug_unimplemented_break_reaction(int po);
+void debug_unimplemented_radiance_order(int order);
+void debug_save_unlink_failed(void);
+void debug_attacking_with_wielded_nonweapon(void);
+void debug_control_flow(char const *func, int line);
+void debug_arbitrary_string(char const *func, int line, char const *s);
+
+bool query_wizmode_death(void);
+
+#define DEBUG_CONTROL_FLOW() debug_control_flow(__FUNCTION__, __LINE__)
+#define DEBUG_ARBITRARY(s) debug_arbitrary_string(__FUNCTION__, __LINE__, (s))
+
+// Provisional object designs
+class Notify_uattkm
+{
+public:
+    int mon;
+    bool hit;
+    int dmg;
+    bool ringflag;
+    int ring_id;
+    bool killed;
+};
+
+#endif
+
+/* notify.hh */
+// vim:cindent
diff --git a/objects.cc b/objects.cc
new file mode 100644 (file)
index 0000000..46788d3
--- /dev/null
@@ -0,0 +1,829 @@
+/* \file objects.cc
+ * \brief Object handling for Obumbrata et Velata
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define OBJECTS_CC
+#include "obumbrata.hh"
+#include "objects.hh"
+#include "monsters.hh"
+
+Obj objects[100];
+int get_random_pobj(void);
+
+char const ring_colours[20][16] = {
+    "gold", "ruby", "sapphire", "ivory", "coral",
+    "amethyst", "silver", "iron", "copper", "jade",
+    "haematite", "bone", "crystal", "platinum", "lead",
+    "diamond", "topaz", "emerald", "electrum", "smoky quartz"
+};
+
+char const scroll_titles[20][16] = {
+    "grem pho", "terra terrax", "phong", "ateh malkuth", "xixaxa",
+    "aku ryo tai san", "qoph shin tau", "ythek shri", "ia ia", "cthulhu fhtagn",
+    "arifech malex", "DOOM", "leme athem", "hail smkznrf", "rorrim foo",
+    "ad aerarium", "ligemrom", "asher ehiyeh", "YELLOW SIGN", "ELDER SIGN"
+};
+
+char const potion_colours[20][16] = {
+    "purple", "red", "blue", "green", "yellow",
+    "orange", "white", "black", "brown", "fizzy",
+    "grey", "silver", "gold", "shimmering", "glowing",
+    "navy blue", "bottle green", "amber", "lilac", "ivory"
+};
+
+Action_cost read_scroll(int obj)
+{
+    Obj *optr = objects + obj;
+    int i;
+    switch (optr->obj_id)
+    {
+    case PO_TELEPORT_SCROLL:
+        teleport_u();
+        break;
+    case PO_FIRE_SCROLL:
+        notify_item_explodes_flames(obj);
+        if (u.resistances[DT_FIRE])
+        {
+            notify_player_ignore_damage(DT_FIRE);
+        }
+        else
+        {
+            damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
+        }
+        for (i = 0; i < MONSTERS_IN_PLAY; ++i)
+        {
+            if (mon_visible(i))
+            {
+                if (!pmon_resists_fire(monsters[i].mon_id))
+                {
+                    notify_fireitem_hit(i);
+                    damage_mon(i, dice(4, 10), true);
+                }
+            }
+        }
+        break;
+    case PO_PROTECTION_SCROLL:
+        notify_read_scroll_protection();
+        if (!u.protection)
+        {
+            /* Do not prolong existing protection, only grant
+             * protection to the unprotected. */
+            u.protection = 100;
+        }
+        if (u.withering)
+        {
+            u.withering = 0;
+            notify_wither_recovered();
+        }
+        if (u.armourmelt)
+        {
+            u.armourmelt = 0;
+            notify_armourmelt_recovered();
+        }
+        if (u.leadfoot)
+        {
+            u.leadfoot = 0;
+            notify_leadfoot_recovered();
+        }
+        recalc_defence();
+        break;
+    default:
+        debug_read_non_scroll();
+        return Cost_none;
+    }
+    consume_obj(obj);
+    return Cost_std;
+}
+
+bool consume_obj(int obj)
+{
+    int i;
+    objects[obj].quan--;
+    if (objects[obj].quan == 0)
+    {
+        objects[obj].used = false;
+        if (objects[obj].with_you)
+        {
+            if (obj == u.armour)
+            {
+                u.armour = NO_OBJ;
+                recalc_defence();
+                notify_armour_broke(obj);
+            }
+            else if (obj == u.weapon)
+            {
+                u.weapon = NO_OBJ;
+                recalc_defence();
+                notify_weapon_broke(obj);
+            }
+            else if (obj == u.ring)
+            {
+                u.ring = NO_OBJ;
+                recalc_defence();
+            }
+            if (!objects[obj].used)
+            {
+                for (i = 0; i < 19; i++)
+                {
+                    if (u.inventory[i] == obj)
+                    {
+                        u.inventory[i] = NO_OBJ;
+                        break;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+Action_cost eat_food(int obj)
+{
+    Obj *optr = objects + obj;
+    bool ravenous = (u.food < 0);
+    u.food += 1500;
+    if (permobjs[optr->obj_id].poclass != POCLASS_FOOD)
+    {
+        debug_eat_non_food(obj);
+        return Cost_none;
+    }
+    if (optr->obj_id == PO_DEVIL_SPLEEN)
+    {
+        notify_ingest_spleen();
+        if (zero_die(2))
+        {
+            gain_body(1);
+        }
+        else
+        {
+            gain_agility(1);
+        }
+        // TODO add more tracking state to this item so we don't have to randomize the fell power choice
+        ++u.sympathy[zero_die(TOTAL_FELL_POWERS)];
+    }
+    else
+    {
+        notify_eat_food(ravenous);
+    }
+    consume_obj(obj);
+    return Cost_std;
+}
+
+Action_cost quaff_potion(int obj)
+{
+    Obj *optr = objects + obj;
+    switch (optr->obj_id)
+    {
+    case PO_BODY_POTION:
+        gain_body(1);
+        break;
+    case PO_AGILITY_POTION:
+        gain_agility(1);
+        break;
+    case PO_HEALING_POTION:
+        {
+            int healpercent = inc_flat(30, 50);
+            int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100;
+            heal_u(healamount, 1, 1);
+        }
+        break;
+    case PO_RESTORATION_POTION:
+        notify_quaff_potion_restoration();
+        if (u.bdam && ((!u.adam) || zero_die(2)))
+        {
+            u.bdam = 0;
+            notify_body_restore();
+        }
+        else if (u.adam)
+        {
+            u.adam = 0;
+            notify_agility_restore();
+        }
+        break;
+    default:
+        debug_quaff_non_potion();
+        return Cost_none;
+    }
+    consume_obj(obj);
+    return Cost_std;
+
+}
+
+void flavours_init(void)
+{
+    int colour_choices[10];
+    int i;
+    int j;
+    int done;
+    /* Flavoured items use "power" to track their flavour.  This is a
+     * gross and unforgiveable hack. */
+    /* Rings */
+    for (i = 0; i < 10;)
+    {
+        colour_choices[i] = zero_die(20);
+        done = 1;
+        for (j = 0; j < i; j++)
+        {
+            if (colour_choices[i] == colour_choices[j])
+            {
+                done = 0;
+            }
+        }
+        if (done)
+        {
+            i++;
+        }
+    }
+    permobjs[PO_REGENERATION_RING].power = colour_choices[0];
+    permobjs[PO_FIRE_RING].power = colour_choices[1];
+    permobjs[PO_VAMPIRE_RING].power = colour_choices[2];
+    permobjs[PO_FROST_RING].power = colour_choices[3];
+    permobjs[PO_TELEPORT_RING].power = colour_choices[4];
+    /* Scrolls */
+    for (i = 0; i < 10;)
+    {
+        colour_choices[i] = zero_die(20);
+        done = 1;
+        for (j = 0; j < i; j++)
+        {
+            if (colour_choices[i] == colour_choices[j])
+            {
+                done = 0;
+            }
+        }
+        if (done)
+        {
+            i++;
+        }
+    }
+    permobjs[PO_FIRE_SCROLL].power = colour_choices[0];
+    permobjs[PO_TELEPORT_SCROLL].power = colour_choices[1];
+    permobjs[PO_PROTECTION_SCROLL].power = colour_choices[2];
+    /* Potions */
+    for (i = 0; i < 10;)
+    {
+        colour_choices[i] = zero_die(20);
+        done = 1;
+        for (j = 0; j < i; j++)
+        {
+            if (colour_choices[i] == colour_choices[j])
+            {
+                done = 0;
+            }
+        }
+        if (done)
+        {
+            i++;
+        }
+    }
+    permobjs[PO_HEALING_POTION].power = colour_choices[0];
+    permobjs[PO_BODY_POTION].power = colour_choices[1];
+    permobjs[PO_AGILITY_POTION].power = colour_choices[2];
+    permobjs[PO_RESTORATION_POTION].power = colour_choices[3];
+}
+
+int get_first_free_obj(void)
+{
+    int obj;
+    for (obj = 0; obj < 100; obj++)
+    {
+        if (!objects[obj].used)
+        {
+            break;
+        }
+    }
+    return (obj == 100) ? NO_OBJ : obj;
+}
+
+int create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coord c)
+{
+    int po_idx;
+    int tryct;
+    int obj = get_first_free_obj();
+    if (obj == 100)
+    {
+        debug_object_pool_exhausted();
+        return NO_OBJ;
+    }
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+        switch (po_class)
+        {
+        case POCLASS_POTION:
+            po_idx = inc_flat(PO_FIRST_POTION, PO_LAST_POTION);
+            break;
+        case POCLASS_SCROLL:
+            po_idx = inc_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL);
+            break;
+        case POCLASS_RING:
+            po_idx = inc_flat(PO_FIRST_RING, PO_LAST_RING);
+            break;
+        default:
+            /* No getting armour/weapons by class... yet. */
+            return NO_OBJ;
+        }
+        if (zero_die(100) < permobjs[po_idx].rarity)
+        {
+            continue;
+        }
+        break;
+    }
+    objects[obj].obj_id = po_idx;
+    objects[obj].quan = quantity;
+    return obj;
+}
+
+int get_random_pobj(void)
+{
+    int tryct;
+    int po_idx;
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+        po_idx = zero_die(NUM_OF_PERMOBJS);
+        if (zero_die(100) < permobjs[po_idx].rarity)
+        {
+            po_idx = NO_POBJ;
+            continue;
+        }
+        /* v1.3: Do not permit generation of particularly powerful
+         * items (runeswords, mage armour, etc.) at shallow depths.
+         * (game balance fix) */
+        if (depth < permobjs[po_idx].depth)
+        {
+            po_idx = NO_POBJ;
+            continue;
+        }
+        break;
+    }
+    return po_idx;
+}
+
+int create_obj(int po_idx, int quantity, bool with_you, Coord c)
+{
+    int obj = get_first_free_obj();
+    if (obj == 100)
+    {
+        debug_object_pool_exhausted();
+        return NO_OBJ;
+    }
+    if (po_idx == NO_POBJ)
+    {
+        po_idx = get_random_pobj();
+        if (po_idx == NO_POBJ)
+        {
+            debug_pobj_select_failed();
+            return NO_OBJ;
+        }
+    }
+    objects[obj].obj_id = po_idx;
+    objects[obj].with_you = with_you;
+    objects[obj].used = true;
+    objects[obj].pos = c;
+    objects[obj].quan = quantity;
+    switch (permobjs[po_idx].poclass)
+    {
+    case POCLASS_WEAPON:
+    case POCLASS_ARMOUR:
+        /* Set durability of weapons and armour to a suitable value.
+         * 100 has been chosen for the moment, but this may need
+         * tuning. */
+        objects[obj].durability = OBJ_MAX_DUR;
+        break;
+    default:
+        break;
+    }
+    if (!objects[obj].with_you)
+    {
+        lvl.set_obj_at(c, obj);
+    }
+    return obj;
+}
+
+void sprint_obj_name(char *buf, int obj, int len)
+{
+    Obj *optr;
+    Permobj *poptr;
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (optr->quan > 1)
+    {
+        snprintf(buf, len, "%d %s", optr->quan, poptr->plural);
+    }
+    else if (po_is_stackable(optr->obj_id))
+    {
+        snprintf(buf, len, "1 %s", poptr->name);
+    }
+    else if ((poptr->poclass == POCLASS_WEAPON) ||
+             (poptr->poclass == POCLASS_ARMOUR))
+    {
+        snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+    }
+    else
+    {
+        snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+    }
+}
+
+void fprint_obj_name(FILE *fp, int obj)
+{
+    Obj *optr;
+    Permobj *poptr;
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (optr->quan > 1)
+    {
+        fprintf(fp, "%d %s", optr->quan, poptr->plural);
+    }
+    else if (po_is_stackable(optr->obj_id))
+    {
+        fprintf(fp, "1 %s", poptr->name);
+    }
+    else if ((poptr->poclass == POCLASS_WEAPON) ||
+             (poptr->poclass == POCLASS_ARMOUR))
+    {
+        fprintf(fp, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+    }
+    else
+    {
+        fprintf(fp, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+    }
+}
+
+Action_cost drop_obj(int inv_idx)
+{
+    Obj *optr;
+    optr = objects + u.inventory[inv_idx];
+    if (lvl.obj_at(u.pos) == NO_OBJ)
+    {
+        optr->pos = u.pos;
+        lvl.set_obj_at(u.pos, u.inventory[inv_idx]);
+        if (u.weapon == u.inventory[inv_idx])
+        {
+            u.weapon = NO_OBJ;
+        }
+        u.inventory[inv_idx] = NO_OBJ;
+        optr->with_you = false;
+        notify_drop_item(lvl.obj_at(u.pos));
+        return Cost_std;
+    }
+    else
+    {
+        notify_drop_blocked();
+        return Cost_none;
+    }
+}
+
+bool po_is_stackable(int po)
+{
+    switch (permobjs[po].poclass)
+    {
+    default:
+        return false;
+    case POCLASS_POTION:
+    case POCLASS_SCROLL:
+    case POCLASS_FOOD:
+        return true;
+    }
+}
+
+void attempt_pickup(void)
+{
+    int i;
+    int stackable;
+    stackable = po_is_stackable(objects[lvl.obj_at(u.pos)].obj_id);
+    if (stackable)
+    {
+        for (i = 0; i < 19; i++)
+        {
+            if ((objects[u.inventory[i]].obj_id == objects[lvl.obj_at(u.pos)].obj_id))
+            {
+                int stale_obj = lvl.obj_at(u.pos);
+                objects[u.inventory[i]].quan += objects[lvl.obj_at(u.pos)].quan;
+                objects[stale_obj].used = false;
+                lvl.set_obj_at(u.pos, NO_OBJ);
+                notify_get_item(stale_obj, i);
+                return;
+            }
+        }
+    }
+    for (i = 0; i < 19; i++)
+    {
+        if (u.inventory[i] == NO_OBJ)
+        {
+            break;
+        }
+    }
+    if (i == 19)
+    {
+        notify_pack_full();
+        return;
+    }
+    u.inventory[i] = lvl.obj_at(u.pos);
+    lvl.set_obj_at(u.pos, NO_OBJ);
+    objects[u.inventory[i]].with_you = true;
+    objects[u.inventory[i]].pos = Nowhere;
+    notify_get_item(NO_OBJ, i);
+}
+
+void break_reaction(int obj)
+{
+    switch (objects[obj].obj_id)
+    {
+    case PO_TORMENTORS_LASH:
+        if (u.food < 500)
+        {
+            int shortfall = (u.food < 0) ? 500 : 500 - u.food;
+            int damage = (shortfall + 24) / 25;
+            u.food = (u.food < 0) ? u.food : 0;
+            notify_lash_activation(3);
+            damage_u(damage, DEATH_LASH, "");
+        }
+        else
+        {
+            u.food -= 500;
+            objects[obj].durability = 100;
+            notify_lash_activation(1);
+        }
+        break;
+
+    default:
+        if (permobjs[objects[obj].obj_id].flags[0] & POF_DRESS)
+        {
+            objects[obj].durability = 50 + zero_die(51);
+            objects[obj].obj_id = PO_RAGGED_SHIFT;
+            notify_dress_shredded();
+            recalc_defence();
+        }
+        else
+        {
+            debug_unimplemented_break_reaction(objects[obj].obj_id);
+        }
+        break;
+    }
+}
+
+void damage_obj(int obj)
+{
+    /* Only weapons and armour have non-zero durability. */
+    if (objects[obj].durability == 0)
+    {
+        /* Break the object. Weapons and armour don't stack. */
+        consume_obj(obj);
+    }
+    else
+    {
+        objects[obj].durability--;
+        if ((objects[obj].durability == 0) && (permobjs[objects[obj].obj_id].flags[0] & POF_BREAK_REACT))
+        {
+            break_reaction(obj);
+        }
+    }
+} 
+
+int evasion_penalty(int obj)
+{
+    if (permobjs[objects[obj].obj_id].poclass == POCLASS_ARMOUR)
+    {
+        return permobjs[objects[obj].obj_id].power2;
+    }
+    return 100;
+}
+
+Action_cost magic_ring(void)
+{
+    Obj *optr = objects + u.ring;
+    switch (optr->obj_id)
+    {
+    case PO_TELEPORT_RING:
+        if (u.food >= 50)
+        {
+            u.food -= 50;
+            notify_telering_activation(1);
+            teleport_u();
+            return Cost_std;
+        }
+        else
+        {
+            notify_telering_activation(0);
+        }
+        return Cost_none;
+    default:
+        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+        {
+            debug_unimplemented_activation(optr->obj_id);
+        }
+        else
+        {
+            notify_magic_powerless_ring();
+        }
+        return Cost_none;
+    }
+}
+
+Action_cost emanate_armour(void)
+{
+    Obj *optr = objects + u.armour;
+    switch (optr->obj_id)
+    {
+    default:
+        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+        {
+            debug_unimplemented_activation(optr->obj_id);
+        }
+        else
+        {
+            notify_emanate_powerless_armour();
+        }
+        break;
+    }
+    return Cost_none;
+}
+
+Action_cost zap_weapon(void)
+{
+    Obj *optr = objects + u.weapon;
+    switch (optr->obj_id)
+    {
+    case PO_STAFF_OF_FIRE:
+        if (u.food > 150)
+        {
+            Coord c;
+            u.food -= 150;
+            notify_firestaff_activation(1);
+            for (c.y = u.pos.y - 1; c.y <= u.pos.y + 1; ++c.y)
+            {
+                if ((c.y < lvl.min_y()) || (c.y >= lvl.max_y()))
+                {
+                    continue;
+                }
+                for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x)
+                {
+                    int mon;
+                    if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x()))
+                    {
+                        continue;
+                    }
+                    mon = lvl.mon_at(c);
+                    if (mon != NO_MON)
+                    {
+                        Mon *mptr = monsters + mon;
+                        if (!pmon_resists_fire(mptr->mon_id))
+                        {
+                            notify_fireitem_hit(mon);
+                            damage_mon(mon, dice(4, 10), true);
+                        }
+                    }
+                }
+            }
+            damage_obj(u.weapon);
+        }
+        else
+        {
+            notify_firestaff_activation(0);
+            return Cost_none;
+        }
+        return Cost_std;
+    default:
+        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+        {
+            debug_unimplemented_activation(optr->obj_id);
+        }
+        else
+        {
+            notify_zap_powerless_weapon();
+        }
+        return Cost_none;
+    }
+}
+
+/*! \brief Unwield the player's currently equipped weapon
+ *
+ *  \todo Stickycurse?
+ *  \todo Life-essential resistances?
+ */
+Action_cost player_unwield(Noisiness noisy)
+{
+    int saved_weapon = u.weapon;
+    if (u.weapon == NO_OBJ)
+    {
+        debug_unwield_nothing();
+        return Cost_none;
+    }
+    u.weapon = NO_OBJ;
+    recalc_defence();
+    notify_unwield(saved_weapon, Noise_std);
+    return Cost_std;
+}
+
+Action_cost player_wield(int slot, Noisiness noisy)
+{
+    if (u.weapon != NO_OBJ)
+    {
+        player_unwield(Noise_low);
+    }
+    u.weapon = u.inventory[slot];
+    notify_wield_weapon(u.weapon);
+    recalc_defence();
+    return Cost_std;
+}
+
+Action_cost wear_armour(int slot)
+{
+    int obj;
+    if (u.armour != NO_OBJ)
+    {
+        debug_wear_while_wearing();
+        return Cost_none;
+    }
+    obj = u.inventory[slot];
+    if (!objects[obj].with_you)
+    {
+        debug_wear_uncarried_armour();
+        return Cost_none;
+    }
+    u.armour = obj;
+    recalc_defence();
+    notify_armour_equip(u.armour);
+    return Cost_std;
+}
+
+Action_cost put_on_ring(int obj)
+{
+    if (u.ring != NO_OBJ)
+    {
+        debug_put_on_second_ring();
+        return Cost_none;
+    }
+    if (!objects[obj].with_you)
+    {
+        debug_put_on_uncarried_ring();
+        return Cost_none;
+    }
+    u.ring = obj;
+    notify_ring_equip(u.ring);
+    recalc_defence();
+    return Cost_std;
+}
+
+Action_cost remove_ring(void)
+{
+    int saved_ring = u.ring;
+    if (u.ring == NO_OBJ)
+    {
+        debug_remove_no_ring();
+        return Cost_none;
+    }
+    u.ring = NO_OBJ;
+    recalc_defence();
+    notify_ring_unequip(saved_ring);
+    return Cost_std;
+}
+
+Pass_fail ring_removal_unsafe(Noisiness noisy)
+{
+    if ((lvl.terrain_at(u.pos) == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING))
+    {
+        if (noisy != Noise_silent)
+        {
+            notify_lava_blocks_unequip();
+        }
+        return You_fail;
+    }
+    else if ((objects[u.ring].obj_id == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER))
+    {
+        if (noisy != Noise_silent)
+        {
+            notify_water_blocks_unequip();
+        }
+        return You_fail;
+    }
+    return You_pass;
+}
+
+/* objects.cc */
+// vim:cindent
diff --git a/objects.hh b/objects.hh
new file mode 100644 (file)
index 0000000..719c9e1
--- /dev/null
@@ -0,0 +1,89 @@
+/*! \file objects.hh
+ *  \brief object-related header for Obumbrata et Velata
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef OBJECTS_HH
+#define OBJECTS_HH
+
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#ifndef PERMOBJ_HH
+#include "permobj.hh"
+#endif
+
+/* XXX Obj */
+#define OBJ_MAX_DUR 100
+#define OBJECTS_IN_PLAY 100
+class Obj {
+public:
+    int obj_id;
+    bool used;   /* Entry is occupied. */
+    bool with_you;       /* Preserved when item DB is reaped on level change. */
+    int quan;
+    Coord pos;
+    int durability;     /* Weapons and armour degrade with use. */
+};
+extern Obj objects[OBJECTS_IN_PLAY];
+
+#define NO_OBJ (-1)
+
+/* XXX objects.c data and funcs */
+extern void flavours_init(void);
+extern void sprint_obj_name(char *s, int obj, int len);
+extern void fprint_obj_name(FILE *fp, int obj);
+extern void print_obj_name(int obj);
+extern void describe_object(int obj);
+extern int create_obj(int po_idx, int quantity, bool with_you, Coord c);
+extern bool consume_obj(int obj);
+extern int create_obj_class(enum poclass_num pocl, int quantity, bool with_you, Coord c);
+extern void damage_obj(int obj);
+extern int evasion_penalty(int obj);
+
+extern Action_cost drop_obj(int inv_idx);
+extern Action_cost put_on_ring(int obj);
+extern Action_cost remove_ring(void);
+extern Action_cost wear_armour(int obj);
+extern Action_cost take_off_armour(void);
+extern Action_cost read_scroll(int obj);
+extern Action_cost quaff_potion(int obj);
+extern Action_cost eat_food(int obj);
+extern Action_cost magic_ring(void);
+extern Action_cost emanate_armour(void);
+extern Action_cost zap_weapon(void);
+extern Action_cost player_unwield(Noisiness noisy = Noise_std);
+extern Action_cost player_wield(int slot, Noisiness noisy = Noise_std);
+extern void attempt_pickup(void);
+
+extern Pass_fail ring_removal_unsafe(Noisiness noisy = Noise_std);
+extern Pass_fail armour_removal_unsafe(Noisiness noisy = Noise_std);
+
+#endif
+
+/* objects.hh */
+// vim:cindent
diff --git a/obumbrata.6 b/obumbrata.6
new file mode 100644 (file)
index 0000000..cd13d48
--- /dev/null
@@ -0,0 +1,18 @@
+.TH OBUMBRATA 6 "March 8, 2014" "Obumbrata Version 1.0" "Obumbrata et Velata User Manual"
+.SH NAME
+obumbrata \- Obumbrata, a roguelike game
+.SH SYNOPSIS
+obumbrata
+
+.SH DESCRIPTION
+.I obumbrata
+(properly
+.I "Obumbrata et Velata"
+) is a roguelike game.
+
+.SH BUGS
+Unknown, probably multiple.
+
+.SH AUTHOR
+Martin Read <martin@blackswordsonics.com>
+
diff --git a/obumbrata.hh b/obumbrata.hh
new file mode 100644 (file)
index 0000000..c711a55
--- /dev/null
@@ -0,0 +1,93 @@
+/*! \file obumbrata.hh
+ *  \brief Main header for Obumbrata et Velata
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef OBUMBRATA_HH
+#define OBUMBRATA_HH
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#include "core.hh"
+
+/* change WIZARD_MODE to 1 if you want the wizard mode commands. */
+#define WIZARD_MODE 0
+
+#ifndef PLAYER_HH
+#include "player.hh"
+#endif
+
+#ifndef MONSTERS_HH
+#include "monsters.hh"
+#endif
+
+#ifndef DISPLAY_HH
+#include "display.hh"
+#endif
+
+#ifndef NOTIFY_HH
+#include "notify.hh"
+#endif
+
+#ifndef MAP_HH
+#include "map.hh"
+#endif
+
+#ifndef FOV_HH
+#include "fov.hh"
+#endif
+
+/* XXX main.c data and funcs */
+extern bool game_finished;
+extern int game_tick;
+extern bool wizard_mode;
+Action_cost do_player_action(Action *act);
+void new_game(char const *name);
+void main_loop(void);
+
+/* XXX misc.c data and funcs */
+extern char const *damtype_names[DT_COUNT];
+
+/* XXX  log.cc */
+extern void log_death(Death d, char const *what);
+extern void load_config(void); 
+extern void wrapped_system(char const *cmd);
+extern void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *stream);
+extern int load_game(void);
+extern void save_game(void);
+
+#endif
+
+/* obumbrata.hh */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/permobj.hh b/permobj.hh
new file mode 100644 (file)
index 0000000..20eb221
--- /dev/null
@@ -0,0 +1,99 @@
+/*! \file permobj.hh
+ *  \brief permobj-related header for Obumbrata et Velata
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PERMOBJ_HH
+#define PERMOBJ_HH
+
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#include "pobj_id.hh"
+
+/*! \brief Quality level of object.
+ */
+enum Item_quality
+{
+    Quality_bad, //!< Useless or hypothetically actively harmful
+    Quality_normal, //!< Useful
+    Quality_good,
+    Quality_excellent,
+    Quality_legendary
+};
+
+/* XXX enum poclass_num */
+/* Categories of permanent object. */
+enum poclass_num {
+    POCLASS_NONE = 0, POCLASS_WEAPON, POCLASS_POTION,
+    POCLASS_SCROLL, POCLASS_FLASK, POCLASS_ARMOUR, POCLASS_RING,
+    POCLASS_FOOD, POCLASS_CARRION
+};
+
+#include "pobj_id.hh"
+
+#define POBJ_FLAG_WORDS 1
+
+// POF field 0
+#define POF_NOTIFY_EQUIP 0x00000001u // item has special messages when changing equip status
+#define POF_ACTIVATABLE 0x00000002u // item can be activated when equipped
+#define POF_STACKABLE 0x00000004u // item can stack
+#define POF_DAMAGEABLE 0x00000008u // track durability
+#define POF_BREAK_REACT 0x00000010u // item reacts to breakage attempts
+/* Yes, DRESS and MELEE_WEAPON have the same value. This is OK because
+ * only an ARMOUR can possibly be a DRESS, and only a WEAPON can possibly be
+ * a MELEE_WEAPON. */
+#define POF_DRESS 0x00010000u
+#define POF_MELEE_WEAPON 0x00010000u
+#define POF_RANGED_WEAPON 0x00020000u
+
+/*! \brief The 'permanent object' database */
+class Permobj {
+public:
+    char const *name; //!< English-language name of item
+    char const *plural; //!< English-language plural of item
+    char const *description; //!< English-language description of item
+    enum poclass_num poclass; //!< Category of item
+    int rarity; //!< Chance in 100 of being thrown away and regen'd.
+    char sym;   //!< must be in range 32...126
+    char const *unicode; //!< must be a C-style string in UTF-8 encoding
+    Gamecolour colour; //!< colour used for terminal display
+    int power;  //!< first POCLASS-specific data field
+    int power2; //!< second POCLASS-specific data field
+    int depth;  //!< shallowest depth at which item can be randomly gen'd
+    uint32_t flags[POBJ_FLAG_WORDS];
+};
+#define NO_POBJ (-1)
+
+extern Permobj permobjs[NUM_OF_PERMOBJS];
+extern bool po_is_stackable(int po);
+
+#endif
+
+/* permobj.hh */
+// vim:cindent
+
diff --git a/permon.hh b/permon.hh
new file mode 100644 (file)
index 0000000..0fc6fcb
--- /dev/null
+++ b/permon.hh
@@ -0,0 +1,127 @@
+/*! \file permon.hh
+ *  \brief permon-related header
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PERMON_HH
+#define PERMON_HH
+
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#include "pmon_id.hh"
+
+#define PMF_RESIST_FIRE 0x00000001
+#define PMF_RESIST_COLD 0x00000002
+#define PMF_RESIST_ELEC 0x00000004
+#define PMF_RESIST_POIS 0x00000008
+#define PMF_RESIST_NECR 0x00000010
+#define PMF_RESIST_SLAM 0x00000020
+#define PMF_RESIST_DRWN 0x00000040
+#define PMF_UNDEAD      0x00010000
+#define PMF_DEMONIC     0x00020000
+#define PMF_MAGICIAN    0x00040000
+#define PMF_ARCHER      0x00080000
+#define PMF_SMART       0x00100000
+#define PMF_STUPID      0x00200000
+#define PMF_ETHEREAL    0x00400000
+#define PMF_FLYING      0x00800000
+#define PMF_HUMANOID    0x01000000
+#define PMF_CENTAUROID  0x02000000
+#define PMF_SERPENTINE  0x04000000
+#define PMF_AMORPHOUS   0x08000000
+#define PMF_QUADRUPED   0x10000000
+#define PMF_SKITTERISH  0x20000000
+#define PMF_HUGE        0x40000000
+#define PMF_SMALL       0x80000000
+
+#define PERMON_FLAG_FIELDS 2
+/*! \brief Describes the baseline stats of a specific kind of monster */
+class Permon {
+public:
+    char const *name;
+    char const *plural;
+    char const *description;
+    char sym;
+    char const *unicode;
+    int colour;
+    int rarity;         /* Chance in 100 of being thrown back and regen'd. */
+    int power;          /* Used to determine OOD rating. */
+    /* All OOD-improved stats cap out at base + (power * base) */
+    int hp;             /* Improved by OOD rating at 1:1. */
+    int mtohit;         /* Improved by OOD rating at 1:3. */
+    int rtohit;         /* Improved by OOD rating at 1:3. */
+    int mdam;           /* Improved by OOD rating at 1:5. */
+    int rdam;           /* Improved by OOD rating at 1:5. */
+    Damtyp rdtyp;  /* type of damage used by ranged attack. */
+    char const *shootverb;   /* shooting verb e.g. "fires an arrow", "breathes". */
+    int defence;        /* Improved by OOD rating at 1:3. */
+    int exp;            /* Unaffected by OOD rating. */
+    int speed;          /* 0 = slow; 1 = normal; 2 = quick */
+    uint32_t flags[PERMON_FLAG_FIELDS];          /* resistances, AI settings, etc. */
+};
+extern struct Permon permons[NUM_OF_PERMONS];
+
+#define NO_ATK (-1)
+#define NO_PMON (-1)
+
+enum AI_mode {
+    AI_charger, AI_archer, AI_dodger, AI_drunk, AI_seeker, AI_chaser
+};
+
+/* XXX pmon2.c data and funcs */
+extern bool pmon_is_archer(int pm);
+extern bool pmon_is_magician(int pm);
+extern bool pmon_is_smart(int pm);
+extern bool pmon_is_stupid(int pm);
+
+extern bool pmon_resists_cold(int pm);
+extern bool pmon_resists_fire(int pm);
+extern bool pmon_resists_poison(int pm);
+extern bool pmon_resists_necro(int pm);
+extern bool pmon_resists_elec(int pm);
+extern bool pmon_resists_drowning(int pm);
+extern bool pmon_resists_knockback(int pm);
+
+extern bool pmon_can_fly(int pm);
+
+extern bool pmon_is_ethereal(int pm);
+extern bool pmon_is_undead(int pm);
+extern bool pmon_is_demonic(int pm);
+
+extern bool pmon_has_hands(int pm);
+extern bool pmon_is_humanoid(int pm);
+extern bool pmon_is_quadruped(int pm);
+extern bool pmon_is_skitterish(int pm);
+extern bool pmon_is_amorphous(int pm);
+extern bool pmon_is_huge(int pm);
+extern bool pmon_is_small(int pm);
+
+#endif
+
+/* permon.hh */
+// vim:cindent:expandtab
diff --git a/player.hh b/player.hh
new file mode 100644 (file)
index 0000000..6913afe
--- /dev/null
+++ b/player.hh
@@ -0,0 +1,106 @@
+/*! \file player.hh
+ *  \brief Player character  header for Obumbrata et Velata
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PLAYER_HH
+#define PLAYER_HH
+
+#include "obumbrata.hh"
+
+/*! \brief Internal representation of the player character 
+ */
+class Player {
+public:
+    char name[17];  //!< Allows 16 actual characters, plus 0 terminator
+    Coord pos;      //!< Position within current dungeon level.
+    int body;       //!< Combined stamina and strength stat.
+    int bdam;       //!< Current level of temporary Body drain
+    int agility;    //!< Combined accuracy and avoidance stat.
+    int adam;       //!< Current level of temporary agility drain.
+    int hpmax;      //!< Max hit points.
+    int hpcur;      //!< Current hit points.
+    int food;       //!< Current level of satiation; < 0 is hungry.
+    int experience; //!< Experience points accumulated.
+    int defence;    //!< To-hit target number for monsters.
+    int protection; //!< Temporary protection from cursing.
+    int leadfoot;   //!< Feet-of-lead curse.
+    int withering;  //!< Vile withering curse.
+    int armourmelt; //!< Armour-like-dust curse.
+    int speed;      //!< Controls how often you act.
+    uint32_t resistances[DT_COUNT]; //!< Resistance masks per damage type
+    int level;      //!< Current experience level.
+    int inventory[19];  //!< Object handles of currently carried items.
+    int weapon;     //!< Object handle of currently equipped weapon.
+    int armour;     //!< Object handle of currently equipped armour.
+    int ring;       //!< Object handle of currently equipped ring.
+    int sympathy[TOTAL_FELL_POWERS]; //!< Level of alignment with fell powers
+    /* Methods only after here plzkthx */
+    bool resists(Damtyp dtype) const; //!< Does player resist this Damtyp?
+    bool martial(void) const //!< Is player significantly influenced by iron?
+    { return sympathy[FePo_iron] > 10; }
+    bool rotten(void) const //!< Is player significantly influenced by decay?
+    { return sympathy[FePo_decay] > 10; }
+    bool thanatoic(void) const //!< Is player significantly influenced by bone?
+    { return sympathy[FePo_bone] > 10; }
+    bool sybaritic(void) const //!< Is player significantly influenced by flesh?
+    { return sympathy[FePo_flesh] > 10; }
+    bool corrupt(void) const //!< Is player significantly influenced by any?
+    { return martial() || rotten() || thanatoic() || sybaritic(); }
+};
+
+#define MIN_FOOD (-1950)
+#define SLOT_CANCEL (-1)
+#define SLOT_NOTHING (-2)
+
+/* XXX u.c data and funcs */
+extern Player u;
+
+void u_init(char const *name);
+void write_char_dump(void);
+int do_death(Death d, char const *what);
+void heal_u(int amount, int boost, int loud);
+int damage_u(int amount, Death d, char const *what);
+int gain_body(int amount);
+int gain_agility(int amount);
+int drain_body(int amount, char const *what, int permanent);
+int drain_agility(int amount, char const *what, int permanent);
+void gain_experience(int amount);
+int lev_threshold(int level);
+Action_cost move_player(Offset delta);
+void reloc_player(Coord c);
+void recalc_defence(void);
+Pass_fail teleport_u(void);
+void update_player(void);
+inline bool empty_handed(void) { return u.weapon == NO_OBJ; }
+bool wielding_melee_weapon(void);
+bool wielding_ranged_weapon(void);
+
+void player_cleanup(void);
+#endif
+
+/* player.hh */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/pmon2.cc b/pmon2.cc
new file mode 100644 (file)
index 0000000..51ecc99
--- /dev/null
+++ b/pmon2.cc
@@ -0,0 +1,104 @@
+/*! \file pmon2.cc
+ *  \brief permons flag-check functions
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define PMON2_CC
+#include "obumbrata.hh"
+#include "monsters.hh"
+
+bool pmon_resists_fire(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_FIRE);
+}
+
+bool pmon_resists_cold(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_COLD);
+}
+
+bool pmon_resists_poison(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_POIS);
+}
+
+bool pmon_resists_necro(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_NECR);
+}
+
+bool pmon_resists_elec(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_ELEC);
+}
+
+bool pmon_resists_knockback(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_SLAM);
+}
+
+bool pmon_resists_drowning(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_RESIST_DRWN);
+}
+
+bool pmon_is_undead(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_UNDEAD);
+}
+
+bool pmon_is_stupid(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_STUPID);
+}
+
+bool pmon_is_smart(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_SMART);
+}
+
+bool pmon_is_magician(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_MAGICIAN);
+}
+
+bool pmon_is_archer(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_ARCHER);
+}
+
+bool pmon_can_fly(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_FLYING);
+}
+
+bool pmon_is_ethereal(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_ETHEREAL);
+}
+
+/* pmon2.c */
+// vim:cindent
diff --git a/pmon_comp b/pmon_comp
new file mode 100755 (executable)
index 0000000..e2405e1
--- /dev/null
+++ b/pmon_comp
@@ -0,0 +1,383 @@
+#! /usr/bin/env perl
+
+use strict;
+use warnings;
+use English;
+use Time::HiRes qw( time );
+
+sub usage()
+{
+    print "Usage:\n    pmon_comp INPUT_FILE\n\npmon_comp reads a single .permon file INPUT_FILE and generates a permon.cc\nand pmon_id.hh based on its contents.";
+    exit(1);
+}
+
+our @monsters;
+
+our %flag_indices =
+(
+    'RESIST_FIRE' => 0,
+    'RESIST_COLD' => 0,
+    'RESIST_ELEC' => 0,
+    'RESIST_POIS' => 0,
+    'RESIST_NECR' => 0,
+    'RESIST_SLAM' => 0,
+    'RESIST_DRWN' => 0,
+    'UNDEAD' => 0,
+    'DEMONIC' => 0,
+    'MAGICIAN' => 0,
+    'ARCHER' => 0,
+    'SMART' => 0,
+    'STUPID' => 0,
+    'ETHEREAL' => 0,
+    'FLYING' => 0,
+    'HUMANOID' => 0,
+    'CENTAUROID' => 0,
+    'SERPENTINE' => 0,
+    'AMORPHOUS' => 0,
+    'QUADRUPED' => 0,
+    'SKITTERISH' => 0,
+    'HUGE' => 0,
+    'SMALL' => 0,
+);
+
+
+sub macroify_name($)
+{
+    my $name = "".shift @_;
+    $name =~ tr/'//d;
+    return uc(($name =~ tr/a-zA-Z/_/csr));
+}
+
+sub flag_string($)
+{
+    my $aref = shift @_;
+    my @flag_fields = ();
+    if (!defined($aref) || scalar(@$aref == 0))
+    {
+        return "0";
+    }
+    else
+    {
+        my $name;
+        for $name (@$aref)
+        {
+            die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name});
+            my $idx = $flag_indices{$name};
+            $#flag_fields = $idx if ($idx > $#flag_fields);
+            if (!defined($flag_fields[$idx]))
+            {
+                $flag_fields[$idx] = "0 ";
+            }
+            $flag_fields[$idx] .= "| PMF_$name ";
+        }
+    }
+    return join(", ", @flag_fields);
+}
+
+sub commit_monster($)
+{
+    my $href = shift(@_);
+    die("Attempt to commit an unnamed monster!") if !exists($href->{name});
+    die("Attempt to commit a ASCIIless monster ".$href->{name}."!") if !exists($href->{ascii});
+    die("Attempt to commit a UTF8less monster ".$href->{name}."!") if !exists($href->{uni});
+    if (!exists($href->{plural}))
+    {
+        # naive fallback, guaranteed to look shit sooner or later
+        $href->{plural} = $href->{name}."s";
+    }
+    my $new_hash = {
+        'name' => $href->{name},
+        'plural' => $href->{plural},
+        'desc' => $href->{desc},
+        'ascii' => $href->{ascii},
+        'uni' => $href->{uni},
+        'colour' => $href->{colour},
+        'rarity' =>  $href->{rarity},
+        'power' =>  $href->{power},
+        'hp' =>  $href->{hp},
+        'mtohit' =>  $href->{mtohit},
+        'rtohit' =>  $href->{rtohit},
+        'mdam' =>  $href->{mdam},
+        'rdam' =>  $href->{rdam},
+        'rdtyp' =>  $href->{rdtyp},
+        'shootverb' => $href->{shootverb},
+        'defence' =>  $href->{defence},
+        'exp' =>  $href->{exp},
+        'speed' =>  $href->{speed},
+        'power' =>  $href->{power},
+        'flags' => $href->{flags}
+    };
+    push @monsters, $new_hash;
+}
+
+our $argc = scalar(@ARGV);
+usage() if ($argc != 1);
+our $output_fname;
+our $input_fname = "$ARGV[0]";
+open(INFILE, "<", $input_fname) or die "pmon_comp: could not open $input_fname for read: $!";
+our @input_file = <INFILE>;
+close INFILE;
+our %blank_monster = (
+    'desc' => "An monster some useless slacker hasn't bothered to describe.",
+    'colour' => "l_grey",
+    'rarity' => 100,
+    'power' => 1,
+    'hp' => 1,
+    'mtohit' => 0,
+    'rtohit' => -1,
+    'mdam' => 0,
+    'rdam' => -1,
+    'rdtyp' => "PHYS",
+    'shootverb' => "shoots",
+    'defence' => 0,
+    'exp' => 0,
+    'speed' => 0
+);
+
+our %working_monster;
+
+sub reinit_working_monster($)
+{
+    %working_monster = %blank_monster;
+    $working_monster{name} = shift;
+    $working_monster{plural} = "$working_monster{name}s";
+    $working_monster{flags} = [];
+}
+
+my $input_line;
+
+our $start_time = time();
+print "Processing permons database $input_fname";
+for $input_line (@input_file)
+{
+    chomp $input_line;
+    next if ($input_line =~ /^\s*$/);
+    next if ($input_line =~ /^\s*#/);
+    if ($input_line =~ /^\s*monster\s+([^[:space:]].*)$/i)
+    {
+        my $name = $1;
+        if (exists($working_monster{name}))
+        {
+            commit_monster(\%working_monster);
+            %working_monster = ();
+        }
+        reinit_working_monster($name);
+        print ".";
+    }
+    elsif (!exists($working_monster{name}))
+    {
+        die("Attempt to specify monster properties without starting an monster");
+    }
+    elsif ($input_line =~ /^\s*(power|depth)\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^(-?[0-9]+)/)
+        {
+            $working_monster{power} = $1;
+        }
+        else
+        {
+            die("Non-numeric power value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*hp\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^(-?[0-9]+)/)
+        {
+            $working_monster{hp} = $1;
+        }
+        else
+        {
+            die("Non-numeric hp value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*m(elee)?(to)?hit\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{mtohit} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric mtohit value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*m(elee)?dam\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{mdam} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric mdam value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*r(anged)?(to)?hit\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{rtohit} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric rtohit value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*sp(ee)?d\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{speed} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric speed value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*exp(erience)?\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{exp} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric exp value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*def(en[cs]e)?\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{defence} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric defence value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*r(anged)?dam\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{rdam} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric rdam value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*rarity\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_monster{rarity} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric rarity value $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*shootverb\s+/i)
+    {
+        $working_monster{shootverb} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*colour\s+/i)
+    {
+        $working_monster{colour} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*desc\s+/i)
+    {
+        $working_monster{desc} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*plural\s+/i)
+    {
+        $working_monster{plural} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*r(anged)?dtyp\s+/i)
+    {
+        $working_monster{rdtyp} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*ascii\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^'[[:ascii:]]'/)
+        {
+            $working_monster{ascii} = "$MATCH";
+        }
+        else
+        {
+            die("Malformed 'ASCII' argument $pm in monster $working_monster{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*utf-?8\s+/i)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^"[^"]+"/)
+        {
+            $working_monster{uni} = "$MATCH";
+        }
+        else
+        {
+            die("Malformed 'UTF8' argument $pm in monster $working_monster{name}");
+        }
+    }
+    else
+    {
+        my $test_line = "$input_line";
+        $test_line =~ s/\s+//;
+        if (exists($flag_indices{$test_line}))
+        {
+            my $aref = $working_monster{flags};
+            push @$aref, $test_line;
+        }
+        else
+        {
+            die("Malformed/unrecognized line $input_line in monster $working_monster{name}");
+        }
+    }
+}
+commit_monster(\%working_monster);
+print "\n";
+
+open(HEADERFILE, ">", "pmon_id.hh") or die "pmon_comp: could not open pmon_id.hh for write: $!";
+open(SOURCEFILE, ">", "permons.cc") or die "pmon_comp: could not open permons.cc for write: $!";
+print HEADERFILE "// pmon_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pmon_comp to regenerate this file and permons.cc\n#pragma once\nenum Pmon_id {\n";
+print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[NUM_OF_PERMONS] = {\n";
+my $phref;
+my $i;
+my $total_mons = 0;
+my @firsts;
+my $tagname;
+
+for ($i = 0; $i <= $#monsters; ++$i, ++$total_mons)
+{
+    $phref = $monsters[$i];
+    $tagname = "PM_".macroify_name($phref->{name});
+    if ($total_mons != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", %s, %s, Gcol_%s, %d, %d, %d, %d, %d, %d, %d, DT_%s, \"%s\", %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{rarity}, $phref->{power}, $phref->{hp}, $phref->{mtohit}, $phref->{rtohit}, $phref->{mdam}, $phref->{rdam}, $phref->{rdtyp}, $phref->{shootverb}, $phref->{defence}, $phref->{exp}, $phref->{speed},  flag_string($phref->{flags});
+}
+
+print SOURCEFILE "};\n// permons.cc\n";
+printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMONS %d\n\n// pmon_id.hh\n", $total_mons;
+
+close(SOURCEFILE);
+close(HEADERFILE);
+our $end_time = time();
+printf "Processed $total_mons monsters in %1.4f seconds.\n", ($end_time - $start_time);
+
+# vim:autoindent:smartindent
diff --git a/pmon_id.hh b/pmon_id.hh
new file mode 100644 (file)
index 0000000..d98d87f
--- /dev/null
@@ -0,0 +1,42 @@
+// pmon_id.hh
+// This file is autogenerated from default.permons
+// and is subject to the same copyright licensing terms as that file.
+// Do not edit this file directly; edit default.permons
+// then use pmon_comp to regenerate this file and permons.cc
+#pragma once
+enum Pmon_id {
+    PM_NEWT,
+    PM_RAT,
+    PM_WOLF,
+    PM_SNAKE,
+    PM_THUG,
+    PM_GOON,
+    PM_HUNTER,
+    PM_DUELLIST,
+    PM_WARLORD,
+    PM_GOBLIN,
+    PM_BAD_ELF,
+    PM_TROLL,
+    PM_GIANT,
+    PM_GIANT_JARL,
+    PM_WIZARD,
+    PM_ARCHMAGE,
+    PM_ZOMBIE,
+    PM_WRAITH,
+    PM_LICH,
+    PM_MASTER_LICH,
+    PM_VAMPIRE,
+    PM_FIRE_IMP,
+    PM_DEMON,
+    PM_DEFILER,
+    PM_PUTRID_EMISSARY,
+    PM_TORMENTOR,
+    PM_IRON_LORD,
+    PM_CENTAUR,
+    PM_ICE_MONSTER,
+    PM_DRAGON,
+    PM_MOONDRAKE};
+
+#define NUM_OF_PERMONS 31
+
+// pmon_id.hh
diff --git a/pobj_comp b/pobj_comp
new file mode 100755 (executable)
index 0000000..a86e72d
--- /dev/null
+++ b/pobj_comp
@@ -0,0 +1,506 @@
+#! /usr/bin/env perl
+
+use strict;
+use warnings;
+use English;
+use Time::HiRes qw( time );
+
+sub usage()
+{
+    print "Usage:\n    pobj_comp INPUT_FILE\n\npobj_comp reads a single .permobj file INPUT_FILE and generates a permobj.cc\nand pobj_id.hh based on its contents.";
+    exit(1);
+}
+
+our @weapons;
+our @armour;
+our @potions;
+our @scrolls;
+our @rings;
+our @food;
+our @carrion;
+
+our %flag_indices =
+(
+    'NOTIFY_EQUIP' => 0,
+    'ACTIVATABLE' => 0,
+    'STACKABLE' => 0,
+    'DAMAGEABLE' => 0,
+    'BREAK_REACT' => 0,
+    'DRESS' => 0,
+    'RANGED_WEAPON' => 0,
+    'MELEE_WEAPON' => 0,
+);
+
+
+sub macroify_objname($)
+{
+    my $name = "".shift @_;
+    $name =~ tr/'//d;
+    return uc(($name =~ tr/a-zA-Z/_/csr));
+}
+
+sub flag_string($)
+{
+    my $aref = shift @_;
+    my @flag_fields = ();
+    if (!defined($aref))
+    {
+        return "0";
+    }
+    else
+    {
+        my $name;
+        for $name (@$aref)
+        {
+            die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name});
+            my $idx = $flag_indices{$name};
+            $#flag_fields = $idx if ($idx > $#flag_fields);
+            if (!defined($flag_fields[$idx]))
+            {
+                $flag_fields[$idx] = "0 ";
+            }
+            $flag_fields[$idx] .= "| POF_$name ";
+        }
+    }
+    return join(", ", @flag_fields);
+}
+
+sub commit_object($)
+{
+    my $href = shift(@_);
+    die("Attempt to commit an unnamed object!") if !exists($href->{name});
+    die("Attempt to commit a ASCIIless object ".$href->{name}."!") if !exists($href->{ascii});
+    die("Attempt to commit a UTF8less object ".$href->{name}."!") if !exists($href->{uni});
+    if (!exists($href->{plural}))
+    {
+        # naive fallback, guaranteed to look shit sooner or later
+        $href->{plural} = $href->{name}."s";
+    }
+    my $new_hash = {
+        'name' => $href->{name},
+        'plural' => $href->{plural},
+        'desc' => $href->{desc},
+        'ascii' => $href->{ascii},
+        'uni' => $href->{uni},
+        'colour' => $href->{colour},
+        'rarity' =>  $href->{rarity},
+        'power' =>  $href->{power},
+        'power2' =>  $href->{power2},
+        'depth' =>  $href->{depth},
+        'flags' => $href->{flags}
+    };
+    if ($href->{tag} eq 'WEAPON')
+    {
+        push @weapons, $new_hash;
+    }
+    elsif ($href->{tag} eq 'ARMOUR')
+    {
+        push @armour, $new_hash;
+    }
+    elsif ($href->{tag} eq 'FOOD')
+    {
+        push @food, $new_hash;
+    }
+    elsif ($href->{tag} eq 'CARRION')
+    {
+        push @carrion, $new_hash;
+    }
+    elsif ($href->{tag} eq 'RING')
+    {
+        push @rings, $new_hash;
+    }
+    elsif ($href->{tag} eq 'SCROLL')
+    {
+        push @scrolls, $new_hash;
+    }
+    elsif ($href->{tag} eq 'POTION')
+    {
+        push @potions, $new_hash;
+    }
+}
+
+our $argc = scalar(@ARGV);
+usage() if ($argc != 1);
+our $output_fname;
+our $input_fname = "$ARGV[0]";
+open(INFILE, "<", $input_fname) or die "pobj_comp: could not open $input_fname for read: $!";
+our @input_file = <INFILE>;
+close INFILE;
+our %blank_object = (
+    'desc' => "An object some useless slacker hasn't bothered to describe.",
+    'rarity' => 100,
+    'power' => 0,
+    'power2' => 0,
+    'depth' => 1
+);
+
+our %working_object;
+
+sub reinit_working_object($$)
+{
+    %working_object = %blank_object;
+    $working_object{name} = shift;
+    $working_object{plural} = "$working_object{name}s";
+    $working_object{tag} = shift;
+    $working_object{flags} = [];
+}
+
+my $input_line;
+
+our $start_time = time();
+print "Processing permobj database $input_fname";
+for $input_line (@input_file)
+{
+    chomp $input_line;
+    next if ($input_line =~ /^\s*$/);
+    next if ($input_line =~ /^\s*#/);
+    if ($input_line =~ /^\s*WEAPON\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'WEAPON');
+        print ".";
+    }
+    # Yes, the format enforces British spelling in tags
+    elsif ($input_line =~ /^\s*ARMOUR\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'ARMOUR');
+        print ".";
+    }
+    elsif ($input_line =~ /^\s*POTION\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'POTION');
+        print ".";
+    }
+    elsif ($input_line =~ /^\s*SCROLL\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'SCROLL');
+        print ".";
+    }
+    elsif ($input_line =~ /^\s*FOOD\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'FOOD');
+        print ".";
+    }
+    elsif ($input_line =~ /^\s*RING\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'RING');
+        print ".";
+    }
+    elsif ($input_line =~ /^\s*CARRION\s+([^[:space:]].*)$/)
+    {
+        my $name = $1;
+        if (exists($working_object{name}))
+        {
+            commit_object(\%working_object);
+            %working_object = ();
+        }
+        reinit_working_object($name, 'CARRION');
+        print ".";
+    }
+    elsif (!exists($working_object{name}))
+    {
+        die("Attempt to specify object properties without starting an object");
+    }
+    elsif ($input_line =~ /^\s*POWER\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^(-?[0-9]+)/)
+        {
+            $working_object{power} = $1;
+        }
+        else
+        {
+            die("Non-numeric power value $pm in object $working_object{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*POWER2\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^(-?[0-9]+)/)
+        {
+            $working_object{power2} = $1;
+        }
+        else
+        {
+            die("Non-numeric power2 value $pm in object $working_object{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*DEPTH\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_object{depth} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric depth value $pm in object $working_object{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*RARITY\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^([0-9]+)/)
+        {
+            $working_object{rarity} = $1;
+        }
+        else
+        {
+            die("Negative or non-numeric rarity value $pm in object $working_object{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*COLOUR\s+/)
+    {
+        $working_object{colour} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*DESC\s+/)
+    {
+        $working_object{desc} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*PLURAL\s+/)
+    {
+        $working_object{plural} = "$POSTMATCH";
+    }
+    elsif ($input_line =~ /^\s*ASCII\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^'[[:ascii:]]'/)
+        {
+            $working_object{ascii} = "$MATCH";
+        }
+        else
+        {
+            die("Malformed 'ASCII' argument $pm in object $working_object{name}");
+        }
+    }
+    elsif ($input_line =~ /^\s*UTF-?8\s+/)
+    {
+        my $pm = "$POSTMATCH";
+        if ($pm =~ /^"[^"]+"/)
+        {
+            $working_object{uni} = "$MATCH";
+        }
+        else
+        {
+            die("Malformed 'UTF8' argument $pm in object $working_object{name}");
+        }
+    }
+    else
+    {
+        my $test_line = "$input_line";
+        $test_line =~ s/\s+//;
+        if (exists($flag_indices{$test_line}))
+        {
+            my $aref = $working_object{flags};
+            push @$aref, $test_line;
+        }
+        else
+        {
+            die("Malformed/unrecognized line $input_line in object $working_object{name}");
+        }
+    }
+}
+commit_object(\%working_object);
+print "\n";
+
+open(HEADERFILE, ">", "pobj_id.hh") or die "pobj_comp: could not open pobj_id.hh for write: $!";
+open(SOURCEFILE, ">", "permobj.cc") or die "pobj_comp: could not open permobj.cc for write: $!";
+print HEADERFILE "// pobj_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pobj_comp to regenerate this file and permobj.cc\n#pragma once\nenum Pobj_id {\n";
+print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[NUM_OF_PERMOBJS] = {\n";
+my $phref;
+my $i;
+my $total_objs = 0;
+my @firsts;
+my $tagname;
+
+for ($i = 0; $i <= $#weapons; ++$i, ++$total_objs)
+{
+    $phref = $weapons[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_WEAPON ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_WEAPON, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_WEAPON ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#armour; ++$i, ++$total_objs)
+{
+    $phref = $armour[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_ARMOUR ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_ARMOUR ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#food; ++$i, ++$total_objs)
+{
+    $phref = $food[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_FOOD ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_FOOD ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#scrolls; ++$i, ++$total_objs)
+{
+    $phref = $scrolls[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_SCROLL ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_SCROLL ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#potions; ++$i, ++$total_objs)
+{
+    $phref = $potions[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_POTION ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_POTION ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#rings; ++$i, ++$total_objs)
+{
+    $phref = $rings[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_RING ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_RING ${tagname}\n";
+    undef $tagname;
+}
+
+for ($i = 0; $i <= $#carrion; ++$i, ++$total_objs)
+{
+    $phref = $carrion[$i];
+    $tagname = "PO_".macroify_objname($phref->{name});
+    if ($i == 0)
+    {
+        push @firsts, "#define PO_FIRST_CARRION ${tagname}\n";
+    }
+    if ($total_objs != 0)
+    {
+        print HEADERFILE ",\n";
+    }
+    print HEADERFILE "    ${tagname}";
+    printf SOURCEFILE "    { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+}
+if (defined($tagname))
+{
+    push @firsts, "#define PO_LAST_CARRION ${tagname}\n";
+    undef $tagname;
+}
+
+print SOURCEFILE "};\n// permobj.cc\n";
+printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMOBJS %d\n\n// pobj_id.hh\n", $total_objs;
+
+close(SOURCEFILE);
+close(HEADERFILE);
+our $end_time = time();
+printf "Processed $total_objs objects in %1.4f seconds.\n", ($end_time - $start_time);
+
+# vim:autoindent:smartindent
diff --git a/pobj_id.hh b/pobj_id.hh
new file mode 100644 (file)
index 0000000..b1fcf5f
--- /dev/null
@@ -0,0 +1,73 @@
+// pobj_id.hh
+// This file is autogenerated from default.permobjs
+// and is subject to the same copyright licensing terms as that file.
+// Do not edit this file directly; edit default.permobjs
+// then use pobj_comp to regenerate this file and permobj.cc
+#pragma once
+enum Pobj_id {
+    PO_DAGGER,
+    PO_LONG_SWORD,
+    PO_MACE,
+    PO_RUNESWORD,
+    PO_HELLGLAIVE,
+    PO_PLAGUE_SCYTHE,
+    PO_TORMENTORS_LASH,
+    PO_DEATH_STAFF,
+    PO_STAFF_OF_FIRE,
+    PO_BOW,
+    PO_CROSSBOW,
+    PO_THUNDERBOW,
+    PO_LEATHER_ARMOUR,
+    PO_CHAINMAIL,
+    PO_PLATE_ARMOUR,
+    PO_MAGE_ARMOUR,
+    PO_MUNDANE_ROBE,
+    PO_ROBE_OF_SWIFTNESS,
+    PO_ROBE_OF_SHADOWS,
+    PO_DRAGONHIDE_ARMOUR,
+    PO_METEORIC_PLATE_ARMOUR,
+    PO_SACRED_CHAINMAIL,
+    PO_RAGGED_SHIFT,
+    PO_BATTLE_BALLGOWN,
+    PO_IMPERATRIX_GOWN,
+    PO_FOETID_VESTMENTS,
+    PO_LICHS_ROBE,
+    PO_INFERNITE_ARMOUR,
+    PO_IRON_RATION,
+    PO_PARCEL_OF_DRIED_FRUIT,
+    PO_ROUND_OF_ELVEN_WAYBREAD,
+    PO_DEVIL_SPLEEN,
+    PO_TELEPORT_SCROLL,
+    PO_FIRE_SCROLL,
+    PO_PROTECTION_SCROLL,
+    PO_HEALING_POTION,
+    PO_BODY_POTION,
+    PO_AGILITY_POTION,
+    PO_RESTORATION_POTION,
+    PO_REGENERATION_RING,
+    PO_FIRE_RING,
+    PO_VAMPIRE_RING,
+    PO_FROST_RING,
+    PO_TELEPORT_RING,
+    PO_SLIME_RING,
+    PO_PROTECTION_RING,
+    PO_IMPERIAL_SEAL,
+    PO_CORPSE};
+#define PO_FIRST_WEAPON PO_DAGGER
+#define PO_LAST_WEAPON PO_THUNDERBOW
+#define PO_FIRST_ARMOUR PO_LEATHER_ARMOUR
+#define PO_LAST_ARMOUR PO_INFERNITE_ARMOUR
+#define PO_FIRST_FOOD PO_IRON_RATION
+#define PO_LAST_FOOD PO_DEVIL_SPLEEN
+#define PO_FIRST_SCROLL PO_TELEPORT_SCROLL
+#define PO_LAST_SCROLL PO_PROTECTION_SCROLL
+#define PO_FIRST_POTION PO_HEALING_POTION
+#define PO_LAST_POTION PO_RESTORATION_POTION
+#define PO_FIRST_RING PO_REGENERATION_RING
+#define PO_LAST_RING PO_IMPERIAL_SEAL
+#define PO_FIRST_CARRION PO_CORPSE
+#define PO_LAST_CARRION PO_CORPSE
+
+#define NUM_OF_PERMOBJS 48
+
+// pobj_id.hh
diff --git a/rng.cc b/rng.cc
new file mode 100644 (file)
index 0000000..bd47dc4
--- /dev/null
+++ b/rng.cc
@@ -0,0 +1,168 @@
+/* \file rng.cc
+ * \brief xorshift PRNG
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "rng.hh"
+#include <time.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+
+Smallprng rng;
+void *saved_state_buffer;
+int saved_state_size;
+
+void Smallprng::extract_serialization(void *data, int size) const
+{
+    if (size >= state_size())
+    {
+        uint32_t *foo = (uint32_t *) data;
+        foo[0] = htonl(a);
+        foo[1] = htonl(b);
+        foo[2] = htonl(c);
+        foo[3] = htonl(d);
+    }
+    else
+    {
+        // TODO throw a wobbly or at least an exception
+    }
+}
+
+void Smallprng::inject_serialization(void const *data, int size)
+{
+    if (size >= state_size())
+    {
+        uint32_t const *foo = (uint32_t const *) data;
+        a = ntohl(foo[0]);
+        b = ntohl(foo[1]);
+        c = ntohl(foo[2]);
+        d = ntohl(foo[3]);
+    }
+    else
+    {
+        // TODO throw a wobbly or at least an exception
+    }
+}
+
+void rng_init(void)
+{
+    /* To make manipulating the Really Nasty God by monitoring the system time
+     * marginally harder, we use PID and UID to perturb the return value of
+     * time() used to initialise it.
+     *
+     * (The currently selected PRNG has an initialization function which
+     * only takes one input value.)
+     */
+    rng.init((uint32_t) (time(nullptr) ^ getpid() ^ (getuid() << 16)));
+    saved_state_size = rng.state_size();
+    saved_state_buffer = malloc(saved_state_size);
+}
+
+int exc_flat(int lower, int upper)
+{
+    return lower + one_die(upper - lower - 1);
+}
+
+int inc_flat(int lower, int upper)
+{
+    return lower + zero_die(upper - lower + 1);
+}
+
+Coord inc_boxed(Coord topleft, Coord botright)
+{
+    Coord c = { inc_flat(topleft.y, botright.y), inc_flat(topleft.x, botright.x) };
+    return c;
+}
+
+Coord exc_boxed(Coord topleft, Coord botright)
+{
+    Coord c = { exc_flat(topleft.y, botright.y), exc_flat(topleft.x, botright.x) };
+    return c;
+}
+
+int dice(int count, int sides)
+{
+    int total = 0;
+    for ( ; count > 0; count--)
+    {
+        total += one_die(sides);
+    }
+    return total;
+}
+
+int zero_die(int sides)
+{
+    int rval;
+    if (sides < 2)
+    {
+        return 0;
+    }
+    // This imposes a very slight bias. The more correct approach is
+    // clumsy to read.
+    rval = rng() / ((RNG_MAX / sides) + 1);
+    return rval;
+}
+
+int one_die(int sides)
+{
+    int rval;
+    if (sides < 2)
+    {
+        return 1;
+    }
+    rval = 1 + (rng() / ((RNG_MAX / sides) + 1));
+    return rval;
+}
+
+/*! \brief pick a random Chebyshev cardinal */
+Offset random_step(void)
+{
+    switch (zero_die(8))
+    {
+    case 0:
+        return Northwest;
+    case 1:
+        return North;
+    case 2:
+        return Northwest;
+    case 3:
+        return West;
+    case 4:
+        return East;
+    case 5:
+        return Southwest;
+    case 6:
+        return South;
+    case 7:
+        return Southeast;
+    }
+    return Stationary;
+}
+
+/* rng.cc */
+// vim:cindent
diff --git a/rng.hh b/rng.hh
new file mode 100644 (file)
index 0000000..b42a427
--- /dev/null
+++ b/rng.hh
@@ -0,0 +1,101 @@
+/*! \file rng.hh
+ *  \brief RNG-related header
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RNG_HH
+#define RNG_HH
+
+#include <stdint.h>
+
+#ifndef COORD_HH
+#include "coord.hh"
+#endif
+
+#define RNG_MAX 0xFFFFFFFFu
+extern void *saved_state_buffer;
+extern int saved_state_size;
+//extern uint32_t rng(void);
+void rng_init(void);
+int zero_die(int sides); /* 0..n-1 */
+int one_die(int sides);  /* 1..n */
+int dice(int count, int sides);
+int exc_flat(int lower, int upper); /* l+1 ... u-1 */
+int inc_flat(int lower, int upper); /* l ... u */
+Coord inc_boxed(Coord topleft, Coord botright);
+Coord exc_boxed(Coord topleft, Coord botright);
+Offset random_step(void);
+
+/*! \brief An instance of Bob Jenkins' "small noncryptographic PRNG" 
+ *
+ * See http://burtleburtle.net/bob/rand/smallprng.html for an explanation
+ * of this generator.
+ */
+class Smallprng
+{
+private:
+    uint32_t a;
+    uint32_t b;
+    uint32_t c;
+    uint32_t d;
+public:
+    enum { magic = 0xf1ea5eed };
+    void init(uint32_t seed)
+    {
+        int i;
+        a = magic; b = c = d = seed;
+        for (i = 0; i < 20; ++i)
+        {
+            (*this)();
+        }
+    }
+    static uint32_t rot(uint32_t val, int bits)
+    {
+        return (val << bits) | (val >> (32 - bits));
+    }
+    uint32_t operator()(void)
+    {
+        uint32_t e = a - rot(b, 27);
+        a = b ^ rot(c, 17);
+        b = c + d;
+        c = d + e;
+        d = e + a;
+        return d;
+    }
+    static int state_size()
+    {
+        return 16;
+    }
+    void extract_serialization(void * buf, int size) const;
+    void inject_serialization(void const * buf, int size);
+};
+
+extern Smallprng rng;
+
+#endif
+
+/* rng.hh */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/shrine.cc b/shrine.cc
new file mode 100644 (file)
index 0000000..1f00063
--- /dev/null
+++ b/shrine.cc
@@ -0,0 +1,265 @@
+/*! \file shrine.cc
+ *  \brief Shrine level generation
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#include <string.h>
+/*! \brief Construction descriptors for shrines */
+struct shrine shrines[4] =
+{
+    {
+        true,
+        ROOMCONN_EAST | ROOMCONN_WEST,
+        {
+            "11111111111",
+            "1#########1",
+            "1#.......#1",
+            "1#..._...#1",
+            "1##.....##1",
+            "0+..###..+0",
+            "1##.....##1",
+            "1#..._...#1",
+            "1#.......#1",
+            "1#########1",
+            "11111111111",
+        },
+    },
+    {
+        true,
+        ROOMCONN_SOUTH,
+        {
+            "11111111111",
+            "1LLL###LLL1",
+            "1####L####1",
+            "1#..L_L..#1",
+            "1##.....##1",
+            "1#..###..#1",
+            "1##.....##1",
+            "1#.......#1",
+            "1#.......#1",
+            "1#+#####+#1",
+            "11011111011",
+        },
+    },
+    {
+        true,
+        ROOMCONN_EAST | ROOMCONN_WEST,
+        {
+            "11111111111",
+            "1#########1",
+            "1#WW...WW#1",
+            "1#W.._..W#1",
+            "1#..WWW..#1",
+            "0+..WWW..+0",
+            "1#..WWW..#1",
+            "1#W.._..W#1",
+            "1#WW...WW#1",
+            "1#########1",
+            "11111111111",
+        },
+    },
+    {
+        true,
+        ROOMCONN_NORTH | ROOMCONN_EAST | ROOMCONN_SOUTH | ROOMCONN_WEST,
+        {
+            "11111011111",
+            "1####+####1",
+            "1#.......#1",
+            "1#.#.#.#.#1",
+            "1#.......#1",
+            "0+.#._.#.+0",
+            "1#.......#1",
+            "1#.#.#.#.#1",
+            "1#.......#1",
+            "1####+####1",
+            "11111011111",
+        },
+    },
+};
+
+/*! \brief Construction helper for shrines */
+void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall)
+{
+    Coord c;
+    Coord start;
+    int shy;
+    int shx;
+    Offset outer_mapstep;
+    Offset inner_mapstep;
+    uint32_t tstmask;
+    int r = 0;
+    if (accept_conns)
+    {
+        for (r = 0; r < 4; ++r)
+        {
+            tstmask = rotate_connection_mask(sh->connection_mask, r);
+            if (tstmask & accept_conns)
+            {
+                break;
+            }
+        }
+    }
+    switch (r)
+    {
+    default:
+        DEBUG_CONTROL_FLOW();
+        // fallthru
+    case 0:
+        outer_mapstep = South;
+        inner_mapstep = East;
+        start.y = topleft.y;
+        start.x = topleft.x;
+        break;
+    case 1:
+        outer_mapstep = West;
+        inner_mapstep = South;
+        start.y = topleft.y;
+        start.x = topleft.x + SHRINE_WIDTH - 1;
+        break;
+    case 2:
+        outer_mapstep = North;
+        inner_mapstep = West;
+        start.y = topleft.y + SHRINE_HEIGHT - 1;
+        start.x = topleft.x + SHRINE_WIDTH - 1;
+        break;
+    case 3:
+        outer_mapstep = East;
+        inner_mapstep = North;
+        start.y = topleft.y + SHRINE_HEIGHT - 1;
+        start.x = topleft.x;
+        break;
+    }
+    c = start;
+    for (shy = 0; shy < SHRINE_HEIGHT; ++shy, (c += outer_mapstep))
+    {
+        for (shx = 0; shx < SHRINE_WIDTH; ++shx, (c += inner_mapstep))
+        {
+            switch (sh->grid[shy][shx])
+            {
+            case '0':
+                lvl.set_terrain_at(c, shfloor);
+                /* We want to be able to slap this down as a room, which means
+                 * that at least one square on the connectable edges must not
+                 * be HARDWALL. Those squares are marked '0'. */
+                break;
+            case '1':
+                lvl.set_terrain_at(c, margin_is_wall ? shwall : shfloor);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case '.':
+                lvl.set_terrain_at(c, shfloor);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case '#':
+                lvl.set_terrain_at(c, shwall);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case '+':
+                lvl.set_terrain_at(c, DOOR);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case '_':
+                lvl.set_terrain_at(c, ALTAR);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case 'L':
+                lvl.set_terrain_at(c, LAVA);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            case 'W':
+                lvl.set_terrain_at(c, WATER);
+                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                break;
+            }
+        }
+        c -= inner_mapstep * SHRINE_WIDTH;
+    }
+}
+
+/*! \brief Excavate a cave-with-shrine level. */
+void build_level_shrine(void)
+{
+    Coord shrinepos = { inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2), inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2) };
+    Coord c;
+    Coord c2;
+    Coord c3;
+    uint32_t mask = 0;
+    int walk_data[4] = { 1, FLOOR, WALL };
+    int shrine_num = zero_die(sizeof shrines / sizeof shrines[0]);
+    int intrusions;
+    int i;
+
+    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    intrusions = dice(2, 4) - 1;
+    for (i = 0; i < 6; ++i)
+    {
+        place_random_intrusion(WALL);
+    }
+    switch (zero_die(4))
+    {
+    case 0:
+        c.y = shrinepos.y - 1;
+        c.x = shrinepos.x + SHRINE_WIDTH / 2;
+        c2 = c + East;
+        c3 = c + West;
+        mask = ROOMCONN_NORTH;
+        break;
+    case 1:
+        c.y = shrinepos.y + SHRINE_HEIGHT;
+        c.x = shrinepos.x + SHRINE_WIDTH / 2;
+        c2 = c + East;
+        c3 = c + West;
+        mask = ROOMCONN_SOUTH;
+        break;
+    case 2:
+        c.y = shrinepos.y + SHRINE_WIDTH / 2;
+        c.x = shrinepos.x - 1;
+        c2 = c + South;
+        c3 = c + North;
+        mask = ROOMCONN_WEST;
+        break;
+    case 3:
+        c.y = shrinepos.y + SHRINE_WIDTH / 2;
+        c.x = shrinepos.x + SHRINE_WIDTH;
+        c2 = c + South;
+        c3 = c + North;
+        mask = ROOMCONN_EAST;
+    }
+    place_shrine(shrinepos, shrines + shrine_num, mask);
+    lvl.set_terrain_at(c, FLOOR);
+    lvl.set_terrain_at(c2, FLOOR);
+    lvl.set_terrain_at(c3, FLOOR);
+    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    place_cave_stairs();
+}
+
+/* shrine.cc */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/sorcery.cc b/sorcery.cc
new file mode 100644 (file)
index 0000000..e6b03b3
--- /dev/null
@@ -0,0 +1,458 @@
+/*! \file sorcery.cc
+ *  \brief evil magic used by monsters
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "obumbrata.hh"
+#include "sorcery.hh"
+#include "monsters.hh"
+#include "combat.hh"
+
+/* SORCERY
+ *
+ * Certain of the denizens of the dungeon have the power to use sorcery
+ * against the player.
+ * 
+ * The "ordinary" lich may unleash bolts of necromantic force against
+ * the player, or smite him at close quarters with their staves of necromancy,
+ * or invoke grim curses against him.
+ *
+ * The dreaded master liches can smite the player from a distance with
+ * their necromantic powers without lying on a cardinal direction from
+ * him, and can steal the player's vitality with a touch, as well as having
+ * the spells of their lesser brethren.  Furthermore, they may attempt to
+ * evade the player in the same manner as wizards.
+ *
+ * Itinerant wizards roaming the dungeon cast bolts of lightning, and strike
+ * in hand-to-hand combat with staves wreathed with enchantments of shattering
+ * force; if sorely pressed, they may invoke their powers to teleport across
+ * the dungeon level, cheating the player of his victory.
+ * 
+ * Archmages, learned scholars of the Black Arts and veterans of many a
+ * confrontation, have the powers of wizards. In addition, an archmage who
+ * teleports away from the player to evade death may well leave him with a
+ * group of summoned monsters.
+ * 
+ * The more potent order of demons known as defilers may cast curses against
+ * the player, or call down a column of fire to smite him.
+ *
+ * Some forms of sorcery may be defended against by wearing the proper
+ * armour or putting on a suitable ring; others bypass all such defences to
+ * strike the player directly, although some of these can be evaded by those
+ * with high enough agility.
+ */
+
+int mon_use_sorcery(int mon)
+{
+    /* Returns zero for no spell selected, -1 for unsupported spell
+     * selected, 1 for supported spell selected. */
+    Mon *mptr = monsters + mon;
+    Offset delta = u.pos.delta(mptr->pos);
+    Offset step = mysign(delta);
+    enum monspell to_cast = MS_REJECT;
+    int rval = 1;       /* Default to success; failure paths will force this
+                         * to an appropriate value. */
+    int range = delta.len_cheb();
+    bool meleerange = (range < 2);
+    bool oncardinal = delta.rcardinal();
+    int dieroll;
+    bool cansee = mon_visible(mon);
+    int i;
+
+    switch (monsters[mon].mon_id)
+    {
+    case PM_ARCHMAGE:
+        if (cansee)
+        {
+            /* We have LOS; choose a spell on that basis. */
+            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+            {
+                to_cast = zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+            }
+            else if (meleerange && (zero_die(10) > 3))
+            {
+                to_cast = MS_STRIKE_STAFF;
+            }
+            else if (oncardinal)
+            {
+                to_cast = MS_LIGHTNING;
+            }
+        }
+        else if (!zero_die(40))
+        {
+            /* 
+             * We lack LOS, but pass the 1-in-40 chance; use
+             * sorcery to relocate us to the player's location.
+             */
+            to_cast = MS_TELEPORT_ASSAULT;
+        }
+        break;
+
+    case PM_WIZARD:
+        if (cansee)
+        {
+            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+            {
+                to_cast = MS_TELEPORT_ESCAPE;
+            }
+            else if (meleerange && (zero_die(10) > 2))
+            {
+                to_cast = MS_STRIKE_STAFF;
+            }
+            else if (oncardinal)
+            {
+                to_cast = MS_LIGHTNING;
+            }
+        }
+        else if (!zero_die(80))
+        {
+            /* we lack LOS, but passed the 1-in-80 chance to
+             * close with the player by means of sorcery. */
+            to_cast = MS_TELEPORT_ASSAULT;
+        }
+        break;
+
+    case PM_MASTER_LICH:
+        if (cansee)
+        {
+            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 4))
+            {
+                to_cast = ((mptr->next_summon < game_tick) || !zero_die(3)) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+            }
+            else if (meleerange)
+            {
+                switch (zero_die(7))
+                {
+                case 6:
+                    if (!u.withering)
+                    {
+                        to_cast = MS_CURSE_WITHERING;
+                        break;
+                    }
+                case 5:
+                    if (!u.leadfoot)
+                    {
+                        to_cast = MS_CURSE_LEADFOOT;
+                        break;
+                    }
+                    /* fall through */
+                case 4:
+                    if (!u.armourmelt)
+                    {
+                        to_cast = MS_CURSE_ARMOURMELT;
+                        break;
+                    }
+                    /* fall through */
+                default:
+                    to_cast = zero_die(2) ? MS_CHILLING_TOUCH : MS_STRIKE_STAFF;
+                    break;
+                }
+            }
+            else if (range < 3)
+            {
+                switch (zero_die(10))
+                {
+                case 9:
+                    if (!u.withering)
+                    {
+                        to_cast = MS_CURSE_WITHERING;
+                        break;
+                    }
+                case 8:
+                    if (!u.leadfoot)
+                    {
+                        to_cast = MS_CURSE_LEADFOOT;
+                        break;
+                    }
+                    /* fall through */
+                case 7:
+                    if (!u.armourmelt)
+                    {
+                        to_cast = MS_CURSE_ARMOURMELT;
+                        break;
+                    }
+                    /* fall through */
+                default:
+                    to_cast = MS_NECRO_SMITE;
+                    break;
+                }
+            }
+            else if (range < 8)
+            {
+                switch (zero_die(7))
+                {
+                case 6:
+                    if (!u.withering)
+                    {
+                        to_cast = MS_CURSE_WITHERING;
+                        break;
+                    }
+                case 4:
+                    if (!u.leadfoot)
+                    {
+                        to_cast = MS_CURSE_LEADFOOT;
+                        break;
+                    }
+                    /* fall through */
+                case 5:
+                    if (!u.armourmelt)
+                    {
+                        to_cast = MS_CURSE_ARMOURMELT;
+                        break;
+                    }
+                    /* fall through */
+                default:
+                    to_cast = MS_NECRO_SMITE;
+                    break;
+                }
+            }
+        }
+        else if (!zero_die(40))
+        {
+            /* we lack LOS, but passed the 1-in-40 chance to
+             * close with the player by means of sorcery. */
+            to_cast = MS_TELEPORT_ASSAULT;
+        }
+        break;
+    case PM_LICH:
+        if (cansee)
+        {
+            if (meleerange)
+            {
+                dieroll = zero_die(6);
+                switch (dieroll)
+                {
+                case 4:
+                    if (!u.leadfoot)
+                    {
+                        to_cast = MS_CURSE_LEADFOOT;
+                        break;
+                    }
+                    /* fall through */
+                case 5:
+                    if (!u.armourmelt)
+                    {
+                        to_cast = MS_CURSE_ARMOURMELT;
+                        break;
+                    }
+                    /* fall through */
+                default:
+                    to_cast = MS_NECRO_STAFF;
+                    break;
+                }
+            }
+            else if (oncardinal)
+            {
+                if (range < 3)
+                {
+                    switch (zero_die(6))
+                    {
+                    case 4:
+                        if (!u.leadfoot)
+                        {
+                            to_cast = MS_CURSE_LEADFOOT;
+                            break;
+                        }
+                        /* fall through */
+                    case 5:
+                        if (!u.armourmelt)
+                        {
+                            to_cast = MS_CURSE_ARMOURMELT;
+                            break;
+                        }
+                        /* fall through */
+                    default:
+                        to_cast = MS_NECRO_BOLT;
+                        break;
+                    }
+                }
+                else
+                {
+                    to_cast = MS_NECRO_BOLT;
+                }
+            }
+            break;
+        }
+        break;
+    case PM_DEFILER:
+        if (cansee)
+        {
+            if (!meleerange || !zero_die(3))
+            {
+                // 1-in-7 chance of cursing you when in melee range
+                // 3-in-7 chance when out of melee range
+                switch (zero_die(7))
+                {
+                case 6:
+                    if (!u.withering)
+                    {
+                        to_cast = MS_CURSE_WITHERING;
+                        break;
+                    }
+                case 4:
+                    if (!u.leadfoot)
+                    {
+                        to_cast = MS_CURSE_LEADFOOT;
+                        break;
+                    }
+                    /* fall through */
+                case 5:
+                    if (!u.armourmelt)
+                    {
+                        to_cast = MS_CURSE_ARMOURMELT;
+                        break;
+                    }
+                    /* fall through */
+                default:
+                    to_cast = MS_REJECT;
+                    break;
+                }
+            }
+        }
+        break;
+
+    default:
+        break;
+    }
+    switch (to_cast)
+    {
+    default:
+        /* If this happens, we're trying to cast an unimplemented
+         * spell. */
+        debug_bad_monspell(to_cast);
+        rval = -1;
+        break;
+
+    case MS_REJECT:
+        /* No usable spell available. */
+        rval = 0;
+        break;
+
+    case MS_STRIKE_STAFF:
+        mhitu(mon, DT_PHYS);
+        break;
+
+    case MS_NECRO_STAFF:
+        mhitu(mon, DT_NECRO);
+        break;
+
+    case MS_CHILLING_TOUCH:
+        mhitu(mon, DT_COLD);
+        break;
+
+    case MS_LIGHTNING:
+    case MS_NECRO_BOLT:
+        mshootu(mon);
+        break;
+
+    case MS_TELEPORT_AND_SUMMON:
+        mptr->next_summon = game_tick + 1000;
+        /* (Try to) summon 2-6 monsters. */
+        i = summoning(mptr->pos, dice(2, 3));
+        notify_summon_help(mon, (i > 0));
+        /* ... and fall through. */
+    case MS_TELEPORT_ESCAPE:
+        teleport_mon(mon);
+        notify_mon_disappear(mon);
+        break;
+
+    case MS_TELEPORT_ASSAULT:
+        /* It is rare that a monster will cast this spell, but not
+         * unheard of. */
+        teleport_mon_to_you(mon);
+        break;
+
+    case MS_CURSE_ARMOURMELT:
+        notify_monster_cursing(mon);
+        if (u.protection)
+        {
+            notify_moncurse_fail();
+        }
+        else
+        {
+            u.armourmelt = 10 + one_die(10);
+            recalc_defence();
+            notify_start_armourmelt();
+        }
+        break;
+
+    case MS_CURSE_LEADFOOT:
+        notify_monster_cursing(mon);
+        if (u.protection)
+        {
+            notify_moncurse_fail();
+        }
+        else
+        {
+            u.leadfoot = 10 + one_die(10);
+            recalc_defence();
+            notify_start_leadfoot();
+        }
+        break;
+
+    case MS_CURSE_WITHERING:
+        notify_monster_cursing(mon);
+        if (u.protection)
+        {
+            notify_moncurse_fail();
+        }
+        else
+        {
+            u.withering = 10 + one_die(10);
+            recalc_defence();
+            notify_start_withering();
+        }
+        break;
+
+    case MS_NECRO_SMITE:
+        notify_monster_cursing(mon);
+        if (u.resists(DT_NECRO))
+        {
+            notify_necrosmite_fail();
+        }
+        else
+        {
+            damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+        }
+        break;
+
+    case MS_FIRE_COLUMN:
+        notify_monster_cursing(mon);
+        notify_hellfire_hit(u.resists(DT_FIRE));
+        if (u.resists(DT_FIRE))
+        {
+            damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+        }
+        else
+        {
+            damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+        }
+        break;
+    }
+    return rval;
+}
+
+/* sorcery.cc */
+// vim:cindent
diff --git a/sorcery.hh b/sorcery.hh
new file mode 100644 (file)
index 0000000..dfd647b
--- /dev/null
@@ -0,0 +1,77 @@
+/*! \file sorcery.hh
+ *  \brief Monster sorcery header
+ */
+
+/* Copyright 2005-2013 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SORCERY_HH
+#define SORCERY_HH
+
+/* XXX DATA TYPES XXX */
+
+enum monspell {
+    MS_REJECT = -1,         /* Rejection tag. */
+    /* "Melee" attacks */
+    MS_STRIKE_STAFF,        /* Wizard */
+    MS_NECRO_STAFF,         /* Lich */
+    MS_CHILLING_TOUCH,      /* Master Lich */
+    /* Ranged Attacks */
+    MS_LIGHTNING,   /* Wizard */
+    MS_NECRO_BOLT,  /* Lich */
+    MS_NECRO_SMITE, /* Master Lich - no cardinal alignment needed */
+    MS_FIRE_COLUMN, /* Defiler */
+    /* Curses */
+    MS_CURSE_ARMOURMELT,    /* All cursers */
+    MS_CURSE_LEADFOOT,      /* All cursers */
+    MS_CURSE_WITHERING,     /* Master Lich and Defiler only */
+    /* Evasion */
+    MS_TELEPORT_ESCAPE,     /* Wizard, Archmage, Master Lich */
+    MS_TELEPORT_AND_SUMMON, /* Archmage */
+    MS_TELEPORT_ASSAULT,    /* Wizard, Archmage, Master Lich */
+};
+
+enum Monspell_mode
+{
+    Msmode_melee,
+    Msmode_bolt,
+    Msmode_smite,
+    Msmode_self
+};
+
+class Monspell_desc
+{
+public:
+    char const *name;
+    Monspell_mode mode;
+};
+
+extern int mon_use_sorcery(int mon);
+extern void mon_curses(int mon);
+extern void malignant_aura(void);
+
+#endif
+
+/* sorcery.h */
+// vim:cindent
diff --git a/u.cc b/u.cc
new file mode 100644 (file)
index 0000000..ce957bf
--- /dev/null
+++ b/u.cc
@@ -0,0 +1,894 @@
+/*! \file u.cc
+ *  \brief Player-character functions and data
+ */
+
+/* 
+ * Copyright 2005-2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+bool action_rewrite(Action const *act, Action *revised_act);
+struct Player u;
+
+bool wielding_ranged_weapon(void)
+{
+    return permobjs[objects[u.weapon].obj_id].flags[0] & POF_RANGED_WEAPON;
+}
+
+bool wielding_melee_weapon(void)
+{
+    return permobjs[objects[u.weapon].obj_id].flags[0] & POF_MELEE_WEAPON;
+}
+
+void recalc_defence(void)
+{
+    int i;
+    for (i = 0; i < DT_COUNT; i++)
+    {
+        u.resistances[i] &= RESIST_MASK_TEMPORARY;
+    }
+    u.speed = (u.leadfoot ? 0 : 1);
+    if (u.armour != NO_OBJ)
+    {
+        u.defence = u.armourmelt ? 0 : permobjs[objects[u.armour].obj_id].power;
+        u.defence += u.withering ? (u.agility / 10) : (u.agility / 5);
+        switch (objects[u.armour].obj_id)
+        {
+        case PO_DRAGONHIDE_ARMOUR:
+        case PO_METEORIC_PLATE_ARMOUR:
+            u.resistances[DT_FIRE] |= RESIST_ARMOUR;
+            break;
+        case PO_ROBE_OF_SWIFTNESS:
+            u.speed++;
+            break;
+        default:
+            break;
+        }
+    }
+    else
+    {
+        u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
+    }
+    if (u.ring != NO_OBJ)
+    {
+        switch (objects[u.ring].obj_id)
+        {
+        case PO_FIRE_RING:
+            u.resistances[DT_FIRE] |= RESIST_RING;
+            break;
+        case PO_FROST_RING:
+            u.resistances[DT_COLD] |= RESIST_RING;
+            break;
+        case PO_VAMPIRE_RING:
+            u.resistances[DT_NECRO] |= RESIST_RING;
+            break;
+        }
+    }
+    notify_defence_recalc();
+}
+
+Action_cost move_player(Offset delta)
+{
+    Coord c = u.pos + delta;
+    if (!lvl.in_bounds(c))
+    {
+        debug_move_oob(c);
+        return Cost_none;       /* No movement. */
+    }
+    if (lvl.mon_at(c) != NO_MON)
+    {
+        if (u.weapon != NO_OBJ)
+        {
+            if ((objects[u.weapon].obj_id == PO_BOW) ||
+                (objects[u.weapon].obj_id == PO_CROSSBOW) ||
+                (objects[u.weapon].obj_id == PO_THUNDERBOW))
+            {
+                notify_swing_bow();
+                return Cost_none;
+            }
+        }
+        return player_attack(delta);
+    }
+    switch (lvl.terrain_at(c))
+    {
+    case WALL:
+    case MASONRY_WALL:
+    case AMETHYST_WALL:
+    case IRON_WALL:
+    case SKIN_WALL:
+    case BONE_WALL:
+        notify_cant_go();
+        return Cost_none;
+    case FLOOR:
+    case AMETHYST_FLOOR:
+    case IRON_FLOOR:
+    case SKIN_FLOOR:
+    case BONE_FLOOR:
+    case DOOR:
+    case STAIRS_UP:
+    case STAIRS_DOWN:
+    case ALTAR:
+        reloc_player(c);
+        return Cost_std;
+    case LAVA:
+        if (u.resistances[DT_FIRE])
+        {
+            if (lvl.terrain_at(u.pos) != LAVA)
+            {
+                notify_start_lavawalk();
+            }
+            reloc_player(c);
+            return Cost_std;
+        }
+        else
+        {
+            notify_blocked_lava();
+            return Cost_none;
+        }
+    case WATER:
+        if ((u.ring != NO_OBJ) && (objects[u.ring].obj_id == PO_FROST_RING))
+        {
+            if (lvl.terrain_at(u.pos) != WATER)
+            {
+                notify_start_waterwalk();
+            }
+            reloc_player(c);
+            return Cost_std;
+        }
+        else
+        {
+            notify_blocked_water();
+            return Cost_none;
+        }
+    }
+    return Cost_none;
+}
+
+void reloc_player(Coord c)
+{
+    Coord oc = u.pos;
+    u.pos = c;
+    look_around_you();
+    if (lvl.obj_at(c) != NO_OBJ)
+    {
+        notify_obj_at(c);
+    }
+}
+
+int gain_body(int amount)
+{
+    if (amount < 1)
+    {
+        debug_body_gain(amount);
+        return 0;
+    }
+    if (u.body < 99)
+    {
+        if (u.body + amount > 99)
+        {
+            amount = 99 - u.body;
+        }
+        u.body += amount;
+        notify_body_gain(amount);
+        return amount;
+    }
+    else
+    {
+        notify_wasted_gain();
+        return 0;
+    }
+}
+
+int drain_body(int amount, char const *what, int permanent)
+{
+    if (permanent)
+    {
+        u.body -= amount;
+    }
+    else
+    {
+        u.bdam += amount;
+    }
+    notify_body_drain(amount);
+    if ((u.body - u.bdam) < 0)
+    {
+        return do_death(DEATH_BODY, what);
+    }
+    return 0;
+}
+
+int gain_agility(int amount)
+{
+    if (amount < 1)
+    {
+        debug_agility_gain(amount);
+        return 0;
+    }
+    if (u.agility < 99)
+    {
+        if (u.agility + amount > 99)
+        {
+            amount = 99 - u.agility;
+        }
+        u.agility += amount;
+        recalc_defence();
+        notify_agility_gain(amount);
+        return amount;
+    }
+    else
+    {
+        notify_wasted_gain();
+        return 0;
+    }
+}
+
+int drain_agility(int amount, char const *what, int permanent)
+{
+    if (permanent)
+    {
+        u.agility -= amount;
+    }
+    else
+    {
+        u.adam += amount;
+    }
+    notify_agility_drain(amount);
+    if ((u.agility - u.adam) < 0)
+    {
+        return do_death(DEATH_AGILITY, what);
+    }
+    recalc_defence();
+    return 0;
+}
+
+int damage_u(int amount, Death d, char const *what)
+{
+    u.hpcur -= amount;
+    notify_player_damage_taken(amount);
+    if (u.hpcur < 0)
+    {
+        u.hpcur = 0;
+        return do_death(d, what);
+    }
+    return 0;
+}
+
+void heal_u(int amount, int boost, int loud)
+{
+    if (u.hpcur + amount > u.hpmax)
+    {
+        if (boost)
+        {
+            boost = 1;
+            u.hpmax++;
+        }
+        amount = u.hpmax - u.hpcur;
+    }
+    else
+    {
+        boost = 0;
+        u.hpcur += amount;
+    }
+    notify_player_heal(amount, boost, loud);
+    return;
+}
+
+int do_death(Death d, char const *what)
+{
+    bool really = true;
+
+    if (wizard_mode)
+    {
+        really = query_wizmode_death();
+    }
+    if (!really)
+    {
+        heal_u(1000, 0, true);
+        u.adam = 0;
+        u.bdam = 0;
+        recalc_defence();
+    }
+    else
+    {
+        game_finished = 1;
+        if (!wizard_mode)
+        {
+            log_death(d, what);
+        }
+        notify_death(d, what);
+    }
+    return really;
+}
+
+void u_init(char const *name)
+{
+    if (!name || !*name)
+    {
+        strcpy(u.name, "Matilda");
+    }
+    else
+    {
+        strncpy(u.name, name, 16);
+    }
+    u.body = 10;
+    u.bdam = 0;
+    u.agility = 10;
+    u.adam = 0;
+    u.hpmax = 20;
+    u.hpcur = 20;
+    u.experience = 0;
+    u.level = 1;
+    u.food = 2000;
+    memset(u.inventory, -1, sizeof u.inventory);
+    u.inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
+    u.inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+    u.inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere);
+    u.weapon = u.inventory[0];
+    u.ring = NO_OBJ;
+    u.armour = u.inventory[2];
+    recalc_defence();
+}
+
+int lev_threshold(int level)
+{
+    if (level < 10)
+    {
+        return 20 * (1 << (level - 1));
+    }
+    if (level < 20)
+    {
+        return 10000 * (level - 9);
+    }
+    if (level < 30)
+    {
+        return 100000 * (level - 18);
+    }
+    return INT_MAX;
+}
+
+void gain_experience(int amount)
+{
+    int hpgain;
+    int bodygain;
+    int agilgain;
+    u.experience += amount;
+    notify_exp_gain(amount);
+    if (u.experience > lev_threshold(u.level))
+    {
+        u.level++;
+        notify_level_gain();
+        if (!zero_die(2))
+        {
+            bodygain = gain_body(2);
+            agilgain = gain_agility(1);
+        }
+        else
+        {
+            bodygain = gain_body(1);
+            agilgain = gain_agility(2);
+        }
+        hpgain = u.body / 10 + 10;
+        if (u.hpmax + hpgain > 999)
+        {
+            hpgain = 999 - u.hpmax;
+        }
+        if (hpgain > 0)
+        {
+            /* v1.3: Policy change - gaining a level effectively
+             * heals you. */
+            u.hpcur += hpgain;
+            u.hpmax += hpgain;
+            notify_hp_gain(hpgain);
+        }
+    }
+}
+
+Pass_fail teleport_u(void)
+{
+    int cell_try;
+    Coord c;
+    for (cell_try = 0; cell_try < 200; cell_try++)
+    {
+        c = lvl.random_point(1);
+        if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos))
+        {
+            reloc_player(c);
+            notify_player_teleport();
+            return You_pass;
+        }
+    }
+    notify_player_telefail();
+    return You_fail;
+}
+
+void update_player(void)
+{
+    if (!(game_tick % 5) && (u.food >= 0) && (u.hpcur < u.hpmax))
+    {
+        /* Heal player for one hit point; do not allow HP gain,
+         * and don't say anything. */
+        heal_u(1, 0, 0);
+    }
+    else if (!(game_tick % 60) && (u.hpcur < u.hpmax * 3 / 4))
+    {
+        /* Hungry player heals much, much slower, and cannot regain
+         * all their hit points. */
+        heal_u(1, 0, 0);
+    }
+    /* Once you hit the nutrition endstop, your ring of regeneration stops
+     * working, and like normal regen, it won't raise you above 75% HP if
+     * your food counter is negative. */
+    if (((game_tick % 10) == 5) &&
+        (objects[u.ring].obj_id == PO_REGENERATION_RING) &&
+        (u.hpcur < ((u.food >= 0) ? u.hpmax : ((u.hpmax * 3) / 4))) &&
+        (u.food > MIN_FOOD))
+    {
+        /* Heal player for 1d3 hit points; do not allow HP gain,
+         * and don't say anything apart from the regen ring message. */
+        notify_player_regen();
+        heal_u(one_die(3), 0, 0);
+    }
+    if (u.food > MIN_FOOD)
+    {
+        int food_use = 1;
+        int squeal = 0;
+        if ((objects[u.ring].obj_id == PO_REGENERATION_RING) && !(game_tick % 2) && (u.food > MIN_FOOD))
+        {
+            /* If you are still less hungry than MIN_FOOD nutrition,
+             * use one more food every second tick if you are
+             * wearing a ring of regeneration. */
+            food_use++;
+        }
+        if ((u.food >= 100) && (u.food - food_use < 100))
+        {
+            squeal = 1;
+        }
+        if ((u.food >= 0) && (u.food < food_use))
+        {
+            squeal = 2;
+        }
+        u.food -= food_use;
+        notify_food_use(food_use, squeal);
+    }
+    if (u.leadfoot > 0)
+    {
+        u.leadfoot--;
+        if (!u.leadfoot)
+        {
+            notify_leadfoot_recovered();
+            recalc_defence();
+        }
+    }
+    if (u.armourmelt > 0)
+    {
+        u.armourmelt--;
+        if (!u.armourmelt)
+        {
+            notify_armourmelt_recovered();
+            recalc_defence();
+        }
+    }
+    if (u.withering > 0)
+    {
+        u.withering--;
+        if (!u.withering)
+        {
+            notify_wither_recovered();
+            recalc_defence();
+        }
+    }
+    if (u.protection > 0)
+    {
+        u.protection--;
+        if (!u.protection)
+        {
+            notify_protection_lost();
+        }
+    }
+}
+
+bool Player::resists(Damtyp dtype) const
+{
+    return resistances[dtype];
+}
+
+/*! \brief Action history for combo detection */
+std::deque<Action> past_actions;
+
+/*! \brief Constants used in combo detection logic */
+enum Combo_phase
+{
+    Combo_invalid,
+    Combo_valid,
+    Combo_finisher
+};
+
+/* \brief Process action combos
+ *
+ * This function is responsible for detecting whether the most recently
+ * attempted action is a possible step in a combo, a combo finisher, or a
+ * combo-invalid move (for example, quaffing a potion is a combo breaker). It
+ * uses the contents of the deque past_actions to make the decision.
+ *
+ * \todo Actually detect some combos
+ * \param act Pointer to attempted action
+ * \param revised_act Pointer to storage location for revised action
+ * \return true if revised_act was written to; false otherwise
+ */
+
+bool action_rewrite(Action const *act, Action *revised_act)
+{
+    Coord c = u.pos;
+    int mon = NO_MON;
+    Offset o;
+    Action tmp_act = *act;
+    Combo_phase p;
+    bool rewrite_flag = false;
+    switch (tmp_act.cmd)
+    {
+    case STAND_STILL:
+        p = Combo_valid;
+        break;
+    case ATTACK:
+        o.y = tmp_act.details[0];
+        o.x = tmp_act.details[1];
+        c = u.pos + o;
+        if (!lvl.in_bounds(c))
+        {
+            debug_move_oob(c); // TODO notify_attack_oob()
+            tmp_act.cmd = REJECTED_ACTION;
+            rewrite_flag = true;
+            p = Combo_invalid;
+            break;
+        }
+        mon = lvl.mon_at(c);
+        p = Combo_finisher;
+        break;
+    case WALK:
+        o.y = tmp_act.details[0];
+        o.x = tmp_act.details[1];
+        c = u.pos + o;
+        if (!lvl.in_bounds(c))
+        {
+            debug_move_oob(c);
+            tmp_act.cmd = REJECTED_ACTION;
+            rewrite_flag = true;
+            p = Combo_invalid;
+        }
+        else if (lvl.mon_at(c) != NO_MON)
+        {
+            mon = lvl.mon_at(c);
+            tmp_act.cmd = ATTACK;
+            rewrite_flag = true;
+            p = Combo_finisher;
+        }
+        else
+        {
+            p = Combo_valid;
+        }
+        break;
+    default:
+        rewrite_flag = false;
+        p = Combo_invalid;
+        break;
+    }
+    switch (p)
+    {
+    case Combo_invalid:
+        break;
+    case Combo_valid:
+        if (past_actions.empty())
+        {
+            past_actions.push_front(tmp_act);
+        }
+        else
+        {
+            switch (tmp_act.cmd)
+            {
+            case STAND_STILL:
+                /* For now, no combo opens with more than one stand still,
+                 * and no combo starts with something else then chains through
+                 * a stand still */
+                if (past_actions.front().cmd != STAND_STILL)
+                {
+                    past_actions.clear();
+                    past_actions.push_front(tmp_act);
+                }
+                break;
+            case WALK:
+                /* For now, no combo chains through a WALK, but the charge
+                 * combo will when I have the mental effort to write it. */
+                past_actions.clear();
+                break;
+            default:
+                DEBUG_CONTROL_FLOW();
+                break;
+            }
+        }
+        break;
+    case Combo_finisher:
+        if (!past_actions.empty())
+        {
+            switch (past_actions.front().cmd)
+            {
+            case STAND_STILL:
+                if ((empty_handed() || wielding_melee_weapon()) &&
+                    (mon != NO_MON))
+                {
+                    rewrite_flag = true;
+                    tmp_act.cmd = CMD_POWER_ATTACK;
+                }
+                break;
+            case WALK:
+                break;
+            default:
+                DEBUG_CONTROL_FLOW();
+                break;
+            }
+        }
+        past_actions.clear();
+        break;
+    }
+    if (rewrite_flag)
+    {
+        *revised_act = tmp_act;
+    }
+    return rewrite_flag;
+}
+
+/*! \brief Process a player action.
+ *
+ * \todo Make CMD_POWER_ATTACK do something special
+ */
+Action_cost do_player_action(Action *act)
+{
+    int slot;
+    Offset step;
+    Action redact;
+    bool rewritten = action_rewrite(act, &redact);
+    if (rewritten)
+    {
+        act = &redact;
+    }
+    switch (act->cmd)
+    {
+    case REJECTED_ACTION:
+        return Cost_none;
+
+    case CMD_POWER_ATTACK:
+        step.y = act->details[0];
+        step.x = act->details[1];
+        return player_power_attack(step);
+
+    case WALK:
+        step.y = act->details[0];
+        step.x = act->details[1];
+        return move_player(step);
+
+    case ATTACK:
+        step.y = act->details[0];
+        step.x = act->details[1];
+        return player_attack(step);
+
+    case USE_ACTIVE_SKILL:
+        debug_unimplemented();
+        return Cost_none;
+
+    case ALLOCATE_SKILL_POINT:
+        debug_unimplemented();
+        return Cost_none;
+
+    case GET_ITEM:
+        if (lvl.obj_at(u.pos) != NO_OBJ)
+        {
+            attempt_pickup();
+            return Cost_std;
+        }
+        else
+        {
+            notify_nothing_to_get();
+            return Cost_none;
+        }
+
+    case WIELD_WEAPON:
+        slot = act->details[0];
+        if (slot == SLOT_NOTHING)
+        {
+            return player_unwield(Noise_std);
+        }
+        else
+        {
+            return player_wield(slot, Noise_std);
+        }
+        break;
+
+    case WEAR_ARMOUR:
+        slot = act->details[0];
+        return wear_armour(slot);
+
+    case EMANATE_ARMOUR:
+        if (u.armour == NO_OBJ)
+        {
+            notify_emanate_no_armour();
+            return Cost_none;
+        }
+        return emanate_armour();
+
+    case ZAP_WEAPON:
+        if (u.weapon == NO_OBJ)
+        {
+            notify_zap_no_weapon();
+            return Cost_none;
+        }
+        return zap_weapon();
+
+    case MAGIC_RING:
+        if (u.weapon == NO_OBJ)
+        {
+            notify_magic_no_ring();
+            return Cost_none;
+        }
+        return magic_ring();
+
+    case TAKE_OFF_ARMOUR:
+        if (u.armour != NO_OBJ)
+        {
+            int saved_armour = u.armour;
+            if ((u.resistances[DT_FIRE] == RESIST_ARMOUR) &&
+                (lvl.terrain_at(u.pos) == LAVA))
+            {
+                notify_lava_blocks_unequip();
+                return Cost_none;
+            }
+            u.armour = NO_OBJ;
+            recalc_defence();
+            notify_armour_unequip(saved_armour);
+            return Cost_std;
+        }
+        else
+        {
+            debug_take_off_no_armour();
+            return Cost_none;
+        }
+
+    case GO_UP_STAIRS:
+        if (lvl.terrain_at(u.pos) == STAIRS_UP)
+        {
+            notify_ascent_blocked();
+        }
+        else
+        {
+            debug_ascend_non_stairs();
+        }
+        return Cost_none;
+
+    case GO_DOWN_STAIRS:
+        if (lvl.terrain_at(u.pos) == STAIRS_DOWN)
+        {
+            leave_level();
+            make_new_level();
+        }
+        else
+        {
+            debug_descend_non_stairs();
+        }
+        return Cost_none;
+
+    case STAND_STILL:
+        return Cost_std;
+
+    case READ_SCROLL:
+        slot = act->details[0];
+        return read_scroll(u.inventory[slot]);
+
+    case EAT_FOOD:
+        slot = act->details[0];
+        return eat_food(u.inventory[slot]);
+
+    case QUAFF_POTION:
+        slot = act->details[0];
+        return quaff_potion(u.inventory[slot]);
+
+    case THROW_FLASK:
+        return Cost_none;
+
+    case REMOVE_RING:
+        return remove_ring();
+
+    case PUT_ON_RING:
+        slot = act->details[0];
+        return put_on_ring(u.inventory[slot]);
+
+    case DROP_ITEM:
+        slot = act->details[0];
+        return drop_obj(slot);
+
+    case SAVE_GAME:
+        game_finished = 1;
+        save_game();
+        return Cost_none;
+
+    case QUIT:
+        game_finished = 1;
+        return Cost_none;
+
+    case WIZARD_DESCEND:
+        if (wizard_mode)
+        {
+            leave_level();
+            make_new_level();
+        }
+        else
+        {
+            debug_wizmode_violation();
+        }
+        return Cost_none;
+
+    case WIZARD_LEVELUP:
+        if (wizard_mode)
+        {
+            if (lev_threshold(u.level) != INT_MAX)
+            {
+                gain_experience((lev_threshold(u.level) - u.experience) + 1);
+            }
+        }
+        else
+        {
+            debug_wizmode_violation();
+        }
+        return Cost_none;
+    }
+    return Cost_none;
+}
+
+void player_cleanup(void)
+{
+    int i;
+    memset(u.sympathy, '\0', sizeof u.sympathy);
+    memset(u.resistances, '\0', sizeof u.resistances);
+    u.experience = u.level = 0;
+    u.body = u.agility = u.bdam = u.adam = 0;
+    memset(u.name, '\0', sizeof u.name);
+    u.name[0] = '\0';
+    u.pos.y = u.pos.x = 0;
+    u.hpmax = u.hpcur = 0;
+    u.food = 0;
+    u.leadfoot = u.protection = u.withering = u.armourmelt = 0;
+    u.level = 0;
+    u.weapon = u.armour = u.ring = NO_OBJ;
+    for (i = 0; i < 19; ++i)
+    {
+        u.inventory[i] = NO_OBJ;
+    }
+}
+
+/* u.cc */
+// vim:cindent:expandtab
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..0c666cc
--- /dev/null
+++ b/util.c
@@ -0,0 +1,380 @@
+/* \file util.c
+ * \brief Various helper functions in pure C, originally for Victrix Abyssi
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "util.h"
+#include <basedir.h>
+
+#undef UTIL_DEBUG
+//#define UTIL_DEBUG
+
+#define UTIL_PATHSEP_CHAR '/'
+#define UTIL_PATHSEP_STRING "/"
+
+static char *app_suffix(char const *vendor, char const *app);
+
+/*! \brief Check whether a specified path points to an existing directory.
+ */
+int verify_dir_available(char const *path)
+{
+    struct stat s;
+    int i;
+    if (!path || !*path)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+    i = stat(path, &s);
+    if (i != 0)
+    {
+        return -1;
+    }
+    if ((s.st_mode & S_IFMT) != S_IFDIR)
+    {
+        errno = ENOTDIR;
+        return -1;
+    }
+    return 0;
+}
+
+static xdgHandle util_xdg_handle;
+static char const *util_data_home;
+static char const *util_config_home;
+static char *util_data_dir;
+static char *util_config_dir;
+static char *util_app_subdir;
+static DIR *util_data_dp;
+static DIR *util_config_dp;
+
+/*! \brief Set up directory access for config and data files
+ *
+ * This function uses libxdg-basedir to obtain the XDG-specified configuration
+ * and data home directories, then autovivifies app-specific subdirectories
+ * within those directories.
+ *
+ * \param vendor "Vendor name" for building directory suffix
+ * \param app "Application name" for building directory suffix
+ * \param data_fd Location to store data directory fd
+ * \param config_fd Location to store config directory fd
+ */
+int setup_dirs(char const *vendor, char const *app, int *data_fd, int *config_fd)
+{
+    int i;
+    xdgHandle *x;
+    if (!data_fd || !config_fd)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+    if (!util_data_dp || !util_config_dp)
+    {
+        x = xdgInitHandle(&util_xdg_handle);
+        if (!x)
+        {
+            return -1;
+        }
+        if (!util_app_subdir)
+        {
+            if (!vendor || !app || !*vendor || !*app)
+            {
+                errno = EINVAL;
+                return -1;
+            }
+            util_app_subdir = app_suffix(vendor, app);
+            if (!util_app_subdir)
+            {
+                return -1;
+            }
+        }
+        if (!util_data_home)
+        {
+            util_data_home = xdgDataHome(x);
+            if (!util_data_home)
+            {
+                return -1;
+            }
+        }
+        if (!util_config_home)
+        {
+            util_config_home = xdgConfigHome(x);
+            if (!util_config_home)
+            {
+                return -1;
+            }
+        }
+        if (!util_data_dir)
+        {
+            util_data_dir = strbuild(util_data_home, util_app_subdir);
+            if (!util_data_dir)
+            {
+                return -1;
+            }
+        }
+        if (!util_config_dir)
+        {
+            util_config_dir = strbuild(util_config_home, util_app_subdir);
+            if (!util_config_dir)
+            {
+                return -1;
+            }
+        }
+        if (!util_config_dp)
+        {
+            i = verify_dir_available(util_config_dir);
+            if (i != 0)
+            {
+                if (errno == ENOENT)
+                {
+                    i = my_makepath(util_config_dir, S_IRWXU);
+                    if (i != 0)
+                    {
+                        return -1;
+                    }
+                }
+            }
+            util_config_dp = opendir(util_config_dir);
+            if (!util_config_dp)
+            {
+                return -1;
+            }
+        }
+        if (!util_data_dp)
+        {
+            i = verify_dir_available(util_data_dir);
+            if (i != 0)
+            {
+                if (errno == ENOENT)
+                {
+                    i = my_makepath(util_data_dir, S_IRWXU);
+                    if (i != 0)
+                    {
+                        return -1;
+                    }
+                }
+            }
+            util_data_dp = opendir(util_data_dir);
+            if (!util_data_dp)
+            {
+                return -1;
+            }
+        }
+    }
+    *data_fd = dirfd(util_data_dp);
+    *config_fd = dirfd(util_config_dp);
+    return 0;
+}
+
+/*! \brief Create a C string that is a concatenation of two existing strings
+ *
+ * The string returned by this function has been allocated with malloc() and
+ * should be released with free() when it is no longer required.
+ */
+char *strbuild(char const *left, char const *right)
+{
+    if (!left || !right)
+    {
+        return NULL;
+    }
+    size_t s1 = strlen(left);
+    size_t s2 = strlen(right);
+    size_t s = s1 + s2 + 1;
+    char *str = (char *) malloc(s);
+    if (str)
+    {
+        strcpy(str, left);
+        strcpy(str + s1, right);
+    }
+    return str;
+}
+
+/*! \brief Iteratively build the directories of a given path.
+ *
+ * This function exists because there is no POSIX (or Linux) syscall that does
+ * the equivalent of system("mkdir -p my/path/here") - which is really
+ * unfortunate, because this means that creating such a directory structure
+ * is inherently racy.
+ */
+int my_makepath(char const *path, int mode)
+{
+    if (!path)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+#ifdef UTIL_DEBUG
+    fprintf(stderr, "my_makepath(\"%s\", %d)\n", path, mode);
+#endif
+    char *realpath = strdup(path);
+    char *pathtrak = ((realpath[0] == UTIL_PATHSEP_CHAR) ? realpath + 1 : realpath);
+    /* this will FUBAR on Windows, but since I'm not responsible for the
+     * Windows port, whoever *is* responsible for the Windows port can have
+     * the pleasure of figuring out how to do the equivalent job. */
+    int rv = -1;
+    do
+    {
+        struct stat s;
+        int i;
+        pathtrak = strchr(pathtrak, UTIL_PATHSEP_CHAR);
+        if (pathtrak)
+        {
+            pathtrak[0] = '\0';
+#ifdef UTIL_DEBUG
+            fprintf(stderr, "%d Checking %s (pathtrak offset %d)\n", __LINE__, realpath, (int) (pathtrak - realpath));
+#endif
+            i = stat(realpath, &s);
+            if (i == 0)
+            {
+#ifdef UTIL_DEBUG
+                fprintf(stderr, "%d stat(\"%s\") returned 0\n", __LINE__, realpath);
+#endif
+                if ((s.st_mode & S_IFMT) == S_IFDIR)
+                {
+                    pathtrak[0] = UTIL_PATHSEP_CHAR;
+                    ++pathtrak;
+                }
+                else
+                {
+#ifdef UTIL_DEBUG
+                    fprintf(stderr, "%d not a directory\n", __LINE__);
+#endif
+                    errno = ENOTDIR;
+                    rv = -1;
+                    break;
+                }
+            }
+            else
+            {
+#ifdef UTIL_DEBUG
+                fprintf(stderr, "%d stat(\"%s\") returned %d, errno %d (%s)\n",
+                        __LINE__, realpath, i, errno, strerror(errno));
+#endif
+                if (errno == ENOENT)
+                {
+#ifdef UTIL_DEBUG
+                    fprintf(stderr, "%d mkdir(\"%s\", %d)\n", __LINE__, realpath, mode);
+#endif
+                    i = mkdir(realpath, mode);
+                    if (i != 0)
+                    {
+#ifdef UTIL_DEBUG
+                        fprintf(stderr, "%d mkdir(\"%s\", %d) returned %d, errno %d (%s)\n", __LINE__, realpath, mode, i, errno, strerror(errno));
+#endif
+                        rv = -1;
+                        break;
+                    }
+                    pathtrak[0] = UTIL_PATHSEP_CHAR;
+                    ++pathtrak;
+                }
+            }
+        }
+        else
+        {
+            i = stat(realpath, &s);
+            if (i == 0)
+            {
+#ifdef UTIL_DEBUG
+                fprintf(stderr, "%d stat(\"%s\") returned 0\n", __LINE__, realpath);
+#endif
+                if ((s.st_mode & S_IFMT) == S_IFDIR)
+                {
+                    rv = 0;
+                }
+                else
+                {
+#ifdef UTIL_DEBUG
+                    fprintf(stderr, "not a directory\n");
+#endif
+                    errno = ENOTDIR;
+                    rv = -1;
+                }
+            }
+            else
+            {
+                if (errno == ENOENT)
+                {
+#ifdef UTIL_DEBUG
+                    fprintf(stderr, "%d mkdir(\"%s\", %d)\n", __LINE__, realpath, mode);
+#endif
+                    i = mkdir(realpath, mode);
+                    if (i != 0)
+                    {
+#ifdef UTIL_DEBUG
+                        fprintf(stderr, "%d mkdir(\"%s\", %d) returned %d, errno %d (%s)\n", __LINE__, realpath, mode, i, errno, strerror(errno));
+#endif
+                        rv = -1;
+                    }
+                    else
+                    {
+                        fprintf(stderr, "%d mkdir(\"%s\", %d) returned 0\n", __LINE__, realpath, mode);
+                        rv = 0;
+                    }
+                }
+            }
+        }
+    } while (pathtrak);
+    free(realpath);
+    return rv;
+}
+
+/*! \brief compose a directory suffix from a vendor name and an app name
+ *
+ * \todo include proper sanitization of vendor and app strings
+ * \param vendor "Vendor name" to use in suffix
+ * \param app "Application name" to use in suffix
+ */
+static char *app_suffix(char const *vendor, char const *app)
+{
+    size_t s1;
+    size_t s2;
+    size_t s3;
+    size_t s4;
+    char *str;
+    if (!vendor || !*vendor || !app || !*app)
+    {
+        errno = EINVAL;
+        return NULL;
+    }
+    // TODO
+    s1 = strlen(vendor);
+    s2 = strlen(UTIL_PATHSEP_STRING);
+    s3 = strlen(app);
+    s4 = 1 + s3 + 2*s2 + s1;
+    str = (char *) malloc(s4);
+    if (str)
+    {
+        strcpy(str, UTIL_PATHSEP_STRING);
+        strcpy(str + s2, vendor);
+        strcpy(str + s1 + s2, UTIL_PATHSEP_STRING);
+        strcpy(str + s1 + 2*s2, app);
+    }
+    return str;
+}
+
+/*
+ * vim:ts=8:sts=4:sw=4:expandtab:cindent
+ */
+/* util.c */
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..126ecad
--- /dev/null
+++ b/util.h
@@ -0,0 +1,61 @@
+/* \file util.h
+ * \brief Header for pure C helper functions
+ */
+
+/* Copyright 2014 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int setup_dirs(char const *vendor, char const *app, int *data_fd, int *config_fd);
+int verify_dir_available(char const *path);
+int my_makepath(char const *path, int mode);
+char *strbuild(char const *left, char const *right);
+int stdio_mode_to_unistd_flags(char const *mode);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
+
+/*
+ * vim:ts=8:sts=4:sw=4:expandtab:cindent
+ */
+/* util.h */