Added trunk

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@4 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 17 years ago
parent 7d576895e2
commit 055d1b1dd6

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

@ -0,0 +1,232 @@
OpenMW - the completely unofficial reimplementation of Morrowind
================================================================
Written by Nicolay Korslund
Email: korslund@gmail.com
WWW: http://openmw.snaptoad.com
License: See GPL3.txt
Current version: 0.2 (second release, very pre-alpha)
Date: 2008 jun. 17
QUICK NOTE: You must own and install Morrowind before you can use
OpenMW. Let me repeat that: OpenMW will NOT run if you do not have
Morrowind installed on your system!
New in version 0.2
==================
The second release should now work with the GDC compiler (the D
frontend for GCC) and DSSS. Since GDC is the only option for compiling
on other Unixes, on 64 bit and on Mac, this will probably make porting
to these platforms much easier. DSSS (a specialized D build tool) will
hopefully make porting to Windows a lot easier.
To compile, you no longer use 'make'. Instead you use 'dsss build',
which will run 'make' for you. (More details below.)
See the changelog at the end.
Installation from source
========================
These instructions are for the brave souls who seek to compile OpenMW
on their own. If you are using the binary version, you can skip to the
next section.
Supported platforms:
--------------------
The only operating system that has been tested and is known to work is
32bit Ubuntu Linux 8.04. Windows and other platforms have not been
tested as of yet.
Dependencies:
-------------
Dependencies needed to build OpenMW:
OGRE 1.4.5 (3d engine)
Audiere 1.9.4 (sound engine)
OIS-1.2.0 (input system)
gcc and g++ (C++ compiler)
GNU make (build tool for C++ files)
DMD 1.030 (D compiler)
or GDC 4.1.3 (alternative D compiler)
Monster 0.8 (scripting language and tools)
DSSS 0.75 (D build tool)
curl (for DSSS)
The above versions are the ones I have tested recently, but other
versions might work. OGRE and Audiere will require their own set of
dependencies. I recommend using an automated package tool to install
as many of these as possible. On ubuntu, try typing:
sudo apt-get install libogre-dev libaudiere-dev build-essential g++ curl gdc
This takes care of OGRE, Audiere, the C and D compilers, make and
curl. The rest have to be installed manually. There is a libois-dev
package in Ubuntu (OIS version 0.99), but this has NOT been tested.
You can find the other libraries and tools here:
OIS: http://sourceforge.net/projects/wgois/
DMD: http://digitalmars.com/d/1.0/dmd-linux.html
DSSS: http://svn.dsource.org/projects/dsss/downloads/
Monster: http://monster.snaptoad.com/download.html
If you are using a DSSS binary and not compiling from source (I
recommend the binary), make sure to get one that matches your D
compiler. Ie. get the GDC version if you installed GDC, and the DMD
version for DMD.
If you want to install Ogre and Audiere manually as well, try:
OGRE: http://ogre3d.org
Audiere: http://audiere.sourceforge.net/
Once everything is set up correctly, you might need to alter a couple
of lines in the Makefile. The Makefile is only used to compile the C++
parts of OpenMW, that interfaces with Ogre, OIS and Audiere. On the
line:
OGCC=g++ `pkg-config --cflags OGRE`
Insert the path to your OIS include directory (or leave it blank if
you installed OIS in /usr). For example, you might add:
-I/home/me/my/path/to/OIS/include
You might also need to add other include paths or options for Ogre if
the pkg-config command doesn't work for some reason. Mine outputs
$ pkg-config --cflags OGRE
-DOGRE_GUI_GLX -DOGRE_CONFIG_LITTLE_ENDIAN -I/usr/include/OGRE
After you are reasonably satisfied, you can try running make manually
to see if the C++ parts compile. When you are ready to compile the D
parts and link it all together, type:
dsss build
If something goes terribly wrong during the build (it probably will),
_and_ you figure out how to solve it, I would appreciate if you told
me about it so I could update these instructions.
Running the binary
==================
The binary downloads have been compiled on a 32-bit Ubuntu 8.04 box
with the libraries mentioned below. They might not work on other linux
systems due to differing library setups. If this is the case, then
your only option is to compile from source.
Dependencies:
-------------
The binary depends on the following libraries:
OGRE 1.4
Audiere 1.9.4
OIS-1.2.0
and Morrowind of course. If you followed the compilation instructions
above, you will have these already. If not, you can find them here:
OGRE: http://ogre3d.org
Audiere: http://audiere.sourceforge.net/
OIS: http://sourceforge.net/projects/wgois/
Alternatively (on Ubuntu 8.04) you can at least get Ogre and Audiere
with the command:
sudo apt-get install libogre14 libaudiere-1.9.4
Configuration:
--------------
Before you can run OpenMW, you have to help it find the Morrowind data
files. The 'morro' program needs the files Morrowind.esm and
Morrowind.bsa, and the directories Sound/ and Music/ from your
Morrowind Data Files directory. By default it expects to find these in
the data/ directory. (Can be changed in morro.ini.)
I recommend creating a symbolic link to your original Morrowind
install. For example, if you have Morrowind installed in:
c:\Program Files\Bethesda Softworks\Morrowind\
and your windows c: drive is mounted on /media/hda1, then run the
following command:
ln -s "/media/hda1/Program Files/Bethesda Softworks/Morrowind/Data Files/" data
Also, if you have OGRE installed in a non-standard directory (ie. NOT
to /usr/lib/OGRE), you have to change the PluginFolder in plugins.cfg.
Finally, you can change screen resolution and fullscreen mode in
ogre.cfg. (I don't recommend fullscreen mode yet, since it may mess up
your screen and input settings if the program crashes.)
Running OpenMW:
---------------
If Azura is with you and all the stars and planets are aligned in your
favor, you should now be able to run OpenMW using the program called
'morro'.
Write morro -h to see a list of options.
Running without parameters should bring you into the cave called Sud,
or the last cell loaded. You are in free float mode. Move around with
WASD (or arrow keys), move up and down with left shift and ctrl, exit
with 'q' or escape.
To load another cell, specify the cell name on the command line. Use
the 'esmtool' program to get a list of cells (see below.) Note that
you must use quotation marks "" if the cell name contains spaces or
other weird characters. Exterior cells are disabled at the moment.
Enjoy! ;-)
Other included tools:
=====================
esmtool - Used to inspect ES files (ESM, ESP, ESS). Run without
arguments to get a list of options.
bsatool - Tool for viewing and extracting files from BSA archives.
(Can also be used to test the NIF parser on a BSA.)
niftool - Decodes one or more NIF files and prints the details.
Changelog:
==========
0.3 (work in progress)
- updated Makefile and sources for increased portability (thanks to
Dmitry Marakasov for FreeBSD tips and testing!)
0.2 (2008 jun. 17) - latest release
- compiles with gdc
- switched to DSSS for building D code
- includes the program esmtool
0.1 (2008 jun. 03)
- first release

@ -0,0 +1,33 @@
# Designed for GNU Make
# Compiler settings
CXX?= g++
CXXFLAGS?=
# Replace this path with your OIS include path
OIS_PATH=../../software/include
# Compiler settings for Ogre + OIS. Change as needed.
OGCC=$(CXX) $(CXXFLAGS) `pkg-config --cflags OGRE` -I$(OIS_PATH)
# Compiler settings for Audiere
AGCC=$(CXX) $(CXXFLAGS) `audiere-config --cxxflags`
# Ogre C++ files, on the form ogre/cpp_X.cpp. Only the first file is
# passed to the compiler, the rest are dependencies.
ogre_cpp=ogre framelistener interface overlay bsaarchive
# Audiere C++ files, on the form sound/cpp_X.cpp.
audiere_cpp=audiere
## The rest of this file is automatic ##
ogre_cpp_files=$(ogre_cpp:%=ogre/cpp_%.cpp)
audiere_cpp_files=$(audiere_cpp:%=sound/cpp_%.cpp)
all: cpp_ogre.o cpp_audiere.o
cpp_ogre.o: $(ogre_cpp_files)
$(OGCC) -c $<
cpp_audiere.o: $(audiere_cpp_files)
$(AGCC) -c $<

@ -0,0 +1,364 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bored.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module bored;
import std.stdio;
import std.cstream;
import std.stream;
import std.random;
import std.file;
double rnd()
{
return cast(double)std.random.rand()/uint.max;
}
int rand(int a, int b)
{
return cast(int)((1+b-a)*rnd()+a);
}
void main()
{
int gold = 0;
int loot = 0;
int level = 1;
int life = 20;
bool play = true;
writefln("\nWelcome to Norrowind!");
while(play)
{
writefln();
if(life < 4) writefln("You are badly hurt.");
else if(life < 10) writefln("You are injured.");
else if(life < 15) writefln("You are slightly wounded.");
if(gold) writefln("You have %d gold.", gold);
if(loot) writefln("You have loot.");
if(level>1) writefln("You are level ", level);
writefln("
1) Kill monster
2) Read a book
3) Read an NPC
4) Sell some loot
5) Walk around and get drunk on skooma
6) Watch TEH k33wL P1XAL SHADID W4T3R!!!!1
7) Exit
");
uint input;
dout.writef("Your choice: ");
do
{
input = cast(uint)(din.getc() - '0');
}
while(input >= 8);
if(rnd() < 0.01)
{
writefln("Program has performed an illegal instruction, the programmer will be shot.");
break;
}
writefln();
if(((input == 5) || (input == 3) || (input == 1)) && (loot > 100))
{
writefln("You are encumbered and cannot move. Try selling some of your junk.");
continue;
}
char[] str;
int lootinc, goldinc;
int oldlife = life;
switch(input)
{
case 1: // Hunting
if(rnd() < 0.02)
{
writefln("You killed a Bodak and a Greater Mumm.. oops, wrong game, never mind.");
break;
}
if(rnd() < 0.02)
{
writefln(
"You were killed by an white striped aquatic flying dire gigant dragon
bear in a Balmora mansion. This is largely your own fault for using all
those plugins.");
play=false;
break;
}
switch(rand(0,15))
{
case 0: str = "Fjol the Outlaw"; goldinc = rand(0,70); lootinc = rand(10,120); break;
case 1: str = "a Betty Netch"; lootinc = rand(0,7); break;
case 2: str = "a Vampire"; goldinc = rand(0,10); lootinc = rand(20,40); break;
case 3: str = "a Dremora"; lootinc = rand(50,200); break;
case 4: str = "some NPC"; goldinc = rand(0,80); lootinc = rand(3,35); break;
case 5: str = "an Ordinator"; lootinc = rand(30,45); break;
case 6: str = "a Skeleton"; lootinc = 1; break;
case 7: str = "Fargoth"; goldinc = 10; lootinc = 4; break;
case 8: str = "a Cliff Racer"; lootinc = 2; break;
case 9: str = "Vivec"; lootinc = rand(0,20); goldinc = rand(0,60); life-=rand(1,2); break;
case 10: str = "a soultrapped Vivec"; goldinc = rand(0,60); lootinc = rand(100,300);
life-=rand(1,3); break;
case 11: str = "an Ascended Sleeper"; lootinc = rand(5,12); goldinc = rand(0,10); break;
case 12: str = "the entire town of Gnaar Mok"; goldinc = rand(40,50); lootinc = rand(70,140);
life-=rand(0,2); break;
case 13: str = "a Bethesda programmer for being so late with Oblivion"; break;
case 14: str = "a Werewolf. Which is kinda strange since you don't have Bloodmoon"; lootinc = rand(4,50); break;
case 15: str = "an important quest character. Way to go"; goldinc = rand(0,40); lootinc = rand(0,70); break;
}
if(rnd() < 0.65)
life -= rand(1,8);
if(life > 0)
{
writefln("You killed ", str, ".");
if(life < oldlife) writefln("You were hurt in the fight.");
else writefln("You survived the fight unscathed.");
if(goldinc) writefln("You got ", goldinc, " bucks.");
if(lootinc) writefln("You found some loot.");
gold += goldinc;
loot += lootinc;
if(rnd() < 0.2)
{
writefln("You have gained a level!");
life += rand(3,10);
level++;
}
}
else
{
writefln("You met ", str, " and were killed.");
play = false;
}
break;
case 2:// Book
switch(rand(0,5))
{
case 0:
writefln("You read The History of The Emipire and fell asleep.");
break;
case 1:
writefln("You read The Pilgrim's Path and became a fanatical religious nut.");
break;
case 2:
writefln("You read the scroll 'Divine Intervention' and suddenly found yourself
outside, wearing only your night gown and slippers.");
break;
case 3:
writefln("You read Divine Metaphysics. Again");
if(rnd()<0.09)
{
writefln("You discovered where the dwarwes went! And, more importantly, where
they stashed all their loot.");
loot += 1000;
}
break;
case 4:
writefln("You learned a new skill.");
if(rnd() < 0.4) level++;
break;
case 5:
writefln("You dropped a book on you toe.");
life--;
if(life == 0)
{
writefln("You are dead.");
play = false;
}
break;
}
break;
case 3://NPC
if(rnd()<0.05)
{
writefln("Nobody wants to speak with you.");
break;
}
writefln("You met an NPC");
switch(rand(0,9))
{
case 0: writefln("He had nothing interesting to say."); break;
case 1: writefln("She was really boring."); break;
case 2: writefln("You got a quest!"); break;
case 3: writefln("You completed a quest and got some dough."); gold += rand(1,10); break;
case 4: writefln("The nice NPC gave you a healing potion."); life+=rand(2,4); break;
case 5: writefln("You robbed 'em blind and got some loot."); loot+=(10,20); break;
case 6: writefln("The guard took some of your money, saying you were
late on your child support payments."); gold = gold/3; break;
case 7: writefln("You spent some money on bribes"); gold -= gold/4; break;
case 8: writefln("You had to travel all the way accross the island to talk to this person."); gold -= gold/4; break;
case 9: writefln("The Breton mistook you for his mother, and gave you tons of gold."); gold += 100; break;
}
break;
case 4://Sell
if(loot == 0)
writefln("You have nothing to sell (except that moon sugar and the home made poetry that nobody wants)");
else if(rnd()<0.93)
{
goldinc = cast(int)(loot*rnd()*2);
if(goldinc > loot) writefln("The merchant likes you, you got ", goldinc, " gold for stuff worth only ", loot, ".");
if(goldinc <= loot) writefln("The merchant didn't like you, your ", loot, " worth of stuff
only got you ", goldinc, " gold.");
}
else
{
writefln("You met a talking mudcrab and an unfunny scamp! You got lots of\ncash for your loot.");
goldinc = 5*loot;
}
gold += goldinc;
loot = 0;
break;
case 5://Skooma
switch(rand(0,7))
{
case 0:
str = "gigant, flesh eating mushrooms"; break;
case 1:
str = "a firm, slender and agile female argonian"; break;
case 2:
str = "dead people and some stupid guy in a golden mask"; break;
case 3:
str = "the whole world only being part of a computer game"; break;
case 4:
str = "nothing in particular"; break;
case 5:
str = "an old, half naked guy giving you orders, insisting you\ncall him 'spymaster'";
break;
case 6:
str = "being a geek who sits in front of a screen all day long"; break;
case 7:
str = "the clouds, man, the crazy clouds!"; break;
}
writefln("You fall asleep in a ditch and dream about ", str, ".");
break;
case 6: //Water effects
switch(rand(0,5))
{
case 0: writefln("Them waves sure are pretty!"); break;
case 1:
writefln("A slaughter fish jumps up and bites you in the nose.");
life--;
if(life == 0)
{
writefln("You are dead.");
play = false;
}
break;
case 2: writefln("Those graphics might have looked impressive six years ago...");
break;
case 3: writefln("You were eaten by a Mudcrab. You are dead."); play=false; break;
case 4: writefln("You suddenly realize that the person who made this program has way too much time on his hands.");break;
case 5: writefln("You found a note with cheat codes on them."); level+=2; life+=rand(5,15); break;
}
break;
// Exit
case 7: play=false; break;
}
}
writefln("\nScore:");
writefln("Gold: %d : %d points", gold, gold);
writefln("Level: %d : %d points", level, (level-1)*40);
if(loot) writefln("Loot: you have to sell the loot to get any points for it.");
Entry n;
n.score = gold + (level-1) * 40;
writefln("Total score: ", n.score);
Entry[] high = getScores();
int index = 10;
foreach(int i, Entry e; high)
if(n.score > e.score)
{
index = i;
break;
}
writefln();
if(index != 10)
{
writef("Congratulations! You've made it to the Hall of Fame.\nEnter your name: ");
din.readLine();
n.name = din.readLine();
for(int i = 9; i>index; i--)
high[i] = high[i-1];
high[index] = n;
setScores(high);
}
writefln("Hall of Fame:");
foreach(int i, Entry e; high)
if(e.score) writefln("%-2d: %-10d %s", i+1, e.score, e.name);
writefln("\n(Apologies to Bethesda Softworks)");
}
struct Entry
{
char[] name;
int score;
}
void setScores(Entry[] l)
{
auto File f = new File("bored.highscores", FileMode.OutNew);
foreach(Entry e; l)
{
f.write(e.name);
f.write(e.score);
}
}
Entry[] getScores()
{
Entry[] l;
l.length = 10;
if(exists("bored.highscores"))
{
auto File f = new File("bored.highscores");
foreach(ref Entry e; l)
{
f.read(e.name);
f.read(e.score);
}
}
return l;
}

@ -0,0 +1,358 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bsafile.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module bsa.bsafile;
//debug=checkHash;
debug(checkHash) import std.stdio;
// This file does not have any unit tests, since you really need the
// data to test it. Use the program named 'bsatool', it uses the NIF
// reader library and scans through a bsa archive, providing a good
// test of both libraries.
//import std.stream;
import std.string;
import std.mmfile;
import core.memory;
import monster.util.aa;
class BSAFileException : Exception
{
this(char[] msg) {super("BSAFileException: " ~ msg);}
}
/**
* This class is used to read "Bethesda Archive Files", or BSAs.
*
* The BSA archives are typically held open for the entire lifetime of
* the application, and are accessed more or less randomly. For that
* reason the BSAFile class uses memory mapped files. However, to be
* reasonably memory efficient, only the last requested file is
* guaranteed to be mapped at any given time, therefore make sure you
* don't use any persistant slices.
*
*/
class BSAFile
{
private:
// Size of the blocks to map with the memory mapped file. If set to
// 0, we map the entire file, but then we use a LOT of system
// memory. There is really no penalty in setting it too small, since
// multiple blocks may be mapped simultaneously. However, it MUST be
// a multiple of the system page size. TODO: On my system it is 4K,
// later on I will have to call getpagesize and the windows
// equivalent to find this. For now I just assume 8K is ok.
static int pageSize = 8*1024;
// Represents one file entry in the archive
struct FileStruct
{
// File size and offset in file. We store the offset from the
// beginning of the file, not the offset into the data buffer
// (which is what is stored in the archive.)
uint fileSize, offset;
char[] name;
void getName(char[] buf, uint start)
{
if(start >= buf.length)
throw new BSAFileException("Name offset outside buffer");
uint end = start;
// We have to search for the end of the name string, marked by a zero.
for(; end<buf.length; end++)
if(buf[end] == 0) break;
if(end == buf.length)
throw new BSAFileException("String buffer overflow");
name = buf[start..end];
}
// This currently isn't needed, but it would be if we wanted to
// write our own bsa archives.
debug(checkHash)
{
void hashName(out uint hash1, out uint hash2)
{
uint sum, off, temp, n;
foreach(char c; name[0..$/2])
{
sum ^= (cast(uint)c) << (off & 31);
off += 8;
}
hash1 = sum;
sum = off = 0;
foreach(char c; name[$/2..$])
{
temp = (cast(uint)c) << (off & 31);
sum ^= temp;
n = temp & 0x1F;
sum = (sum << (32-n)) | (sum >> n); // "rotate right" operation
off += 8;
}
hash2 = sum;
}
}
}
MmFile mmf; // Handle to memory mapped file
char[] filename; // File name
FileStruct files[]; // The file table is stored here
bool isLoaded; // Set to true if a file has been loaded
// An AA for fast file name lookup. The CITextHash is a
// case-insensitive text hasher, meaning that all lookups are case
// insensitive.
HashTable!(char[], int, ESMRegionAlloc, CITextHash) lookup;
void fail(char[] msg)
{
throw new BSAFileException(msg ~ "\nFile: " ~ filename);
}
void read()
{
/*
* The layout of a BSA archive is as follows:
*
* - 12 bytes header, contains 3 ints:
* id number - equal to 0x100
* dirsize - size of the directory block (see below)
* numfiles - number of files
*
* ---------- start of directory block -----------
*
* - 8 bytes*numfiles, each record contains
* fileSize
* offset into data buffer (see below)
*
* - 4 bytes*numfiles, each record is an offset into the following name buffer
*
* - name buffer, indexed by the previous table, each string is
* null-terminated. Size is (dirsize - 12*numfiles).
*
* ---------- end of directory block -------------
*
* - 8*filenum - hast table block, we currently ignore this
*
* Data buffer:
*
* - The rest of the archive is file data, indexed by the
* offsets in the directory block. The offsets start at 0 at
* the beginning of this buffer.
*
*/
assert(!isLoaded);
uint fsize = mmf.length;
if( fsize < 12 )
fail("File too small to be a valid BSA archive");
// Recast the file header as a list of uints
uint[] array = cast(uint[]) mmf[0..12];
if(array[0] != 0x100)
fail("Unrecognized BSA header");
// Total number of bytes used in size/offset-table + filename
// sections.
uint dirsize = array[1];
debug writefln("Directory size: ", dirsize);
// Number of files
uint filenum = array[2];
debug writefln("Number of files: ", filenum);
// Each file must take up at least 21 bytes of data in the
// bsa. So if files*21 overflows the file size then we are
// guaranteed that the archive is corrupt.
if( (filenum*21 > fsize -12) ||
(dirsize+8*filenum > fsize -12) )
fail("Directory information larger than entire archive");
// Map the entire directory (skip the hashes if we don't need them)
debug(checkHash)
void[] mm = mmf[12..(12+dirsize+8*filenum)];
else
void[] mm = mmf[12..(12+dirsize)];
// Allocate the file list from esmRegion
files = esmRegion.allocateT!(FileStruct)(filenum);
// Calculate the offset of the data buffer. All file offsets are
// relative to this.
uint fileDataOffset = 12 + dirsize + 8*filenum;
// Get a slice of the size/offset table
array = cast(uint[])mm[0..(8*filenum)];
int index = 0; // Used for indexing array[]
// Read the size/offset table
foreach(ref FileStruct fs; files)
{
fs.fileSize = array[index++];
fs.offset = array[index++] + fileDataOffset;
if(fs.offset+fs.fileSize > fsize) fail("Archive contains files outside itself");
}
// Get a slice of the name offset table
array = cast(uint[])mm[(8*filenum)..(12*filenum)];
// Get a slice of the name field
char[] nameBuf = cast(char[])mm[(12*filenum)..dirsize];
// And copy it!
nameBuf = esmRegion.copy(nameBuf);
// Tell the lookup table how big it should be
lookup.rehash(filenum);
// Loop through the name offsets and pick out the names
foreach(int idx, ref FileStruct fs; files)
{
fs.getName(nameBuf, array[idx]);
lookup[fs.name] = idx;
debug(2) writefln("%d: %s, %d bytes at %x", idx,
fs.name, fs.fileSize, fs.offset);
}
// Code to check if file hashes are correct - this was mostly
// used to check our hash algorithm.
debug(checkHash)
{
// Slice the Hash table
array = cast(uint[])mm[dirsize..(dirsize+8*filenum)];
index = 0;
foreach(ref FileStruct fs; files)
{
uint h1, h2;
fs.hashName(h1,h2);
uint h11 = array[index++];
uint h22 = array[index++];
if(h1 != h11) writefln("1 : %d vs. %d", h1, h11);
if(h2 != h22) writefln("2 : %d vs. %d", h2, h22);
}
}
isLoaded = true;
}
public:
/* -----------------------------------
* BSA management methods
* -----------------------------------
*/
this() {}
this(char[] file) {open(file);}
// We should clean up after us
~this() {clear();}
// Open a new file. Clears any existing data
void open(char[] file)
{
clear();
filename = file;
// Open a memory mapped file
mmf = new MmFile(
file, // File name
MmFile.Mode.Read, // Read only
0, // We need the entire file
null, // Don't care where it is mapped
pageSize); // DON'T map the entire file, uses
// too much memory.
// Load header and file directory
read();
}
// Clear all data and close file
void clear()
{
delete mmf;
lookup.reset;
files.length = 0;
isLoaded = false;
}
/* -----------------------------------
* Archive file routines
* -----------------------------------
*/
void[] findSlice(int index)
{
if(!isLoaded)
fail("No archive is open");
if(index < 0 || index >= files.length)
fail("Index out of bounds");
//writefln("\noffset %d, filesize %d", files[index].offset, files[index].fileSize);
// Get a slice of the buffer that comprises this file
with(files[index])
return mmf[offset..offset+fileSize];
}
int getIndex(char[] name)
{
int i;
// Look it up in the AA
if( lookup.inList( name, i ) )
return i;
else
return -1;
}
// Return a slice. This routine converts the name to lower case,
// since all BSA file names are stored that way, but references to
// them in ESM/ESPs and NIFs are not.
void[] findSlice(char[] name)
{
int i = getIndex(name);
if(i == -1) return null;
return findSlice(i);
}
// Used by the 'bsatest' program to loop through the entire
// archive.
FileStruct[] getFiles() { return files; }
// Number of files in the archive
uint numFiles() { return files.length; }
// Gets the name of the archive file.
char[] getName() { return filename; }
}

@ -0,0 +1,129 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bsatool.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module bsa.bsatool;
import std.stdio;
import bsa.bsafile;
import nif.nif;
import nif.niffile;
import core.memory;
void scanAllNifs(BSAFile f)
{
uint totSize;
BSAFile.FileStruct[] files = f.getFiles();
foreach(int ind, BSAFile.FileStruct file; files)
{
if(file.name[$-4..$] == ".nif" ||
file.name[$-3..$] == ".kf" )
{
void[] str = f.findSlice(ind);
totSize += str.length;
try nifMesh.open(str, file.name);
catch(NIFFileException e)
writefln("Got exception: %s", e);
}
}
writefln("Total NIF/KF size in this archive: ", totSize);
}
void listFiles(BSAFile f)
{
BSAFile.FileStruct[] files = f.getFiles();
foreach(BSAFile.FileStruct file; files)
writefln(file.name, " (%d bytes)", file.fileSize);
}
int main(char[][] args)
{
//*
int help(char[] msg)
{
writefln("%s", msg);
writefln("Format: bsatool archive.bsa [-x filename] [-l] [-n]");
writefln(" -x filename extract file");
writefln(" -l list all files");
writefln(" -n scan through all nifs");
return 1;
}
if(args.length < 3) return help("Insufficient arguments");
initializeMemoryRegions();
char[] filename, ext;
bool list, nifs, extract;
foreach(char[] a; args[1..$])
if(a == "-l") list = true;
else if(a == "-n") nifs = true;
else if(a == "-x") extract = true;
else if(extract && ext == "") ext = a;
else if(filename == "") filename = a;
else return help("Unknown option " ~ a);
if(filename == "") return help("No file specified");
auto BSAFile f = new BSAFile(filename);
if(list) listFiles(f);
if(nifs) scanAllNifs(f);
if(extract)
{
if(ext == "") return help("No file to extract");
void[] s = cast(ubyte[]) f.findSlice(ext);
if(s.ptr == null)
{
writefln("File '%s' not found in '%s'", ext, filename);
return 1;
}
ext = getWinBaseName(ext);
File o = new File(ext, FileMode.OutNew);
o.writeExact(s.ptr, s.length);
o.close();
writefln("File extracted to '%s'", ext);
}
//din.readLine();
return 0;
}
// Picks out part of a string after the last '\'.
char[] getWinBaseName(char[] input)
{
uint i;
for(i = input.length; i > 0; i--)
if(input[i-1] == '\\') break;
return input[i..$];
}
//import std.cstream;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,276 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (config.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.config;
import std.string;
import std.file;
import std.stdio;
import monster.util.string;
import core.inifile;
import sound.audio;
import input.keys;
import input.ois;
//import sdl.Keysym;
//import input.events : updateMouseSensitivity;
ConfigManager config;
/*
* Structure that handles all user adjustable configuration options,
* including things like file paths, plugins, graphics resolution,
* game settings, window positions, etc. It is also responsible for
* reading and writing configuration files, for importing settings
* from Morrowind.ini and for configuring OGRE. It doesn't currently
* DO all of this, but it is supposed to in the future.
*/
struct ConfigManager
{
IniWriter iniWriter;
float musicVolume;
float sfxVolume;
float mainVolume;
bool useMusic;
// Mouse sensitivity
float mouseSensX;
float mouseSensY;
bool flipMouseY;
// Number of current screen shot. Saved upon exit, so that shots
// from separate sessions don't overwrite each other.
int screenShotNum;
// Directories
char[] esmDir;
char[] bsaDir;
char[] sndDir;
char[] musDir; // Explore music
char[] musDir2; // Battle music
// Cell to load at startup
char[] defaultCell;
// Check that a given volume is within sane limits (0.0-1.0)
private static float saneVol(float vol)
{
if(!(vol >= 0)) vol = 0;
else if(!(vol <= 1)) vol = 1;
return vol;
}
// These set the volume to a new value and updates all sounds to
// take notice.
void setMusicVolume(float vol)
{
musicVolume = saneVol(vol);
jukebox.updateVolume();
battleMusic.updateVolume();
}
void setSfxVolume(float vol)
{
sfxVolume = saneVol(vol);
// TODO: Call some sound manager here to adjust all active sounds
}
void setMainVolume(float vol)
{
mainVolume = saneVol(vol);
// Update the sound managers
setMusicVolume(musicVolume);
setSfxVolume(sfxVolume);
}
// These calculate the "effective" volumes.
float calcMusicVolume()
{
return musicVolume * mainVolume;
}
float calcSfxVolume()
{
return sfxVolume * mainVolume;
}
// Initialize the config manager. Send a 'true' parameter to reset
// all keybindings to the default. A lot of this stuff will be moved
// to script code at some point. In general, all input mechanics and
// distribution of key events should happen in native code, while
// all setup and control should be handled in script code.
void initialize(bool reset = false)
{
// Initialize the key binding manager
keyBindings.initKeys();
readIni();
if(reset) with(keyBindings)
{
// Remove all existing key bindings
clear();
// Bind some default keys
bind(Keys.MoveLeft, KC.A, KC.LEFT);
bind(Keys.MoveRight, KC.D, KC.RIGHT);
bind(Keys.MoveForward, KC.W, KC.UP);
bind(Keys.MoveBackward, KC.S, KC.DOWN);
bind(Keys.MoveUp, KC.LSHIFT);
bind(Keys.MoveDown, KC.LCONTROL);
bind(Keys.MainVolUp, KC.ADD);
bind(Keys.MainVolDown, KC.SUBTRACT);
bind(Keys.MusVolDown, KC.N1);
bind(Keys.MusVolUp, KC.N2);
bind(Keys.SfxVolDown, KC.N3);
bind(Keys.SfxVolUp, KC.N4);
bind(Keys.ToggleBattleMusic, KC.SPACE);
bind(Keys.Pause, KC.PAUSE, KC.P);
bind(Keys.ScreenShot, KC.SYSRQ);
bind(Keys.Exit, KC.Q, KC.ESCAPE);
}
// I think DMD is on the brink of collapsing here. This has been
// moved elsewhere, because DMD couldn't handle one more import in
// this file.
//updateMouseSensitivity();
}
// Read config from morro.ini, if it exists.
void readIni()
{
// Read configuration file, if it exists.
IniReader ini;
// TODO: Right now we have to specify each option twice, once for
// reading and once for writing. Fix it? Nah. Don't do anything,
// this entire configuration scheme is likely to change anyway.
ini.readFile("morro.ini");
screenShotNum = ini.getInt("General", "Screenshots", 0);
mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
useMusic = ini.getBool("Sound", "Enable Music", true);
mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
defaultCell = ini.getString("General", "Default Cell", "");
// Read key bindings
for(int i; i<Keys.Length; i++)
{
char[] s = keyToString[i];
if(s.length)
keyBindings.bindComma(cast(Keys)i, ini.getString("Bindings", s, ""));
}
// Read specific directories
bsaDir = ini.getString("General", "BSA Directory", "./");
esmDir = ini.getString("General", "ESM Directory", "./");
sndDir = ini.getString("General", "SFX Directory", "sound/Sound/");
musDir = ini.getString("General", "Explore Music Directory", "sound/Music/Explore/");
musDir2 = ini.getString("General", "Battle Music Directory", "sound/Music/Battle/");
}
// Create the config file
void writeConfig()
{
writefln("In writeConfig");
with(iniWriter)
{
openFile("morro.ini");
comment("Don't write your own comments in this file, they");
comment("will disappear when the file is rewritten.");
section("General");
writeString("ESM Directory", esmDir);
writeString("BSA Directory", bsaDir);
writeString("SFX Directory", sndDir);
writeString("Explore Music Directory", musDir);
writeString("Battle Music Directory", musDir2);
writeInt("Screenshots", screenShotNum);
writeString("Default Cell", defaultCell);
section("Controls");
writeFloat("Mouse Sensitivity X", mouseSensX);
writeFloat("Mouse Sensitivity Y", mouseSensY);
writeBool("Flip Mouse Y Axis", flipMouseY);
section("Bindings");
comment("Key bindings. The strings must match exactly.");
foreach(int i, KeyBind b; keyBindings.bindings)
{
char[] s = keyToString[i];
if(s.length)
writeString(s, b.getString());
}
section("Sound");
writeFloat("Main Volume", mainVolume);
writeFloat("Music Volume", musicVolume);
writeFloat("SFX Volume", sfxVolume);
writeBool("Enable Music", useMusic);
close();
}
}
// In the future this will import settings from Morrowind.ini, as
// far as this is sensible.
void importIni()
{
/*
IniReader ini;
ini.readFile("../Morrowind.ini");
// Example of sensible options to convert:
tryArchiveFirst = ini.getInt("General", "TryArchiveFirst");
useAudio = ( ini.getInt("General", "Disable Audio") == 0 );
footStepVolume = ini.getFloat("General", "PC Footstep Volume");
subtitles = ini.getInt("General", "Subtitles") == 1;
The plugin list (all esm and esp files) would be handled a bit
differently. In our system they might be a per-user (per
"character") setting, or even per-savegame. It should be safe and
intuitive to try out a new mod without risking your savegame data
or original settings. So these would be handled in a separate
plugin manager.
*/
}
}

@ -0,0 +1,224 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (filefinder.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.filefinder;
import std.file;
import std.string;
import monster.util.string;
import monster.util.aa;
import core.memory;
import std.stdio;
class FileFinderException : Exception
{
this(char[] msg, char[] ext, char[] dir)
{
if(ext.length) super(format("FileFinder for %s files in %s: %s", ext, dir, msg));
else super(format("FileFinder for %s: %s", dir, msg));
}
}
// Do we traverse directories recursively? Default is yes.
enum Recurse { Yes, No }
// The file finder is used to list all files in a directory so we can
// look up files without searching the filesystem each time. It is
// case insensitive on all platforms, and transparently converts to
// the right directory separator character (\ or /). We might extend
// it later with code from other projects.
class FileFinder
{
private:
char[][] files; // Use GC for this, it's not too big and we don't
// have to manage roots pointing to the filenames.
HashTable!(char[], int, ESMRegionAlloc, FilenameHasher) lookup;
char[] dir; // Base directory to search
char[] ext; // Extensions to pick out
void fail(char[] err)
{
throw new FileFinderException(err, ext, dir);
}
// Removes the part of a path that is stored in 'dir'
char[] removeDir(char[] path)
{
//TODO: Should this be case insensitive?
assert(path[0..dir.length] == dir);
return path[dir.length..$];
}
void insert(char[] filename)
{
// Only keep the part of the filename not given in 'dir'.
char[] name = removeDir(filename);
if(!name.iEnds(ext)) return;
// We start counting from 1
uint newVal = files.length+1;
// Insert it, or get the old value if it already exists
uint oldVal = lookup[name, newVal];
if(oldVal != newVal)
fail("Already have " ~ name ~ "\nPreviously inserted as " ~ files[oldVal-1]);
// Store it
files ~= filename;
}
public:
static char[] addSlash(char[] dir)
{
// Add a trailing slash
version(Windows) if(!dir.ends("\\")) dir ~= '\\';
version(Posix) if(!dir.ends("/")) dir ~= '/';
return dir;
}
int length() { return lookup.length; }
this(char[] dir, char[] ext = null, Recurse r = Recurse.Yes)
in
{
if(!dir.length) fail("'dir' can not be empty");
}
out
{
assert(files.length == lookup.length);
}
body
{
// Add a trailing slash
dir = addSlash(dir);
this.dir = dir;
if(ext.length && ext[0] != '.') ext = "." ~ ext;
this.ext = ext;
bool callback(DirEntry* de)
{
if (de.isdir)
{
if(r == Recurse.Yes)
listdir(de.name, &callback);
}
else
insert(de.name);
return true;
}
try listdir(dir, &callback);
catch(FileException e)
fail(e.toString);
}
char[] opIndex(int i) { return files[i-1]; }
int opIndex(char[] file)
{
int i;
// Get value if it exists
if(lookup.inList(file, i))
return i;
return 0;
}
int opApply(int delegate(ref char[]) del)
{
int res = 0;
foreach(char[] s; files)
{
char[] tmp = removeDir(s);
res = del(tmp);
if(res) break;
}
return res;
}
char[] toString()
{
char[] result;
foreach(char[] s; this)
result ~= s ~ "\n";
return result;
}
}
// Hash functions that does not differentiate between linux and
// windows file names. This means that it is case insensitive, and
// treats '\' and '/' as the same character. Only needed in linux, in
// windows just use CITextHasher.
version(Posix)
struct FilenameHasher
{
static const char conv = 'a'-'A';
static int isEqual(char[] aa, char[] bb)
{
if(aa.length != bb.length) return 0;
foreach(int i, char a; aa)
{
char b = bb[i];
if(a == b)
continue;
// Convert both to lowercase and "/ case"
if(a <= 'Z' && a >= 'A') a += conv;
else if(a == '\\') a = '/';
if(b <= 'Z' && b >= 'A') b += conv;
else if(b == '\\') b = '/';
if(a != b) return 0;
}
// No differences were found
return 1;
}
static uint hash(char[] s)
{
uint hash;
foreach (char c; s)
{
if(c <= 'Z' && c >= 'A') c += conv;
else if(c == '\\') c = '/';
hash = (hash * 37) + c;
}
return hash;
}
}
version(Windows) alias CITextHasher FilenameHasher;

@ -0,0 +1,228 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (inifile.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.inifile;
import std.stream;
import std.string;
import std.file;
import monster.util.string;
import monster.util.aa;
// Writes an ini file.
struct IniWriter
{
File ini;
bool firstSection = true;
void openFile(char[] file)
{
if(ini is null) ini = new File();
if(exists(file))
rename(file, file~".old");
ini.open(file, FileMode.OutNew);
}
void close()
{
ini.close();
}
void section(char[] name)
{
if(firstSection)
firstSection = false;
else
ini.writefln();
ini.writefln("[%s]", name);
}
void comment(char[] str)
{
ini.writefln("; %s", str);
}
void writeType(T)(char[] name, T t)
{
ini.writefln("%s=%s", name, t);
}
alias writeType!(int) writeInt;
alias writeType!(float) writeFloat;
alias writeType!(char[]) writeString;
void writeBool(char[] name, bool b)
{
ini.writefln("%s=%s", name, b?"yes":"no");
}
}
// Keytype that holds section name and variable name
struct SecVar
{
private:
char[] section;
char[] variable;
public:
bool opEquals(ref SecVar v)
{
return section == v.section && variable == v.variable;
}
uint toHash()
{
const auto ID = typeid(char[]);
return ID.getHash(&section) + ID.getHash(&variable);
}
static SecVar opCall(char[] sec, char[] var)
{
SecVar sv;
sv.section = sec;
sv.variable = var;
return sv;
}
char[] toString()
{
return "[" ~ section ~ "]:" ~ variable;
}
}
struct IniReader
{
private:
HashTable!(SecVar, char[]) vars;
char[] section;
// Start a new section
void setSection(char[] sec)
{
section = sec.dup;
}
// Insert a new value from file
void set(char[] variable, char[] value)
{
vars[SecVar(section, variable.dup)] = value.dup;
}
public:
void reset()
{
vars.reset();
section = null;
}
int getInt(char[] sec, char[] var, int def)
{
char[] value;
if(vars.inList(SecVar(sec,var), value))
return atoi(value);
else
return def;
}
float getFloat(char[] sec, char[] var, float def)
{
char[] value;
if(vars.inList(SecVar(sec,var), value))
return atof(value);
else
return def;
}
char[] getString(char[] sec, char[] var, char[] def)
{
char[] value;
if(vars.inList(SecVar(sec,var), value))
return value;
else
return def;
}
// Return true if the string matches some case of 'yes', and false
// otherwise.
bool getBool(char[] sec, char[] var, bool def)
{
char[] value;
if(vars.inList(SecVar(sec,var), value))
return icmp(value, "yes") == 0;
else
return def;
}
void readFile(char[] fn)
{
// Reset this struct
reset();
// If the file doesn't exist, simply exit. This will work fine,
// and default values will be used instead.
if(!exists(fn)) return;
// Read buffer. Finite in size but perfectly safe - the readLine
// routine allocates more mem if it needs it.
char[300] buffer;
scope File ini = new File(fn);
while(!ini.eof)
{
char[] line = ini.readLine(buffer);
// Remove leading and trailing whitespace
line = strip(line);
// Ignore comments and blank lines
if(line.length == 0 || line.begins(";")) continue;
// New section?
if(line.begins("["))
{
if(!line.ends("]"))
{
//writefln("Malformed section: %s", line);
continue;
}
setSection(line[1..$-1]);
continue;
}
// Split line into a key and a value
int index = line.find('=');
if(index != -1)
{
char[] value = line[index+1..$];
line = line[0..index];
set(line, value);
}
//else writefln("Malformed value: '%s'", line);
}
}
}

@ -0,0 +1,60 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (memory.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.memory;
import util.regions;
// Global memory managers used by various loading routines
RegionManager
esmRegion, // Memory used by ESMs, BSAs and plugins. Cleared only
// when all files are reloaded. Lifetime is current
// plugin setting session - if we change the set of
// plugins, this region is reset and everything is
// reloaded.
gameRegion, // Memory used in current game. Cleared whenever a new
// game is loaded. Lifetime is the current game
// session, loading a saved game will reset the
// region.
nifRegion; // Used by the NIF loader. Cleared when a NIF file has
// been inserted into OGRE and the file data is no
// longer needed. In other words this is cleared
// immediately after loading each NIF file.
// AA allocator that uses esmRegion
struct ESMRegionAlloc
{
static const bool autoinit = false;
static void* alloc(uint size) { return esmRegion.allocate(size).ptr; }
static void free(void* p) { }
}
void initializeMemoryRegions()
{
// Default block sizes are probably ok
esmRegion = new RegionManager("ESM");
gameRegion = new RegionManager("GAME");
nifRegion = new RegionManager("NIF");
}

@ -0,0 +1,538 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (resource.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.resource;
import std.stdio;
import std.string;
import std.stream;
import std.file;
import std.path;
import monster.util.aa;
import monster.util.string;
import util.random;
import bsa.bsafile;
import core.memory;
import core.config;
import ogre.bindings;
import ogre.meshloader;
import sound.audio;
import sound.sfx;
import nif.nif;
import core.filefinder;
//import core.config;
// Random number generator
DRand rnd;
// These are handles for various resources. They may refer to a file
// in the file system, an entry in a BSA archive, or point to an
// already loaded resource. Resource handles that are not implemented
// yet are typedefed as ints for the moment.
typedef int IconIndex;
alias SoundResource* SoundIndex;
alias TextureResource* TextureIndex;
alias MeshResource* MeshIndex;
ResourceManager resources;
// Called from ogre/cpp_bsaarchive.cpp. We will probably move these
// later.
extern(C)
{
// Does the file exist in the archives?
int d_bsaExists(char *filename)
{
char[] name = toString(filename);
auto res = resources.lookupTexture(name);
if(res.bsaFile != -1)
return 1;
return 0;
}
// Open a file. Return the pointer and size.
void d_bsaOpenFile(char *filename,
void **retPtr, uint *retSize)
{
char[] name = toString(filename);
void[] result;
//writefln("calling d_bsaOpenFile(%s, %s)", bsaFile, name);
auto tex = resources.lookupTexture(name);
if(tex.bsaFile == -1) result = null;
else result = resources.archives[tex.bsaFile].findSlice(tex.bsaIndex);
*retPtr = result.ptr;
*retSize = result.length;
}
}
struct ResourceManager
{
private:
// Holds lists of resources in the file system.
FileFinder
meshes,
icons,
textures,
sounds,
bookart,
bsa, esm, esp;
// Archives
BSAFile archives[];
// List of resources that have already been looked up (case
// insensitive)
HashTable!(char[], MeshIndex, ESMRegionAlloc, CITextHash) meshLookup;
HashTable!(char[], TextureIndex, ESMRegionAlloc, CITextHash) textureLookup;
HashTable!(char[], SoundIndex, ESMRegionAlloc, CITextHash) soundLookup;
public:
// Hack. Set to true in esmtool to disable all the lookup*
// functions.
bool dummy = false;
void initResources()
{
rnd = new DRand;
bsa = new FileFinder(config.bsaDir, "bsa", Recurse.No);
archives.length = bsa.length;
foreach(int i, ref BSAFile f; archives)
f = new BSAFile(bsa[i+1]);
sounds = new FileFinder(config.sndDir);
// Simple playlists, similar to the Morrowind one. Later I imagine
// adding an interactive MP3 player with a bit more finesse, but
// this will do for now.
char[][] music;
char[][] getDir(char[] dir)
{
dir = FileFinder.addSlash(dir);
char[][] res = listdir(dir);
foreach(ref char[] fn; res)
fn = dir ~ fn;
return res;
}
jukebox.setPlaylist(getDir(config.musDir));
battleMusic.setPlaylist(getDir(config.musDir2));
meshLookup.reset();
textureLookup.reset();
soundLookup.reset();
meshBuffer[0..7] = "meshes\\";
texBuffer[0..9] = "textures\\";
}
// These three functions are so similar that I should probably split
// out big parts of them into one common function.
SoundIndex lookupSound(char[] id)
{
if(dummy) return null;
assert(id != "", "loadSound called with empty id");
SoundIndex si;
if( soundLookup.inList(id, si) ) return si;
si = esmRegion.newT!(SoundResource);
// Check if the file exists
int index = sounds[id];
// If so, get the real file name
if(index) si.file = sounds[index];
// Otherwise, make this an empty resource
else
{
writefln("Lookup failed to find sound %s", id);
si.file = null;
}
si.res.loaded = false;
// Copy name and insert. We MUST copy here, since indices during
// load are put in a temporary buffer, and thus overwritten.
si.name = esmRegion.copyz(id);
soundLookup[si.name] = si;
return si;
}
// Quick but effective hack
char[80] meshBuffer;
MeshIndex lookupMesh(char[] id)
{
if(dummy) return null;
MeshIndex mi;
// If it is already looked up, return the result
if( meshLookup.inList(id, mi) ) return mi;
mi = esmRegion.newT!(MeshResource);
// If not, find it. For now we only check the BSA.
char[] search;
if(id.length < 70)
{
// Go to great lengths to avoid the concat :) The speed gain
// is negligible (~ 1%), but the GC memory usage is HALVED!
// This may mean significantly fewer GC collects during a long
// run of the program.
meshBuffer[7..7+id.length] = id;
search = meshBuffer[0..7+id.length];
}
else
search = "meshes\\" ~ id;
//writefln("lookupMesh(%s): searching for %s", id, search);
mi.bsaIndex = -1;
mi.bsaFile = -1;
foreach(int ind, BSAFile bs; archives)
{
mi.bsaIndex = bs.getIndex(search);
if(mi.bsaIndex != -1) // Found something
{
mi.bsaFile = ind;
break;
}
}
if(mi.bsaIndex == -1)
{
writefln("Lookup failed to find mesh %s", search);
assert(mi.bsaFile == -1);
}
// Resource is not loaded
mi.node = null;
// Make a copy of the id
mi.name = esmRegion.copyz(id);
meshLookup[mi.name] = mi;
return mi;
}
char[80] texBuffer;
TextureIndex lookupTexture(char[] id)
{
if(dummy) return null;
TextureIndex ti;
// Checked if we have looked it up before
if( textureLookup.inList(id, ti) ) return ti;
// Create a new resource locator
ti = esmRegion.newT!(TextureResource);
ti.name = esmRegion.copyz(id);
ti.newName = ti.name;
ti.type = ti.name[$-3..$];
char tmp[];
if(id.length < 70)
{
// See comment in lookupMesh
texBuffer[9..9+id.length] = id;
tmp = texBuffer[0..9+id.length];
}
else
tmp = "textures\\" ~ id;
void searchBSAs(char[] search)
{
// Look it up in the BSA
ti.bsaIndex = -1;
ti.bsaFile = -1;
foreach(int ind, BSAFile bs; archives)
{
ti.bsaIndex = bs.getIndex(search);
if(ti.bsaIndex != -1) // Found something
{
ti.bsaFile = ind;
break;
}
}
}
searchBSAs(tmp);
// If we can't find it, try the same filename but with .dds as the
// extension. Bethesda did at some point convert all their
// textures to dds to improve loading times. However, they did not
// update their esm-files or require them to use the correct
// extention (if they had, it would have broken a lot of user
// mods). So we must support files that are referenced as eg .tga
// but stored as .dds.
if(ti.bsaIndex == -1 && ti.type != "dds")
{
tmp[$-3..$] = "dds";
searchBSAs(tmp);
if(ti.bsaIndex != -1)
{
// Store the real name in newName.
ti.newName = esmRegion.copyz(ti.name);
// Get a slice of the extension and overwrite it.
ti.type = ti.newName[$-3..$];
ti.type[] = "dds";
}
}
// Check that extensions match, to be on the safe side
assert(ti.type == ti.newName[$-3..$]);
if(ti.bsaIndex == -1)
{
writefln("Lookup failed to find texture %s", tmp);
assert(ti.bsaFile == -1);
}
ti.ml = null;
textureLookup[ti.name] = ti;
return ti;
}
IconIndex lookupIcon(char[] id) { return -3; }
// Inserts a given mesh into ogre. Currently only reads the BSA
// file. Is only called from within MeshResource itself, and should
// never be called when the mesh is already loaded.
private void loadMesh(MeshIndex mi)
in
{
assert(!mi.isLoaded);
assert(!mi.isEmpty);
}
body
{
// Get a slice of the mesh
void[] s = archives[mi.bsaFile].findSlice(mi.bsaIndex);
// Load the NIF into memory. No need to call close(), the file is
// automatically closed when the data is loaded.
nifMesh.open(s, mi.name);
// Load and insert nif
// TODO: Might add BSA name to the handle name, for clarity
mi.node = meshLoader.loadMesh(mi.name);
// TODO: We could clear the BSA memory mapping here to free some
// mem
}
// Inserts a texture into ogre. Currently it only supports BSA
// textures, will later support textures in the file system. OGRE is
// able to load these itself, so in that case we can IIRC do
// nothing. The tex pointer points to an already existing Texture*
// resource in C++.
void loadTexture(TextureIndex ti, void *tex)
in
{
assert(ti.isLoaded);
assert(!ti.isEmpty);
}
body
{
assert(0, "This function is no longer in use");
void[] s = archives[ti.bsaFile].findSlice(ti.bsaIndex);
// Insert it into ogre. Note that we are actually being called
// from a manual loader at this point, this just dumps the texture
// data into an existing OGRE resource.
cpp_loadMemImage(ti.name.ptr, ti.type.ptr, s.ptr, s.length, tex);
}
}
struct SoundResource
{
private:
char[] name;
char[] file;
SoundFile res;
public:
char[] getName() { return name; }
SoundInstance getInstance()
in
{
assert(!isEmpty());
}
body
{
if(!isLoaded())
res.load(file);
return res.getInstance();
}
bool isEmpty() { return file == ""; }
bool isLoaded() { return res.loaded; }
}
struct MeshResource
{
private:
char[] name;
int bsaFile;
int bsaIndex;
// Points to the 'template' SceneNode of this mesh. Is null if this
// mesh hasn't been inserted yet.
NodePtr node;
public:
NodePtr getNode()
in
{
assert(!isEmpty());
}
body
{
if(node == null) resources.loadMesh(this);
return node;
}
char[] getName() { return name; }
// Returns true if this resource does not exist (ie. file not found)
// TODO: This must be modified later for non-BSA files.
bool isEmpty()
{
return bsaIndex == -1;
}
// Returns true if resource is loaded
bool isLoaded()
{
return node != null;
}
}
struct TextureResource
{
private:
char[] name;
char[] newName; // Converted name, ie. with extension converted to
// .dds if necessary
int bsaFile; // If set to -1, the file is in the file system
int bsaIndex;
char[] type; // Texture format, eg "tga" or "dds";
ManualLoader ml;
public:
// Insert the texture resource into OGRE. THIS IS NO LONGER NEEDED.
void load()
in
{
assert(!isEmpty());
}
body
{
writefln("still calling TextureResource.load");
if(ml == null) ml = cpp_createTexture(name.ptr, this);
}
char[] getName() { return name; }
char[] getNewName() { return newName; }
// Returns true if this resource does not exist (ie. file not found)
bool isEmpty()
{
return bsaIndex == -1;
}
// Returns true if resource is loaded
bool isLoaded()
{
return ml != null;
}
}
// OLD STUFF
/+
void initResourceManager()
{
// Find all resource files
char[] morroDir = config.morrowindDirectory();
bsa = new FileFinder(morroDir, "bsa");
// Jump to the data files directory
morroDir = config.dataFilesDirectory();
esm = new FileFinder(morroDir, "esm", Recurse.No);
esp = new FileFinder(morroDir, "esp", Recurse.No);
meshes = new FileFinder(morroDir ~ "Meshes");
icons = new FileFinder(morroDir ~ "Icons");
textures = new FileFinder(morroDir ~ "Textures");
sounds = new FileFinder(morroDir ~ "Sound");
bookart = new FileFinder(morroDir ~ "BookArt");
char[][] bsas = config.bsaArchives();
archives.length = bsas.length;
writef("Loading BSA archives...");
writefln(" Done\n");
}
void killBSAs()
{
foreach(BSAFile f; archives)
delete f;
}
}
+/

@ -0,0 +1,43 @@
###########################
# Main program #
###########################
[morro.d]
# Add libraries and the two C++ object files
buildflags = -llOgreMain -llaudiere -llOIS cpp_audiere.o cpp_ogre.o
# Make sure the C++ object files are built first
version(Windows) {
prebuild = warn Not designed for Windows yet.
}
version(Posix) {
prebuild = make ;
}
# Make sure we recompile the nif files without the debug output
prebuild += dsss clean niftool
###########################
# Bsa inspection tool #
###########################
[bsa/bsatool.d]
target=bsatool
###########################
# Esm inspection tool #
###########################
[esm/esmtool.d]
target=esmtool
# Because of interdepencies between the ESM code and the resource
# manager, we have to include all the C++ stuff.
buildflags = -llOgreMain -llaudiere -llOIS cpp_audiere.o cpp_ogre.o
###########################
# Nif inspection tool #
###########################
[nif/niftool.d]
target=niftool
buildflags = -debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose
# Clean the nif object files to make sure they are recompiled in debug mode
prebuild = dsss clean niftool
[bored.d]

@ -0,0 +1,137 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (defs.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.defs;
public import std.string;
public import monster.util.string;
/*
* Types and definitions related to parsing esm and esp files
*/
alias char[4] NAME;
alias char[32] NAME32;
alias char[256] NAME256;
union Color
{
align(1) struct
{
ubyte red, green, blue, alpha;
}
ubyte[4] array;
uint value;
char[] toString() { return format("RGBA:%s", array); }
}
static assert(Color.sizeof==4);
// State of a record struct
enum LoadState
{
Unloaded, // This record is not loaded, it has just been
// referenced.
Loaded, // This record has been loaded by the current file
Previous // The record has been loaded by a previous file
// Finalized - might be the case for some record types, but I
// don't know if this actual state value would be used for
// anything.
}
enum VarType { Unknown, None, Short, Int, Long, Float, String, Ignored }
enum SpellSchool : int
{
Alteration = 0,
Conjuration = 1,
Destruction = 2,
Illusion = 3,
Mysticism = 4,
Restoration = 5,
Length
}
enum Attribute : int
{
Strength = 0,
Intelligence = 1,
Willpower = 2,
Agility = 3,
Speed = 4,
Endurance = 5,
Personality = 6,
Luck = 7,
Length
}
enum SkillEnum : int
{
Block = 0,
Armorer = 1,
MediumArmor = 2,
HeavyArmor = 3,
BluntWeapon = 4,
LongBlade = 5,
Axe = 6,
Spear = 7,
Athletics = 8,
Enchant = 9,
Destruction = 10,
Alteration = 11,
Illusion = 12,
Conjuration = 13,
Mysticism = 14,
Restoration = 15,
Alchemy = 16,
Unarmored = 17,
Security = 18,
Sneak = 19,
Acrobatics = 20,
LightArmor = 21,
ShortBlade = 22,
Marksman = 23,
Mercantile = 24,
Speechcraft = 25,
HandToHand = 26,
Length
}
// Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item
// enchantments) records
align(1) struct ENAMstruct
{
// Magical effect
short effectID; // ID of magic effect
// Which skills/attributes are affected (for restore/drain spells etc.)
byte skill, attribute; // -1 if N/A
// Other spell parameters
int range; // 0 - self, 1 - touch, 2 - target
int area, duration, magnMin, magnMax;
static assert(ENAMstruct.sizeof==24);
}

@ -0,0 +1,117 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (esmmain.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.esmmain;
public import esm.records;
/* This file is the main module for loading from ESM, ESP and ESS
files. It stores all the data in the appropriate data structures
for later referal. TODO: Put this in a class or whatever? Nah, we
definately only need one structure like this at any one
time. However, we have to deal with unloading and reloading it,
even though that should be the exceptional case (change of plugins,
etc), not the rule (loading a savegame should not alter the base
data set, I think, but it's to early do decide.)*/
// Load a set of esm and esp files. For now, we just traverse in the
// order given. Later, we should sort these into 'masters' and
// 'plugins', because esms are always supposed to be loaded
// first. TODO: I'm not sure if I should load all these in one
// function. Do we need to be able to respond to errors in each file?
// Nah, if anything fails, give a general error message, remove the
// file from the list and try again. We have to be able to get a list
// of which files depend upon which, though... this can be done before
// this function is called.
void loadTESFiles(char[][] files)
{
// Set up all the lists to hold our data
initializeLists();
foreach(char[] filename; files)
{
esFile.open(filename, esmRegion);
while(esFile.hasMoreRecs())
{
uint flags;
// Read record header
char[] recName = esFile.getRecName();
esFile.getRecHeader(flags);
if(flags & RecordFlags.Unknown)
esFile.fail(format("UNKNOWN record flags: %xh", flags));
loadRecord(recName);
}
// We have to loop through the lists and check for broken
// references at this point, if all forward references were
// loaded. There might be other end-of-file things to do
// also.
endFiles();
}
esFile.close();
// Put all inventory items into one list
items.addList(appas, ItemType.Apparatus);
items.addList(lockpicks, ItemType.Pick);
items.addList(probes, ItemType.Probe);
items.addList(repairs, ItemType.Repair);
items.addList(lights, ItemType.Light);
items.addList(ingreds, ItemType.Ingredient);
items.addList(potions, ItemType.Potion);
items.addList(armors, ItemType.Armor);
items.addList(weapons, ItemType.Weapon);
items.addList(books, ItemType.Book);
items.addList(clothes, ItemType.Clothing);
items.addList(miscItems, ItemType.Misc);
items.addList(itemLists, ItemType.ItemList); // Leveled item lists
// Same with all actors
actors.addList(creatures, ItemType.Creature);
actors.addList(creatureLists, ItemType.CreatureList);
actors.addList(npcs, ItemType.NPC);
// Finally, add everything that might be looked up in a cell into
// one list
cellRefs.addList(items);
cellRefs.addList(actors);
cellRefs.addList(doors, ItemType.Door);
cellRefs.addList(activators, ItemType.Activator);
cellRefs.addList(statics, ItemType.Static);
cellRefs.addList(containers, ItemType.Container);
// Check that all references are resolved
items.endMerge();
actors.endMerge();
cellRefs.endMerge();
// Put all NPC dialogues into the hyperlink list
foreach(char[] id, ref Dialogue dl; dialogues.names)
hyperlinks.add(id, &dl);
// Finally, sort the hyperlink lists
hyperlinks.sort();
}

@ -0,0 +1,357 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (esmtool.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.esmtool;
import std.stdio;
import core.memory;
import esm.esmmain;
import std.utf;
import std.gc;
import gcstats;
import monster.util.string;
// Not used, but we have to link it in along with the C++ stuff.
import input.events;
void poolSize()
{
GCStats gc;
getStats(gc);
writefln("Pool size: ", comma(gc.poolsize));
writefln("Used size: ", comma(gc.usedsize));
}
//alias int[Dialogue*] TopicList;
void main(char[][] args)
{
char[][] files;
bool raw;
bool scptList; // List scripts
bool scptShow; // Show a script
char[] scptName; // Script to show
bool ciList; // List interior cells
bool ceList; // List exterior cells that have names
bool weList; // List weapons
bool numbers; // List how many there are of each record type
foreach(char[] a; args[1..$])
if(a == "-r") raw = true;
else if(a == "-sl") scptList = true;
else if(a == "-s") scptShow = true;
else if(scptShow && scptName == "") scptName = a;
else if(a == "-cil") ciList = true;
else if(a == "-cel") ceList = true;
else if(a == "-wl") weList = true;
else if(a == "-n") numbers = true;
else if(a.begins("-")) writefln("Ignoring unknown option %s", a);
else files ~= a;
int help(char[] msg)
{
writefln("%s", msg);
writefln("Syntax: %s [options] esm-file [esm-file ... ]", args[0]);
writefln(" Options:");
writefln(" -r Display all records in raw format");
writefln(" -n List the number of each record");
writefln(" -sl List scripts");
writefln(" -s name Show given script");
writefln(" -cil List interior cells");
writefln(" -cel List exterior cells with names");
writefln(" -wl List weapons");
return 1;
}
if(files.length == 0) return help("No input files given");
if(scptShow && scptName == "") return help("No script name given");
initializeMemoryRegions();
if(raw)
{
foreach(int fileNum, char[] filename; files)
{
try
{
esFile.open(filename, esmRegion);
printRaw();
}
catch(Exception e)
{
try {writefln(e);}
catch {}
writefln("Error on file %s", filename);
}
catch
{
writefln("Error: Unkown failure on file %s", filename);
}
}
return;
}
// Disable resource lookups.
resources.dummy = true;
try loadTESFiles(files);
catch(Exception e)
{
try {writefln(e);}
catch {writefln("(Invalid UTF in error message)");}
}
catch { writefln("Error: Unkown failure"); }
// List weapons
if(weList) foreach(n, m; weapons.names)
{
alias Weapon.Type WT;
switch(m.data.type)
{
case WT.ShortBladeOneHand: writef("Short Sword"); break;
case WT.LongBladeOneHand: writef("Long Sword, One-Handed"); break;
case WT.LongBladeTwoHand: writef("Long Sword, Two-Handed"); break;
case WT.BluntOneHand: writef("Blunt, One-Handed"); break;
case WT.BluntTwoClose: writef("Blunt, Two-Handed"); break;
case WT.BluntTwoWide: writef("Blunt, Two-Handed Wide"); break;
case WT.SpearTwoWide: writef("Spear, Two-Handed"); break;
case WT.AxeOneHand: writef("Axe, One-Handed"); break;
case WT.AxeTwoHand: writef("Axe, Two-Handed"); break;
case WT.MarksmanBow: writef("Bow"); break;
case WT.MarksmanCrossbow: writef("Crossbow"); break;
case WT.MarksmanThrown: writef("Thrown weapon"); break;
case WT.Arrow: writef("Arrow"); break;
case WT.Bolt: writef("Bolt"); break;
}
writefln(" id '%s': name '%s'", n, m.name);
if(m.data.flags & Weapon.Flags.Magical)
writefln("Magical");
if(m.data.flags & Weapon.Flags.Silver)
writefln("Silver");
writefln("Weight: ", m.data.weight);
writefln("Value: ", m.data.value);
writefln("Health: ", m.data.health);
writefln("Speed: ", m.data.speed);
writefln("Reach: ", m.data.reach);
writefln("Enchantment points: ", m.data.enchant);
writefln("Combat: ", m.data.chop, m.data.slash, m.data.thrust);
if(m.enchant) writefln("Has enchantment '%s'", m.enchant.id);
if(m.script) writefln("Has script '%s'", m.script.id);
writefln();
}
if(numbers)
{
writefln("Activators: ", activators.length);
writefln("Doors: ", doors.length);
writefln("Globals: ", globals.length);
writefln("Sounds: ", sounds.length);
writefln("Game Settings: ", gameSettings.length);
writefln("Factions: ", factions.length);
writefln("Statics: ", statics.length);
writefln("Spells: ", spells.length);
writefln("Potions: ", potions.length);
writefln("Apparatus: ", appas.length);
writefln("Armors: ", armors.length);
writefln("Body parts: ", bodyParts.length);
writefln("Enchantments: ", enchants.length);
writefln("Books: ", books.length);
writefln("Birth signs: ", birthSigns.length);
writefln("Land texture files: ", landTextures.length);
writefln("Weapons: ", weapons.length);
writefln("Lockpicks: ", lockpicks.length);
writefln("Probes: ", probes.length);
writefln("Repairs: ", repairs.length);
writefln("Cells: ", cells.length);
writefln(" Interior: ", cells.numInt);
writefln(" Exterior: ", cells.numExt);
writefln("Regions: ", regions.length);
writefln("Lights: ", lights.length);
writefln("Skills: ", skills.length);
writefln("Sound generators: ", soundGens.length);
writefln("Races: ", races.length);
writefln("Misc items: ", miscItems.length);
writefln("Cloths: ", clothes.length);
writefln("Ingredients: ", ingreds.length);
writefln("Classes: ", classes.length);
writefln("Containers: ", containers.length);
writefln("Creatures: ", creatures.length);
writefln("Leveled item lists: ", itemLists.length);
writefln("Leveled creature lists: ", creatureLists.length);
writefln("NPCs: ", npcs.length);
writefln("Scripts: ", scripts.length);
writefln("Dialogues: ", dialogues.length);
writefln("Hyperlinks: ", hyperlinks.list.length);
writefln("Start scripts: ", startScripts.length);
writefln("\nTotal items: ", items.length);
writefln("Total actors: ", actors.length);
writefln("Total cell placable items: ", cellRefs.length);
}
if(scptList) foreach(a, b; scripts.names) writefln(a);
if(ciList) foreach(a, b; cells.in_cells) writefln(a);
if(ceList)
foreach(uint i, c; .cells.ex_cells)
{
int x, y;
CellList.decompound(i, x, y);
if(c.name.length)
writefln("%s,%s: %s", x, y, c.name);
}
if(scptShow)
{
Script *p = scripts.names.lookup(scptName);
if(p)
writefln("Script '%s', text is:\n-------\n%s\n-------", p.id, p.scriptText);
else writefln("Script '%s' not found", scptName);
writefln();
}
writefln(esmRegion);
poolSize();
}
// Quick function that simply iterates through an ES file and prints
// out all the records and subrecords. Some of this code is really old
// (about 2004-2005)
void printRaw()
{
with(esFile)
{
// Variable length integer (this only works for unsigned ints!)
ulong getHVUint()
{
ulong l;
getSubHeader();
if( (getSubSize != 4) &&
(getSubSize != 2) &&
(getSubSize != 8) )
fail(format("Unknown integer size: ", getSubSize));
readExact(&l, getSubSize);
return l;
}
writefln("Filename: ", getFilename);
writef("Filetype: ");
switch(getFileType())
{
case FileType.Plugin: writefln("Plugin"); break;
case FileType.Master: writefln("Master"); break;
case FileType.Savegame: writefln("Savegame"); break;
case FileType.Unknown: writefln("Unknown"); break;
}
writef("Version: ");
if(isVer12()) writefln("1.2");
else if(isVer13()) writefln("1.3");
else writefln("Unknown");
writefln("Records: ", getRecords);
writefln("Master files:");
for(int i; i<getMasters.length; i++)
writefln(" %s", getMasters[i].name, ", ", getMasters[i].size, " bytes");
writefln("Author: %s", getAuthor);
//writefln("Description: %s", desc);
writefln("Total file size: %d\n", getFileSize);
writefln("List of records:");
while(hasMoreRecs())
{
uint flags;
// Read record header
char[] recName = getRecName();
getRecHeader(flags);
if(flags)
{
writef("Flags: ");
if(flags & RecordFlags.Persistent) writef("Persistent ");
if(flags & RecordFlags.Blocked) writef("Blocked ");
if(flags & RecordFlags.Flag6) writef("Flag6 ");
if(flags & RecordFlags.Flag13) writef("Flag13 ");
writefln();
if(flags & RecordFlags.Unknown)
writefln("UNKNOWN flags are set: %xh", flags);
}
// Process sub record
writef("%s %d bytes", recName, getRecLeft());
writefln();
while(hasMoreSubs())
{
getSubName();
char[] subName = retSubName();
writef(" %s = ", subName);
// Process header
if(subName == "NAME" || subName == "STRV" ||
subName == "FNAM" || subName == "MODL" ||
subName == "SCRI" || subName == "RGNN" ||
subName == "BNAM" || subName == "ONAM" ||
subName == "INAM" || subName == "SCVR" ||
subName == "RNAM" || subName == "DNAM" ||
subName == "ANAM")
//subName == "SCTX") // For script text
//getHString();
{
try{writefln("'%s'", getHString());}
catch(UtfException e)
{
writefln("Got an UTF-ie, ", e);
}
}
else if(subName == "FLTV" || subName == "XSCL") writefln(getHFloat());
else if(subName == "INTV" /*|| subName == "NAM0"*/ || subName == "FRMR")
writefln(getHVUint());
else
{
int left = skipHSub();
writefln(left, " bytes");
}
}
writefln();
}
}
}

@ -0,0 +1,736 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (filereader.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.filereader;
private:
import std.stdio;
import std.stream;
import std.string;
import util.regions;
import core.resource;
import esm.listkeeper;
import esm.defs;
public:
/*
* Exception class for TES3File
*/
class TES3FileException: Exception
{
this(char[] msg) {super("Error reading TES3 file: " ~ msg);}
this() {this("Unknown error");}
}
// Some flags are in use that we don't know. But we don't really know
// any of them.
enum RecordFlags : uint
{
Flag6 = 0x20, // Eg. adventurers_v2.0.esp (only once per file?)
Persistent = 0x400,
Flag13 = 0x1000, // Eg. Astarsis_BR.esm (several times per file?)
Blocked = 0x2000,
Unknown = 0xffffffff - 0x3420
}
enum FileType
{
Unknown,
Esp, Plugin = Esp,
Esm, Master = Esm,
Ess, Savegame = Ess
}
// Special files
enum SpecialFile
{
Other,
Morrowind,
Tribunal,
Bloodmoon
}
enum Version { Unknown, v12, v13 }
// This struct should contain enough data to put a TES3File object
// back into a specific file position and state. We use it to save the
// "position" of objects in a file (eg. a cell), so we can return
// there later and continue where we stopped (eg. when we want to load
// that specific cell.)
struct TES3FileContext
{
char[] filename;
uint leftFile, leftRec, leftSub;
NAME recName, subName;
FileType type;
Version ver;
ulong filepos;
}
/**
* Instance used to read TES3 files. Since we will only be reading one
* file at a time, we might as well make one global instance.
*/
TES3File esFile;
/**
* This struct reads an Elder Scrolls 3 file (esp, esm or ess)
*
* Makes heavy use of private variables to represent current
* state.
*
* Relevant exceptions are
* TES3FileException - error interpreting file
* StreamFileException - file IO error
*/
struct TES3File
{
private:
BufferedFile file;// Input file
// These are only used by getRecHeader and getSubHeader for
// asserting the file's integrity.
uint leftFile; // Number of unread bytes in file
uint leftRec; // Number of unread bytes in record
// This is used by sub-record readers for integrity checking.
uint leftSub; // Number of bytes in subrecord
// Name of current record and current sub-record.
NAME recName, subName;
char[] filename; // Filename
FileType type; // File type
Version ver; // File format version
char[] author; // File author (max 32 bytes (with null?))
char[] desc; // Description (max 256 bytes (ditto?))
uint records; // Number of records in the file (doesn't seem to be right?)
SpecialFile spf; // Is this a file we have to treat in a special way?
struct _mast
{
char[] name; // File name of an esm master for this file
ulong size; // The master file's size in bytes (used for
// version control)
}
// List of esm masters for this file. For savegames this list also
// contains all plugins.
_mast masters[];
// TES3.HEDR, file header struct
align(1) struct HEDRstruct
{
union
{
float ver; // File format version, 1.2 and 1.3 supported.
uint verHex; // 1.2 = 0x3f99999a, 1.3 = 0x3fa66666
}
int type; // 0=esp, 1=esm, 32=ess
NAME32 author; // Author's name
NAME256 desc; // File description blurb
uint records; // Number of records in file (?)
}
static assert(HEDRstruct.sizeof == 300);
// Which memory region to use for allocations.
RegionManager region;
public:
// Get file information
char[] getFilename() { return filename; }
ulong getFileSize() { return file.size; }
ulong getPosition() { return file.position; }
SpecialFile getSpecial() { return spf; }
char[] retSubName() { return subName; }
bool isVer12() { return ver == Version.v12;}
bool isVer13() { return ver == Version.v13;}
FileType getFileType() { return type; }
_mast[] getMasters() { return masters; }
uint getRecords() { return records; }
char[] getAuthor() { return author; }
RegionManager getRegion() { return region; }
// Store the current file state (position, file name, version, debug
// info). The info should be enough to get us back on track for
// reading from a file, without having to reread the header or any
// previous records.
void getContext(ref TES3FileContext c)
{
c.filename = filename;
c.leftFile = leftFile;
c.leftRec = leftRec;
c.leftSub = leftSub;
c.recName[] = recName;
c.subName[] = subName;
c.type = type;
c.ver = ver;
c.filepos = file.position;
}
// Opens the file if it is not already opened. A region manager has
// to be specified.
void restoreContext(TES3FileContext c, RegionManager r)
{
if(filename != c.filename)
openFile(c.filename, r);
file.seekSet(c.filepos);
// File is now open, copy state information
filename = c.filename;
leftFile = c.leftFile;
leftRec = c.leftRec;
leftSub = c.leftSub;
recName[] = c.recName;
subName[] = c.subName;
type = c.type;
ver = c.ver;
}
// Open a new file and assign a region
private void openFile(char[] filename, RegionManager r)
{
close();
debug writefln("Opening file");
if(file is null) file = new BufferedFile(new File());
file.open(filename);
region = r;
}
void open(char[] filename, RegionManager r)
{
uint flags;
debug writefln("openFile(%s, %s)", filename, r);
openFile(filename, r);
if(iEnds(filename, "Morrowind.esm")) spf = SpecialFile.Morrowind;
else if(iEnds(filename, "Tribunal.esm")) spf = SpecialFile.Tribunal;
else if(iEnds(filename, "Bloodmoon.esm")) spf = SpecialFile.Bloodmoon;
else spf = SpecialFile.Other;
debug writefln("Reading header");
this.filename = filename;
// Make a copy so the file name is not overwritten (not really needed?)
// this.filename = region.copy(filename);
// Oops, this was bad. The file name ends up in various
// TES3FileContext structs, allocated outside the GC's reach. A
// .dup'ed copy might be released and overwritten.
// this.filename = filename.dup;
leftFile = file.size;
// First things first
if(getRecName() != "TES3")
fail("Not a valid Morrowind file");
// Record header
getRecHeader(flags);
if(flags)
writefln("WARNING: Header flags are non-zero");
// Read and analyse the header data
HEDRstruct hedr;
readHNExact(&hedr, hedr.sizeof, "HEDR");
// The float hedr.ver signifies the file format version. It can
// take on these two values:
// 0x3f99999a = 1.2
// 0x3fa66666 = 1.3
if( hedr.verHex == 0x3f99999a )
ver = Version.v12;
else if( hedr.verHex == 0x3fa66666 )
ver = Version.v13;
else
{
ver = Version.Unknown;
writefln("WARNING: Unknown version: ", hedr.ver);
writefln(" Hex: %X h", *(cast(uint*)&hedr.ver));
}
switch(hedr.type)
{
case 0: type = FileType.Esp; break;
case 1: type = FileType.Esm; break;
case 32: type = FileType.Ess; break;
default:
type = FileType.Unknown;
writefln("WARNING: Unknown file type: ", hedr.type);
}
author = region.copy(stripz(hedr.author));
desc = region.copy(stripz(hedr.desc));
records = hedr.records;
masters = null;
// Reads a MAST and a DATA fields
while(isNextSub("MAST"))
{
_mast ma;
// MAST entry - master file name
ma.name = getHString();
// DATA entry - master file size
ma.size = getHNUlong("DATA");
// Add to the master list!
masters ~= ma;
}
if(type == FileType.Savegame)
{
// What are these again? I don't remember.
getSubNameIs("GMDT");
skipHSubSize(124);
getSubNameIs("SCRD");
skipHSubSize(20);
// Screenshot, used for save games.
getSubNameIs("SCRS");
skipHSubSize(65536);
}
}
// Close the file. We do not clear any object data at this point.
void close()
{
debug writefln("close()");
if(file !is null)
file.close();
leftFile = leftRec = leftSub = 0;
debug writefln("Clearing strings");
recName[] = '\0';
subName[] = '\0';
// This tells restoreContext() that we have to reopen the file
filename = null;
debug writefln("exit close()");
}
/*
* Error reporting
*/
void fail(char[] msg)
{
throw new TES3FileException
(msg ~ "\nFile: " ~ filename ~ "\nRecord name: " ~ recName
~ "\nSubrecord name: " ~ subName);
}
/************************************************************************
*
* Highest level readers, reads a name and looks it up in the given
* list.
*
************************************************************************/
// This should be more than big enough for references.
private char lookupBuffer[200];
char[] tmpHString()
{
getSubHeader();
// Use this to test the difference in memory consumption.
//return getString(region.getString(leftSub));
return getString(lookupBuffer[0..leftSub]);
}
// These are used for file lookups
MeshIndex getMesh()
{ getSubNameIs("MODL"); return resources.lookupMesh(tmpHString()); }
SoundIndex getSound()
{ getSubNameIs("FNAM"); return resources.lookupSound(tmpHString()); }
IconIndex getIcon(char[] s = "ITEX")
{ getSubNameIs(s); return resources.lookupIcon(tmpHString()); }
TextureIndex getTexture()
{ getSubNameIs("DATA"); return resources.lookupTexture(tmpHString()); }
// The getO* functions read optional records. If they are not
// present, return null.
MeshIndex getOMesh()
{ return isNextSub("MODL") ? resources.lookupMesh(tmpHString()) : MeshIndex.init; }
/*
SoundIndex getOSound()
{ return isNextSub("FNAM") ? resources.lookupSound(tmpHString()) : SoundIndex.init; }
*/
IconIndex getOIcon()
{ return isNextSub("ITEX") ? resources.lookupIcon(tmpHString()) : IconIndex.init; }
TextureIndex getOTexture(char[] s="TNAM")
{ return isNextSub(s) ? resources.lookupTexture(tmpHString()) : TextureIndex.init; }
// Reference with name s
template getHNPtr(Type)
{
Type* getHNPtr(char[] s, ListKeeper list)
{ getSubNameIs(s); return cast(Type*) list.lookup(tmpHString()); }
}
// Reference, only get header
template getHPtr(Type)
{
Type* getHPtr(ListKeeper list)
{ return cast(Type*) list.lookup(tmpHString()); }
}
// Optional reference with name s
template getHNOPtr(Type)
{
Type* getHNOPtr(char[] s, ListKeeper list)
{ return isNextSub(s) ? cast(Type*)list.lookup(tmpHString()) : null; }
}
/************************************************************************
*
* Somewhat high level reading methods. Knows about headers and
* leftFile/leftRec/leftSub.
*
************************************************************************/
// "Automatic" versions. Sets and returns recName and subName and
// updates leftFile/leftRec.
char[] getRecName()
{
if(!hasMoreRecs())
fail("No more records, getRecName() failed");
getName(recName);
leftFile-= 4;
return recName;
}
// This is specially optimized for LoadINFO
bool isEmptyOrGetName()
{
if(leftRec)
{
file.readBlock(subName.ptr, 4);
leftRec -= 4;
return false;
}
return true;
}
// I've tried to optimize this slightly, since it gets called a LOT.
void getSubName()
{
if(leftRec <= 0)
fail("No more sub-records, getSubName() failed");
// Don't bother with error checking, we will catch an EOF upon
// reading the subrecord data anyway.
file.readBlock(subName.ptr, 4);
leftRec -= 4;
}
// We often expect a certain subrecord type, this makes it easy to
// check.
void getSubNameIs(char[] s)
{
getSubName();
if( subName != s )
fail("Expected subrecord "~s~" but got "~subName);
}
// Checks if the next sub-record is called s. If it is, run
// getSubName, if not, return false.
bool isNextSub(char[] s)
{
if(!leftRec) return false;
getName(subName);
if(subName != s)
{
file.seekCur(-4);
return false;
}
leftRec -= 4;
//getSubName();
return true;
}
// Same as isNextSub, only it works on records instead of
// sub-records. It also loads the record header.
bool isNextHRec(char[] s)
{
if(!leftFile) return false;
getName(recName);
if(recName != s)
{
file.seekCur(-4);
return false;
}
leftFile -= 4;
uint flags;
getRecHeader(flags);
return true;
}
bool hasMoreSubs() { return leftRec > 0; }
bool hasMoreRecs() { return leftFile > 0; }
// Remaining size of current record
uint getRecLeft() { return leftRec; }
// Size of current sub record
uint getSubSize() { return leftSub; }
// Skip the rest of this record
void skipRecord()
{
file.seekCur(leftRec);
leftRec = 0;
}
// Skip current sub record and return size
uint skipHSub()
{
getSubHeader();
file.seekCur(leftSub);
return leftSub;
}
// Skip sub record and check it's size
void skipHSubSize(uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
file.seekCur(leftSub);
}
// These read an entire sub-record, including the header. They also
// adjust and check leftSub and leftRecord variables through calling
// getSubHeader().
void readHExact(void * p, uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
readExact(p, leftSub);
}
template TgetHType(T)
{ T TgetHType() { T t; readHExact(&t, t.sizeof); return t;} }
// To make these easier to use (and to further distinguish them from
// the above "raw" versions), these return their value instead of
// using an ref argument.
alias TgetHType!(uint) getHUint;
alias TgetHType!(int) getHInt;
alias TgetHType!(float) getHFloat;
alias TgetHType!(ulong) getHUlong;
alias TgetHType!(byte) getHByte;
// Reads a string sub-record, including header
char[] getHString()
{
getSubHeader();
// Hack to make MultiMark.esp load
if(leftSub == 0)
{
// Skip the following zero byte
leftRec--;
assert(file.getc() == 0);
// Report this by setting a flag or something?
return null;
}
return getString(region.getString(leftSub));
}
// Other quick aliases (this is starting to get messy)
// Get string sub record string with name s
char[] getHNString(char[] s)
{ getSubNameIs(s); return getHString(); }
// Get optional sub record string with name s
char[] getHNOString(char[] s)
{ return isNextSub(s) ? getHString() : null; }
template TgetHNType(T)
{ T TgetHNType(char[] s) { T t; readHNExact(&t, t.sizeof, s); return t;} }
template TgetHNOType(T)
{
T TgetHNOType(char[] s, T def)
{
if(isNextSub(s))
{
T t;
readHExact(&t, t.sizeof);
return t;
}
else return def;
}
}
alias TgetHNType!(uint) getHNUint;
alias TgetHNType!(int) getHNInt;
alias TgetHNType!(float) getHNFloat;
alias TgetHNType!(ulong) getHNUlong;
alias TgetHNType!(byte) getHNByte;
alias TgetHNType!(short) getHNShort;
alias TgetHNType!(byte) getHNByte;
alias TgetHNOType!(float) getHNOFloat;
alias TgetHNOType!(int) getHNOInt;
alias TgetHNOType!(byte) getHNOByte;
void readHNExact(void* p, uint size, char[] s)
{ getSubNameIs(s); readHExact(p,size); }
// Record header
// This updates the leftFile variable BEYOND the data that follows
// the header, ie beyond the entire record. You are supposed to use
// the leftRec variable when reading record data.
void getRecHeader(out uint flags)
{
// General error checking
if(leftFile < 12)
fail("End of file while reading record header");
if(leftRec)
fail(format("Previous record contains %d unread bytes", leftRec));
getUint(leftRec);
getUint(flags);// This header entry is always zero
assert(flags == 0);
getUint(flags);
leftFile -= 12;
// Check that sizes add up
if(leftFile < leftRec)
fail(format(leftFile, " bytes left in file, but next record contains ",
leftRec," bytes"));
// Adjust number of bytes left in file
leftFile -= leftRec;
}
// Sub-record head
// This updates leftRec beyond the current sub-record as
// well. leftSub contains size of current sub-record.
void getSubHeader()
{
if(leftRec < 4)
fail("End of record while reading sub-record header");
if(file.readBlock(&leftSub, 4) != 4)
fail("getSubHeader could not read header length");
leftRec -= 4;
// Adjust number of record bytes left
leftRec -= leftSub;
// Check that sizes add up
if(leftRec < 0)
fail(format(leftRec+leftSub,
" bytes left in record, but next sub-record contains ",
leftSub," bytes"));
}
void getSubHeaderIs(uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Expected header size to be ", size, ", not ", leftSub));
}
/*************************************************************************
*
* Low level reading methods
*
*************************************************************************/
/// Raw data of any size
void readExact(void *buf, uint size)
{
assert(size != 0);
file.readExact(buf,size);
}
// One byte
void getByte(out byte b) { file.read(b); }
void getUByte(out ubyte b) { file.read(b); }
// Two bytes
void getUShort(out ushort s) { file.read(s); }
// Four bytes
void getUint(out uint u) { file.read(u); }
void getInt(out int i) { file.read(i); }
void getFloat(out float f) { file.read(f); }
// Eight bytes
void getUlong(out ulong l) { file.read(l); }
// Get a record or subrecord name, four bytes
void getName(NAME name)
{
file.readBlock(name.ptr, 4);
/*
if(file.readBlock(name.ptr, 4) != 4)
fail("getName() could not find more data");
*/
}
// Fill buffer of predefined length. If actual string is shorter
// (ie. null terminated), the buffer length is set
// accordingly. Chopped string is returned.
char[] getString(char[] str)
{
if(str.length != file.readBlock(str.ptr,str.length))
fail("getString() could not find enough data in stream");
str = stripz(str);
return str;
}
// Use this to allocate and read strings of predefined length
char[] getString(int l)
{
char[] str = region.getString(l);
return getString(str);
}
}

@ -0,0 +1,44 @@
module esm.imports;
/* This is a file that imports common modules used by the load*.d
record loaders. It is really a cut down version of the start of
records.d.
This file MUST NOT import records.d - directly or indirectly -
because that will trigger a nice three page long list of template
forwarding errors from the compiler.
What happens is that when DMD/GDC compiles one of the load* files,
it is forced to read records.d first (since it is an imported
module) - but then it sees a template that referes to a struct in
the current load* file, before that struct is defined. Curriously
enough, DMD has no problems when you specify all the source files
on the command line simultaneously. This trick doesn't work with
GDC though, and DSSS doesn't use it either.
This file was created to work around this compiler bug.
*/
public
{
import esm.defs;
import esm.filereader;
import esm.listkeeper;
import core.resource;
import core.memory;
import util.regions;
import monster.util.aa;
import std.stdio;
import std.string;
alias RegionBuffer!(ENAMstruct) EffectList;
// Records that are cross referenced often
import esm.loadscpt;
import esm.loadsoun;
import esm.loadspel;
import esm.loadench;
}

@ -0,0 +1,367 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (listkeeper.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.listkeeper;
import monster.util.aa;
import core.memory;
import esm.filereader;
import esm.defs;
import std.stdio;
// Item types, used in the lookup table for inventory items, creature
// lists and leveled lists. We also use it for all types of references
// that can exist in cells.
enum ItemType
{
// Items
None = 0, Potion, Apparatus, Armor, Weapon, Book, Clothing,
Light, Ingredient, Pick, Probe, Repair, Misc, ItemList,
// Used for creature lists
Creature, CreatureList, NPC,
// Other cell references
Door, Activator, Static, Container//, SoundGen
}
abstract class ListKeeper
{
int listIndex;
new(uint size)
{
return esmRegion.allocate(size).ptr;
}
delete(void *p) { assert(0); }
this()
{
// Store our index for later use
listIndex = recordLists.length;
// Add the class to the global list
recordLists ~= this;
}
// Load a record from a master or plugin file
void load();
// Looks up a reference. If it does not exist it is assumed to be a
// forward reference within a file, and is inserted.
void* lookup(char[] s);
// Tell the loader that current file has ended, so it can do things
// like check that all referenced objects have been loaded.
void endFile();
// Number of inserted elements
uint length();
void addToList(ref ItemBaseList l, ItemType t) { assert(0); }
}
ListKeeper recordLists[];
// Keep the list of Type structures for records where the first
// subrecord is an id string called NAME. This id is used for
// lookup. Although almost all lookups match in case, there are a few
// sounds that don't, so we treat these id lookups as generally case
// insensitive. This hasn't posed any problems so far.
class ListID(Type) : ListKeeper
{
HashTable!(char[], Type, ESMRegionAlloc, CITextHash) names;
this(uint size)
{
names = names.init;
if(size) names.rehash(size);
}
// Reads the id for this header. Override if the id is not simply
// getHNString("NAME")
char[] getID()
{
return esFile.getHNString("NAME");
}
// Load a record from a master of plugin file
void load()
{
assert(esFile.getFileType == FileType.Esm ||
esFile.getFileType == FileType.Esp);
// Get the identifier of this record
char[] id = getID();
// Get pointer to a new or existing object.
Type *p;
if(names.insertEdit(id, p))
// A new item was inserted
{
p.state = LoadState.Unloaded;
p.id = id;
p.load();
p.state = LoadState.Loaded;
}
else
// Item already existed, either from a previous file or as a
// forward reference from this file. Load on top of it. The
// LoadState tells the struct whether it contains loaded data.
{
if(p.state == LoadState.Loaded)
// Make a special case for this, perhaps, or just ignore it.
writefln("WARNING: Duplicate record in file %s: '%s'",
esFile.getFilename(), id);
//esFile.fail("Duplicate record in file: '" ~ id ~ "'");
assert(icmp(p.id, id) == 0);
p.load();
p.state = LoadState.Loaded;
}
}
// Looks up a reference. If it does not exist it is assumed to be a
// forward reference within a file, and is inserted.
void* lookup(char[] id)
{
if(!id.length) return null; // Empty reference
Type *p = names.lookup(id);
// Is the value in the list?
if(!p)
// No, assume it is a forward reference.
{
// Since the lookup name is stored in an internal buffer in
// esFile, we have to copy it.
id = esmRegion.copy(id);
// To avoid copying the string on every lookup, we have to
// insert in a separate step. But a double lookup isn't
// really THAT expensive. Besides, my tests show that this
// is used in less than 10% of the cases.
names.insertEdit(id, p);
p.id = id;
p.state = LoadState.Unloaded;
}
return cast(void*)p;
}
// Check that all referenced objects are actually loaded.
void endFile()
in
{
// We can skip this in release builds
names.validate();
}
body
{
foreach(char[] id, ref Type t; names)
// Current file is now counted as done
if(t.state == LoadState.Loaded) t.state = LoadState.Previous;
else if(t.state == LoadState.Unloaded)
//writefln("WARNING: Unloaded reference " ~ id);
esFile.fail("Unloaded reference " ~ id);
}
// Number of inserted elements
uint length() {return names.length;}
// Add the names in this list to an ItemList
void addToList(ref ItemBaseList l, ItemType t)
{
foreach(char[] id, ref Type s; names)
l.insert(id, &s, t);
}
}
/*
class Dummy : ListKeeper
{
this() {}
void load() {}
void* lookup(char[] s) { return null; }
void endFile() {}
uint length() { return 0; }
}
*/
// A pointer to an item
struct ItemBase
{
ItemType type;
void *p;
}
struct ItemBaseList
{
HashTable!(char[],ItemBase,ESMRegionAlloc) list;
void addList(ItemBaseList l)
{
foreach(char[] id, ItemBase b; l.list)
insert(id, b.p, b.type);
}
void addList(ListKeeper source, ItemType type)
{
source.addToList(*this, type);
}
void insert(char[] id, void* p, ItemType type)
{
ItemBase *b;
if(!list.insertEdit(id, b))
{
//writefln("Replacing item ", id);
if(b.type != ItemType.None)
esFile.fail("Replaced valid item: " ~ id);
}
//else writefln("Inserting new item ", id);
b.type = type;
b.p = p;
}
// Called at the end to check that all referenced items have been resolved
void endMerge()
{
foreach(char[] id, ref ItemBase t; list)
// Current file is now counted as done
if(t.type == ItemType.None)
// TODO: Don't use esFile.fail for this
esFile.fail("ItemBaseList: Unresolved forward reference: " ~ id);
}
// Look up an item, return a pointer to the ItemBase representing
// it. If it does not exist, it is inserted.
ItemBase *lookup(char[] id)
{
if(!id.length) return null; // Empty reference
ItemBase *b = list.lookup(id);
// Is the value in the list?
if(!b)
// No, assume it is a forward reference.
{
// Since the lookup name is stored in an internal buffer in
// esFile, we have to copy it.
id = esmRegion.copy(id);
// To avoid copying the string on every lookup, we have to
// insert in a separate step. But a double lookup isn't
// really THAT expensive.
list.insertEdit(id, b);
b.p = null;
b.type = ItemType.None;
}
return b;
}
}
// Um... I think this code is double up because I had to split it into
// separate files to avoid template stuff. I don't think we need to do
// that anymore.
// An item. Contains a reference to an ItemBase, which again is a
// reference to an item. The ItemBase might change after we have
// looked it up (for forward references), so we have to use a pointer.
struct Item
{
ItemBase *i;
void* getPtr(ItemType type)
{
if(i != null && i.type == type) return i.p;
return null;
}
T* getType(T, ItemType Type)()
{
return cast(T*)getPtr(Type);
}
/* Avoid the templates for now
alias getType!(Potion, ItemType.Potion) getPotion;
alias getType!(Apparatus, ItemType.Apparatus) getApparatus;
alias getType!(Armor, ItemType.Armor) getArmor;
alias getType!(Weapon, ItemType.Weapon) getWeapon;
alias getType!(Book, ItemType.Book) getBook;
alias getType!(Clothing, ItemType.Clothing) getClothing;
alias getType!(Light, ItemType.Light) getLight;
alias getType!(Ingredient, ItemType.Ingredient) getIngredient;
alias getType!(Tool, ItemType.Pick) getPick;
alias getType!(Tool, ItemType.Probe) getProbe;
alias getType!(Tool, ItemType.Repair) getRepair;
alias getType!(Misc, ItemType.Misc) getMisc;
alias getType!(LeveledItems, ItemType.ItemList) getItemList;
alias getType!(Creature, ItemType.Creature) getCreature;
alias getType!(LeveledCreatures, ItemType.CreatureList) getCreatureList;
alias getType!(NPC, ItemType.NPC) getNPC;
alias getType!(Door, ItemType.Door) getDoor;
alias getType!(Activator, ItemType.Activator) getActivator;
alias getType!(Static, ItemType.Static) getStatic;
alias getType!(Container, ItemType.Container) getContainer;
*/
}
struct ItemList
{
private:
ItemBaseList list;
public:
void addList(ItemList l)
{ list.addList(l.list); }
void addList(ListKeeper source, ItemType type)
{ list.addList(source, type); }
Item lookup(char[] id)
{
Item i;
i.i = list.lookup(id);
return i;
}
void endMerge()
{ list.endMerge(); }
void endFile()
in { list.list.validate(); }
body {}
void rehash(uint size)
{ list.list.rehash(size); }
uint length() { return list.list.length(); }
}
// Aggregate lists, made by concatinating several other lists.
ItemList items; // All inventory items, including leveled item lists
ItemList actors; // All actors, ie. NPCs, creatures and leveled lists
ItemList cellRefs; // All things that are referenced from cells

@ -0,0 +1,53 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadacti.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadacti;
import esm.imports;
/*
* Activator
*
* Activators are static in-world objects that pop up a caption when
* you stand close and look at them, for example signs. Some
* activators have scrips, such as beds that let you sleep when you
* activate them.
*/
struct Activator
{
char[] id;
LoadState state;
char[] name;
Script *script;
MeshIndex model;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
script = getHNOPtr!(Script)("SCRI", scripts);
}}
}
ListID!(Activator) activators;

@ -0,0 +1,74 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadalch.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadalch;
import esm.imports;
/*
* Alchemy item (potions)
*/
struct Potion
{
char[] id;
LoadState state;
align(1) struct ALDTstruct
{
float weight;
int value;
int autoCalc;
static assert(ALDTstruct.sizeof == 12);
}
ALDTstruct data;
char[] name;
MeshIndex model;
IconIndex icon;
Script *script;
EffectList effects;
void load()
{with(esFile){
model = getMesh();
icon = getIcon("TEXT");
script = getHNOPtr!(Script)("SCRI", scripts);
name = getHNOString("FNAM");
readHNExact(&data, data.sizeof, "ALDT");
effects = getRegion().getBuffer!(ENAMstruct)(0,1);
while(isNextSub("ENAM"))
{
effects.length = effects.length + 1;
readHExact(&effects.array[$-1], effects.array[$-1].sizeof);
}
}}
}
ListID!(Potion) potions;

@ -0,0 +1,67 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadappa.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadappa;
import esm.imports;
/*
* Alchemist apparatus
*/
struct Apparatus
{
char[] id, name;
LoadState state;
enum AppaType : int
{
MortarPestle = 0,
Albemic = 1,
Calcinator = 2,
Retort = 3
}
align(1) struct AADTstruct
{
AppaType type;
float quality;
float weight;
int value;
static assert(AADTstruct.sizeof == 16);
}
AADTstruct data;
MeshIndex model;
IconIndex icon;
Script *script;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
readHNExact(&data, data.sizeof, "AADT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getIcon();
}}
}
ListID!(Apparatus) appas;

@ -0,0 +1,143 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadarmo.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadarmo;
import esm.imports;
import esm.loadbody, esm.loadench;
/*
* Armor
*/
// Reference to body parts
struct PartReference
{
enum Index
{
Head = 0,
Hair = 1,
Neck = 2,
Cuirass = 3,
Groin = 4,
Skirt = 5,
RHand = 6,
LHand = 7,
RWrist = 8,
LWrist = 9,
Shield = 10,
RForearm = 11,
LForearm = 12,
RUpperarm = 13,
LUpperarm = 14,
RFoot = 15,
LFoot = 16,
RAnkle = 17,
LAnkle = 18,
RKnee = 19,
LKnee = 20,
RLeg = 21,
LLeg = 22,
RPauldron = 23,
LPauldron = 24,
Weapon = 25,
Tail = 26
}
Index part;
BodyPart* male, female;
}
struct PartReferenceList
{
RegionBuffer!(PartReference) parts;
void load()
{with(esFile){
parts = getRegion().getBuffer!(PartReference)(0,1);
while(isNextSub("INDX"))
{
parts.length = parts.length + 1;
with(parts.array[$-1])
{
part = cast(PartReference.Index) getHByte;
male = getHNOPtr!(BodyPart)("BNAM", bodyParts);
female = getHNOPtr!(BodyPart)("CNAM", bodyParts);
}
}
}}
}
struct Armor
{
enum Type : int
{
Helmet = 0,
Cuirass = 1,
LPauldron = 2,
RPauldron = 3,
Greaves = 4,
Boots = 5,
LGauntlet = 6,
RGauntlet = 7,
Shield = 8,
LBracer = 9,
RBracer = 10
}
align(1) struct AODTstruct
{
Type type;
float weight;
int value, health, enchant, armor;
static assert(AODTstruct.sizeof==24);
}
AODTstruct data;
char[] name, id;
LoadState state;
PartReferenceList parts;
MeshIndex model;
IconIndex icon;
Script *script;
Enchantment *enchant;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
script = getHNOPtr!(Script)("SCRI", scripts);
readHNExact(&data, data.sizeof, "AODT");
icon = getOIcon();
parts.load();
enchant = getHNOPtr!(Enchantment)("ENAM", enchants);
}}
}
ListID!(Armor) armors;

@ -0,0 +1,90 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadbody.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadbody;
import esm.imports;
/*
* Body part mesh. NPCs have several mesh files, one for each element
* in the enum below. One BODY record is given for each part.
*/
struct BodyPart
{
enum MeshType : byte
{
Head = 0,
Hair = 1,
Neck = 2,
Chest = 3,
Groin = 4,
Hand = 5,
Wrist = 6,
Forearm = 7,
Upperarm = 8,
Foot = 9,
Ankle = 10,
Knee = 11,
Upperleg = 12,
Clavicle = 13,
Tail = 14
}
enum Type : byte
{
Skin = 0,
Clothing = 1,
Armor = 2
}
enum Flags : byte
{
Female = 1,
Playable = 2
}
align(1) struct BYDTstruct
{
MeshType part;
byte vampire;
Flags flags;
Type type;
static assert(BYDTstruct.sizeof==4);
}
BYDTstruct data;
char[] name, id;
LoadState state;
MeshIndex model;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
readHNExact(&data, data.sizeof, "BYDT");
}}
}
ListID!(BodyPart) bodyParts;

@ -0,0 +1,62 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadbook.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadbook;
import esm.imports;
/*
* Books
*/
struct Book
{
align(1) struct BKDTstruct
{
float weight;
int value, isScroll, skillID, enchant;
static assert(BKDTstruct.sizeof == 20);
}
BKDTstruct data;
MeshIndex model;
IconIndex icon;
Script *script;
Enchantment *enchant;
char[] name, text, id;
LoadState state;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
readHNExact(&data, data.sizeof, "BKDT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getIcon();
text = getHNOString("TEXT");
enchant = getHNOPtr!(Enchantment)("ENAM", enchants);
}}
}
ListID!(Book) books;

@ -0,0 +1,53 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadbsgn.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadbsgn;
import esm.imports;
/*
* Birth signs
*/
struct BirthSign
{
LoadState state;
char[] id, name, description;
TextureIndex texture;
// List of powers and abilities that come with this birth sign.
SpellList powers;
void load()
{with(esFile){
name = getHNString("FNAM");
texture = getOTexture();
description = getHNOString("DESC");
powers.load();
}}
}
ListID!(BirthSign) birthSigns;

@ -0,0 +1,371 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadcell.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadcell;
import esm.imports;
import esm.loadregn;
/* Cells can be seen as a collection of several records, and holds
* data about objects, creatures, statics and landscape (for exterior
* cells). This data can be huge, and has to be loaded when we need
* it, not when parsing the file.
*/
align(1) struct AMBIStruct
{
Color ambient, sunlight, fog;
float fogDensity;
static assert(AMBIStruct.sizeof == 16);
}
enum CellFlags : uint
{
Interior = 0x01,
HasWater = 0x02,
NoSleep = 0x04,
QuasiExt = 0x80 // Behave like exterior (tribunal, requires
// version 1.3? TODO: Check this (and for other
// tribunal-only stuff))
}
struct InteriorCell
{
char[] id;
CellFlags flags;
LoadState state;
// Stores file position so we can load the cell later
TES3FileContext context;
// This struct also holds a file context for use later
PathGrid paths;
void load()
{with(esFile){
// Save the file position and skip to the next record (after the CELL)
getContext(context);
skipRecord();
// Check for path grid data
paths.state = LoadState.Unloaded;
if(isNextHRec("PGRD")) paths.load();
}}
}
struct ExteriorCell
{
// This isn't an id, so we call it 'name' instead. May be empty, if
// so use region name to display
char[] name;
CellFlags flags;
int gridX, gridY;
LoadState state;
Region* region;
// We currently don't load all cell data (these can be huge!),
// anyway it makes sense to only load these when accessed. Use this
// to reopen the file later.
TES3FileContext context;
// Landscape and path grid data
Land land; // There can be TWO landscapes! Or maybe I have
// misunderstood something. Need to check what it means.
PathGrid paths;
void load()
{with(esFile){
this.region = getHNOPtr!(Region)("RGNN", regions);
// Save the file position and skip to the next record (after the CELL)
getContext(context);
skipRecord();
// Exterior cells might have landscape data
land.state = LoadState.Unloaded;
if(isNextHRec("LAND")) land.load();
// Check for path grid data
paths.state = LoadState.Unloaded;
if(isNextHRec("PGRD")) paths.load();
// Land can also be here instead. In fact, it can be both
// places. I have to figure out what it means.
if(isNextHRec("LAND")) land.load();
}}
}
struct ExtCellHash
{
// This is a pretty good hash, gives no collisions at all for
// Morrowind.esm and a table size of 2048, and very few collisions
// overall. Not that it matters of course.
static uint hash(uint val)
{
uint res = cast(ushort)val;
res += *(cast(ushort*)&val+1)*41;
return res;
}
static bool isEqual(uint a, uint b) { return a==b; }
}
class CellList : ListKeeper
{
// Again, these are here to avoid DMD template bugs
alias _aaNode!(char[], InteriorCell) _unused1;
alias _aaNode!(uint, ExteriorCell) _unused2;
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
this()
{
in_cells = in_cells.init;
in_cells.rehash(1600);
ex_cells = ex_cells.init;
ex_cells.rehash(1800);
}
align(1) struct DATAstruct
{
CellFlags flags;
int gridX, gridY;
static assert(DATAstruct.sizeof==12);
}
DATAstruct data;
// Look up an interior cell, throws an error if not found (might
// change later)
InteriorCell *getInt(char[] s)
{
return in_cells.getPtr(s);
}
// Exterior cell, same as above
ExteriorCell *getExt(int x, int y)
{
return ex_cells.getPtr(compound(x,y));
}
void *lookup(char[] s)
{ assert(0); }
void endFile()
out
{
in_cells.validate();
ex_cells.validate();
}
body
{
foreach(id, ref c; in_cells)
{
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
// We never forward reference cells!
assert(c.state != LoadState.Unloaded);
}
foreach(id, ref c; ex_cells)
{
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
// We never forward reference cells!
assert(c.state != LoadState.Unloaded);
}
}
uint length() { return numInt() + numExt(); }
uint numInt() { return in_cells.length; }
uint numExt() { return ex_cells.length; }
// Turn an exterior cell grid position into a unique number
static uint compound(int gridX, int gridY)
{
return cast(ushort)gridX + ((cast(ushort)gridY)<<16);
}
static void decompound(uint val, out int gridX, out int gridY)
{
gridX = cast(short)(val&0xffff);
gridY = cast(int)(val&0xffff0000) >> 16;
}
void load()
{with(esFile){
char[] id = getHNString("NAME");
// Just ignore this, don't know what it does.
if(isNextSub("DELE")) getHInt();
readHNExact(&data, data.sizeof, "DATA");
if(data.flags & CellFlags.Interior)
{
InteriorCell *p;
if(in_cells.insertEdit(id, p))
// New item was inserted
{
p.state = LoadState.Unloaded;
p.id = id;
p.flags = data.flags;
p.load();
p.state = LoadState.Loaded;
}
else
// Overloading an existing cell
{
if(p.state != LoadState.Previous)
fail("Duplicate internal cell " ~ id);
assert(id == p.id);
p.load();
p.state = LoadState.Loaded;
}
}
else // Exterior cell
{
uint key = compound(data.gridX, data.gridY);
ExteriorCell *p;
if(ex_cells.insertEdit(key, p))
// New cell
{
p.state = LoadState.Unloaded;
p.name = id;
p.flags = data.flags;
p.gridX = data.gridX;
p.gridY = data.gridY;
p.load();
p.state = LoadState.Loaded;
}
else
{
if(p.state != LoadState.Previous)
fail(format("Duplicate exterior cell %d %d",
data.gridX, data.gridY));
assert(p.gridX == data.gridX);
assert(p.gridY == data.gridY);
p.load();
p.state = LoadState.Loaded;
}
}
}}
}
/*
* Landscape data. The landscape is stored as a hight map, and that is
* pretty much all I know at the moment.
*/
struct Land
{
LoadState state;
uint flags; // ?? - only first four bits seem to be used?
TES3FileContext context;
void load()
{with(esFile){
getHNUlong("INTV"); // Grid location. See next line.
//writefln("x=%d, y=%d", *(cast(int*)&grid), *(cast(int*)&grid+1));
flags = getHNInt("DATA");
// Save file position
getContext(context);
// TODO: We don't decode these yet. Se ESLand.h from
// Morrowindremake for a lot of hints.
if(isNextSub("VNML")) skipHSubSize(12675);
if(isNextSub("VHGT")) skipHSubSize(4232);
if(isNextSub("WNAM")) skipHSubSize(81);
if(isNextSub("VCLR")) skipHSubSize(12675);
if(isNextSub("VTEX")) skipHSubSize(512);
if(state == LoadState.Loaded)
writefln("Duplicate landscape data in " ~ getFilename());
state = LoadState.Loaded;
}}
}
/*
* Path grid.
*/
struct PathGrid
{
struct DATAstruct
{
int x, y; // Grid location, matches cell for exterior cells
short s1; // ?? Usually but not always a power of 2. Doesn't seem
// to have any relation to the size of PGRC.
short s2; // Number of path points? Size of PGRP block is always 16 * s2;
}
DATAstruct data;
TES3FileContext context;
LoadState state;
void load()
{with(esFile){
assert(state == LoadState.Unloaded);
readHNExact(&data, data.sizeof, "DATA");
getHNString("NAME"); // Cell name, we don't really need it.
// Remember this file position
getContext(context);
// Check that the sizes match up. Size = 16 * s2 (path points?)
if(isNextSub("PGRP"))
{
int size = skipHSub();
if(size != 16*data.s2)
fail("Path grid table size");
}
// Size varies. Path grid chances? Connections? Multiples of 4
// suggest either int or two shorts, or perhaps a float. Study
// it later.
if(isNextSub("PGRC"))
{
int size = skipHSub();
if(size % 4 != 0)
fail("PGRC size not a multiple of 4");
}
state = LoadState.Loaded;
}}
}
CellList cells;

@ -0,0 +1,90 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadclas.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadclas;
import esm.imports;
import esm.loadskil;
/*
* Character class definitions
*/
// These flags tells us which items should be auto-calculated for this
// class
struct Class
{
enum AutoCalc : uint
{
Weapon = 0x00001,
Armor = 0x00002,
Clothing = 0x00004,
Books = 0x00008,
Ingredient = 0x00010,
Lockpick = 0x00020,
Probe = 0x00040,
Lights = 0x00080,
Apparatus = 0x00100,
Repair = 0x00200,
Misc = 0x00400,
Spells = 0x00800,
MagicItems = 0x01000,
Potions = 0x02000,
Training = 0x04000,
Spellmaking = 0x08000,
Enchanting = 0x10000,
RepairItem = 0x20000
}
align(1) struct CLDTstruct
{
Attribute attribute[2]; // Attributes that get class bonus
Specialization specialization; // 0 = Combat, 1 = Magic, 2 = Stealth
SkillEnum[2][5] skills; // Minor and major skills.
uint isPlayable; // 0x0001 - Playable class
// I have no idea how to autocalculate these items...
AutoCalc calc;
static assert(CLDTstruct.sizeof == 60);
}
LoadState state;
char[] id, name, description;
CLDTstruct data;
void load()
{
name = esFile.getHNString("FNAM");
esFile.readHNExact(&data, data.sizeof, "CLDT");
if(data.isPlayable > 1)
esFile.fail("Unknown bool value");
description = esFile.getHNOString("DESC");
}
}
ListID!(Class) classes;

@ -0,0 +1,83 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadclot.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadclot;
import esm.imports;
import esm.loadarmo;
/*
* Clothing
*/
struct Clothing
{
enum Type : int
{
Pants = 0,
Shoes = 1,
Shirt = 2,
Belt = 3,
Robe = 4,
RGlove = 5,
LGlove = 6,
Skirt = 7,
Ring = 8,
Amulet = 9
}
struct CTDTstruct
{
Type type;
float weight;
short value;
short enchantPoints;
static assert(CTDTstruct.sizeof == 12);
}
CTDTstruct data;
LoadState state;
char[] id, name;
PartReferenceList parts;
MeshIndex model;
IconIndex icon;
Enchantment *enchant;
Script *script;
void load()
{with(esFile){
model = getMesh();
name = getHNOString("FNAM");
readHNExact(&data, data.sizeof, "CTDT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getOIcon();
parts.load();
enchant = getHNOPtr!(Enchantment)("ENAM", enchants);
}}
}
ListID!(Clothing) clothes;

@ -0,0 +1,106 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadcont.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadcont;
import esm.imports;
/*
* Container definition
*/
struct ContItem
{
Item item;
int count;
}
struct InventoryList
{
RegionBuffer!(ContItem) list;
private static char[32] buffer;
void load()
{
list = esFile.getRegion().getBuffer!(ContItem)(0,10);
while(esFile.isNextSub("NPCO"))
{
esFile.getSubHeaderIs(36);
list.length = list.length + 1;
with(list.array[$-1])
{
esFile.getInt(count);
// String is automatically chopped
item = items.lookup(esFile.getString(buffer));
}
}
}
}
struct Container
{
enum Flags
{
Organic = 1, // Objects cannot be placed in this container
Respawn = 2, // Respawns after 4 months
Unknown = 8
}
char[] id, name;
LoadState state;
MeshIndex model;
Script *script;
float weight; // Not sure, might be max total weight allowed?
Flags flags;
InventoryList inventory;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
weight = getHNFloat("CNDT");
flags = cast(Flags)getHNUint("FLAG");
if(flags & 0xf4) fail("Unknown flags");
if(!(flags & 0x8)) fail("Flag 8 not set");
/*
if(getFileType == FileType.Savegame)
{
int tmp = getHNInt("INDX");
if(tmp) writefln("WARNING, INDX != 0: ", tmp);
}
*/
script = getHNOPtr!(Script)("SCRI", scripts);
inventory.load();
}}
}
ListID!(Container) containers;

@ -0,0 +1,127 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadcrea.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadcrea;
import esm.imports;
import esm.loadcont;
/*
* Creature definition
*
* Container structs are in loadcont.d
*/
struct Creature
{
// Default is 0x48?
enum Flags
{
Biped = 0x001,
Respawn = 0x002,
Weapon = 0x004, // Has weapon and shield
None = 0x008, // ??
Swims = 0x010,
Flies = 0x020, // Don't know what happens if several
Walks = 0x040, // of these are set
Essential = 0x080,
Skeleton = 0x400, // Does not have normal blood
Metal = 0x800 // Has 'golden' blood
}
enum Type : int
{
Creature = 0,
Deadra = 1,
Undead = 2,
Humanoid = 3
}
align(1) struct NPDTstruct
{
Type type;
// For creatures we obviously have to use ints, not shorts and
// bytes like we use for NPCs.... this file format just makes so
// much sense! (Still, _much_ easier to decode than the NIFs.)
int level;
int strength, intelligence, willpower, agility, speed, endurance,
personality, luck, health, mana, fatigue; // Stats
int soul; // The creatures soul value (used with soul gems.)
int combat, magic, stealth; // Don't know yet.
int attack[6]; // AttackMin1, AttackMax1, ditto2, ditto3
int gold;
static assert(NPDTstruct.sizeof == 96);
}
NPDTstruct data;
Flags flags;
LoadState state;
char[] id, name;
MeshIndex model;
// Base creature. Any missing data must be taken from here, I quess.
Creature *original;
Script *script;
float scale;
InventoryList inventory;
void load()
{with(esFile){
model = getMesh();
original = getHNOPtr!(Creature)("CNAM", creatures);
name = getHNOString("FNAM");
script = getHNOPtr!(Script)("SCRI", scripts);
readHNExact(&data, data.sizeof, "NPDT");
flags = cast(Flags)getHNInt("FLAG");
scale = getHNOFloat("XSCL", 1.0);
inventory.load();
// AIDT - data (12 bytes, unknown)
// AI_W - wander (14 bytes, i don't understand it)
// short distance
// byte duration
// byte timeOfDay
// byte idle[10]
//
// Rest is optional:
// AI_T - travel?
// AI_F - follow?
// AI_E - escort?
// AI_A - activate?
//*
skipRecord();
//*/
}}
}
ListID!(Creature) creatures;

@ -0,0 +1,59 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadcrec.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadcrec;
import esm.imports;
/*
* Creature and container change information? Used in save games only.
*/
struct LoadCREC
{
void load(TES3File f)
{
writefln("Creature-changer %s", f.getHNString("NAME"));
writefln(" Index: ", f.getHNInt("INDX"));
while(f.hasMoreSubs)
{
f.getSubName();
f.skipHSub();
}
}
}
struct LoadCNTC
{
void load(TES3File f)
{
writefln("Itemlist-changer %s", f.getHNString("NAME"));
writefln(" Index: ", f.getHNInt("INDX"));
while(f.hasMoreSubs)
{
f.getSubName();
f.skipHSub();
}
}
}

@ -0,0 +1,291 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loaddial.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loaddial;
import esm.imports;
import esm.loadinfo;
/*
* Dialogue topic and journal entries. The acutal data is contained in
* the following INFO records.
*/
// Keep a list of possible hyper links. This list is used when parsing
// text and finding references to topic names. Eg. if a character says
// "Would you like to join the mages guild?", we must be able to pick
// out the phrase "join the mages guild?" and make a hyperlink of
// it. Each link is indexed by their first word. The structure
// contains the rest of the phrase, so the phrase above would be
// indexed by "join" and contain the string "the mages guild?", for
// quick comparison with the text are currently parsing. It also
// contains a pointer to the corresponding dialogue struct. The lists
// are sorted by descending string length, in order to match the
// longest possible term first.
struct Hyperlink
{
Dialogue *ptr;
char[] rest;
// Returns a < b if a.length > b.length.
int opCmp(Hyperlink *h) {return h.rest.length - rest.length;}
}
alias RegionBuffer!(Hyperlink) HyperlinkArray;
// This is much nicer now that we use our own AA.
struct HyperlinkList
{
// Make a case insensitive hash table of Hyperlink arrays.
HashTable!(char[], HyperlinkArray, ESMRegionAlloc, CITextHash) list;
void add(char[] topic, Dialogue* ptr)
{
// Only add dialogues
if(ptr.type != Dialogue.Type.Topic) return;
Hyperlink l;
l.ptr = ptr;
l.rest = topic;
// Use the first word as the index
topic = nextWord(l.rest);
// Insert a new array, or get an already existing one
HyperlinkArray ha = list.get(topic,
// Create a new array
delegate void (ref HyperlinkArray a)
{ a = esmRegion.getBuffer!(Hyperlink)(0,1); }
);
// Finally, add it to the list
ha ~= l;
}
Hyperlink[] getList(char[] word)
{
HyperlinkArray p;
if(list.inList(word, p)) return p.array();
return null;
}
void rehash(uint size)
{
list.rehash(size);
}
// We wouldn't need this if we only dealt with one file, since the
// topics are already sorted in Morrowind.esm. However, other files
// might add items out of order later, so we have to sort it. To
// understand why this is needed, consider the following example:
//
// Morrowind.esm contains the topic 'join us'. When ever the text
// ".. join us blahblah ..." is encountered, this match is
// found. However, if a plugin adds the topic 'join us today', we
// have to place this _before_ 'join us' in the list, or else it
// will never be matched.
void sort()
{
foreach(char[] s, HyperlinkArray l; list)
{
l.array().sort;
/*
writefln("%s: ", s, l.length);
foreach(Hyperlink h; l.array())
writefln(" %s (%s)", h.rest, h.ptr.id);
*/
}
}
}
// List of dialogue hyperlinks
HyperlinkList hyperlinks;
struct Dialogue
{
enum Type
{
Topic = 0,
Voice = 1,
Greeting = 2,
Persuasion = 3,
Journal = 4,
Deleted = -1
}
//Type type;
DialogueType type;
DialInfoList infoList;
char[] id; // This is the 'dialogue topic' that the user actually
// sees.
LoadState state;
void load()
{with(esFile){
getSubNameIs("DATA");
getSubHeader();
int si = getSubSize();
if(si == 1)
{
byte b;
getByte(b);
DialogueType t = cast(DialogueType)b;
// Meet the new type, same as the old type
if(t != this.type && state == LoadState.Previous)
fail("Type changed in dialogue " ~ id);
this.type = t;
}
else if(si == 4)
{
// These are just markers, their values are not used.
int i;
getInt(i);
//writefln("In file %s:", getFilename());
//writefln(" WARNING: DIAL.DATA was size 4 and contains: ", i);
i = getHNInt("DELE");
//writefln(" DELE contains ", i);
this.type = Type.Deleted;
}
else fail("Unknown sub record size " ~ toString(si));
infoList.state = state;
while(isNextHRec("INFO"))
infoList.load(this.type);
//skipRecord();
}}
}
// TODO/FIXME: HACK suddenly needed with version 0.167 :(
// Haven't re-tested it with non-ancient compiler
typedef Dialogue.Type DialogueType;
/+
// I don't remember when I commented out this code or what state
// it is in. Probably highly experimental.
// --------------
// Loop through the info blocks in this dialogue, and update the
// master as necessary.
// TODO: Note that the dialogue system in Morrowind isn't very
// robust. If several mods insert dialogues at exactly the same
// place, the mods loaded last might overwrite the previous mods,
// completely removing the previous entry even if the two entries
// do not have the same id. This is because a change also
// overwrites the previous and the next entry, in order to update
// their "previous" and "next" fields. Furthermore, we might put
// ourselves in a situation where the forward and backward chains
// do not match, or in a situation where we update a deleted
// info. For now I do nothing about it, but I will have to develop
// a "conflict manager" later on. It SHOULD be possible to merge
// these info lists automatically in most cases, but it
// complicates the code.
// Whoa, seems we have a case study already with just tribunal and
// bloodmoon loaded! See comments below.
foreach(char[] id, ref DialInfoLoad m; mod.infoList)
{
// Remove the response if it is marked as deleted.
if(m.deleted)
{
if((id in master.infoList) == null)
writefln("Cannot delete info %s, does not exist", id);
else master.infoList.remove(id);
}
else
// Just plain copy it in.
master.infoList[id] = m;
}
}
// Here we have to fix inconsistencies. A good case example is the
// dialogue "Apelles Matius" in trib/blood. Trib creates a
// dialogue of a few items, bloodmoon adds another. But since the
// two are independent, the list in bloodmoon does not change the
// one in trib but rather creates a new one. In other words, we
// will have to deal with the possibility of several "independent"
// lists within each topic. We can do this by looking for several
// start points (ie. infos with prev="") and just latch them onto
// each other. I'm not sure it gives the correct result,
// though. For example, which list comes first would be rather
// arbitrarily decided by the order we traverse the infoList AA. I
// will just have to assume that they truly are "independent".
// There still seems to be a problem though. Bloodmoon overwrites
// some stuff added by Tribunal, see "Boots of the Apostle" for an
// example. Looks like the editor handles it just fine... We need
// to make sure that all the items in our AA are put into the
// list, and in the right place too. We obviously cannot fully
// trust the 'next' and 'prev' fields, but they are the only
// guidance we have. Deal with it later!
// At this point we assume "master" to contain the final dialogue
// list, so at this point we can set it in stone.
infoList.length = master.infoList.length;
// Find the first entry
DialInfoLoad* starts[]; // starting points for linked lists
DialInfoLoad *current;
foreach(char[] id, ref DialInfoLoad l; master.infoList)
if(l.prev == "") starts ~= &l;
foreach(int num, ref DialInfo m; infoList)
{
if(current == null)
{
if(starts.length == 0)
{
writefln("Error: No starting points!");
infoList.length = num;
break;
}
// Pick the next starting point
current = starts[0];
starts = starts[1..$];
}
m.copy(*current, this);
if((*current).next == "")
current = null;
else
{
current = (*current).next in master.infoList;
if(current == null)
{
writefln("Error in dialouge info lookup!");
break;
}
}
}
if(infoList.length != master.infoList.length)
writefln("Dialogue list lengths do not match, %d != %d",
infoList.length, master.infoList.length);
}
}
+/

@ -0,0 +1,49 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loaddoor.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loaddoor;
import esm.imports;
/*
* Doors
*/
struct Door
{
LoadState state;
char[] id, name;
MeshIndex model;
Script *script;
Sound* openSound, closeSound;
void load()
{with(esFile){
model = getMesh();
name = getHNOString("FNAM");
script = getHNOPtr!(Script)("SCRI", scripts);
openSound = getHNOPtr!(Sound)("SNAM", sounds);
closeSound = getHNOPtr!(Sound)("ANAM", sounds);
}}
}
ListID!(Door) doors; // Break on through

@ -0,0 +1,70 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadench.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadench;
import esm.imports;
/*
* Enchantments
*/
struct Enchantment
{
enum Type : int
{
CastOnce = 0,
WhenStrikes = 1,
WhenUsed = 2,
ConstantEffect = 3
}
align(1) struct ENDTstruct
{
Type type;
int cost;
int charge;
int autocalc; // Guessing this is 1 if we are supposed to auto
// calculate
static assert(ENDTstruct.sizeof == 16);
}
ENDTstruct data;
EffectList effects;
char[] id;
LoadState state;
void load()
{with(esFile){
readHNExact(&data, data.sizeof, "ENDT");
effects = getRegion().getBuffer!(ENAMstruct)(0,1);
while(isNextSub("ENAM"))
{
effects.length = effects.length + 1;
readHExact(&effects.array[$-1], effects.array[$-1].sizeof);
}
}}
}
ListID!(Enchantment) enchants;

@ -0,0 +1,106 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadfact.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadfact;
import esm.imports;
/*
* Faction definitions
*/
// Requirements for each rank
align(1) struct RankData
{
int attribute1, attribute2; // Attribute level
int skill1, skill2; // Skill level (faction skills given in
// skillID below.) You need one skill at
// level 'skill1' and two skills at level
// 'skill2' to advance to this rank.
int factReaction; // Reaction from faction members
}
struct Faction
{
char[] id, name;
LoadState state;
align(1) struct FADTstruct
{
// Which attributes we like
int attribute1, attribute2;
RankData rankData[10];
int skillID[6]; // IDs of skills this faction require
int unknown; // Always -1?
uint isHidden; // 1 - hidden from player
static assert(RankData.sizeof == 20);
static assert(FADTstruct.sizeof == 240);
}
FADTstruct data;
RankData rankData[10];
struct Reaction
{
Faction* faction;
int reaction;
}
RegionBuffer!(Reaction) reactions;
char[] ranks[10]; // Name of faction ranks (may be empty for NPC
// factions)
void load()
{with(esFile){
name = getHNString("FNAM");
// Read rank names. These are optional.
ranks[] = null;
int i = 0;
while(isNextSub("RNAM") && i<10) ranks[i++] = getHString();
if(isNextSub("RNAM")) fail("Too many rank names");
// Main data struct
readHNExact(&data, data.sizeof, "FADT");
if(data.isHidden > 1) fail("Unknown flag!");
// Read faction response values
reactions = getRegion().getBuffer!(Reaction)();
while(hasMoreSubs())
{
Reaction r;
r.faction = getHNPtr!(Faction)("ANAM", factions);
r.reaction = getHNInt("INTV");
reactions ~= r;
}
}}
}
ListID!(Faction) factions;

@ -0,0 +1,61 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadglob.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadglob;
import esm.imports;
/*
* Global script variables
*/
struct Global
{
LoadState state;
char[] id;
float value;
VarType type;
void load()
{
VarType t;
char[] tmp = esFile.getHNString("FNAM");
switch(tmp)
{
case "s": t = VarType.Short; break;
case "l": t = VarType.Int; break;
case "f": t = VarType.Float; break;
default:
esFile.fail("Illegal global variable type " ~ tmp);
}
if(state == LoadState.Previous && t != type)
esFile.fail(format("Variable changed type from %d to %d",
cast(int)type, cast(int)t));
type = t;
value = esFile.getHNFloat("FLTV");
}
}
ListID!(Global) globals;

@ -0,0 +1,305 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadgmst.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadgmst;
import esm.imports;
/*
* Game setting
*/
struct GameSetting
{
LoadState state;
char[] id;
union
{
char[] str;
int i;
float f;
}
VarType type;
// These functions check if this game setting is one of the "dirty"
// GMST records found in many mods. These are due to a serious bug
// in the official TES3 editor. It only occurs in the newer editor
// versions that came with Tribunal and Bloodmoon, and only if a
// modder tries to make a mod without loading the corresponding
// expansion master file. For example, if you have Tribunal
// installed and try to make a mod without loading Tribunal.esm, the
// editor will insert these GMST records.
// The values of these "dirty" records differ in general from their
// values as defined in Tribunal.esm and Bloodmoon.esm, and are
// always set to the same "default" values. Most of these values are
// nonsensical, ie. changing the "Seller Max" string to "Max Sale",
// or change the stats of werewolves to useless values like 1. Some
// of them break certain spell effects.
// It is most likely that these values are just leftover values from
// an early stage of development that are inserted as default values
// by the editor code. They are supposed to be overridden when the
// correct esm file is loaded. When it isn't loaded, you get stuck
// with the initial value, and this gets written to every mod for
// some reason.
// Bethesda themselves have fallen for this bug. If you install both
// Tribunal and Bloodmoon, the updated Tribunal.esm will contain the
// dirty GMST settings from Bloodmoon, and Bloodmoon.esm will
// contain some of the dirty settings from Tribunal. In other words,
// this bug affects the game EVEN IF YOU DO NOT USE ANY MODS!
// The guys at Bethesda are well aware of this bug (and many
// others), as the mod community and fan base complained about them
// for a long time. But it was never fixed.
// There are several programs available to help modders remove these
// records from their files, but not all modders use them, and they
// really shouldn't have to. In this file we choose instead to
// reject all the corrupt values at load time.
// Checks if the current game setting is one of the "dirty" ones as
// described above. TODO: I have not checked this against other
// sources yet, do that later. Currently recognizes 22 values for
// tribunal and 50 for bloodmoon.
// Checks for dirty tribunal values. These will be ignored if found
// in any file except when they are found in "Tribunal.esm".
bool isDirtyTribunal()
{
bool result = false;
void cI(int ii) { if(ii == i) result = true; }
void cF(float ff) { if(ff == f) result = true; }
void cS(char[] ss) { if(ss == str) result = true; }
// Here, id contains the game setting name, and we check the
// setting for certain values. If it matches, this is a "dirty"
// entry. The correct entry (as defined in Tribunal and Bloodmoon
// esms) are given in the comments. Many of the values are
// correct, and are marked as 'same'. We still ignore them though,
// as they are still in the wrong file and might override custom
// values from other mods.
switch(id)
{
// Strings
case "sProfitValue": cS("Profit Value"); break; // 'Profit:'
case "sEditNote": cS("Edit Note"); break; // same
case "sDeleteNote": cS("Delete Note?"); break; // same
case "sMaxSale": cS("Max Sale"); break; // 'Seller Max'
case "sMagicFabricantID": cS("Fabricant"); break; // 'Fabricant_summon'
case "sTeleportDisabled": cS("Teleportation magic does not work here.");
break; // same
case "sLevitateDisabled": cS("Levitation magic does not work here.");
break; // same
case "sCompanionShare": cS("Companion Share"); break; // 'Share'
case "sCompanionWarningButtonOne": cS("Let the mercenary quit.");
break; // same
case "sCompanionWarningButtonTwo": cS("Return to Companion Share display."); break; // same
case "sCompanionWarningMessage": cS("Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); break;
// 'Your mercenary is poorer now than when he contracted with
// you. Your mercenary will quit if you do not give him gold
// or goods to bring his Profit to a positive value.'
// [The difference here is "Profit Value" -> "Profit"}
// Strings that matches the id
case "sEffectSummonFabricant": // 'Summon Fabricant'
cS(id);
break;
default:
}
return result;
}
// Bloodmoon variant
bool isDirtyBloodmoon()
{
bool result = false;
void cI(int ii) { if(ii == i) result = true; }
void cF(float ff) { if(ff == f) result = true; }
void cS(char[] ss) { if(ss == str) result = true; }
switch(id)
{
// Strings
case "sWerewolfPopup": cS("Werewolf"); break; // same
case "sWerewolfRestMessage": cS("You cannot rest in werewolf form.");
break; // same
case "sWerewolfRefusal": cS("You cannot do this as a werewolf.");
break; // same
case "sWerewolfAlarmMessage": cS("You have been detected changing from a werewolf state."); break;
// 'You have been detected as a known werewolf.'
// Strings that matches the id
case "sMagicCreature01ID": // 'BM_wolf_grey_summon'
case "sMagicCreature02ID": // 'BM_bear_black_summon'
case "sMagicCreature03ID": // 'BM_wolf_bone_summon'
case "sMagicCreature04ID": // same
case "sMagicCreature05ID": // same
case "sEffectSummonCreature01": // 'Calf Wolf'
case "sEffectSummonCreature02": // 'Calf Bear'
case "sEffectSummonCreature03": // 'Summon Bonewolf'
case "sEffectSummonCreature04": // same
case "sEffectSummonCreature05": // same
cS(id);
break;
// Integers
case "iWereWolfBounty": cI(10000); break; // 1000
case "iWereWolfFightMod": cI(100); break; // same
case "iWereWolfFleeMod": cI(100); break; // same
case "iWereWolfLevelToAttack": cI(20); break; // same
// Floats
case "fFleeDistance": cF(3000); break; // same
case "fCombatDistanceWerewolfMod": cF(0.3); break; // same
case "fWereWolfFatigue": cF(400); break; // same
case "fWereWolfEnchant": cF(1); break; // 0
case "fWereWolfArmorer": cF(1); break; // 0
case "fWereWolfBlock": cF(1); break; // 0
case "fWereWolfSneak": cF(1); break; // 95
case "fWereWolfDestruction": cF(1); break; // 0
case "fWereWolfEndurance": cF(150); break; // same
case "fWereWolfConjuration": cF(1); break; // 0
case "fWereWolfRestoration": cF(1); break; // 0
case "fWereWolfAthletics": cF(150); break; // 50
case "fWereWolfLuck": cF(1); break; // 25
case "fWereWolfSilverWeaponDamageMult": cF(1.5); break; // 2
case "fWereWolfMediumArmor": cF(1); break; // 0
case "fWereWolfShortBlade": cF(1); break; // 0
case "fWereWolfAcrobatics": cF(150); break; // 80
case "fWereWolfSpeechcraft": cF(1); break; // 0
case "fWereWolfAlteration": cF(1); break; // 0
case "fWereWolfIllusion": cF(1); break; // 0
case "fWereWolfLongBlade": cF(1); break; // 0
case "fWereWolfMarksman": cF(1); break; // 0
case "fWereWolfHandtoHand": cF(100); break; // same
case "fWereWolfIntellegence": cF(1); break; // 0
case "fWereWolfAlchemy": cF(1); break; // 0
case "fWereWolfUnarmored": cF(100); break; // same
case "fWereWolfAxe": cF(1); break; // 0
case "fWereWolfRunMult": cF(1.5); break; // 1.3
case "fWereWolfMagicka": cF(100); break; // same
case "fWereWolfAgility": cF(150); break; // same
case "fWereWolfBluntWeapon": cF(1); break; // 0
case "fWereWolfSecurity": cF(1); break; // 0
case "fWereWolfPersonality": cF(1); break; // 0
case "fWereWolfMerchantile": cF(1); break; // 0
case "fWereWolfHeavyArmor": cF(1); break; // 0
case "fWereWolfSpear": cF(1); break; // 0
case "fWereWolfStrength": cF(150); break; // same
case "fWereWolfHealth": cF(2); break; // same
case "fWereWolfMysticism": cF(1); break; // 0
case "fWereWolfLightArmor": cF(1); break; // 0
case "fWereWolfWillPower": cF(1); break; // 0
case "fWereWolfSpeed": cF(150); break; // 90
default:
}
return result;
}
char[] toString()
{
if(type == VarType.Int) return format(i);
else if(type == VarType.Float) return format(f);
else if(type == VarType.String) return str;
else if(type == VarType.None) return "(no value)";
}
void load()
{
// We are apparently allowed not to have any value at all.
if(!esFile.hasMoreSubs())
{
if(state == LoadState.Previous)
writefln("Warning: Overwriting game setting %s with void value", id);
type = VarType.None;
return;
}
// If this is not the first record of this type to be loaded, then
// we do a little "trick" to avoid overwriting the current data
// with one of the dirty GMSTs.
if(state == LoadState.Previous)
{
// Load the data in a temporary game setting instead
GameSetting g;
g.state = LoadState.Unloaded;
g.id = id;
g.load();
// Only copy it if it was valid
if(g.type != VarType.Ignored)
{
// Don't allow a change of type, unless the setting we are
// overwriting is an ignored value.
if(g.type != type && type != VarType.Ignored)
esFile.fail(format("GMST changed type from %d to %d",
cast(int)type, cast(int)g.type));
str = g.str; // This will copy all the data no matter what
// type it is
}
return;
}
// Actually load some data
esFile.getSubName();
switch(esFile.retSubName())
{
case "STRV":
str = esFile.getHString();
type = VarType.String;
break;
case "INTV":
i = esFile.getHInt();
type = VarType.Int;
break;
case "FLTV":
f = esFile.getHFloat();
type = VarType.Float;
break;
default:
esFile.fail("Unwanted subrecord type");
}
SpecialFile spf = esFile.getSpecial();
// Check if this is one of the dirty values mentioned above. If it
// is, we set the VarType to Ignored. This leaves the posibility
// that this becomes the final var type, for example if you load a
// plugin with tribunal-gmst settings without loading tribunal
// first. (Then there would only exist dirty values for this
// settings, no "real" value.) But this is not likely a problem,
// since these empty values will never be used and we can check
// for them in any case.
if( ( spf != SpecialFile.Tribunal && isDirtyTribunal() ) ||
( spf != SpecialFile.Bloodmoon && isDirtyBloodmoon() ) )
type = VarType.Ignored;
}
}
ListID!(GameSetting) gameSettings;

@ -0,0 +1,249 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadinfo.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadinfo;
import esm.imports;
import esm.loaddial;
import esm.loadrace;
import esm.loadclas;
import esm.loadfact;
/*
* Dialogue information. These always follow a DIAL record and form a
* linked list
*/
struct DialInfo
{
enum Gender : byte
{
Male = 0,
Female = 1,
NA = -1
}
align(1) struct DATAstruct
{
uint unknown1;
uint disposition;
byte rank; // Rank of NPC
Gender gender; // 0 - male, 1 - female, 0xff - none
byte PCrank; // Player rank
byte unknown2;
static assert(DATAstruct.sizeof==12);
}
DATAstruct data;
// The rules for whether or not we will select this dialog item.
struct SelectStruct
{
char[] selectRule; // This has a complicated format
union { float f; int i; }
VarType type;
}
// Id of prevous and next in linked list
char[] id, prev, next;
// Various references used in determining when to select this item.
Item actor;
Race *race;
Class *cls;
Faction* npcFaction, pcFaction;
bool factionLess; // ONLY select this item the NPC is not part of any faction.
char[] cell; // Use this to compare with current cell name
RegionBuffer!(SelectStruct) selects; // Selection rules
SoundIndex sound; // Sound to play when this is selected.
char[] response; // The text content of this info item.
// TODO: This should probably be compiled at load time?
char[] resultScript; // Result script (uncompiled) - is run if this
// dialog item is selected.
// Journal quest indices (introduced with the quest system in tribunal)
enum Quest
{
None, Name, Finished, Restart
}
bool deleted;
Quest questStatus;
void load(DialogueType tp)
{with(esFile){
id = getHNString("INAM");
prev = getHNString("PNAM");
next = getHNString("NNAM");
// Not present if deleted
if(isNextSub("DATA"))
readHExact(&data, data.sizeof);
// What follows is terrible spaghetti code, but I like it! ;-) In
// all seriousness, it's an attempt to make things faster, since
// INFO is by far the most common record type.
// subName is a slice of the original, so it changes when new sub
// names are read.
getSubName();
char[] subName = retSubName();
if(subName == "ONAM")
{
actor = actors.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else actor.i = null;
if(subName == "RNAM")
{
race = cast(Race*)races.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else race = null;
if(subName == "CNAM")
{
cls = cast(Class*)classes.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else cls = null;
npcFaction = null;
factionLess = false;
if(subName == "FNAM")
{
char[] tmp = tmpHString();
if(tmp == "FFFF") factionLess = true;
else npcFaction = cast(Faction*)factions.lookup(tmp);
if(isEmptyOrGetName) return;
}
if(subName == "ANAM")
{
cell = getHString();
if(isEmptyOrGetName) return;
} else cell = null;
if(subName == "DNAM")
{
pcFaction = cast(Faction*)factions.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else pcFaction = null;
if(subName == "SNAM")
{
sound = resources.lookupSound(tmpHString());
if(isEmptyOrGetName) return;
} else sound = SoundIndex.init;
if(subName == "NAME")
{
response = getHString();
if(isEmptyOrGetName) return;
} else response = null;
selects = null;
while(subName == "SCVR")
{
if(selects is null) selects = esmRegion.getBuffer!(SelectStruct)(1,1);
else selects.length = selects.length + 1;
with(selects.array()[$-1])
{
selectRule = getHString();
isEmptyOrGetName();
if(subName == "INTV") type = VarType.Int;
else if(subName == "FLTV") type = VarType.Float;
else
fail("INFO.SCVR must precede INTV or FLTV, not "
~ subName);
readHExact(&i, 4);
}
if(isEmptyOrGetName) return;
}
if(subName == "BNAM")
{
resultScript = getHString();
if(isEmptyOrGetName) return;
} else resultScript = null;
deleted = false;
questStatus = Quest.None;
// Figure out how we check the owner's type
if(tp == Dialogue.Type.Journal)
{
if(subName == "QSTN") questStatus = Quest.Name;
else if(subName == "QSTF") questStatus = Quest.Finished;
else if(subName == "QSTR") questStatus = Quest.Restart;
if(questStatus != Quest.None) getHByte();
}
else if(subName == "DELE") {getHInt(); deleted = true;}
else fail("Don't know what to do with " ~ subName ~ " here.");
}}
}
/*
// We only need to put each item in ONE list. For if your NPC
// matches this response, then it must match ALL criteria, thus it
// will have to look up itself in all the lists. I think the order
// is well optimized in making the lists as small as possible.
if(this.actor.index != -1) actorDial[this.actor][parent]++;
else if(cell != "") cellDial[cell][parent]++;
else if(this.Class != -1) classDial[this.Class][parent]++;
else if(this.npcFaction != -1)
factionDial[this.npcFaction][parent]++;
else if(this.race != -1) raceDial[this.race][parent]++;
else allDial[parent]++; // Lists dialogues that might
// apply to all npcs.
*/
struct DialInfoList
{
LoadState state;
DialInfo d;
void load(DialogueType type)
{
d.load(type);
}
}
// List of dialogue topics (and greetings, voices, etc.) that
// reference other objects. Eg. raceDial is indexed by the indices of
// all races referenced. The value of raceDial is a new AA, which is
// basically used as a map (the int value is just a count and isn't
// used for anything important.) The indices (or elements of the map)
// are the dialogues that reference the given race. I use an AA
// instead of a list or array, since each dialogue can be added lots
// of times.
/*
int allDial[Dialogue*];
int classDial[int][Dialogue*];
int factionDial[int][Dialogue*];
int actorDial[Item][Dialogue*];
// If I look up cells on cell load, I don't have to resolve these
// names into anything!
int cellDial[char[]][Dialogue*];
int raceDial[int][Dialogue*];
*/

@ -0,0 +1,63 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadingr.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadingr;
import esm.imports;
/*
* Alchemy ingredient
*/
struct Ingredient
{
align(1) struct IRDTstruct
{
float weight;
int value;
int[4] effectID; // Effect, 0 or -1 means none
SkillEnum[4] skills; // Skill related to effect
Attribute[4] attributes; // Attribute related to effect
static assert(IRDTstruct.sizeof==56);
}
IRDTstruct data;
LoadState state;
char[] id, name;
MeshIndex model;
IconIndex icon;
Script *script;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
readHNExact(&data, data.sizeof, "IRDT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getOIcon();
}}
}
ListID!(Ingredient) ingreds;

@ -0,0 +1,201 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadlevlist.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadlevlist;
import esm.imports;
import esm.loadcrea;
import util.random;
/* Outdated comments:
*
* Leveled lists. Since these have identical layout, I only bothered
* to implement it once.
*
* We should later implement the ability to merge leveled lists from
* several files.
*
* The Item indices can point to several different lists (potions,
* weapons, armor, etc.) and this is a problem also for npcs,
* containers and cells. Deal with it in a uniform way. In fact, we
* won't have to make any difference between the creature and item
* lists.
*
* EDIT 2007.08.18: Looks like DMD is exploding from template forward
* references again. It works for very small cases, so I assume it
* should be legal in our case also, and that this is a BUG. I have
* cut it down and am going to report it. Moving ListItem out of the
* struct helped, but I get the same problem in other places, where it
* is not possible to fix. I should probably cut down the other case
* as well...
*
* EDIT 2007.09.09: In Monster we managed to cut down template
* forwarding with LinkedList by making the list use Value pointers
* instead of Node pointers. If I do the same for HashTable and move
* the value to the top, I JUST might be able to work around this DMD
* bug.
*
* UPDATE: Well, it works now, but only if you compile with DMD using
* the all-files-on-one-line approach. It will not work with DSSS
* until these bugs are gone.
*/
// Moved here for template bug reasons...
struct _ListItem
{
Item item; // Definded in records.d
short level;
}
struct LeveledListT(bool creature)
{
char[] id;
LoadState state;
enum Flags
{
AllLevels = 0x01, // Calculate from all levels <= player
// level, not just the closest below
// player.
Each = 0x02 // Select a new item each time this
// list is instantiated, instead of
// giving several identical items
} // (used when a container has more
// than one instance of one leveled
// list.)
alias _ListItem ListItem;
Flags flags;
ubyte chanceNone; // Chance that none are selected (0-255??)
ListItem list[];
// Get a random creature from this list
Creature* instCreature(short PCLevel)
in
{
assert(creature);
}
body
{
int index = instIndex(PCLevel);
if(index == -1) return null; // No creature was selected
Creature *c = cast(Creature*)list[index].item.getPtr(ItemType.Creature);
assert(c != null);
return c;
}
// Get a random item from the list
Item *instItem(short PCLevel)
in
{
assert(!creature);
}
body
{
int index = instIndex(PCLevel);
if(index == -1) return null;
return &list[index].item;
}
// Get a random index in the right range
int instIndex(short PCLevel)
{
int top, bottom, i;
// TODO: Find out if this is indeed correct.
// Test if no creature is to be selected
if(rnd.randInt(0, 255) < chanceNone) return -1;
// Find the highest item below or equal to the Player level
for(i=list.length-1; i>=0; i--)
if(list[i].level <= PCLevel) break;
top = i;
// Select none if the player level is too low
if(top < 0) return -1;
// Now find the lowest element to select
if(flags & Flags.AllLevels) bottom = 0;
else
{
// Find the lowest index i such that item[i] has the same
// level as item[top].
do { i--; }
while(i>=0 && list[i].level == list[top].level);
bottom = i+1;
}
// Select a random item
return rnd.randInt(bottom, top);
}
void load()
{with(esFile){
flags = cast(Flags)getHNUint("DATA");
chanceNone = cast(ubyte) getHNByte("NNAM");
if(hasMoreSubs())
list = getRegion().allocateT!(ListItem)(getHNInt("INDX"));
else list = null;
// TODO: Merge the lists here. This can be done simply by adding
// the lists together, making sure that they are sorted by
// level. A better way might be to exclude repeated items. Also,
// some times we don't want to merge lists, just overwrite. Figure
// out a way to give the user this option.
foreach(ref ListItem l; list)
{
static if(creature)
{
getSubNameIs("CNAM");
l.item = actors.lookup(tmpHString());
}
else
{
getSubNameIs("INAM");
// Morrowind.esm actually contains an invalid reference,
// to "imperial cuirass" in the list
// "random_imp_armor". We just ignore it to avoid
// irritating warning messages.
char[] tmp = tmpHString();
if( (tmp != "imperial cuirass") || (id != "random_imp_armor")
|| (getSpecial() != SpecialFile.Morrowind) )
l.item = items.lookup(tmp);
//l.item = items.lookup(tmpHString());
}
l.level = getHNShort("INTV");
}
}}
}
alias LeveledListT!(false) LeveledItems;
alias LeveledListT!(true) LeveledCreatures;
ListID!(LeveledCreatures) creatureLists;
ListID!(LeveledItems) itemLists;

@ -0,0 +1,82 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadligh.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadligh;
import esm.imports;
/*
* Lights
*/
struct Light
{
char[] id;
LoadState state;
enum Flags : uint
{
Dynamic = 0x001,
Carry = 0x002, // Can be carried
Negative = 0x004, // Negative light?
Flicker = 0x008,
Fire = 0x010,
OffDefault = 0x020, // Off by default
FlickerSlow = 0x040,
Pulse = 0x080,
PulseSlow = 0x100
}
align(1) struct LHDTstruct
{
float weight;
int value;
int time; // Duration
int radius;
Color color;
Flags flags;
static assert(LHDTstruct.sizeof == 24);
}
LHDTstruct data;
char[] name;
MeshIndex model;
IconIndex icon;
Sound* sound;
Script* script;
void load()
{with(esFile){
model = getMesh();
name = getHNOString("FNAM");
icon = getOIcon();
readHNExact(&data, data.sizeof, "LHDT");
script = getHNOPtr!(Script)("SCRI", scripts);
sound = getHNOPtr!(Sound)("SNAM", sounds);
}}
}
ListID!(Light) lights;

@ -0,0 +1,77 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadlocks.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadlocks;
import esm.imports;
/*
* This file covers lockpicks, probes and armor repair items, since
* these have nearly identical structure.
*/
struct Tool
{
align(1) struct Data
{
float weight;
int value;
float quality; // And when I say nearly identical structure, I
int uses; // mean perfectly identical except that these two
// variables are swaped for repair items. Don't ask
// me why.
}
static assert(Data.sizeof == 16);
Data data;
LoadState state;
char[] name, id;
MeshIndex model;
IconIndex icon;
Script* script;
void load()
{with(esFile){
model = getMesh();
name = getHNString("FNAM");
if(isNextSub("LKDT") || isNextSub("PBDT"))
readHExact(&data, data.sizeof);
else
{
getSubNameIs("RIDT");
readHExact(&data, data.sizeof);
// Swap t.data.quality and t.data.uses (sigh)
float tmp = *(cast(float*)&data.uses);
data.uses = *(cast(int*)&data.quality);
data.quality = tmp;
}
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getOIcon();
}}
}
ListID!(Tool) lockpicks, probes, repairs;

@ -0,0 +1,107 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadltex.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadltex;
import esm.imports;
/*
* Texture used for texturing landscape?
*
* Not sure how to store these yet. They are probably indexed by
* 'num', not 'id', but I don't know for sure. And num is not unique
* between files, so one option is to keep a separate list for each
* input file (that has LTEX records, of course.) We also need to
* resolve references to already existing land textures to save space.
* I'm not sure if it is even possible to override existing land
* textures, probably not. I'll have to try it, and have to mimic the
* behaviour of morrowind. First, check what you are allowed to do in
* the editor. Then make an esp which changes a commonly used land
* texture, and see if it affects the game.
*/
class LandTextureList : ListKeeper
{
// Number of textures inserted in this file.
int num;
alias RegionBuffer!(TextureIndex) TextureList;
// Contains the list of land textures for each file, indexed by
// file. TODO: Use some handle system here too instead of raw
// filename?
HashTable!(char[], TextureList, ESMRegionAlloc) files;
// The texture list for the current file
TextureList current;
this()
{
num = 0;
endFile();
// The first file (Morrowind.esm) typically needs a little more
// than most others
current = esmRegion.getBuffer!(TextureIndex)(0,120);
// Overkill, just leave it as it is
//files.rehash(resources.esm.length + resources.esp.length);
}
void load()
{with(esFile){
getHNString("NAME");
int n = getHNInt("INTV");
if(n != num++)
{
//writefln("Warning: Wanted land texture %d, got %d", num-1, n);
current.length = n;
num = n+1;
}
current ~= resources.lookupTexture(getHNString("DATA"));
}}
void endFile()
{
if(num)
{
files[esFile.getFilename()] = current;
current = esmRegion.getBuffer!(TextureIndex)(0,50);
num = 0;
}
}
uint length() { return files.length; }
void* lookup(char[] s) { assert(0); }
// This requires the correct file to be open
TextureIndex lookup(int index)
{
return files[esFile.getFilename()][index];
}
}
LandTextureList landTextures;

@ -0,0 +1,114 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadmgef.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadmgef;
import esm.imports;
import esm.loadweap;
import esm.loadstat;
struct MagicEffect
{
enum Flags : int
{
SpellMaking = 0x0200,
Enchanting = 0x0400,
Negative = 0x0800 // A harmful effect
}
align(1) struct MEDTstruct
{
SpellSchool school;
float baseCost;
Flags flags;
int red, blue, green;
float speed, size, sizeCap; // Noe clue
static assert(MEDTstruct.sizeof == 36);
}
MEDTstruct data;
IconIndex icon;
TextureIndex particle;
Static* casting, hit, area;
Weapon* bolt;
Sound* castSound, boltSound, hitSound, areaSound;
char[] description;
int index;
void load()
{with(esFile){
readHNExact(&data, data.sizeof,"MEDT");
icon = getIcon();
particle = getOTexture("PTEX");
boltSound = getHNOPtr!(Sound)("BSND", sounds);
castSound = getHNOPtr!(Sound)("CSND", sounds);
hitSound = getHNOPtr!(Sound)("HSND", sounds);
areaSound = getHNOPtr!(Sound)("ASND", sounds);
casting = getHNOPtr!(Static)("CVFX", statics);
bolt = getHNOPtr!(Weapon)("BVFX", weapons);
hit = getHNOPtr!(Static)("HVFX", statics);
area = getHNOPtr!(Static)("AVFX", statics);
description = getHNOString("DESC");
}}
}
class MagicEffectList : ListKeeper
{
// 0-136 in Morrowind
// 137 in Tribunal
// 138-140 in Bloodmoon (also changes 64?)
// 141-142 are summon effects introduced in bloodmoon, but not used
// there. They can be redefined in mods by setting the name in GMST
// sEffectSummonCreature04/05 creature id in
// sMagicCreature04ID/05ID.
MagicEffect[143] list;
override:
void load()
{
int index = esFile.getHNInt("INDX");
if(index < 0 || index >= list.length)
esFile.fail("Invalid magic effect number " ~ .toString(index));
list[index].load();
}
void endFile() {}
uint length() { return list.length; }
void* lookup(char[] s) { assert(0); }
}
MagicEffectList effects;

@ -0,0 +1,63 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadmisc.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadmisc;
import esm.imports;
/*
* Misc inventory items, basically things that have no use but can be
* carried, bought and sold. It also includes keys.
*/
struct Misc
{
struct MCDTstruct
{
float weight;
int value;
int isKey; // There are many keys in Morrowind.esm that has this
// set to 0. TODO: Check what this field corresponds to
// in the editor.
static assert(MCDTstruct.sizeof == 12);
}
MCDTstruct data;
char[] id, name;
LoadState state;
MeshIndex model;
IconIndex icon;
Script *script;
void load()
{with(esFile){
model = getMesh();
name = getHNOString("FNAM");
readHNExact(&data, data.sizeof, "MCDT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getOIcon();;
}}
}
ListID!(Misc) miscItems;

@ -0,0 +1,171 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadnpc.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadnpc;
import esm.imports;
import esm.loadcont;
import esm.loadrace;
import esm.loadclas;
import esm.loadfact;
import esm.loadbody;
/*
* NPC definition
*/
struct NPC
{
// Services
enum Services : int
{
// Buys
Weapon = 0x00001,
Armor = 0x00002,
Clothing = 0x00004,
Books = 0x00008,
Ingredients = 0x00010,
Picks = 0x00020,
Probes = 0x00040,
Lights = 0x00080,
Apparatus = 0x00100,
RepairItem = 0x00200,
Misc = 0x00400,
// Services
Spells = 0x00800,
MagicItems = 0x01000,
Potions = 0x02000,
Training = 0x04000, // What skills?
Spellmaking = 0x08000,
Enchanting = 0x10000,
Repair = 0x20000
}
enum Flags
{
Female = 0x0001,
Essential = 0x0002,
Respawn = 0x0004,
Autocalc = 0x0008,
Skeleton = 0x0400, // Skeleton blood effect (white)
Metal = 0x0800 // Metal blood effect (golden?)
}
align(1) struct NPDTstruct52
{
short level;
byte strength, intelligence, willpower, agility,
speed, endurance, personality, luck;
byte skills[27];
//byte reputation; // Total confusion!
short health, mana, fatigue;
byte disposition;
byte reputation; // Was "factionID", but that makes no sense.
byte rank, unknown, u2;
int gold;
}
align(1) struct NPDTstruct12
{
short level;
byte disposition, reputation, rank,
unknown1, unknown2, unknown3;
int gold; // ?? Not sure
}
align(1) struct AIDTstruct
{
// These are probabilities
byte hello, u1, fight, flee, alarm, u2, u3, u4;
// The u's might be the skills that this NPC can train you in
Services services;
static assert(AIDTstruct.sizeof == 12);
}
static assert(NPDTstruct52.sizeof==52);
static assert(NPDTstruct12.sizeof==12);
union
{
NPDTstruct52 npdt52;
NPDTstruct12 npdt12; // Use this if npdt52.gold == -10
}
Flags flags;
InventoryList inventory;
SpellList spells;
AIDTstruct AI;
bool hasAI;
LoadState state;
char[] name, id;
MeshIndex model;
Race* race;
Class* cls;
Faction* faction;
Script* script;
BodyPart* hair, head;
void load()
{with(esFile){
npdt52.gold = -10;
model = getOMesh();
name = getHNOString("FNAM");
race = getHNPtr!(Race)("RNAM", races);
cls = getHNPtr!(Class)("CNAM", classes);
faction = getHNPtr!(Faction)("ANAM", factions);
head = getHNPtr!(BodyPart)("BNAM", bodyParts);
hair = getHNPtr!(BodyPart)("KNAM", bodyParts);
script = getHNOPtr!(Script)("SCRI", scripts);
getSubNameIs("NPDT");
getSubHeader();
if(getSubSize() == 52) readExact(&npdt52, npdt52.sizeof);
else if(getSubSize() == 12) readExact(&npdt12, npdt12.sizeof);
else fail("NPC_NPDT must be 12 or 52 bytes long");
flags = cast(Flags) getHNInt("FLAG");
inventory.load();
spells.load();
if(isNextSub("AIDT"))
{
readHExact(&AI, AI.sizeof);
hasAI = true;
}
else hasAI = false;
skipRecord();
}}
}
ListID!(NPC) npcs;

@ -0,0 +1,104 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadnpcc.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadnpcc;
import esm.imports;
/*
* NPC change information (found in savegame files only). We can't
* read these yet.
*
* Some general observations about savegames:
*
* Magical items/potions/spells/etc are added normally as new ALCH,
* SPEL, etc. records, with unique numeric identifiers.
*
* Books with ability enhancements are listed in the save if they have
* been read.
*
* GLOB records set global variables.
*
* SCPT records do not define new scripts, but assign values to the
* variables of existing ones.
*
* STLN - stolen items, ONAM is the owner
*
* GAME - contains a GMDT (game data) of unknown format
*
* VFXM, SPLM, KLST - no clue
*
* PCDT - seems to contain a lot of DNAMs, strings?
*
* FMAP - MAPH and MAPD, probably map data.
*
* JOUR - the entire bloody journal in html
*
* QUES - seems to contain all the quests in the game, not just the
* ones you have done or begun.
*
* REGN - lists all regions in the game, even unvisited ones.
*
* The DIAL/INFO blocks contain changes to characters dialog status.
*
* Dammit there's a lot of stuff in there! Should really have
* suspected as much. The strategy further is to completely ignore
* save files for the time being.
*
* Several records have a "change" variant, like NPCC, CNTC
* (contents), and CREC (creature.) These seem to alter specific
* instances of creatures, npcs, etc. I have not identified most of
* their subrecords yet.
*
* Several NPCC records have names that begin with "chargen ", I don't
* know if it means something special yet.
*
* The CNTC blocks seem to be instances of leveled lists. When a
* container is supposed to contain this leveled list of this type,
* but is referenced elsewhere in the file by an INDX, the CNTC with
* the corresponding leveled list identifier and INDX will determine
* the container contents instead.
*
* Some classes of objects seem to be altered, and these include an
* INDX, which is probably an index used by specific references other
* places within the save file. I guess this means 'use this class for
* these objects, not the general class.' All the indices I have
* encountered so far are zero, but they have been for different
* classes (different containers, really) so possibly we start from
* zero for each class. This looks like a mess, but is probably still
* easier than to duplicate everything. I think WRITING this format
* will be harder than reading it.
*/
struct LoadNPCC
{
void load(TES3File f)
{
writefln("NPC-changer: %s", f.getHNString("NAME"));
while(f.hasMoreSubs)
{
f.getSubName();
f.skipHSub();
}
}
}

@ -0,0 +1,82 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadrace.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadrace;
import esm.imports;
import esm.loadbsgn; // Defines PowerList
/*
* Race definition
*/
struct Race
{
align(1) struct SkillBonus
{
SkillEnum skill;
int bonus;
}
static assert(SkillBonus.sizeof == 8);
enum Flags
{
Playable = 0x01,
Beast = 0x02
}
align(1) struct RADTstruct
{
// List of skills that get a bonus
SkillBonus bonus[7];
// Attribute values for male/female
int[2] strength, intelligence, willpower, agility,
speed, endurance, personality, luck, height,
weight;
Flags flags; // 0x1 - playable, 0x2 - beast race
static assert(RADTstruct.sizeof == 140);
}
RADTstruct data;
LoadState state;
char[] id, name, description;
SpellList powers;
void load()
{with(esFile){
name = getHNString("FNAM");
readHNExact(&data, data.sizeof, "RADT");
powers.load();
description = getHNOString("DESC");
}}
}
ListID!(Race) races;

@ -0,0 +1,113 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadregn.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadregn;
import esm.imports;
import esm.loadlevlist;
/*
* Region data
*/
struct Region
{
align(1) struct WEATstruct
{
byte clear, // Don't know if these are probabilities or what.
cloudy,
foggy,
overcast,
rain,
thunder,
ash,
blight,
a,b;// Unknown weather, probably snow and something. Only
// present in file version 1.3.
}
static assert(WEATstruct.sizeof==10);
LoadState state;
char[] id, name;
WEATstruct data;
Color mapColor;
// Leveled list of creatures you can meet if you sleep outside in
// this region.
LeveledCreatures *sleepList;
// Sounds that are played randomly when you are in this region
struct SoundRef{ Sound* sound; ubyte chance; }
RegionBuffer!(SoundRef) soundList;
void load()
{with(esFile){
name = getHNString("FNAM");
if(isVer12())
readHNExact(&data, data.sizeof-2, "WEAT");
else if(isVer13())
readHNExact(&data, data.sizeof, "WEAT");
else fail("Don't know what to do in this version");
// TODO: Calculate weather probabilities here? Or sum them, or
// whatever?
sleepList = getHNOPtr!(LeveledCreatures)("BNAM", creatureLists);
/*
if(getFileType == FileType.Savegame)
{
// Probably says which weather condition this region is
// currently experiencing.
writefln("WNAM: ", getHNInt("WNAM"));
return
}
*/
readHNExact(&mapColor, mapColor.sizeof, "CNAM");
soundList = esmRegion.getBuffer!(SoundRef)(0,20);
while(isNextSub("SNAM"))
{
char[32] buffer;
getSubHeaderIs(33);
soundList.length = soundList.length + 1;
with(soundList.array[$-1])
{
// Get and chop sound name
sound = cast(Sound*) sounds.lookup(getString(buffer));
// Get sound probability
getUByte(chance);
}
}
}}
}
ListID!(Region) regions;

@ -0,0 +1,140 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadscpt.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadscpt;
import esm.imports;
//private import std.string;
/*
* Script
*/
struct Script
{
char[] id;
LoadState state;
uint numShorts, numLongs, numFloats;
char[][] localVars;
ubyte[] scriptData;
char[] scriptText;
uint scriptDataSize, localVarSize;
void load()
{with(esFile){
/* Assume start of the header has already been read
getSubNameIs("SCHD");
getSubHeaderIs(52);
id = getString(32);
*/
getUint(numShorts);
getUint(numLongs);
getUint(numFloats);
getUint(scriptDataSize);
getUint(localVarSize);
/* // In save games this is all that follows the header
if(getFileType == FileType.Ess)
{
getSubNameIs("SLCS");
skipHSubSize(12);
if(isNextSub("SLSD"))
skipHSub();
if(isNextSub("SLFD"))
skipHSub();
if(isNextSub("RNAM"))
skipHSubSize(4);
return;
}//*/
// List of local variables
if(isNextSub("SCVR"))
{
char[] tmp = getRegion().getString(localVarSize);
readHExact(tmp.ptr, tmp.length);
// At this point we can't use GC allocations at all, since
// our references are not in a root area. Thus the data
// could be collected while still in use.
localVars = getRegion().allocateT!(char[])
( numShorts + numLongs + numFloats );
// The tmp buffer is a null-byte separated string list, we
// just have to pick out one string at a time.
foreach(ref char[] result; localVars)
{
result = stripz(tmp);
tmp = tmp[result.length+1..$];
}
if(tmp.length) fail("Variable table size mismatch");
}
else localVars = null;
// Script data
scriptData = getRegion().allocate(scriptDataSize);
readHNExact(scriptData.ptr, scriptData.length, "SCDT");
// Script text
scriptText = getHNOString("SCTX");
}}
}
class ScriptList : ListID!(Script)
{
this(uint s) { super(s); }
override char[] getID()
{
// Script header
esFile.getSubNameIs("SCHD");
esFile.getSubHeaderIs(52);
char[] id = esFile.getString(32);
// TODO: Handle multiple Main scripts here. With tribunal,
// modders got the ability to add 'start scripts' to their mods,
// which is a script that is run at startup (I think.) Before
// that, there was only one startup script, called
// "Main". Although most mods have switched to using startup
// scripts, some legacy mods might still overwrite Main, and
// this can cause problems if several mods do it. I think the
// best course of action is to NEVER overwrite main, but instead
// add each with a separate unique name and add them to the
// start script list.
if(esFile.getSpecial() != SpecialFile.Morrowind && icmp(id,"Main")==0)
writefln("Found MAIN script in %s ", esFile.getFilename());
return id;
}
}
ScriptList scripts;

@ -0,0 +1,85 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadskil.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadskil;
import esm.imports;
/*
* Skill information
*/
enum Specialization : int
{
Combat = 0,
Magic = 1,
Stealth = 2
}
struct Skill
{
align(1) struct SKDTstruct
{
Attribute attribute;
Specialization specialization; // 0 - Combat, 1 - Magic, 2 - Stealth
float useValue[4]; // How much skill improves. Meaning of each
// field depends on what skill this is.
}
static assert(SKDTstruct.sizeof == 24);
SKDTstruct data;
char[] description; // Description
void load()
{
esFile.readHNExact(&data, data.sizeof, "SKDT");
description = esFile.getHNOString("DESC");
}
}
class SkillList : ListKeeper
{
// See SkillEnum in defs.d for a definition of skills
Skill[SkillEnum.Length] list;
static assert(list.length == 27);
override:
void load()
{
int index = esFile.getHNInt("INDX");
if(index < 0 || index >= list.length)
esFile.fail("Invalid skill number " ~ .toString(index));
list[index].load();
}
void endFile() {}
uint length() { return list.length; }
void *lookup(char[] s) { assert(0); }
}
SkillList skills;

@ -0,0 +1,66 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadsndg.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadsndg;
import esm.imports;
import esm.loadcrea;
/*
* Sound generator. This describes the sounds a creature make.
*/
struct SoundGenerator
{
enum Type
{
LeftFoot = 0,
RightFoot = 1,
SwimLeft = 2,
SwimRight = 3,
Moan = 4,
Roar = 5,
Scream = 6,
Land = 7
}
// Type
Type type;
char[] id;
LoadState state;
// Which creature this applies to, if any.
Creature *creature;
Sound *sound;
void load()
{with(esFile){
this.type = cast(Type)getHNInt("DATA");
creature = getHNOPtr!(Creature)("CNAM", creatures);
sound = getHNPtr!(Sound)("SNAM", sounds);
}}
}
ListID!(SoundGenerator) soundGens;

@ -0,0 +1,54 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadsoun.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadsoun;
import esm.imports;
/*
* Sounds
*/
align(1) struct SOUNstruct
{
ubyte volume, minRange, maxRange;
}
static assert(SOUNstruct.sizeof == 3);
struct Sound
{
LoadState state;
char[] id;
SOUNstruct data;
SoundIndex sound;
void load()
{
sound = esFile.getSound();
esFile.readHNExact(&data, data.sizeof, "DATA");
}
}
ListID!(Sound) sounds;

@ -0,0 +1,96 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadspel.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadspel;
import esm.imports;
/*
* Spell
*/
struct SpellList
{
RegionBuffer!(Spell*) list;
void load()
{
list = esFile.getRegion().getBuffer!(Spell*)(0,1);
while(esFile.isNextSub("NPCS"))
list ~= esFile.getHPtr!(Spell)(spells);
}
}
struct Spell
{
char[] id;
LoadState state;
enum SpellType
{
Spell = 0, // Normal spell, must be cast and costs mana
Ability = 1, // Inert ability, always in effect
Blight = 2, // Blight disease
Disease = 3, // Common disease
Curse = 4, // Curse (?)
Power = 5 // Power, can use once a day
}
enum Flags
{
Autocalc = 1,
PCStart = 2,
Always = 4 // Casting always succeeds
}
align(1) struct SPDTstruct
{
SpellType type;
int cost;
Flags flags;
static assert(SPDTstruct.sizeof==12);
}
SPDTstruct data;
char[] name;
EffectList effects;
void load()
{with(esFile){
name = getHNOString("FNAM");
readHNExact(&data, data.sizeof, "SPDT");
effects = getRegion().getBuffer!(ENAMstruct)(0,1);
while(isNextSub("ENAM"))
{
effects.length = effects.length + 1;
readHExact(&effects.array[$-1], effects.array[$-1].sizeof);
}
}}
}
ListID!(Spell) spells;

@ -0,0 +1,55 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadsscr.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadsscr;
import esm.imports;
/*
* Startup script. I think this is simply a 'main' script that is run
* from the begining. The SSCR records contain a DATA identifier which
* is totally useless, and a NAME which is simply a script reference.
*/
struct StartScriptList
{
RegionBuffer!(Script*) list;
void init()
{
// Set up the list
list = esmRegion.getBuffer!(Script*)(0,1);
}
// Load a record and add it to the list
void load()
{with(esFile){
getSubNameIs("DATA");
skipHSub();
list ~= getHNPtr!(Script)("NAME", scripts);
}}
uint length()
{ return list.length; }
}
StartScriptList startScripts;

@ -0,0 +1,53 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadstat.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadstat;
import esm.imports;
/*
* Definition of static object.
*
* A stat record is basically just a reference to a nif file. Some
* esps seem to contain copies of the STAT entries from the esms, and
* the esms themselves contain several identical entries. Perhaps all
* statics referenced in a file is also put in the file? Since we are
* only reading files it doesn't much matter to us, but it would if we
* were writing. You can check some files later, when you decode the
* CELL blocks, if you want to test this hypothesis.
*/
struct Static
{
char[] id;
LoadState state;
MeshIndex model;
void load()
{
model = esFile.getMesh();
}
}
ListID!(Static) statics;

@ -0,0 +1,92 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadweap.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadweap;
import esm.imports;
/*
* Weapon definition
*/
struct Weapon
{
enum Type : short
{
ShortBladeOneHand = 0,
LongBladeOneHand = 1,
LongBladeTwoHand = 2,
BluntOneHand = 3,
BluntTwoClose = 4,
BluntTwoWide = 5,
SpearTwoWide = 6,
AxeOneHand = 7,
AxeTwoHand = 8,
MarksmanBow = 9,
MarksmanCrossbow = 10,
MarksmanThrown = 11,
Arrow = 12,
Bolt = 13,
Length
}
enum Flags : uint
{
Magical = 0x01,
Silver = 0x02
}
align(1) struct WPDTstruct
{
float weight;
int value;
Type type;
short health;
float speed, reach;
short enchant; // Enchantment points
ubyte[2] chop, slash, thrust; // Min and max
Flags flags;
static assert(WPDTstruct.sizeof == 32);
}
WPDTstruct data;
LoadState state;
char[] name, id;
MeshIndex model;
IconIndex icon;
Enchantment* enchant;
Script* script;
void load()
{with(esFile){
model = getMesh();
name = getHNOString("FNAM");
readHNExact(&data, data.sizeof, "WPDT");
script = getHNOPtr!(Script)("SCRI", scripts);
icon = getOIcon();
enchant = getHNOPtr!(Enchantment)("ENAM", enchants);
}}
}
ListID!(Weapon) weapons;

@ -0,0 +1,219 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (records.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.records;
public
{
import monster.util.aa;
import util.regions;
import core.memory;
import core.resource;
import esm.filereader;
import esm.defs;
import esm.listkeeper;
import std.stdio; // Remove later
}
public import
esm.loadacti, esm.loaddoor, esm.loadglob, esm.loadscpt, esm.loadsoun, esm.loadgmst,
esm.loadfact, esm.loadstat, esm.loadspel, esm.loadalch, esm.loadappa, esm.loadarmo,
esm.loadbody, esm.loadench, esm.loadbook, esm.loadbsgn, esm.loadltex, esm.loadmgef,
esm.loadweap, esm.loadlocks,esm.loadcell, esm.loadregn, esm.loadligh, esm.loadskil,
esm.loadsndg, esm.loadrace, esm.loadmisc, esm.loadclot, esm.loadingr, esm.loadclas,
esm.loadcont, esm.loadcrea, esm.loadnpc, esm.loaddial, esm.loadinfo, esm.loadsscr,
esm.loadlevlist;
void loadRecord(char[] recName)
{
switch(recName)
{
case "ACTI": activators.load(); break;
case "DOOR": doors.load(); break;
case "GLOB": globals.load(); break;
case "SCPT": scripts.load(); break;
case "SOUN": sounds.load(); break;
case "GMST": gameSettings.load(); break;
case "FACT": factions.load(); break;
case "STAT": statics.load(); break;
case "SPEL": spells.load(); break;
case "ALCH": potions.load(); break;
case "APPA": appas.load(); break;
case "ARMO": armors.load(); break;
case "BODY": bodyParts.load(); break;
case "ENCH": enchants.load(); break;
case "BOOK": books.load(); break;
case "BSGN": birthSigns.load(); break;
case "LTEX": landTextures.load(); break;
case "MGEF": effects.load(); break;
case "WEAP": weapons.load(); break;
case "REPA": repairs.load(); break;
case "LOCK": lockpicks.load(); break;
case "PROB": probes.load(); break;
case "CELL": cells.load(); break;
case "REGN": regions.load(); break;
case "LIGH": lights.load(); break;
case "SKIL": skills.load(); break;
case "SNDG": soundGens.load(); break;
case "RACE": races.load(); break;
case "MISC": miscItems.load(); break;
case "CLOT": clothes.load(); break;
case "INGR": ingreds.load(); break;
case "CLAS": classes.load(); break;
case "CONT": containers.load(); break;
case "CREA": creatures.load(); break;
case "LEVI": itemLists.load(); break;
case "LEVC": creatureLists.load(); break;
case "NPC_": npcs.load(); break;
case "DIAL": dialogues.load(); break;
case "SSCR": startScripts.load(); break;
/*
// Tribunal / Bloodmoon only
case "SSCR": loadSSCR.load(); break;
*/
// For save games:
// case "NPCC": loadNPCC;
// case "CNTC": loadCNTC;
// case "CREC": loadCREC;
// These should never be looked up
case "TES3":
case "INFO":
case "LAND":
case "PGRD":
esFile.fail("Misplaced record " ~ recName);
default:
esFile.fail("Unknown record type " ~ recName);
}
//*/
}
// Um, this has to be in this file for some reason.
ListID!(Dialogue) dialogues;
struct ItemT
{
Item item;
ItemBase *i;
T* getType(T, ItemType Type)()
{
return item.getType!(T, Type)();
}
alias getType!(Potion, ItemType.Potion) getPotion;
alias getType!(Apparatus, ItemType.Apparatus) getApparatus;
alias getType!(Armor, ItemType.Armor) getArmor;
alias getType!(Weapon, ItemType.Weapon) getWeapon;
alias getType!(Book, ItemType.Book) getBook;
alias getType!(Clothing, ItemType.Clothing) getClothing;
alias getType!(Light, ItemType.Light) getLight;
alias getType!(Ingredient, ItemType.Ingredient) getIngredient;
alias getType!(Tool, ItemType.Pick) getPick;
alias getType!(Tool, ItemType.Probe) getProbe;
alias getType!(Tool, ItemType.Repair) getRepair;
alias getType!(Misc, ItemType.Misc) getMisc;
alias getType!(LeveledItems, ItemType.ItemList) getItemList;
alias getType!(Creature, ItemType.Creature) getCreature;
alias getType!(LeveledCreatures, ItemType.CreatureList) getCreatureList;
alias getType!(NPC, ItemType.NPC) getNPC;
alias getType!(Door, ItemType.Door) getDoor;
alias getType!(Activator, ItemType.Activator) getActivator;
alias getType!(Static, ItemType.Static) getStatic;
alias getType!(Container, ItemType.Container) getContainer;
static ItemT opCall(Item it)
{
ItemT itm;
itm.item = it;
itm.i = it.i;
return itm;
}
}
void endFiles()
{
foreach(ListKeeper l; recordLists)
l.endFile();
items.endFile();
}
void initializeLists()
{
recordLists = null;
// Initialize all the lists here. The sizes have been chosen big
// enough to hold the main ESM files and a large number of mods
// without rehashing.
activators = new ListID!(Activator)(1400);
doors = new ListID!(Door)(300);
globals = new ListID!(Global)(300);
scripts = new ScriptList(1800);
sounds = new ListID!(Sound)(1000);
gameSettings = new ListID!(GameSetting)(1600);
factions = new ListID!(Faction)(30);
statics = new ListID!(Static)(4000);
spells = new ListID!(Spell)(1300);
potions = new ListID!(Potion)(300);
appas = new ListID!(Apparatus)(30);
armors = new ListID!(Armor)(500);
bodyParts = new ListID!(BodyPart)(2300);
enchants = new ListID!(Enchantment)(1000);
books = new ListID!(Book)(700);
birthSigns = new ListID!(BirthSign)(30);
landTextures = new LandTextureList;
effects = new MagicEffectList;
weapons = new ListID!(Weapon)(700);
lockpicks = new ListID!(Tool)(10);
probes = new ListID!(Tool)(10);
repairs = new ListID!(Tool)(10);
cells = new CellList;
regions = new ListID!(Region)(20);
lights = new ListID!(Light)(1000);
skills = new SkillList;
soundGens = new ListID!(SoundGenerator)(500);
races = new ListID!(Race)(100);
miscItems = new ListID!(Misc)(700);
clothes = new ListID!(Clothing)(700);
ingreds = new ListID!(Ingredient)(200);
classes = new ListID!(Class)(100);
containers = new ListID!(Container)(1200);
creatures = new ListID!(Creature)(800);
itemLists = new ListID!(LeveledItems)(600);
creatureLists = new ListID!(LeveledCreatures)(400);
npcs = new ListID!(NPC)(3500);
dialogues = new ListID!(Dialogue)(3000);
startScripts.init();
hyperlinks.rehash(1600);
items.rehash(5500);
actors.rehash(5000);
cellRefs.rehash(17000);
}

@ -0,0 +1,320 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (events.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module input.events;
import std.stdio;
import std.string;
import sound.audio;
import core.config;
import scene.soundlist;
import scene.player;
import ogre.bindings;
import input.keys;
import input.ois;
// Debug output
//debug=printMouse; // Mouse button events
//debug=printMouseMove; // Mouse movement events
//debug=printKeys; // Keypress events
// TODO: Jukebox controls and other state-related data will later be
// handled entirely in script code, as will some of the key bindings.
// Are we currently playing battle music?
bool battle = false;
// Pause?
bool pause = false;
// Temporarily store volume while muted
float muteVolume = -1;
void toggleMute()
{
if(muteVolume < 0)
{
muteVolume = config.mainVolume;
config.setMainVolume(0);
writefln("Muted");
}
else
{
config.setMainVolume(muteVolume);
muteVolume = -1;
writefln("Mute off");
}
}
// Switch between normal and battle music
void toggleBattle()
{
if(battle)
{
writefln("Changing to normal music");
jukebox.resumeMusic();
battleMusic.pauseMusic();
battle=false;
}
else
{
writefln("Changing to battle music");
jukebox.pauseMusic();
battleMusic.resumeMusic();
battle=true;
}
}
const float volDiff = 0.05;
void musVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setMusicVolume(diff + config.musicVolume);
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.musicVolume);
}
void sfxVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setSfxVolume(diff + config.sfxVolume);
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.sfxVolume);
}
void mainVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setMainVolume(diff + config.mainVolume);
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.mainVolume);
}
void takeScreenShot()
{
char[] file = format("screenshot_%06d.png", config.screenShotNum++);
cpp_screenshot(toStringz(file));
writefln("Wrote '%s'", file);
}
// Mouse sensitivity
float effMX, effMY;
void updateMouseSensitivity()
{
effMX = config.mouseSensX;
effMY = config.mouseSensY;
if(config.flipMouseY) effMY = -effMY;
}
void togglePause()
{
pause = !pause;
if(pause) writefln("Pause");
else writefln("Pause off");
}
bool doExit = false;
void exitProgram()
{
doExit = true;
}
extern(C) void d_handleMouseMove(MouseState *state)
{
debug(printMouseMove)
writefln("handleMouseMove: Abs(%s, %s, %s) Rel(%s, %s, %s)",
state.X.abs, state.Y.abs, state.Z.abs,
state.X.rel, state.Y.rel, state.Z.rel);
cpp_rotateCamera( state.X.rel * effMX,
state.Y.rel * effMY );
}
extern(C) void d_handleMouseButton(MouseState *state, int button)
{
debug(printMouse)
writefln("handleMouseButton %s: Abs(%s, %s, %s)", button,
state.X.abs, state.Y.abs, state.Z.abs);
// For the moment, just treat mouse clicks as normal key presses.
d_handleKey(cast(KC) (KC.Mouse0 + button));
}
// Handle a keyboard event through key bindings. Direct movement
// (eg. arrow keys) is not handled here, see d_frameStarted() below.
extern(C) void d_handleKey(KC keycode, dchar text = 0)
{
// Do some preprocessing on the data to account for OIS
// shortcommings.
// Some keys (especially international keys) have no key code but
// return a character instead.
if(keycode == 0)
{
// If no character is given, just drop this event since OIS did
// not manage to give us any useful data at all.
if(text == 0) return;
keycode = KC.CharOnly;
}
// Debug output
debug(printKeys)
{
char[] str;
if(keycode >= 0 && keycode < keysymToString.length)
str = keysymToString[keycode];
else str = "OUT_OF_RANGE";
writefln("Key %s, text '%s', name '%s'", keycode, text, str);
}
// Look up the key binding. We have to send both the keycode and the
// text.
Keys k = keyBindings.findMatch(keycode, text);
if(k)
switch(k)
{
case Keys.ToggleBattleMusic: toggleBattle(); break;
case Keys.MainVolUp: mainVolume(true); break;
case Keys.MainVolDown: mainVolume(false); break;
case Keys.MusVolUp: musVolume(true); break;
case Keys.MusVolDown: musVolume(false); break;
case Keys.SfxVolUp: sfxVolume(true); break;
case Keys.SfxVolDown: sfxVolume(false); break;
case Keys.Mute: toggleMute(); break;
case Keys.Debug: cpp_debug(0); break;
case Keys.ScreenShot: takeScreenShot(); break;
case Keys.Pause: togglePause(); break;
case Keys.Exit: exitProgram(); break;
default:
assert(k >= 0 && k < keyToString.length);
writefln("WARNING: Event %s has no effect", keyToString[k]);
}
return false;
}
// Refresh rate for sfx placements, in seconds.
const float sndRefresh = 0.17;
// Refresh rate for music fadeing, seconds.
const float musRefresh = 0.2;
float sndCumTime = 0;
float musCumTime = 0;
void initializeInput()
{
// Move the camera in place
with(playerData.position)
{
cpp_moveCamera(position[0], position[1], position[2],
rotation[0], rotation[1], rotation[2]);
}
// TODO/FIXME: This should have been in config, but DMD's module
// system is on the brink of collapsing, and it won't compile if I
// put another import in core.config. I should probably check the
// bug list and report it.
updateMouseSensitivity();
}
float tmpTime = 0;
int cnt;
extern(C) int cpp_isPressed(int keysym);
// Check if a key is currently down
bool isPressed(Keys key)
{
KeyBind *b = &keyBindings.bindings[key];
foreach(i; b.syms)
if(i != 0 && cpp_isPressed(i)) return true;
return false;
}
extern(C) int d_frameStarted(float time)
{
/*
tmpTime += time;
cnt++;
if(tmpTime >= 1.0)
{
writefln("\nfps: ", cnt/tmpTime);
cnt = 0;
tmpTime = 0;
}
//*/
if(doExit) return 0;
musCumTime += time;
if(musCumTime > musRefresh)
{
jukebox.addTime(musRefresh);
battleMusic.addTime(musRefresh);
musCumTime -= musRefresh;
}
// The rest is ignored in pause mode
if(pause) return 1;
const float moveSpeed = 900;
// Check if the movement keys are pressed
float moveX = 0, moveY = 0, moveZ = 0;
if(isPressed(Keys.MoveLeft)) moveX -= moveSpeed;
if(isPressed(Keys.MoveRight)) moveX += moveSpeed;
if(isPressed(Keys.MoveForward)) moveZ -= moveSpeed;
if(isPressed(Keys.MoveBackward)) moveZ += moveSpeed;
if(isPressed(Keys.MoveUp)) moveY += moveSpeed;
if(isPressed(Keys.MoveDown)) moveY -= moveSpeed;
// Move camera. We only support "ghost-mode" at the moment, so we
// move without physics or collision detection.
cpp_moveCameraRel(moveX*time, moveY*time, moveZ*time);
sndCumTime += time;
if(sndCumTime > sndRefresh)
{
float x, y, z;
cpp_getCameraPos(&x, &y, &z);
soundScene.update(x,y,z);
sndCumTime -= sndRefresh;
}
return 1;
}

@ -0,0 +1,262 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (keys.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
/*
* This module handles keyboard and mouse button configuration
*/
module input.keys;
import std.string;
import std.stdio;
import input.ois;
// List of all functions we need to map to keys. If you add new keys,
// REMEMBER to add strings for them below as well. TODO: We should
// redo this entire section so that we insert actual functions into
// the system instead. That way, if we do not insert a function, the
// key gets treated as a "non-event" key. Then we will also force the
// definition of strings and function call to be in the same
// place. The Keys enum can be eliminated, really. Default keysyms can
// be added when the functions are inserted. But don't do anything
// until you know how this will interact with script code.
enum Keys
{
None = 0,
// Movement
MoveLeft, MoveRight,
TurnLeft, TurnRight,
MoveForward, MoveBackward,
// Used eg. when flying or swimming
MoveUp, MoveDown,
// These are handled as events, while the above are not.
FirstEvent,
// Sound control
MainVolUp, MainVolDown,
MusVolUp, MusVolDown,
SfxVolUp, SfxVolDown,
Mute,
// This will not be part of the finished product :)
ToggleBattleMusic,
Debug,
// Misc
Pause,
ScreenShot,
Exit,
Length
}
// List of keyboard-bound functions
char[][] keyToString;
// Lookup for keyboard key names. TODO: This is currently case
// sensitive, we should use my own AA to fix that.
int[char[]] stringToKeysym;
// Represents a key binding for a single function. Each function can
// have any number keys bound to it, including mouse buttons. This
// might be extended later to allow joystick buttons and other input
// devices.
struct KeyBind
{
int syms[];
// Does the given keysym match this binding?
bool isMatch(int sym, char ch = 0)
{
assert(sym != 0, "don't send empty syms to isMatch");
// We don't match characters yet
if(sym == KC.CharOnly)
return false;
foreach(s; syms)
if(sym == s) return true;
return false;
}
// Does any of the given syms match this binding?
bool isMatchArray(int arr[] ...)
{
foreach(i; arr)
if(i!=0 && isMatch(i)) return true;
return false;
}
// Assign key bindings to this structure. Can be called multiple
// times or with multiple paramters (or an array) to bind multiple
// keys.
void bind(int symlist[] ...)
{
syms ~= symlist;
}
// Remove all bindings to this function
void clear()
{
syms = null;
}
// Remove the given syms from this binding, if found.
void remove(int symlist[] ...)
{
foreach(rs; symlist)
// Just loop though all the syms and set matching values to
// zero. isMatch() will ignore zeros.
foreach(ref s; syms)
if(s == rs) s = 0;
}
// Turn the keysym list into a comma separated list of key names
char[] getString()
{
char[] res = null;
bool notFirst = false;
foreach(int k; syms)
if(k != 0) // Ignore empty keysyms
{
if(notFirst) res ~= ",";
else notFirst = true;
res ~= keysymToString[k];
}
//writefln("getString returned %s", res);
return res;
}
}
KeyBindings keyBindings;
// This structure holds the bindings of all the functions
struct KeyBindings
{
KeyBind[] bindings;
// Bind the given function to the given key(s)
void bind(Keys func, char[] key1, char[] key2 = "")
{
bind(func, getSym(key1), getSym(key2));
}
void bind(Keys func, int syms[] ...)
{
// Find other bindings that match this key
foreach(int i, ref KeyBind kb; bindings)
if(kb.isMatchArray(syms))
kb.remove(syms);
bindings[func].bind(syms);
}
// Find the function that matches the given keysym. We could
// optimize this, but I'm not sure it's worth it.
Keys findMatch(KC keysym, dchar ch)
{
int start=cast(int)Keys.FirstEvent + 1;
foreach(int i, ref KeyBind kb; bindings[start..$])
if( kb.isMatch(keysym, ch) )
return cast(Keys)(i+start);
return cast(Keys)0; // No match
}
static int getSym(char[] key)
{
key = strip(key);
if(key.length)
{
int *p = key in stringToKeysym;
if(p) return *p;
else writefln("Warning: unknown key '%s'", key);
}
return 0;
}
// Bind a function to a comma-separated key list (intended to be
// used directly with the ini file reader.)
void bindComma(Keys func, char[] keys)
{
int index = keys.find(',');
if(index != -1)
{
// Bind the first in the list
bind(func, keys[0..index]);
// Recurse on the rest
bindComma(func, keys[index+1..$]);
}
// Last or only element in the list
else bind(func, keys);
}
// Remove all key bindings
void clear()
{
foreach(ref kb; bindings)
kb.clear();
}
void initKeys()
{
// Keyboard functions
keyToString.length = Keys.Length;
keyToString[Keys.MoveLeft] = "Move Left";
keyToString[Keys.MoveRight] = "Move Right";
keyToString[Keys.TurnLeft] = "Turn Left";
keyToString[Keys.TurnRight] = "Turn Right";
keyToString[Keys.MoveForward] = "Move Forward";
keyToString[Keys.MoveBackward] = "Move Backward";
keyToString[Keys.MoveUp] = "Move Up";
keyToString[Keys.MoveDown] = "Move Down";
keyToString[Keys.MainVolUp] = "Increase Main Volume";
keyToString[Keys.MainVolDown] = "Decrease Main Volume";
keyToString[Keys.MusVolUp] = "Increase Music Volume";
keyToString[Keys.MusVolDown] = "Decrease Music Volume";
keyToString[Keys.SfxVolUp] = "Increase SFX Volume";
keyToString[Keys.SfxVolDown] = "Decrease SFX Volume";
keyToString[Keys.Mute] = "Mute Sound";
keyToString[Keys.ToggleBattleMusic] = "Toggle Battle Music";
keyToString[Keys.Debug] = "OGRE Test Action";
keyToString[Keys.Pause] = "Pause";
keyToString[Keys.ScreenShot] = "Screen Shot";
keyToString[Keys.Exit] = "Quick Exit";
//keyToString[Keys.] = "";
bindings.length = Keys.Length;
// Store all the key strings in a lookup-table
foreach(int k, ref char[] s; keysymToString)
if(s.length) stringToKeysym[s] = k;
}
}

@ -0,0 +1,428 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (ois.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module input.ois;
// Mouse buttons
enum MB : int
{
Button0 = 0,
Left = Button0,
Button1 = 1,
Right = Button1,
Button2 = 2,
Middle = Button2,
Button3 = 3,
Button4 = 4,
Button5 = 5,
Button6 = 6,
Button7 = 7,
LastMouse
}
// Keyboard scan codes
enum KC : int
{
UNASSIGNED = 0x00,
ESCAPE = 0x01,
N1 = 0x02,
N2 = 0x03,
N3 = 0x04,
N4 = 0x05,
N5 = 0x06,
N6 = 0x07,
N7 = 0x08,
N8 = 0x09,
N9 = 0x0A,
N0 = 0x0B,
MINUS = 0x0C, // - on main keyboard
EQUALS = 0x0D,
BACK = 0x0E, // backspace
TAB = 0x0F,
Q = 0x10,
W = 0x11,
E = 0x12,
R = 0x13,
T = 0x14,
Y = 0x15,
U = 0x16,
I = 0x17,
O = 0x18,
P = 0x19,
LBRACKET = 0x1A,
RBRACKET = 0x1B,
RETURN = 0x1C, // Enter on main keyboard
LCONTROL = 0x1D,
A = 0x1E,
S = 0x1F,
D = 0x20,
F = 0x21,
G = 0x22,
H = 0x23,
J = 0x24,
K = 0x25,
L = 0x26,
SEMICOLON = 0x27,
APOSTROPHE = 0x28,
GRAVE = 0x29, // accent
LSHIFT = 0x2A,
BACKSLASH = 0x2B,
Z = 0x2C,
X = 0x2D,
C = 0x2E,
V = 0x2F,
B = 0x30,
N = 0x31,
M = 0x32,
COMMA = 0x33,
PERIOD = 0x34, // . on main keyboard
SLASH = 0x35, // / on main keyboard
RSHIFT = 0x36,
MULTIPLY = 0x37, // * on numeric keypad
LMENU = 0x38, // left Alt
SPACE = 0x39,
CAPITAL = 0x3A,
F1 = 0x3B,
F2 = 0x3C,
F3 = 0x3D,
F4 = 0x3E,
F5 = 0x3F,
F6 = 0x40,
F7 = 0x41,
F8 = 0x42,
F9 = 0x43,
F10 = 0x44,
NUMLOCK = 0x45,
SCROLL = 0x46, // Scroll Lock
NUMPAD7 = 0x47,
NUMPAD8 = 0x48,
NUMPAD9 = 0x49,
SUBTRACT = 0x4A, // - on numeric keypad
NUMPAD4 = 0x4B,
NUMPAD5 = 0x4C,
NUMPAD6 = 0x4D,
ADD = 0x4E, // + on numeric keypad
NUMPAD1 = 0x4F,
NUMPAD2 = 0x50,
NUMPAD3 = 0x51,
NUMPAD0 = 0x52,
DECIMAL = 0x53, // . on numeric keypad
OEM_102 = 0x56, // < > | on UK/Germany keyboards
F11 = 0x57,
F12 = 0x58,
F13 = 0x64, // (NEC PC98)
F14 = 0x65, // (NEC PC98)
F15 = 0x66, // (NEC PC98)
KANA = 0x70, // (Japanese keyboard)
ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
CONVERT = 0x79, // (Japanese keyboard)
NOCONVERT = 0x7B, // (Japanese keyboard)
YEN = 0x7D, // (Japanese keyboard)
ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
NUMPADEQUALS= 0x8D, // = on numeric keypad (NEC PC98)
PREVTRACK = 0x90, // Previous Track (CIRCUMFLEX on Japanese keyboard)
AT = 0x91, // (NEC PC98)
COLON = 0x92, // (NEC PC98)
UNDERLINE = 0x93, // (NEC PC98)
KANJI = 0x94, // (Japanese keyboard)
STOP = 0x95, // (NEC PC98)
AX = 0x96, // (Japan AX)
UNLABELED = 0x97, // (J3100)
NEXTTRACK = 0x99, // Next Track
NUMPADENTER = 0x9C, // Enter on numeric keypad
RCONTROL = 0x9D,
MUTE = 0xA0, // Mute
CALCULATOR = 0xA1, // Calculator
PLAYPAUSE = 0xA2, // Play / Pause
MEDIASTOP = 0xA4, // Media Stop
VOLUMEDOWN = 0xAE, // Volume -
VOLUMEUP = 0xB0, // Volume +
WEBHOME = 0xB2, // Web home
NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
DIVIDE = 0xB5, // / on numeric keypad
SYSRQ = 0xB7, // Also called print screen
RMENU = 0xB8, // right Alt
PAUSE = 0xC5, // Pause
HOME = 0xC7, // Home on arrow keypad
UP = 0xC8, // UpArrow on arrow keypad
PGUP = 0xC9, // PgUp on arrow keypad
LEFT = 0xCB, // LeftArrow on arrow keypad
RIGHT = 0xCD, // RightArrow on arrow keypad
END = 0xCF, // End on arrow keypad
DOWN = 0xD0, // DownArrow on arrow keypad
PGDOWN = 0xD1, // PgDn on arrow keypad
INSERT = 0xD2, // Insert on arrow keypad
DELETE = 0xD3, // Delete on arrow keypad
LWIN = 0xDB, // Left Windows key
RWIN = 0xDC, // Right Windows key
APPS = 0xDD, // AppMenu key
POWER = 0xDE, // System Power
SLEEP = 0xDF, // System Sleep
WAKE = 0xE3, // System Wake
WEBSEARCH = 0xE5, // Web Search
WEBFAVORITES= 0xE6, // Web Favorites
WEBREFRESH = 0xE7, // Web Refresh
WEBSTOP = 0xE8, // Web Stop
WEBFORWARD = 0xE9, // Web Forward
WEBBACK = 0xEA, // Web Back
MYCOMPUTER = 0xEB, // My Computer
MAIL = 0xEC, // Mail
MEDIASELECT = 0xED, // Media Select
CharOnly = 0xFF, // Set when the keysym is 0 but the
// character is set. This happens with many
// international characters or reassigned
// characters.
Mouse0 = 0x100, // Mouse button events can be handled as
Mouse1 = 0x101, // keypresses too.
Mouse2 = 0x102,
Mouse3 = 0x103,
Mouse4 = 0x104,
Mouse5 = 0x105,
Mouse6 = 0x106,
Mouse7 = 0x107,
}
// Sigh. I guess we have to do this for Monster at some poing anyway,
// so this work isn't completely wasted. Later we can make a generic
// conversion between OIS-keysyms, SDL-keysyms and others to the
// Monster keysyms. It sucks that everybody tries to reinvent the
// wheel as often as they can, but that's the way it goes.
const char[][] keysymToString =
[
KC.UNASSIGNED : "UNASSIGNED",
KC.ESCAPE : "escape",
KC.N1 : "1",
KC.N2 : "2",
KC.N3 : "3",
KC.N4 : "4",
KC.N5 : "5",
KC.N6 : "6",
KC.N7 : "7",
KC.N8 : "8",
KC.N9 : "9",
KC.N0 : "0",
KC.MINUS : "minus",
KC.EQUALS : "equals",
KC.BACK : "backspace",
KC.TAB : "tab",
KC.Q : "q",
KC.W : "w",
KC.E : "e",
KC.R : "r",
KC.T : "t",
KC.Y : "y",
KC.U : "u",
KC.I : "i",
KC.O : "o",
KC.P : "p",
KC.LBRACKET : "{",
KC.RBRACKET : "}",
KC.RETURN : "enter",
KC.LCONTROL : "left_ctrl",
KC.A : "a",
KC.S : "s",
KC.D : "d",
KC.F : "f",
KC.G : "g",
KC.H : "h",
KC.J : "j",
KC.K : "k",
KC.L : "l",
KC.SEMICOLON : "semicolon",
KC.APOSTROPHE : "apostrophe",
KC.GRAVE : "grave",
KC.LSHIFT : "left_shift",
KC.BACKSLASH : "backslash",
KC.Z : "z",
KC.X : "x",
KC.C : "c",
KC.V : "v",
KC.B : "b",
KC.N : "n",
KC.M : "m",
KC.COMMA : "comma",
KC.PERIOD : "period",
KC.SLASH : "slash",
KC.RSHIFT : "right_shift",
KC.MULTIPLY : "numpad_mult",
KC.LMENU : "left_alt",
KC.SPACE : "space",
KC.CAPITAL : "capital",
KC.F1 : "f1",
KC.F2 : "f2",
KC.F3 : "f3",
KC.F4 : "f4",
KC.F5 : "f5",
KC.F6 : "f6",
KC.F7 : "f7",
KC.F8 : "f8",
KC.F9 : "f9",
KC.F10 : "f10",
KC.NUMLOCK : "numlock",
KC.SCROLL : "scroll",
KC.NUMPAD7 : "numpad_7",
KC.NUMPAD8 : "numpad_8",
KC.NUMPAD9 : "numpad_9",
KC.SUBTRACT : "numpad_minus",
KC.NUMPAD4 : "numpad_4",
KC.NUMPAD5 : "numpad_5",
KC.NUMPAD6 : "numpad_6",
KC.ADD : "numpad_plus",
KC.NUMPAD1 : "numpad_1",
KC.NUMPAD2 : "numpad_2",
KC.NUMPAD3 : "numpad_3",
KC.NUMPAD0 : "numpad_0",
KC.DECIMAL : "numpad_period",
KC.OEM_102 : "oem102",
KC.F11 : "f11",
KC.F12 : "f12",
KC.F13 : "f13",
KC.F14 : "f14",
KC.F15 : "f15",
KC.KANA : "kana",
KC.ABNT_C1 : "abnt_c1",
KC.CONVERT : "convert",
KC.NOCONVERT : "noconvert",
KC.YEN : "yen",
KC.ABNT_C2 : "abnt_c2",
KC.NUMPADEQUALS: "numpad_equals",
KC.PREVTRACK : "prev_track",
KC.AT : "at",
KC.COLON : "colon",
KC.UNDERLINE : "underline",
KC.KANJI : "kanji",
KC.STOP : "stop",
KC.AX : "ax",
KC.UNLABELED : "unlabeled",
KC.NEXTTRACK : "next_track",
KC.NUMPADENTER : "numpad_enter",
KC.RCONTROL : "right_control",
KC.MUTE : "mute",
KC.CALCULATOR : "calculator",
KC.PLAYPAUSE : "play_pause",
KC.MEDIASTOP : "media_stop",
KC.VOLUMEDOWN : "volume_down",
KC.VOLUMEUP : "volume_up",
KC.WEBHOME : "webhome",
KC.NUMPADCOMMA : "numpad_comma",
KC.DIVIDE : "numpad_divide",
KC.SYSRQ : "print_screen",
KC.RMENU : "right_alt",
KC.PAUSE : "pause",
KC.HOME : "home",
KC.UP : "up",
KC.PGUP : "page_up",
KC.LEFT : "left",
KC.RIGHT : "right",
KC.END : "end",
KC.DOWN : "down",
KC.PGDOWN : "page_down",
KC.INSERT : "insert",
KC.DELETE : "delete",
KC.LWIN : "left_win",
KC.RWIN : "right_win",
KC.APPS : "app_menu",
KC.POWER : "power",
KC.SLEEP : "sleep",
KC.WAKE : "wake",
KC.WEBSEARCH : "web_search",
KC.WEBFAVORITES: "web_favorites",
KC.WEBREFRESH : "web_refresh",
KC.WEBSTOP : "web_stop",
KC.WEBFORWARD : "web_forward",
KC.WEBBACK : "web_back",
KC.MYCOMPUTER : "my_computer",
KC.MAIL : "mail",
KC.MEDIASELECT : "media_select",
KC.CharOnly : "CHAR_ONLY", // Set when the keysym is 0 but the
// character is set. This happens
// with many international
// characters or reassigned
// characters in OIS (and it
// SUCKS.)
KC.Mouse0 : "mouse0",
KC.Mouse1 : "mouse1",
KC.Mouse2 : "mouse2",
KC.Mouse3 : "mouse3",
KC.Mouse4 : "mouse4",
KC.Mouse5 : "mouse5",
KC.Mouse6 : "mouse6",
KC.Mouse7 : "mouse7",
];
enum ComponentType : int
{
Unknown = 0,
Button = 1, // ie. Key, mouse button, joy button, etc
Axis = 2, // ie. A joystick or mouse axis
Slider = 3, //
POV = 4, // ie. Arrow direction keys
Vector3 = 5 // ie. WiiMote orientation
}
align(4) struct Axis
{
ComponentType type;
int abs, rel;
bool absOnly;
}
// The C++ size of Axis is 16
static assert(Axis.sizeof == 16);
struct MouseState
{
/* Represents the height/width of your display area.. used if mouse
clipping or mouse grabbed in case of X11 - defaults to 50.. Make
sure to set this and change when your size changes.. */
int width, height;
// X Axis component
Axis X;
// Y Axis Component
Axis Y;
// Z Axis Component
Axis Z;
// represents all buttons - bit position indicates button down
int buttons;
// Button down test
bool buttonDown( MB button )
{
return (buttons & ( 1 << button )) != 0;
}
}
// Check that we match the C++ size
static assert(MouseState.sizeof == 60);

@ -0,0 +1,385 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (morro.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module morro;
import std.stdio;
import std.string;
import std.cstream;
import ogre.ogre;
import ogre.bindings;
import scene.celldata;
import scene.soundlist;
import core.resource;
import core.memory;
import core.config;
import monster.util.string;
import sound.audio;
import input.events;
//*
import std.gc;
import gcstats;
void poolSize()
{
GCStats gc;
getStats(gc);
writefln("Pool size: ", comma(gc.poolsize));
writefln("Used size: ", comma(gc.usedsize));
}
//*/
void main(char[][] args)
{
bool render = true;
bool help = false;
bool resetKeys = false;
// Some examples to try:
//
// "Abandoned Shipwreck, Upper Level";
// "Gro-Bagrat Plantation";
// "Abinabi";
// "Abebaal Egg Mine";
// "Ald-ruhn, Ald Skar Inn";
// "Koal Cave";
// "Ald-ruhn, Arobar Manor Bedrooms"
// "Sud";
// "Vivec, The Lizard's Head";
// "ToddTest";
// Cells to load
char[][] cells;
int[] eCells;
foreach(char[] a; args[1..$])
if(a == "-n") render = false;
else if(a.begins("-e"))
{
int i = find(a,',');
eCells ~= atoi(a[2..i]);
eCells ~= atoi(a[i+1..$]);
}
else if(a == "-h") help=true;
else if(a == "-rk") resetKeys = true;
else cells ~= a;
if(cells.length + eCells.length/2 > 1 )
{
writefln("More than one cell specified, rendering disabled");
render=false;
}
initializeMemoryRegions();
config.initialize(resetKeys);
scope(exit) config.writeConfig();
if(cells.length == 0 && eCells.length == 0)
if(config.defaultCell.length)
cells ~= config.defaultCell;
if(cells.length == 1)
config.defaultCell = cells[0];
if(help || (cells.length == 0 && eCells.length == 0))
{
writefln("Syntax: %s [options] cell-name [cell-name]", args[0]);
writefln(" Options:");
writefln(" -n Only load, do not render");
writefln(" -ex,y Load exterior cell (x,y)");
writefln(" -rk Reset key bindings to default");
writefln(" -h Show this help");
writefln("");
writefln("Specifying more than one cell implies -n");
return;
}
initializeSound();
resources.initResources();
// Files to load
/*
const char[][] files = ["Morrowind.esm",
"Tribunal.esm",
"Bloodmoon.esm"];
/*/
const char[][] files = ["Morrowind.esm"];
//*/
// Add the path to the file name. The path is read from the config
// file.
char[][] esmFiles;
esmFiles.length = files.length;
foreach(int i, char[] ef; files)
// TODO: Quick hack, use FileFinder instead.
esmFiles[i] = config.esmDir ~ ef;
// Load all data from the ESM files
loadTESFiles(esmFiles);
CellData cd = cellList.get();
foreach(char[] cName; cells)
{
// Release the last cell data
cellList.release(cd);
// Get a cell data holder and load an interior cell
cd = cellList.get();
try cd.loadIntCell(cName);
catch(Exception e)
{
writefln(e);
writefln("\nUnable to load cell '%s'. Aborting", cName);
return;
}
}
for(int i; i<eCells.length; i+=2)
{
int x = eCells[i];
int y = eCells[i+1];
// Release the last cell data
cellList.release(cd);
// Get a cell data holder and load an interior cell
cd = cellList.get();
writefln("Will load %s,%s", x, y);
try cd.loadExtCell(x,y);
catch(Exception e)
{
writefln(e);
writefln("\nUnable to load cell (%s,%s). Aborting", x,y);
return;
}
}
// Simple safety hack
NodePtr putObject(MeshIndex m, Placement *pos, float scale)
{
if(m == null)
writefln("WARNING: CANNOT PUT NULL OBJECT");
else if(m.isEmpty)
writefln("WARNING: CANNOT INSERT EMPTY MESH '%s'", m.getName);
else return placeObject(m, pos, scale);
return null;
}
/*
Sound *l = cast(Sound*) sounds.lookup("Fire 40");
if(l)
{
writefln("id: %s", l.id);
writefln("volume: ", l.data.volume);
writefln("range: %s-%s", l.data.minRange, l.data.maxRange);
writefln("sound file name: ", l.sound.getName);
writefln("playing... press enter to quit");
SoundInstance inst = SoundList.getInstance(l, true);
inst.setPos(0,0,0);
inst.setPlayerPos(0, 0, 0);
inst.play();
din.readLine();
inst.kill;
render = false;
}
*/
if(render)
{
// Warm up OGRE
setupOgre();
// Clean up ogre when we're finished.
scope(exit) cleanupOgre();
if(cd.inCell)
{
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
cd.ambi.fog, cd.ambi.fogDensity);
// Not all interior cells have water
if(cd.inCell.flags & CellFlags.HasWater)
cpp_createWater(cd.water);
}
else
{
Color c;
c.red = 180;
c.green = 180;
c.blue = 180;
setAmbient(c, c, c, 0);
// Put in the water
cpp_createWater(cd.water);
// Create an ugly sky
cpp_makeSky();
}
// TODO: We get some strange lamp-shaped activators in some scenes,
// eg in Abebaal. These are sound activators (using scripts), but
// they still appear. Find out if they have some special flags
// somewhere (eg. only-show-in-editor), or if we just have to filter
// them by the "Sound_*" name. Deal with it later.
// Insert the meshes of statics into the scene
foreach(ref LiveStatic ls; cd.statics)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Inventory lights
foreach(ref LiveLight ls; cd.lights)
{
NodePtr n = putObject(ls.m.model, &ls.base.pos, ls.base.scale);
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
Sound *s = ls.m.sound;
if(s)
{
writefln("Dynamic light %s has sound %s", ls.m.id, s.id);
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
ls.loopSound.setPos(ls.base.pos.position[0],
ls.base.pos.position[1],
ls.base.pos.position[2]);
}
}
// Static lights
foreach(ref LiveLight ls; cd.statLights)
{
NodePtr n = putObject(ls.m.model, &ls.base.pos, ls.base.scale);
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
Sound *s = ls.m.sound;
if(s)
{
writefln("Static light %s has sound %s", ls.m.id, s.id);
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
ls.loopSound.setPos(ls.base.pos.position[0],
ls.base.pos.position[1],
ls.base.pos.position[2]);
}
}
// Misc items
foreach(ref LiveMisc ls; cd.miscItems)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
/*
// NPCs (these are complicated, usually do not have normal meshes)
foreach(ref LiveNPC ls; cd.npcs)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
*/
// Containers
foreach(ref LiveContainer ls; cd.containers)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Doors
foreach(ref LiveDoor ls; cd.doors)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Activators
foreach(ref LiveActivator ls; cd.activators)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Potions
foreach(ref LivePotion ls; cd.potions)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Apparatus
foreach(ref LiveApparatus ls; cd.appas)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Ingredients
foreach(ref LiveIngredient ls; cd.ingredients)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Armors
foreach(ref LiveArmor ls; cd.armors)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Weapons
foreach(ref LiveWeapon ls; cd.weapons)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Books
foreach(ref LiveBook ls; cd.books)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Clothes
foreach(ref LiveClothing ls; cd.clothes)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Tools
foreach(ref LiveTool ls; cd.tools)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Creatures (these often look like shit
foreach(ref LiveCreature ls; cd.creatures)
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
// Initialize the internal input and event manager. The
// lower-level input system (OIS) is initialized by the
// setupOgre() call further up.
initializeInput();
// Start swangin'
jukebox.enableMusic();
// Run it until the user tells us to quit
startRendering();
}
else debug(verbose) writefln("Skipping rendering");
debug(verbose)
{
writefln();
writefln("%d statics", cd.statics.length);
writefln("%d misc items", cd.miscItems.length);
writefln("%d inventory lights", cd.lights.length);
writefln("%d static lights", cd.statLights.length);
writefln("%d NPCs", cd.npcs.length);
writefln("%d containers", cd.containers.length);
writefln("%d doors", cd.doors.length);
writefln("%d activators", cd.activators.length);
writefln("%d potions", cd.potions.length);
writefln("%d apparatuses", cd.appas.length);
writefln("%d ingredients", cd.ingredients.length);
writefln("%d armors", cd.armors.length);
writefln("%d weapons", cd.weapons.length);
writefln("%d books", cd.books.length);
writefln("%d tools", cd.tools.length);
writefln("%d clothes", cd.clothes.length);
writefln("%d creatures", cd.creatures.length);
writefln();
}
/*
writefln("Statics:");
foreach(ref s; cd.statics)
{
writefln("%s: %s", s.m.id, s.m.model.getName);
}
*/
// This isn't necessary but it's here for testing purposes.
cellList.release(cd);
// Write some memory statistics
poolSize();
writefln(esmRegion);
}

@ -0,0 +1,44 @@
; Don't write your own comments in this file, they
; will disappear when the file is rewritten.
[General]
ESM Directory=data/
BSA Directory=data/
SFX Directory=data/Sound/
Explore Music Directory=data/Music/Explore/
Battle Music Directory=data/Music/Battle/
Screenshots=11
Default Cell=Sud
[Controls]
Mouse Sensitivity X=0.2
Mouse Sensitivity Y=0.2
Flip Mouse Y Axis=no
[Bindings]
; Key bindings. The strings must match exactly.
Move Left=a,left
Move Right=d,right
Turn Left=
Turn Right=
Move Forward=w,up
Move Backward=s,down
Move Up=left_shift
Move Down=left_ctrl
Increase Main Volume=numpad_plus
Decrease Main Volume=numpad_minus
Increase Music Volume=2
Decrease Music Volume=1
Increase SFX Volume=4
Decrease SFX Volume=3
Mute Sound=m
Toggle Battle Music=space
OGRE Test Action=g
Pause=pause,p
Screen Shot=print_screen
Quick Exit=q,escape
[Sound]
Main Volume=0.7
Music Volume=0.4
SFX Volume=0.6
Enable Music=yes

@ -0,0 +1,211 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (base.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// Basic file structure
module nif.base;
import nif.nif;
import nif.niffile;
import nif.misc;
import monster.util.string;
//import nif.controller;
//import nif.node;
import nif.record;
debug(verbose) public import std.stdio;
void parseFile()
{
with(nifFile)
{ // Read header
int ver; // Hex-encoded version
char[40] head; // Header string
// Read and check header string
getString(head);
if(!begins(head,"NetImmerse File Format"))
fail("Not a NetImmerse file");
debug(verbose) writefln("Header: \"%s\"", head[0..39]);
debug(strict)
if(head[0..39] != "NetImmerse File Format, Version 4.0.0.2" || head[39] != 0x0a)
fail("Unrecognized header string, most likely an unsupported NIF version");
// Get binary coded version
ver = getInt;
debug(verbose)
{
writef("BCD version tag: ");
for(ubyte *b = (cast(ubyte*)&ver)+3; b > (cast(ubyte*)&ver); b--)
writef(*b, ".");
writefln(cast(ubyte)ver);
}
if(ver != 0x04_00_00_02)
fail("Don't know how to process this version");
// The number of records in the file
nifMesh.records = nifRegion.allocateT!(Record)(getInt);
debug(verbose) writefln("Number of records: ", nifMesh.records.length);
// The format for 10.0.1.0 seems to be a bit different. After the
// header, it contains the number of records, r (int), just like
// 4.0.0.2, but following that it contains a short x, followed by
// x strings. Then again by r shorts, one for each record, giving
// which of the above strings to use to identify the record. After
// this follows two ints (zero?) and then the record data.
// Besides giving me more work to do, this structure suggests some
// obvious code optimizations we can now apply to the reading
// structure. Since we now know in advance how many records there
// are of each type, we could allocate the structures in bulk. D
// doesn't allow this for classes directly, but we can make a
// custom allocator and whatnot. I doubt we save a lot of time
// with it, though. I'll save this for later.
// As for the record data itself, I do not know what has
// changed. There are some new records. I might as well have
// separate reading functions for the entire main structure.
// EDIT 14. feb 06: Since we are concentrating on Morrowind, let
// us drop other versions for now. It would be nice to support
// other versions (or even other mesh formats), but the priority
// of this is very low at the moment.
}
endFor:
foreach(int i, ref Record o; nifMesh.records)
{
// Get type string, and update the NIFFiles error message to
// match it.
char[] recName = nifFile.getString;
nifFile.setRec(i,recName);
debug(verbose)
{
writefln("\n%d: %s (%x)", i, recName, nifFile.position);
}
switch(recName)
{
// These are the record types we know how to read
// Time controllers
case "NiVisController": o = new NiVisController; break;
case "NiGeomMorpherController": o = new NiGeomMorpherController; break;
case "NiKeyframeController": o = new NiKeyframeController; break;
case "NiAlphaController": o = new NiAlphaController; break;
case "NiUVController": o = new NiUVController; break;
case "NiPathController": o = new NiPathController; break;
case "NiMaterialColorController": o = new NiMaterialColorController; break;
case "NiBSPArrayController": o = new NiBSPArrayController; break;
case "NiParticleSystemController": o = new NiParticleSystemController; break;
// NiNodes
case "RootCollisionNode":
case "NiBSParticleNode":
case "NiBSAnimationNode":
case "NiBillboardNode":
case "AvoidNode":
case "NiNode": o = new NiNode; break;
// Other nodes
case "NiTriShape": o = new NiTriShape; break;
case "NiRotatingParticles": o = new NiRotatingParticles; break;
case "NiAutoNormalParticles": o = new NiAutoNormalParticles; break;
case "NiCamera": o = new NiCamera; break;
// Effects
case "NiAmbientLight":
case "NiDirectionalLight": o = new Light; break;
case "NiTextureEffect": o = new NiTextureEffect; break;
// Properties
case "NiTexturingProperty": o = new NiTexturingProperty; break;
case "NiMaterialProperty": o = new NiMaterialProperty; break;
case "NiZBufferProperty": o = new NiZBufferProperty; break;
case "NiAlphaProperty": o = new NiAlphaProperty; break;
case "NiVertexColorProperty": o = new NiVertexColorProperty; break;
case "NiShadeProperty": o = new NiShadeProperty; break;
case "NiDitherProperty": o = new NiDitherProperty; break;
case "NiWireframeProperty": o = new NiWireframeProperty; break;
case "NiSpecularProperty": o = new NiSpecularProperty; break;
// Extra Data
case "NiVertWeightsExtraData": o = new NiVertWeightsExtraData; break;
case "NiTextKeyExtraData": o = new NiTextKeyExtraData; break;
case "NiStringExtraData": o = new NiStringExtraData; break;
case "NiGravity": o = new NiGravity; break;
case "NiPlanarCollider": o = new NiPlanarCollider; break;
case "NiParticleGrowFade": o = new NiParticleGrowFade; break;
case "NiParticleColorModifier": o = new NiParticleColorModifier; break;
case "NiParticleRotation": o = new NiParticleRotation; break;
// Data
case "NiFloatData": o = new NiFloatData; break;
case "NiTriShapeData": o = new NiTriShapeData; break;
case "NiVisData": o = new NiVisData; break;
case "NiColorData": o = new NiColorData; break;
case "NiPixelData": o = new NiPixelData; break;
case "NiMorphData": o = new NiMorphData; break;
case "NiKeyframeData": o = new NiKeyframeData; break;
case "NiSkinData": o = new NiSkinData; break;
case "NiUVData": o = new NiUVData; break;
case "NiPosData": o = new NiPosData; break;
case "NiRotatingParticlesData": o = new NiRotatingParticlesData; break;
case "NiAutoNormalParticlesData": o = new NiAutoNormalParticlesData; break;
// Other
case "NiSequenceStreamHelper": o = new NiSequenceStreamHelper; break;
case "NiSourceTexture": o = new NiSourceTexture; break;
case "NiSkinInstance": o = new NiSkinInstance; break;
default:
nifFile.fail("Unknown record type " ~ recName);
}
if(o !is null)
{
//nifFile.setRec(i,recName ~ " (" ~ o.toString ~ ")");
o.read();
}
}
// Clear error message.
nifFile.setRec(-1,"");
// The 'footer', which it seems isn't always constant after all. I
// have no clue what it is for though.
int roots = nifFile.getInt;
for(int i; i<roots;i++)
nifFile.getInt();
debug(verbose) if(nifFile.eof()) writefln("End of file");
// A lot of files don't end where they should, so we just have to
// accept it.
//debug(check) if(!nifFile.eof()) nifFile.warn("End of file not reached");
}

@ -0,0 +1,90 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (controlled.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.controlled;
import nif.record;
import nif.extra;
// 'Controlled' is something that has a controller
abstract class Controlled : Extra
{
Controller controller;
override:
void read()
{
super.read();
debug(verbose) writef("Controller ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
controller = lookup!(Controller)(list);
}
}
// Record with name and controller/extra data link
abstract class Named : Controlled
{
char[] name;
//Extra extra;
//Controller controller;
override:
char[] toString()
{
if(name.length)
return super.toString() ~ " '" ~ name ~ "'";
else
return super.toString();
}
void read()
{
//super.read(f);
name = nifFile.getString();
debug(verbose) writefln("Name: %s", name);
/*
debug(verbose) writef("Extra ");
getIndex(f);
debug(verbose) writef("Controller ");
getIndex(f);
*/
super.read();
}
/*
void sortOut(Record[] list)
{
super.sortOut(list);
extra = lookup!(Extra)(list);
controller = lookup!(Controller)(list);
}
*/
}
class NiSequenceStreamHelper : Named {}

@ -0,0 +1,317 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (controller.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.controller;
import nif.record;
// Time controller
abstract class Controller : Record
{
Controller next;
ushort flags;
float frequency, phase; // Don't know what these do.
float timeStart, timeStop;
Controlled target;
override:
void read()
{with(nifFile){
super.read();
debug(verbose) writef("Next controller ");
getIndex();
flags = getUshort;
frequency = getFloatIs(1);
phase = getFloat;
timeStart = getFloat;
timeStop = getFloat;
debug(verbose) writef("Target ");
getIndex();
debug(verbose)
{
writefln("Flags: %x", flags);
writefln("Frequency: ", frequency);
writefln("Phase: ", phase);
writefln("Start time: ", timeStart);
writefln("Stop time: ", timeStop);
}
}}
void sortOut(Record[] list)
{
super.sortOut(list);
next = lookup!(Controller)(list);
target = lookup!(Controlled)(list);
}
}
alias NiBSPArrayController NiParticleSystemController;
class NiBSPArrayController : Controller
{
override:
void read()
{with(nifFile){
super.read();
// At the moment, just skip it all
//*
// 23 floats = 92 bytes
// 3 ints = 12 bytes
// 3 shorts = 6 bytes
// 1 byte = 1 byte
// Total: 111 bytes
seekCur(111);
short s = wgetShort;
seekCur(15 + s*40);
/*/
float speed, speedRandom, angle, angleRandom;
speed = wgetFloat;
speedRandom = wgetFloat;
angle = wgetFloat;
angleRandom = wgetFloat;
wgetFloat(); // Unknown
wgetFloat(); // Sometimes pi
for(int i; i<10; i++)
wgetFloat();
wgetByte();
wgetFloat();
wgetFloat();
wgetFloat();
wgetShort();
wgetVector();
debug(verbose) writef("Emitter: ");
wgetInt();
wgetShort();
wgetFloat();
wgetInt();
wgetInt();
wgetShort();
debug(verbose) writefln("begin particle group");
short s = wgetShort;
wgetShort();
for(short i; i<s; i++)
{
Matrix m;
getMatrix(m);
debug(verbose) writefln("Matrix: ", m.toString);
wgetShort();
wgetShort();
}
debug(verbose) writefln("end particle group");
debug(verbose) writefln("Links:");
wgetInt();
wgetInt();
wgetInt();
wgetByte();
//*/
}}
}
class NiMaterialColorController : Controller
{
NiPosData data;
override:
void read()
{
super.read();
debug(verbose) writef("PosData ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiPosData)(list);
}
}
class NiPathController : Controller
{
NiPosData posData;
NiFloatData floatData;
override:
void read()
{with(nifFile){
super.read();
wgetIntIs(1);
wgetFloat();
wgetFloat();
wgetShortIs(0,1);
debug(verbose) writef("Pos Data ");
getIndex();
debug(verbose) writef("Float Data ");
getIndex();
}}
void sortOut(Record[] list)
{
super.sortOut(list);
posData = lookup!(NiPosData)(list);
floatData = lookup!(NiFloatData)(list);
}
}
class NiUVController : Controller
{
NiUVData data;
override:
void read()
{
super.read();
short s = nifFile.getShortIs(0);
debug(verbose) writef("UV Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiUVData)(list);
}
}
//*
// If there was more than four of these, I would template it.
class NiKeyframeController : Controller
{
NiKeyframeData data;
override:
void read()
{
super.read();
debug(check)
if(flags & 0xf0) nifFile.warn("Unknown flags");
debug(verbose) writef("KeyframeData ");
getIndex();
// If this index is empty, timeStart/timeStop are set to +/-
// float.max.
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiKeyframeData)(list);
}
}
class NiAlphaController : Controller
{
NiFloatData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Float Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiFloatData)(list);
}
}
class NiGeomMorpherController : Controller
{
NiMorphData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Morph Data ");
getIndex();
byte b = nifFile.wgetByteIs(0);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiMorphData)(list);
}
}
class NiVisController : Controller
{
NiVisData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Vis Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiVisData)(list);
}
}

@ -0,0 +1,806 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (data.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.data;
import nif.record;
class NiSourceTexture : Named
{
byte external;
union
{
char[] filename;
NiPixelData data;
}
/* Pixel layout
0 - Palettised
1 - High color 16
2 - True color 32
3 - Compressed
4 - Bumpmap
5 - Default */
int pixel;
/* Mipmap format
0 - no
1 - yes
2 - default */
int mipmap;
/* Alpha
0 - none
1 - binary
2 - smooth
3 - default (use material alpha, or multiply material with texture if present)
*/
int alpha;
override:
void sortOut(Record[] list)
{
super.sortOut(list);
if(!external)
data = lookup!(NiPixelData)(list);
}
void read()
{
super.read();
external = nifFile.getByteIs(0,1);
debug(verbose) writefln("Use external file: ", external);
if(external)
{
filename = nifFile.getString;
debug(verbose) writefln("Filename: ", filename);
}
else
{
nifFile.wgetByteIs(1);
debug(verbose) writef("Pixel Data ");
getIndex();
}
pixel = nifFile.getInt;
mipmap = nifFile.getInt;
alpha = nifFile.getIntIs(3);
debug(verbose)
{
writefln("Pixel layout: ", pixel);
writefln("Mipmap: ", mipmap);
writefln("Alpha: ", alpha);
}
nifFile.wgetByteIs(1);
}
}
// Common ancestor for several classes
class ShapeData : Record
{
float[] vertices, normals, colors, uvlist;
Vector center;
float radius;
override:
void read()
{
super.read();
short verts = nifFile.getShort;
debug(verbose) writefln("Vertices: ", verts);
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading vertices");
if(verts == 0) nifFile.warn("Empty vertex array");
vertices = nifFile.getArraySize!(float)(verts*3);
}
else nifFile.warn("No vertices found");
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading normals");
normals = nifFile.getArraySize!(float)(verts*3);
}
center = nifFile.getVector();
radius = nifFile.getFloat();
debug(verbose)
{
writefln("Center: ", center.toString);
writefln("Radius: ", radius);
}
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading vertex colors");
colors = nifFile.getArraySize!(float)(verts*4);
}
short uvs = nifFile.getShort;
debug(verbose) writefln("Number of UV sets: ", uvs);
if(nifFile.getInt != 0)
{
if(uvs == 0) nifFile.warn("Empty UV list");
uvlist = nifFile.getArraySize!(float)(uvs*verts*2);
}
else if(uvs != 0) nifFile.warn("UV list was unexpectedly missing");
}
}
class NiAutoNormalParticlesData : ShapeData
{
ushort activeCount;
override:
void read()
{
super.read();
nifFile.assertWarn(uvlist.length == 0, "UV coordinates are not expected in this record");
debug(verbose) writef("Active vertices: ");
// Number of active vertices (always matches count?)
activeCount = nifFile.wgetUshortIs(vertices.length/3);
nifFile.wgetFloat(); // Active radius (?)
nifFile.wgetShort(); // Number of valid entries in the following arrays
//writefln("%x", nifFile.position);
if(nifFile.wgetInt == 0) nifFile.warn("Particle size list is missing");
else
for(int i; i<activeCount; i++)
{
float fl = nifFile.getFloat; // Particle sizes
debug(veryverbose)
writefln(" %d: ", i, fl);
}
}
}
class NiRotatingParticlesData : NiAutoNormalParticlesData
{
override:
void read()
{
super.read();
nifFile.assertWarn(normals.length == 0, "Normals are not expected in this record");
Vector4 v;
if(nifFile.wgetInt == 0) nifFile.warn("Rotation data is not present");
else
// This is either activeCount or vertices.length/3, until I
// find a file in which the two differs (if one exists), we
// can't know which is correct.
for(int i; i<activeCount; i++)
{
v = nifFile.getVector4; // Quaternions
debug(veryverbose)
writefln(" %d: ", i, v.toString);
}
//writefln("%x", nifFile.position);
}
}
class NiPosData : Record
{
override:
void read()
{
super.read();
int count = nifFile.getInt;
debug(verbose) writefln("Count: ", count);
int type = nifFile.wgetIntIs(1,2);
for(int i; i<count; i++)
{
// TODO: Make a generalized struct of this? Seems to be the
// same as in NiKeyframeData.
float time = nifFile.getFloat;
debug(verbose) writef("Time %f: ", time);
Vector v;
if(type == 1)
{
v = nifFile.getVector;
debug(verbose) writef(v.toString);
}
else if(type == 2)
{
v = nifFile.getVector;
debug(verbose) writef(v.toString, " ");
v = nifFile.getVector;
debug(verbose) writef(v.toString, " ");
v = nifFile.getVector;
debug(verbose) writef(v.toString);
}
else nifFile.fail("Unknown type");
debug(verbose) writefln();
}
}
}
class NiUVData : Record
{
override:
void read()
{
super.read();
debug(verbose) writef("Count: ");
int count = nifFile.wgetInt();
// TODO: This is claimed to be a "float animation key", which is
// also used in FloatData and KeyframeData
if(count != 5 && count != 3 && count != 2 && count != 0)
nifFile.warn("Untested count");
if(count)
{
nifFile.wgetIntIs(2);
debug(verbose) writefln("%d entries in list 1:", count);
for(int i; i<count; i++)
{
float time = nifFile.getFloat;
Vector v = nifFile.getVector;
debug(verbose)
writefln(" Time %f: ", time, v.toString);
}
}
count = nifFile.getInt;
if(count)
{
nifFile.wgetIntIs(2);
debug(verbose) writefln("%d entries in list 2:", count);
for(int i; i<count; i++)
{
float time = nifFile.getFloat;
Vector v = nifFile.getVector;
debug(verbose)
writefln(" Time %f: ", time, v.toString);
}
}
nifFile.getIntIs(0);
nifFile.getIntIs(0);
}
}
class NiPixelData : Record
{
uint rmask, gmask, bmask, amask;
int bpp;
int mips;
override:
void read()
{
super.read();
nifFile.wgetIntIs(0,1);
rmask = nifFile.getUintIs(0xff);
gmask = nifFile.getUintIs(0xff00);
bmask = nifFile.getUintIs(0xff0000);
amask = nifFile.getUintIs(0xff000000,0);
bpp = nifFile.getIntIs(24,32);
nifFile.wgetByte();
nifFile.wgetByteIs(8);
nifFile.wgetUbyteIs(130);
nifFile.wgetByteIs(0,32);
nifFile.wgetByteIs(0);
nifFile.wgetByteIs(65);
nifFile.wgetByteIs(0,12);
nifFile.wgetByteIs(0);
nifFile.wgetIntIs(-1);
mips = nifFile.getInt;
int bytes = nifFile.getIntIs(bpp>>3);
debug(verbose)
{
writefln("Red mask %8xh", rmask);
writefln("Green mask %8xh", gmask);
writefln("Blue mask %8xh", bmask);
writefln("Alpha mask %8xh", amask);
writefln("Bits per pixel: ", bpp);
writefln("Number of mipmaps: ", mips);
writefln("Bytes per pixel: ", bytes);
}
for(int i; i<mips; i++)
{
int x = nifFile.getInt;
int y = nifFile.getInt;
uint offset = nifFile.getUint;
debug(verbose)
{
writefln("Mipmap %d:", i);
writefln(" Size: %dx%d", x, y);
writefln(" Offset in data field: %xh", offset);
}
}
uint dataSize = nifFile.getUint;
nifFile.seekCur(dataSize);
}
}
class NiColorData : Record
{
struct ColorData
{
float time, R, G, B, A;
}
static assert(ColorData.sizeof == 20);
ColorData colors[];
override:
void read()
{
super.read();
int count = nifFile.getInt;
nifFile.getIntIs(1);
colors = nifFile.getArraySize!(ColorData)(count);
}
}
class NiVisData : Record
{
align(1)
struct VisData
{
float time;
byte isSet;
static assert(VisData.sizeof == 5);
}
VisData vis[];
override:
void read()
{
super.read();
int count = nifFile.getInt;
vis = nifFile.getArraySize!(VisData)(count);
debug(check)
foreach(VisData v; vis)
if(v.isSet!=0 && v.isSet!=1) nifFile.warn("Unknown bool value");
}
}
class NiFloatData : Record
{
struct FloatData
{
float time;
float[3] alpha;
}
static assert(FloatData.sizeof == 16);
FloatData data[];
override:
void read()
{
super.read();
int cnt = nifFile.getInt;
nifFile.getIntIs(2);
data = nifFile.getArraySize!(FloatData)(cnt);
// This might be the "keyfloat" type, ie. a value and forward
// and backward tangents. Might be used for animating in the
// alpha space?
debug(verbose)
foreach(FloatData d; data)
writefln("Time: %f Alpha: %f, %f, %f", d.time,
d.alpha[0], d.alpha[1], d.alpha[2]);
}
}
class NiSkinData : Record
{
align(1)
struct Weight
{
ushort vertex;
float weight;
static assert(Weight.sizeof == 6);
}
Weight weights[][];
override:
void read()
{
super.read();
Matrix m;
Vector v;
Vector4 v4;
nifFile.getMatrix(m);
v = nifFile.getVector();
nifFile.getFloatIs(1);
debug(verbose)
{
writefln("Matrix:\n", m.toString);
writefln("Vector: ", v.toString);
writefln("Float: 1 (always)");
}
int count = nifFile.getInt;
debug(verbose) writefln("Bones: ", count);
nifFile.getIntIs(-1);
nifFile.fitArray(70,count);
weights = nifRegion.allocateT!(Weight[])(count);
foreach(int i, ref Weight[] wl; weights)
{
nifFile.getMatrix(m); // Rotation offset of the skin from this
// bone in bind position.
v = nifFile.getVector(); // Translation -ditto-
nifFile.getFloatIs(1); // Scale -ditto- ?
debug(verbose)
{
writefln("Bone #%d:", i);
writefln(" Rotation:\n", m.toString);
writefln(" Translation: ", v.toString);
writefln(" Scale: 1 (always)");
}
v4 = nifFile.getVector4;
// Number of vertex weights
ushort cnt2 = nifFile.getUshort;
debug(verbose)
{
writefln(" 4-Vector: ", v4.toString);
writefln(" Number of vertex weights: ", cnt2);
}
wl = nifFile.getArraySize!(Weight)(cnt2);
debug(veryverbose)
foreach(ref Weight w; wl)
writefln(" %d: ", w.vertex, w.weight);
}
}
}
class NiTriShapeData : ShapeData
{
short[] triangles;
short[][] matches;
override:
void read()
{
super.read();
ushort verts = vertices.length/3;
short tris = nifFile.getShort;
debug(verbose) writefln("Number of faces: ", tris);
if(tris)
{
int cnt = nifFile.getIntIs(tris*3);
triangles = nifFile.getArraySize!(short)(cnt);
}
short match = nifFile.getShort;
if(match)
{
nifFile.fitArray(match,2);
matches = nifRegion.allocateT!(short[])(match);
if(match != verts) nifFile.warn("Expected verts and match to be equal");
foreach(ref short[] s; matches)
{
short sh = nifFile.getShort;
if(sh>=verts || sh < 0)
nifFile.warn("Presumed vertex index was out of bounds");
s = nifFile.getArraySize!(short)(sh);
}
}
}
}
class NiMorphData : Record
{
//float[] vertexList;
override:
void read()
{
super.read();
int morphCount = nifFile.getInt;
int vertCount = nifFile.getInt;
debug(verbose)
{
writefln("Number of morphs: ", morphCount);
writefln("Vertices: ", vertCount);
}
nifFile.wgetByteIs(1);
//nifFile.getIntIs(0);
//nifFile.getIntIs(1);
//vertexList = nifFile.getArraySize!(float)(3*vertCount);
for(int i=0; i<morphCount; i++)
{
int magic = nifFile.getInt;
debug(veryverbose)
{
writefln("Getting morph batch %d", i);
writefln(" Number of 4-vectors ", magic);
}
nifFile.wgetIntIs(0,1,2); // Is always 1 for i=0, 0 or 2 otherwise
float[] l;
// Time, data, forward, backward tangents
if(magic)
l = nifFile.getArraySize!(float)(magic*4);
debug(veryverbose)
for(int j; j<magic; j++)
writefln(" Time %f : [", l[4*j], l[4*j+1],",", l[4*j+2],",", l[4*j+3],"]");
l = nifFile.getArraySize!(float)(vertCount*3);
debug(veryverbose)
for(int j; j<vertCount; j++)
writefln(" Vertex morph %d: ", j, " [%f, %f, %f]",
l[j*3], l[j*3+1], l[j*3+2]);
}
}
}
class NiKeyframeData : Record
{
// These should be moved out of this class, given their own reader
// functions, and used elsewhere. But it currently works, and I
// haven't really confirmed that these ARE used elsewhere, yet.
struct Rotation1
{
float time;
float[4] quaternion;
static assert(Rotation1.sizeof == 5*4);
}
struct Rotation3
{
float time;
float[4] quaternion;
float tension;
float bias;
float continuity;
static assert(Rotation3.sizeof == 8*4);
}
struct Rotation4
{
float time;
struct R
{
struct R1
{
float time;
float unknown;
static assert(R1.sizeof == 8);
}
struct R2
{
float time;
float unknown[3];
static assert(R2.sizeof == 16);
}
int type;
union
{
R1[] r1;
R2[] r2;
}
}
R r3[3];
}
struct Translation1
{
float time;
float[3] translation;
static assert(Translation1.sizeof==4*4);
}
struct Translation2
{
float time;
float[3] translation;
float[3] forward;
float[3] backward;
static assert(Translation2.sizeof==4*10);
}
struct Translation3
{
float time;
float[3] translation;
float tension;
float bias;
float continuity;
static assert(Translation3.sizeof==4*7);
}
struct Scale1
{
float time;
float scale;
static assert(Scale1.sizeof == 4*2);
}
struct Scale2
{
float time;
float scale;
float forward;
float backward;
static assert(Scale2.sizeof == 4*4);
}
struct Scale3
{
float time;
float scale;
float tension;
float bias;
float continuity;
static assert(Scale3.sizeof == 4*5);
}
// Data arrays. Only one of each type will be used. I used to have
// them in unions, but then I realized that was just asking for trouble.
Rotation1 rot1[];
Rotation3 rot3[];
Rotation4 rot4[];
Translation1 tra1[];
Translation2 tra2[];
Translation3 tra3[];
Scale1 sca1[];
Scale2 sca2[];
Scale3 sca3[];
int rotType, traType, scaleType;
override:
void read()
{
super.read();
int count;
count = nifFile.getInt;
debug(verbose) writefln("Number of rotations: ", count);
if(count)
{
rotType = nifFile.getInt;
if(rotType == 1)
rot1 = nifFile.getArraySize!(Rotation1)(count);
else if(rotType == 3)
rot3 = nifFile.getArraySize!(Rotation3)(count);
else if(rotType == 4)
{
if(count!=1) nifFile.warn("Rotation type 4, but count was not 1, it was "
~ .toString(count));
nifFile.fitArray(count,6*4); // Minimum size
rot4 = nifRegion.allocateT!(Rotation4)(count);
foreach(ref Rotation4 rr; rot4)
{
rr.time = nifFile.getFloat;
foreach(ref rr.R r; rr.r3)
{
int cnt = nifFile.getInt;
r.type = nifFile.getInt;
if(r.type == 1)
nifFile.getArraySize!(r.R1)(cnt);
else if(r.type == 2)
nifFile.getArraySize!(r.R2)(cnt);
else nifFile.fail("Unknown rotation subtype " ~ .toString(r.type));
}
}
}
else nifFile.fail("Don't know how to handle rotation type " ~ .toString(rotType));
} // End of rotations
count = nifFile.getInt;
debug(verbose) writefln("Number of translations: ", count);
if(count)
{
traType = nifFile.getInt;
if(traType == 1)
tra1 = nifFile.getArraySize!(Translation1)(count);
else if(traType == 2)
tra2 = nifFile.getArraySize!(Translation2)(count);
else if(traType == 3)
tra3 = nifFile.getArraySize!(Translation3)(count);
else nifFile.fail("Don't know how to handle translation type "
~ .toString(traType));
} // End of translations
count = nifFile.getInt;
debug(verbose) writefln("Number of scalings: ", count);
if(count)
{
int scaleType = nifFile.getInt;
if(scaleType == 1) sca1 = nifFile.getArraySize!(Scale1)(count);
else if(scaleType == 2) sca2 = nifFile.getArraySize!(Scale2)(count);
else if(scaleType == 3) sca3 = nifFile.getArraySize!(Scale3)(count);
else nifFile.fail("Don't know how to handle scaling type "
~ .toString(scaleType));
} // End of scalings
}
}

@ -0,0 +1,110 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (effect.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.effect;
import nif.record;
class Effect : Node {}
// Used for NiAmbientLight and NiDirectionalLight. Might also work for
// NiPointLight and NiSpotLight?
class Light : Effect
{
float dimmer;
Vector ambient, diffuse, specular;
override:
void read()
{
super.read();
debug(check)
if(flags != 2 && flags != 4) nifFile.warn("Unknown flags");
nifFile.getIntIs(1);
if(nifFile.getInt == 0) nifFile.warn("Light list might be missing");
dimmer = nifFile.getFloat;
ambient = nifFile.getVector;
diffuse = nifFile.getVector;
specular = nifFile.getVector;
debug(verbose)
{
writefln("Dimmer: ", dimmer);
writefln("Ambient: ", ambient.toString);
writefln("Diffuse: ", diffuse.toString);
writefln("Specular: ", specular.toString);
}
}
}
class NiTextureEffect : Effect
{
NiSourceTexture texture;
override:
void read()
{
super.read();
debug(check)
if(flags != 2 && flags != 4) nifFile.warn("Unknown flags");
int i = nifFile.wgetInt; // 0 or 1
if(i == 1) {if(nifFile.getInt == 0) nifFile.warn("List might be missing");}
else if(i != 0) nifFile.warn("Unknown value");
for(int j; j<3; j++)
{
nifFile.wgetFloatIs(1);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
}
nifFile.wgetIntIs(2);
nifFile.wgetIntIs(0,3);
nifFile.wgetIntIs(2);
nifFile.wgetIntIs(2);
debug(verbose) writef("Source Texture ");
getIndex();
nifFile.wgetByteIs(0);
nifFile.wgetFloatIs(1);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetShortIs(0);
nifFile.wgetShortIs(-75);
nifFile.wgetShortIs(0);
}
void sortOut(Record[] list)
{
super.sortOut(list);
texture = lookup!(NiSourceTexture)(list);
}
}

@ -0,0 +1,210 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (extra.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.extra;
import nif.record;
import nif.controlled;
abstract class Extra : Record
{
Extra extra;
override:
void read()
{
super.read();
debug(verbose) nifFile.writef("Extra Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
extra = lookup!(Extra)(list);
}
}
class NiVertWeightsExtraData : Extra
{
override:
void read()
{
super.read();
int i = nifFile.getInt;
short s = nifFile.getShort; // = number of vertices
if(s*4+2 != i)
nifFile.warn("Sizes don't add up");
debug(verbose)
{
writefln("Total bytes in record: ", i);
writefln("Number of vertex weights: ", s);
}
for(int j; j<s; j++)
{
float ff = nifFile.getFloat;
debug(verbose) writefln("Float: ", ff);
}
}
}
class NiTextKeyExtraData : Extra
{
struct Key
{
float time;
char[] string;
}
Key[] keys;
override void read()
{
super.read();
nifFile.getIntIs(0);
int keynum = nifFile.getInt;
nifFile.fitArray(keynum,8);
keys = nifRegion.allocateT!(Key)(keynum);
foreach(int i, ref Key k; keys)
{
k.time = nifFile.getFloat;
k.string = nifFile.getString;
debug(verbose)
writefln(" %d: %s @ %f ", i, k.string.clean().chomp(), k.time);
}
}
}
class NiStringExtraData : Extra
{
char[] string;
override void read()
{
super.read();
int size = nifFile.getInt;
string = nifFile.getString;
if(size != string.length + 4)
nifFile.warn("String size was incorrect.");
debug(verbose)
{
// "NCO" means 'no collision', I think
writefln("String: %s", string.clean());
}
}
}
class NiParticleGrowFade : Controlled
{
//float f1, f2;
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}
class NiParticleColorModifier : Controlled
{
NiColorData data;
override:
void read()
{
super.read();
debug(verbose) writef("Color Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookup!(NiColorData)(list);
}
}
class NiGravity : Controlled
{
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetInt();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}
class NiPlanarCollider : Controlled
{
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetVector();
nifFile.wgetVector();
nifFile.wgetVector();
nifFile.wgetVector();
}
}
class NiParticleRotation : Controlled
{
override:
void read()
{
super.read();
byte b = nifFile.wgetByteIs(0,1);
nifFile.wgetFloatIs(1);
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}

@ -0,0 +1,156 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (misc.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// This doesn't have to be part of the nif package at all.
module nif.misc;
import std.utf;
import std.string;
import monster.util.string;
// Find an alternative to this
char[] clean(char[] s)
{
try{validate(s);}
catch(UtfException e)
{
return "(invalid utf-string)";
}
return s;
}
unittest
{
assert(clean("abc æøå") == "abc æøå");
}
struct Vector
{
float array[3];
void set(float x, float y, float z)
{
array[0] = x;
array[1] = y;
array[2] = z;
}
char[] toString()
{
return format(array);
//return format("[", array[0], ",", array[1], ",", array[2], "]");
}
int opEquals(ref Vector v)
{
return v.array == array;
}
static assert(Vector.sizeof == 4*3);
}
unittest
{
Vector a, b;
a.set(1,2,3);
assert(a!=b);
b = a;
assert(a==b);
}
struct Vector4
{
float array[4];
void set(float x, float y, float z, float a)
{
array[0] = x;
array[1] = y;
array[2] = z;
array[3] = a;
}
char[] toString()
{
return format(array);
//return format("[", array[0], ",", array[1], ",", array[2], ",", array[3], "]");
}
int opEquals(ref Vector4 v)
{
return v.array == array;
}
static assert(Vector4.sizeof == 4*4);
}
unittest
{
Vector4 a, b;
a.set(1,2,3,5);
assert(a!=b);
b = a;
assert(a==b);
}
align(1)
struct Matrix
{
union
{
Vector v[3];
float[9] array;
}
char[] toString()
{
char[] res;
res ~= " Right: " ~ v[0].toString;
res ~= "\n Up: " ~ v[1].toString;
res ~= "\n Front: " ~ v[2].toString;
return res;
}
static assert(Matrix.sizeof == 3*3*4);
}
align(1)
struct Transformation
{
Vector pos;
Matrix rotation;
float scale;
Vector velocity;
char[] toString()
{
char[] res;
res ~= " Translation: " ~ pos.toString();
res ~= "\n" ~ rotation.toString();
res ~= "\n Scale: " ~ format(scale);
res ~= "\n Velocity: " ~ velocity.toString();
return res;
}
static assert(Transformation.sizeof == 5*Vector.sizeof + 4);
}

@ -0,0 +1,106 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (nif.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.nif;
import nif.base;
import nif.niffile;
import nif.record;
import core.memory;
/* The NIFMesh struct will open, read and close a .nif file. Unlike
* NIFFile (which it uses), it stores all the data from the NIF.
*/
NIFMesh nifMesh;
struct NIFMesh
{
public:
Record records[];
// Read from a file
void open(char[] file)
{
nifFile.open(file);
loadData(); // Load the data
}
// Read from a memory slice. The optitional file name is used for
// error messages only.
void open(void[] s, char[] name = "")
{
nifFile.open(s, name);
loadData(); // Load the data
}
// Clear all loaded data
void clear()
{
records = null;
// Clear the region manager
nifRegion.freeAll();
}
/*
void fail(char[] msg)
{
throw new NIFMeshException(msg);
}
*/
private void loadData()
{
// Remove any previously loaded data
clear();
// The actual parsing is done somewhere else.
nif.base.parseFile();
// Close the file
nifFile.close();
// Resolve inter-record references into object pointers.
foreach(int i, Record o; records)
if(o !is null)
{
nifFile.setRec(i, o);
o.sortOut(records);
}
// Let the records do consistency checks on their data.
foreach(int i, Record o; records)
if(o !is null)
{
nifFile.setRec(i, o);
o.check();
}
// Internal state check, used for debugging
debug(statecheck)
foreach(Record o; records)
if(o !is null) o.finalCheck();
}
}

@ -0,0 +1,485 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (niffile.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.niffile;
public import std.stream;
public import util.regions;
import std.string;
import nif.misc;
public import core.memory;
// Exception class thrown by most exceptions originating within the
// nif package
class NIFFileException : Exception
{
this(char[] msg) {super("NIFFile Exception: " ~ msg);}
}
/* The NIFFile struct is used for the task of reading from NIF
files. It provides specialized methods for handling common types,
records, etc., and also provides mechanisms for output and error
handling. It does not store any (non-transient) NIF data.
*/
NIFFile nifFile;
struct NIFFile
{
private:
Stream f; // Input stream
BufferedFile bf; // Used and reused for direct file input.
TArrayStream!(ubyte[]) ss; // For stream reading
// These are used for warnings and error messages only
char[] filename;
int recNum; // Record number
char[] recName; // Record name
Object recObj; // Record object
public:
/* ------------------------------------------------
* Object initialization and file management
* ------------------------------------------------
*/
// Open a file from the file system
void open(char[] file)
{
close();
// Create a BufferedFile, and reuse it later.
if(bf is null)
bf = new BufferedFile();
bf.open(file);
f = bf;
filename = file;
}
// Open a memory slice, the name is used for error messages.
void open(void[] data, char[] name)
{
close();
// Create a TArrayStream, and reuse it
if(ss is null)
{
ss = new TArrayStream!(ubyte[])(cast(ubyte[])data);
ss.writeable = false; // Read-only
}
else
{
// TArrayStream lacks the open() method. Instead, all the
// members are public.
ss.buf = cast(ubyte[])data;
ss.len = ss.buf.length;
ss.cur = 0;
}
f = ss;
filename = name;
}
// Close the file handle
void close()
{
// Close the buffered file
if(f !is null && f is bf) f.close();
f = null;
// Zero out variables
filename = null;
recNum = 0;
recName = null;
recObj = null;
}
long size()
in
{
if(f is null) fail("Cannot get size, file is not open");
}
body
{
return f.size();
}
bool eof() { return f.eof(); }
void seekCur(ulong skip)
in
{
if(f is null) fail("Cannot seek, file is not open");
}
body
{
f.seekCur(skip);
}
ulong position()
{
return f.position();
}
/* ------------------------------------------------
* Error reporting
* ------------------------------------------------
*/
// Used to format error messages
private char[] makeError(char[] str)
{
if(recNum > 0)
{
if(recName == "" && recObj !is null) recName = recObj.toString;
str = format("%s\n Record %d: %s", str, recNum-1, recName);
}
if(filename != "") str = format("%s\n File: %s", str, filename);
if(f !is null)
if(f.eof()) str = format("%s\n At end of file", str);
else str = format("%s\n Offset 0x%x", str, f.position);
return str ~ "\n";
}
void fail(char[] msg)
{
throw new NIFFileException(makeError(msg));
}
debug(warnstd) import std.stdio;
void warn(char[] msg)
{
debug(strict) fail("(fail on warning) " ~ msg);
else
{
debug(warnstd) writefln("WARNING: ", makeError(msg));
debug(warnlog) log.writefln("WARNING: ", makeError(msg));
}
}
void assertWarn(bool condition, char[] str)
{ if(!condition) warn(str); }
/*
void assertFail(bool condition, char[] str)
{ if(!condition) fail(str); }
*/
// Used for error message handling, hackish.
void setRec(int i, char[] n)
{
recNum = i+1;
recName = n;
recObj = null;
}
// This variant takes an object instead of a name. That way we only
// need to call toString when the name is needed (which it mostly
// isn't.)
void setRec(int i, Object o)
{
recNum = i+1;
recObj = o;
recName = null;
}
/* ------------------------------------------------
* Reader method templates (skip to next section
* for usable methods)
* ------------------------------------------------
*/
// Read a variable of a given type T
T getType(T)()
{
T t;
f.read(t);
return t;
}
// Read a variable and compare it to what we want it to be
template getTypeIs(T)
{
T getTypeIs(T[] pt ...)
{
T t = getType!(T)();
debug(check)
{
bool match;
foreach(T a; pt)
if(a == t) {match = true; break;}
if(!match)
{
char[] errMsg = format(typeid(T),
" mismatch: got %s, expected ", t);
if(pt.length > 1) errMsg ~= "one of: ";
foreach(T a; pt)
errMsg ~= format("%s ", a);
warn(errMsg);
}
}
return t;
}
}
debug(verbose)
{
// Read a variable of a given type T and print it to screen
template wgetType(T)
{
T wgetType()
{
T t;
f.read(t);
writefln(typeid(T), ": ", t);
return t;
}
}
// Read a variable and compare it to what we want it to be
template wgetTypeIs(T)
{
T wgetTypeIs(T pt[] ...)
{
T t = getType!(T)();
char[] wanted;
if(pt.length > 1) wanted = "one of: ";
foreach(T a; pt) wanted ~= format("%s ", a);
writefln(typeid(T), ": ", t, " (wanted %s)", wanted);
debug(check)
{
bool match;
foreach(T a; pt)
if(a == t) {match = true; break;}
if(!match)
{
warn(format(typeid(T),
" mismatch: got %s, expected %s", t, wanted));
}
}
return t;
}
}
}
// Fill the provided array of Ts
template getArrayLen(T)
{
T[] getArrayLen(T[] arr)
{
f.readExact(arr.ptr, arr.length*T.sizeof);
return arr;
}
}
// Set the size of the provided array to 'count', and fill it.
template getArraySize(T)
{
T[] getArraySize(int count)
{
T[] arr;
fitArray(count, T.sizeof);
arr = cast(T[])nifRegion.allocate(count * T.sizeof);
getArrayLen!(T)(arr);
return arr;
}
}
// Get an array of Ts preceded by an array length of type Index
// (assumed to be an integer type)
template getArray(T,Index)
{
T[] getArray()
{
// Read array length
Index s = getType!(Index)();
// Is array larger than file?
fitArray(s,T.sizeof);
// Allocate the buffer
T[] result = cast(T[])nifRegion.allocate(s * T.sizeof);
// Read the buffer
return getArrayLen!(T)(result);
}
}
/* ------------------------------------------------
* Reader methods
* ------------------------------------------------
*/
// Strings
alias getArrayLen!(char) getString;
alias getArray!(char,int) getString;
// Other arrays
alias getArray!(int,int) getInts;
// Checks if an array of size elements each of size esize could
// possibly follow in the file.
void fitArray(int size, int esize)
in
{
assert(esize > 0);
}
body
{
if((size*esize+8) > (f.size() - f.position()) || size < 0)
fail(format(
"Array out of bounds: %d*%d + 8 bytes needed, but only %d bytes left in file",
size,esize,(f.size-f.position)));
}
// Base types
alias getType!(byte) getByte;
alias getType!(ubyte) getUbyte;
alias getType!(short) getShort;
alias getType!(ushort) getUshort;
alias getType!(int) getInt;
alias getType!(uint) getUint;
alias getType!(float) getFloat;
// Base types with error checking
alias getTypeIs!(short) getShortIs;
alias getTypeIs!(ushort) getUshortIs;
alias getTypeIs!(byte) getByteIs;
alias getTypeIs!(ubyte) getUbyteIs;
alias getTypeIs!(int) getIntIs;
alias getTypeIs!(uint) getUintIs;
alias getTypeIs!(float) getFloatIs;
debug(verbose)
{
// Base types
alias wgetType!(byte) wgetByte;
alias wgetType!(ubyte) wgetUbyte;
alias wgetType!(short) wgetShort;
alias wgetType!(ushort) wgetUshort;
alias wgetType!(int) wgetInt;
alias wgetType!(uint) wgetUint;
alias wgetType!(float) wgetFloat;
// Base types with error checking
alias wgetTypeIs!(short) wgetShortIs;
alias wgetTypeIs!(ushort) wgetUshortIs;
alias wgetTypeIs!(byte) wgetByteIs;
alias wgetTypeIs!(ubyte) wgetUbyteIs;
alias wgetTypeIs!(int) wgetIntIs;
alias wgetTypeIs!(uint) wgetUintIs;
alias wgetTypeIs!(float) wgetFloatIs;
}
else
{
// Base types
alias getByte wgetByte;
alias getUbyte wgetUbyte;
alias getShort wgetShort;
alias getUshort wgetUshort;
alias getInt wgetInt;
alias getUint wgetUint;
alias getFloat wgetFloat;
// Base types with error checking
alias getByteIs wgetByteIs;
alias getUbyteIs wgetUbyteIs;
alias getShortIs wgetShortIs;
alias getUshortIs wgetUshortIs;
alias getIntIs wgetIntIs;
alias getUintIs wgetUintIs;
alias getFloatIs wgetFloatIs;
}
// Vectors
Vector getVector()
{
Vector v;
getArrayLen!(float)(v.array);
return v;
}
Vector4 getVector4()
{
Vector4 v;
getArrayLen!(float)(v.array);
return v;
}
Vector getVectorIs(float x, float y, float z)
{
Vector v = getVector();
debug(check)
{
Vector u;
u.set(x,y,z);
if(v != u)
warn("Vector mismatch: expected " ~ u.toString ~ ", got " ~ v.toString);
}
return v;
}
debug(verbose)
{
Vector wgetVector()
{
Vector v = getVector();
writefln("vector: ", v.toString);
return v;
}
Vector4 wgetVector4()
{
Vector4 v = getVector4();
writefln("4-vector: ", v.toString);
return v;
}
}
else
{
alias getVector wgetVector;
alias getVector4 wgetVector4;
}
void getMatrix(ref Matrix m)
{
getArrayLen!(float)(m.array);
}
void getTransformation(ref Transformation t)
{
t.pos = getVector();
getMatrix(t.rotation);
t.scale = getFloat/*Is(1)*/;
t.velocity = getVectorIs(0,0,0);
}
}

@ -0,0 +1,63 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (niftool.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.niftool;
import nif.nif;
import nif.niffile;
import std.stdio;
import std.utf;
int main(char[][] args)
{
if(args.length < 2)
{
writefln("You must specify a file");
return 1;
}
initializeMemoryRegions();
bool errors;
foreach(char[] fn; args[1..$])
{
writefln("Opening %s", fn);
try nifMesh.open(fn);
catch(NIFFileException e)
{
writefln("Got exception: %s", e);
errors = true;
}
catch(UtfException e)
{
writefln("Got an UTF-error: %s", e);
writefln("We have to ignore the rest of the file at this point. If you want to");
writefln("test the entire file, compile the NIF lib without verbose output.");
}
nifMesh.clear();
writefln();
}
if(errors) return 1;
return 0;
}

@ -0,0 +1,324 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (node.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.node;
import nif.record;
// Tree node
abstract class Node : Named
{
ushort flags;
Transformation trafo;
//Extra properties[];
Property properties[];
Node parent;
// Bounding box info
bool hasBounding;
Vector boundPos;
Matrix boundRot;
Vector boundXYZ;
void setParent(Node p)
{
debug(veryverbose)
writefln("Setting parent of ", toString, " to ", p);
if(parent !is null)
{
char str[] = toString() ~ " already has parent " ~ parent.toString()
~ ", but setting ";
if(p !is null) str ~= p.toString;
else str ~= "to null";
nifFile.warn(str);
}
parent = p;
}
override:
void read()
{
super.read();
flags = nifFile.getShort();
nifFile.getTransformation(trafo);
debug(verbose)
{
writefln("Flags: %x", flags);
writefln("Transformation:\n", trafo.toString);
writef("Properties: ");
}
properties = nifRegion.allocateT!(Property)(getIndexList());
if(nifFile.getInt != 0)
{
hasBounding = true;
nifFile.getIntIs(1);
boundPos = nifFile.getVector;
nifFile.getMatrix(boundRot);
boundXYZ = nifFile.getVector;
//if(name != "Bounding Box") nifFile.warn("Node name is not 'Bounding Box'");
debug(verbose)
{
writefln("Bounding box: ", boundPos.toString);
writefln(boundRot.toString);
writefln("XYZ: ", boundXYZ.toString);
}
}
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookupList!(Property)(list,properties);
}
}
class NiCamera : Node
{
float left, right, top, bottom, near, far,
vleft, vright, vtop, vbottom, LOD;
override:
void read()
{
super.read();
left = nifFile.getFloat;
right = nifFile.getFloat;
top = nifFile.getFloat;
bottom = nifFile.getFloat;
near = nifFile.getFloat;
far = nifFile.getFloat;
vleft = nifFile.getFloat;
vright = nifFile.getFloat;
vtop = nifFile.getFloat;
vbottom = nifFile.getFloat;
LOD = nifFile.getFloat;
nifFile.getIntIs(-1);
nifFile.getIntIs(0);
debug(verbose)
{
writefln("Camera frustrum:");
writefln(" Left: ", left);
writefln(" Right: ", right);
writefln(" Top: ", top);
writefln(" Bottom: ", bottom);
writefln(" Near: ", near);
writefln(" Far: ", far);
writefln("View port:");
writefln(" Left: ", vleft);
writefln(" Right: ", vright);
writefln(" Top: ", vtop);
writefln(" Bottom: ", vbottom);
writefln("LOD adjust: ", LOD);
}
}
}
class NiAutoNormalParticles : Node
{
NiAutoNormalParticlesData data;
override:
void read()
{
super.read();
debug(check)
{
if(flags & 0xffff-6)
nifFile.warn("Unknown flags");
}
debug(verbose) writef("Particle Data ");
getIndex();
nifFile.getIntIs(-1);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiAutoNormalParticlesData)(list);
debug(check)
{
if(castCheck!(NiParticleSystemController)(controller)
&& castCheck!(NiBSPArrayController)(controller))
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
}
}
}
class NiRotatingParticles : Node
{
NiRotatingParticlesData data;
override:
void read()
{
super.read();
debug(check)
{
if(flags & 0xffff-6)
nifFile.warn("Unknown flags");
}
//nifFile.getIntIs(0);
debug(verbose) writef("Particle Data ");
getIndex();
nifFile.getIntIs(-1);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiRotatingParticlesData)(list);
debug(check)
{
if(castCheck!(NiParticleSystemController)(controller)
&& castCheck!(NiBSPArrayController)(controller) )
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
}
//castWarn!(NiParticleSystemController)(controller);
}
}
class NiTriShape : Node
{
NiTriShapeData data;
NiSkinInstance skin;
override:
void read()
{
super.read();
debug(verbose)
{
// If this is correct, it suggests how one might decode
// other flags. Check if this one is correct in sortOut().
if(flags&0x40) writefln("Mesh has no normals?");
}
debug(check)
{
if(flags & (0xffff-0x47))
nifFile.warn("Unknown flags were set");
}
//nifFile.getIntIs(0);
debug(verbose) writef("Mesh index: ");
getIndex();
debug(verbose) writef("Skin index: ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiTriShapeData)(list);
skin = lookup!(NiSkinInstance)(list);
debug(check)
{
if(data is null) nifFile.warn("Data missing from NiTriShape");
if(castCheck!(NiGeomMorpherController)(controller)
&& castCheck!(NiUVController)(controller)
&& castCheck!(NiVisController)(controller) )
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
if((data.normals.length == 0) && (flags & 0x40 == 0) ||
(data.normals.length != 0) && (flags & 0x40))
nifFile.warn("0x40 present but normals missing, or vice versa");
}
}
}
class NiNode : Node
{
Node children[];
Effect effects[];
override:
void read()
{
super.read();
debug(verbose)
{
if(flags & 1) writefln(" 0x01 Hidden");
if(flags & 2) writefln(" 0x02 Collision detection?");
if(flags & 4) writefln(" 0x04 Collision detection2 ?");
if(flags & 8) writefln(" 0x08 Unknown but common");
if(flags & 0x20) writefln(" 0x20 Unknown");
if(flags & 0x40) writefln(" 0x40 Unknown");
if(flags & 0x80) writefln(" 0x80 Unknown");
}
debug(check)
if(flags & 0x10) nifFile.warn("Unknown flags were set");
debug(verbose) writef("Child nodes: ");
children = nifRegion.allocateT!(Node)(getIndexList());
debug(verbose) writef("Effects: ");
effects = nifRegion.allocateT!(Effect)(getIndexList());
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookupList!(Node)(list,children);
lookupList!(Effect)(list,effects);
if(castCheck!(NiKeyframeController)(controller)
&& castCheck!(NiVisController)(controller)
&& castCheck!(NiPathController)(controller))
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
foreach(Node n; children)
n.setParent(this);
}
}

@ -0,0 +1,261 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (property.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.property;
import nif.record;
abstract class Property : Named
{
ushort flags;
override:
void read()
{
super.read();
flags = nifFile.getUshort;
debug(verbose) writefln("Flags: %x", flags);
}
}
// Check the specs on this one again
class NiTexturingProperty : Property
{
/* Apply mode:
0 - replace
1 - decal
2 - modulate
3 - hilight // These two are for PS2 only?
4 - hilight2
*/
int apply;
struct Texture
{
bool inUse;
NiSourceTexture texture;
/* Clamp mode (i don't understand this)
0 - clampS clampT
1 - clampS wrapT
2 - wrapS clampT
3 - wrapS wrapT
*/
/* Filter:
0 - nearest
1 - bilinear
2 - trilinear
3, 4, 5 - who knows
*/
int clamp, set, filter;
short ps2L, ps2K, unknown2;
void read(NiTexturingProperty caller)
{
if(nifFile.getInt == 0)
{
debug(verbose) writefln("No texture");
return;
}
inUse = 1;
debug(verbose) writef(" Texture ");
caller.getIndex();
clamp = nifFile.getIntIs(0,1,2,3);
filter = nifFile.getIntIs(0,1,2);
set = nifFile.getInt;
ps2L = nifFile.getShortIs(0);
ps2K = nifFile.getShortIs(-75,-2);
debug(verbose)
{
writefln(" Clamp ", clamp);
writefln(" Filter ", filter);
writefln(" Set? ", set);
writefln(" ps2L ", ps2L);
writefln(" ps2K ", ps2K);
}
unknown2 = nifFile.wgetShortIs(0,257);
}
}
/*
* This just in: The textures in this list is as follows:
*
* 0 - Base texture
* 1 - Dark texture
* 2 - Detail texture
* 3 - Gloss texture (never used?)
* 4 - Glow texture
* 5 - Bump map texture
* 6 - Decal texture
*/
Texture[7] textures;
override:
void read()
{
super.read();
if(flags > 1) nifFile.warn("Unknown flags");
apply = nifFile.getInt;
debug(verbose) writefln("Apply mode: ", apply);
nifFile.getIntIs(7);
textures[0].read(this); // Base
textures[1].read(this); // Dark
textures[2].read(this); // Detail
nifFile.getIntIs(0); // Gloss
textures[4].read(this); // Glow
textures[5].read(this); // Bump map
if(textures[5].inUse)
{
float lumaScale = nifFile.wgetFloat;
float lumaOffset = nifFile.wgetFloat;
Vector4 lumaMatrix = nifFile.wgetVector4;
}
textures[6].read(this); // Decal
}
void sortOut(Record[] list)
{
super.sortOut(list);
foreach(ref Texture t; textures)
if(t.inUse) t.texture = lookup!(NiSourceTexture)(list);
}
}
class NiMaterialProperty : Property
{
Vector ambient, diffuse, specular, emissive;
float glossiness, alpha;
override:
void read()
{
super.read();
if(flags != 1) nifFile.warn("Unknown flags");
ambient = nifFile.getVector;
diffuse = nifFile.getVector;
specular = nifFile.getVector;
emissive = nifFile.getVector;
glossiness = nifFile.getFloat;
alpha = nifFile.getFloat; // Use Alpha Property when this is not 1
debug(verbose)
{
writefln("Ambient: ", ambient.toString);
writefln("Diffuse: ", diffuse.toString);
writefln("Specular: ", specular.toString);
writefln("Emissive: ", emissive.toString);
writefln("Glossiness: ", glossiness);
writefln("Alpha: ", alpha);
}
}
}
class NiVertexColorProperty : Property
{
/* Vertex mode:
0 - source ignore
1 - source emmisive
2 - source amb diff
Lighting mode
0 - lighting emmisive
1 - lighting emmisive ambient/diffuse
*/
int vertmode, lightmode;
override:
void read()
{
super.read();
if(flags != 0) nifFile.warn("Unknown flags");
vertmode = nifFile.getIntIs(0,1,2);
lightmode = nifFile.getIntIs(0,1);
debug(verbose)
{
writefln("Vertex mode: ", vertmode);
writefln("Lighting mode: ", lightmode);
}
}
}
alias NiWireframeProperty NiDitherProperty;
alias NiWireframeProperty NiSpecularProperty;
class NiWireframeProperty : Property
{
override:
void read()
{
super.read();
if(flags != 1) nifFile.warn("Unknown flags");
}
}
class NiShadeProperty : Property
{
override:
void read()
{
super.read();
if(flags != 0) nifFile.warn("Unknown flags");
}
}
class NiAlphaProperty : Property
{
override:
void read()
{
super.read();
ubyte b = nifFile.getUbyte;
debug(verbose) writefln("Unknown byte: ", b);
}
}
class NiZBufferProperty : Property
{
override:
void read()
{
super.read();
debug(verbose)
{
if(flags&1) writefln(" 0x01 ZTest");
if(flags&2) writefln(" 0x02 ZWrite");
}
if(flags & 0xfc) nifFile.warn("Unknown flags");
}
}

@ -0,0 +1,302 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (record.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module nif.record;
public
{
import util.regions;
import nif.base;
import nif.controller;
import nif.controlled;
import nif.node;
import nif.data;
import nif.extra;
import nif.property;
import nif.effect;
import nif.niffile;
import nif.misc;
import std.string;
}
// Base class for all NIF records
abstract class Record
{
protected:
// List of dependency indices. We throw this away after the entire
// file is loaded and the dependencies have been converted to object
// pointers.
RegionBuffer!(int) depList;
debug(statecheck)
{
// An internal 'state', this check is only intended for
// debugging purposes (hence the surrounding debug block ;-)
int state;
/* 0 - nothing has been done
* 1 - record has been read
* 2 - sortOut has been called
* 3 - check has been called
*/
}
public:
new(uint sz)
{
// After eliminating all GC dependence, this is no longer
// needed.
//return nifRegion.allocateGC(sz).ptr;
return nifRegion.allocate(sz).ptr;
}
delete(void *p) { assert(0); }
debug(statecheck)
final void finalCheck()
{
debug(veryverbose)
writefln("Final check on ", this);
assert(state==3);
}
// Read record data from file f. All indices are stored temporarily
// as integers, as they appear in the file.
void read()
{
// Allocate the dependency list. 50 should be enough entries.
depList = nifRegion.getBuffer!(int)(0,50);
debug(veryverbose)
writefln("Reading ", this, " at offset %x", nifFile.position);
debug(statecheck) assert(state++ == 0);
}
// Sort out dependencies between records. Called after all records
// have been read from file. Paramter 'list' contains all records in
// the order they appear in the file. Used to convert integer
// indices into object pointers, and checking that all types are
// correct. Can also be used for data checks than only require this
// objects dependencies.
void sortOut(Record[] list)
{
debug(veryverbose) writefln("Sorting out ", this);
debug(statecheck) assert(state++ == 1);
}
// Consistancy check. Called after all dependencies have been
// sorted. Can be used for checking that vertex counts are the same
// in different records, etc. It checks the depList array.
void check()
{
debug(veryverbose) writefln("Consistancy check on ", this);
// Check that all dependencies have been resolved. delList is
// successively sliced down to zero when indices are looked up.
assert(depList.length == 0);
debug(statecheck) assert(state++ == 2);
}
// Convert an integer record index to an object pointer of type T.
template lookup(T: Record)
{
T lookup(Record[] list)
{
// Get the next dependency from the list
int i = depList[0];
depList = depList[1..depList.length()];
debug(verbose)
{
T t = lookupCast!(T)(i, list);
debug(veryverbose)
{
writef(" Resolved ", i, " to ");
if(t is null) writefln("(null)");
else writefln(t);
}
return t;
}
else
return lookupCast!(T)(i, list);
}
}
template lookupList(T: Record)
{
void lookupList(Record[] list, T[] deps)
{
foreach(ref T t; deps)
t = lookup!(T)(list);
}
}
// Reads an index (int) and adds it to the list of
// dependencies. These can be returned later by lookup().
void getIndex()
{
depList ~= nifFile.getInt;
debug(verbose) writefln("Index ", depList[depList.length()-1]);
}
int getIndexList()
{
int[] l = nifFile.getInts;
int num;
// Only add non-null references
foreach(int i; l)
if(i != -1)
{
depList ~= i;
num++;
}
//depList ~= l;
debug(verbose)
{
writef("Index list: ");
foreach(int i; l)
writef(i, " ");
writefln("(%d kept)", num);
}
return num;
}
}
// Lookup with casting and error checking.
template lookupCast(T: Record)
{
T lookupCast(int i, Record[] list)
{
if(i == -1) return null;
if(i < 0 || i >= list.length)
nifFile.fail(format("Record index %d out of bounds (got %d records.)",
i, list.length));
Record r = list[i];
if(r is null)
{
nifFile.warn("Referenced an unknown record type");
return null;
}
T t = cast(T)r;
if(t is null)
nifFile.fail("Cannot convert " ~ r.toString() ~ " to " ~
(cast(TypeInfo_Class)typeid(T)).info.name);
return t;
}
}
template castCheck(T: Record)
{
bool castCheck(Record r)
{
if(r !is null && cast(T)r is null) return true;
else return false;
}
}
template castWarn(T: Record)
{
debug(check)
{
void castWarn(Record r)
{
if(castCheck!(T)(r))
nifFile.warn("Could not cast " ~ r.toString() ~ " to " ~
(cast(TypeInfo_Class)typeid(T)).info.name);
}
}
else
void castWarn(Record r) {};
}
class NiSkinInstance : Record
{
NiSkinData data;
NiNode root;
NiNode bones[];
override:
void read()
{
super.read();
debug(verbose) writef("Skin data ");
getIndex();
debug(verbose) writef("Scene root ");
getIndex();
debug(verbose) writef("Bone ");
bones = nifRegion.allocateT!(NiNode)(getIndexList());
}
void sortOut(Record[] l)
{
super.sortOut(l);
data = lookup!(NiSkinData)(l);
root = lookup!(NiNode)(l);
lookupList!(NiNode)(l,bones);
}
void check()
{
super.check();
debug(check) if(data !is null)
{
if(bones.length != data.weights.length)
nifFile.warn(format("NiSkinInstance has ", bones.length,
" bones, but NiSkinData has ", data.weights.length));
/*
int meshCount = root.getFirstMesh(r).data.vertices.length/3;
foreach(int i, NiSkinData.Weight[] wl; data.weights)
{
if(bones[i] is null)
r.warn("Bone object missing!");
if(meshCount < data.weights[i].length)
r.warn(format("More skin weights than vertices in bone %d (%d < %d)",
i, meshCount, data.weights[i].length));
foreach(ref NiSkinData.Weight w; data.weights[i])
if(w.vertex >= meshCount)
r.warn("Skin weight vertex index out of bounds");
}
*/
}
}
}

@ -0,0 +1,7 @@
Render System=OpenGL Rendering Subsystem
[OpenGL Rendering Subsystem]
FSAA=0
Full Screen=No
RTT Preferred Mode=FBO
Video Mode=800 x 600

@ -0,0 +1,171 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bindings.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module ogre.bindings;
import nif.misc; // for Transformation
import ogre.ogre; // for Placement
import core.resource;
/*
* This module is the interface to OGRE from D. Since OGRE is written
* in C++, all the code that deals directly with the graphics engine
* is packaged in a bunch of C++ functions. These functions are
* exported from C++ through the C calling convention, and imported
* here.
*
* Note that the C calling convension is not in any way type
* safe. This is convenient, as it allows us to send pointers as one
* type and recieve them as another, without casting, but also
* dangerous since it opens for some nasty bugs.
*/
// Represents a pointer to a Node in the OGRE engine. We never use
// these directly in D code, only pass them back to the C++ code.
typedef void* NodePtr;
// Pointer to a manual loader class in C++.
typedef void* ManualLoader;
extern(C):
// Do engine configuration. Returns 0 if we should continue, 1 if
// not.
int cpp_configure();
// Sets up the window
void cpp_initWindow();
// Set up an empty scene.
void cpp_makeScene();
// Set the ambient light and "sunlight"
void cpp_setAmbient(float r, float g, float b,
float rs, float gs, float bs);
// Set fog color and view distance
void cpp_setFog(float rf, float gf, float bf,
float flow, float fhigh);
// Create a simple sky dome
int cpp_makeSky();
// Enter main rendering loop
void cpp_startRendering();
// Cleans up after ogre
void cpp_cleanup();
// Create a manual loader for textures
ManualLoader cpp_createTexture(
char* name, // Resource name
TextureIndex ti); // Texture index
// Inserts texture from memory data. This isn't used anymore and will
// be removed.
void cpp_loadMemImage(
char* name, // Name to give the resource
char* type, // Image type (eg. "dds")
void* data, // Pointer to file data
uint size, // Size
void* texture); // Texture resource
// Gets a child SceneNode from the root node, then detatches it to
// hide it from view. Used for creating the "template" node associated
// with a NIF mesh.
NodePtr cpp_getDetachedNode();
// Create a copy of the given scene node, with the given coordinates
// and rotation.
NodePtr cpp_insertNode(NodePtr base, char* name,
Placement *pos, float scale);
// Create a (very crappy looking) plane to simulate the water level
void cpp_createWater(float level);
// Creates a scene node as a child of 'parent', then translates and
// rotates it according to the data in 'trafo'.
NodePtr cpp_createNode(
char *name, // Name to give the node
Transformation *trafo, // Transformation
NodePtr parent, // Parent node
int noRot); // If 1, don't rotate node
// Create a light with the given diffuse color. Attach it to SceneNode
// 'parent'.
NodePtr cpp_attachLight(char* name, NodePtr parent,
float r, float g, float b,
float radius);
// Create the specified material
void cpp_createMaterial(char *name, // Name to give resource
float *ambient, // Ambient RBG value
float *diffuse,
float *specular,
float *emissive,// Self illumination
float glossiness,// Same as shininess?
float alpha, // Use this in all alpha values?
char *texture); // Texture name
// Creates a mesh and gives it a bounding box. Also creates an entity
// and attached it to the given SceneNode 'owner'.
void cpp_createMesh(
char* name, // Name of the mesh
int numVerts, // Number of vertices
float* vertices, // Vertex list
float* normals, // Normal list
float* colors, // Vertex colors
float* uvs, // Texture coordinates
int numFaces, // Number of faces*3
short* faces, // Faces
float radius, // Bounding sphere
char* material, // Material name, if any
// Bounding box
float minX,float minY,float minZ,
float maxX,float maxY,float maxZ,
NodePtr owner // Scene node to attach to.
);
// Save a screen shot to the given file name
void cpp_screenshot(char *filename);
// Camera controll
void cpp_rotateCamera(float x, float y);
void cpp_moveCamera(float x, float y, float z, float r1, float r2, float r3);
void cpp_getCameraPos(float *x, float *y, float *z);
void cpp_moveCameraRel(float x, float y, float z);
// Do some debug action. Check the menu for today's specials!
void cpp_debug(int i);
// CALLBACKS:
// Called when a texture wants to be loaded. Not used anymore.
void d_loadTexture(TextureIndex ti, void *tex)
{
assert(0);
resources.loadTexture(ti, tex);
}

@ -0,0 +1,137 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_bsaarchive.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// This file inserts an archive manager for .bsa files into the ogre
// resource loading system. It uses callbacks to D to interact with
// the d-based bsa handling code. The D bindings are in
// core/resource.d.
// Callbacks to D code
// Does the file exist in the archives?
extern "C" int d_bsaExists(const char *filename);
// Open a file. Return the pointer and size.
extern "C" void d_bsaOpenFile(const char *filename,
void **retPtr, unsigned int *retSize);
// This archive does not cover one .bsa file, instead it interacts
// with the all the loaded bsas and treates them as one archive.
class BSAArchive : public Archive
{
public:
BSAArchive(const String& name)
: Archive(name, "BSA") {}
~BSAArchive() {}
bool isCaseSensitive(void) const { return false; }
// The archives are already loaded in D code, and they are never
// unloaded.
void load() {}
void unload() {}
DataStreamPtr open(const String& filename) const
{
//std::cout << "open(" << filename << ")\n";
void *ptr;
unsigned int size;
// Open the file
d_bsaOpenFile(filename.c_str(), &ptr, &size);
return DataStreamPtr(new MemoryDataStream(ptr, size));
}
// This is never called as far as I can see.
StringVectorPtr list(bool recursive = true, bool dirs = false)
{
std::cout << "list(" << recursive << ", " << dirs << ")\n";
StringVectorPtr ptr = StringVectorPtr(new StringVector());
return ptr;
}
// Also never called.
FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
{
std::cout << "listFileInfo(" << recursive << ", " << dirs << ")\n";
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
return ptr;
}
// After load() is called, find("*") is called once. It doesn't seem
// to matter that we return an empty list, exists() gets called on
// the correct files anyway.
StringVectorPtr find(const String& pattern, bool recursive = true,
bool dirs = false)
{
//std::cout << "find(" << pattern << ", " << recursive
// << ", " << dirs << ")\n";
StringVectorPtr ptr = StringVectorPtr(new StringVector());
return ptr;
}
// Gets called once for each of the ogre formats, *.program,
// *.material etc. We can ignore that.
FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
bool dirs = false)
{
//std::cout << "findFileInfo(" << pattern << ", " << recursive
// << ", " << dirs << ")\n";
FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
//std::cout << "BSAArchive::findFileInfo is not implemented!\n";
return ptr;
}
// Check if the file exists.
bool exists(const String& filename)
{
//std::cout << "exists(" << filename << ")\n";
return d_bsaExists(filename.c_str()) != 0;
}
};
// An archive factory for BSA archives
class BSAArchiveFactory : public ArchiveFactory
{
public:
virtual ~BSAArchiveFactory() {}
const String& getType() const
{
static String name = "BSA";
return name;
}
Archive *createInstance( const String& name )
{
return new BSAArchive(name);
}
void destroyInstance( Archive* arch) { delete arch; }
};
BSAArchiveFactory mBSAFactory;

@ -0,0 +1,161 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_framelistener.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// Callbacks to D code.
// Called once each frame
extern "C" int d_frameStarted(float time);
// Handle events
extern "C" void d_handleKey(int keycode, unsigned int text);
extern "C" void d_handleMouseMove(const OIS::MouseState *state);
extern "C" void d_handleMouseButton(const OIS::MouseState *state, int button);
// Frame listener, passed to Ogre. The only thing we use this for is
// to capture input and pass control to D code.
class MorroFrameListener: public FrameListener
{
public:
// Start of frame
bool frameStarted(const FrameEvent& evt)
{
if(mWindow->isClosed())
return false;
// Capture keyboard and mouse events
mKeyboard->capture();
mMouse->capture();
return d_frameStarted(evt.timeSinceLastFrame);
}
};
// Recieves input events and sends them to the D event handler.
class InputListener : public OIS::KeyListener, public OIS::MouseListener
{
public:
bool keyPressed( const OIS::KeyEvent &arg )
{
/*
std::cout << "KeyPressed {" << arg.key
<< ", " << ((OIS::Keyboard*)(arg.device))->getAsString(arg.key)
<< "} || Character (" << (char)arg.text << ")\n";
//*/
d_handleKey(arg.key, arg.text);
return true;
}
bool mouseMoved( const OIS::MouseEvent &arg )
{
/*
const OIS::MouseState& s = arg.state;
std::cout << "MouseMoved: Abs("
<< s.X.abs << ", " << s.Y.abs << ", " << s.Z.abs << ") Rel("
<< s.X.rel << ", " << s.Y.rel << ", " << s.Z.rel << ")\n";
*/
d_handleMouseMove(&arg.state);
return true;
}
bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
/*
const OIS::MouseState& s = arg.state;
std::cout << "Mouse button #" << id << " pressed. Abs("
<< s.X.abs << ", " << s.Y.abs << ", " << s.Z.abs << ") Rel("
<< s.X.rel << ", " << s.Y.rel << ", " << s.Z.rel << ")\n";
*/
d_handleMouseButton(&arg.state, id);
return true;
}
// Unused
bool keyReleased( const OIS::KeyEvent &arg ) { return true; }
bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) { return true; }
};
MorroFrameListener mFrameListener;
InputListener mInput;
// Functions called from D during event handling
extern "C" int cpp_isPressed(int keysym)
{
return mKeyboard->isKeyDown((OIS::KeyCode)keysym);
}
// Dump screen contents to file
extern "C" void cpp_screenshot(char* filename)
{
mWindow->writeContentsToFile(filename);
//This doesn't work, I think I have to set up an overlay or
//something first and display the text manually.
//mWindow->setDebugText(String("Wrote ") + filename);
}
// Rotate camera as result of mouse movement
extern "C" void cpp_rotateCamera(float x, float y)
{
mCamera->yaw(Degree(-x));
mCamera->pitch(Degree(-y));
}
// Get current camera position
extern "C" void cpp_getCameraPos(float *x, float *y, float *z)
{
Vector3 pos = mCamera->getPosition();
*x = pos[0];
*y = -pos[2];
*z = pos[1];
}
// Move and rotate camera in place. Transforms Morrowind coordinates
// to OGRE coordinates.
extern "C" void cpp_moveCamera(float x, float y, float z,
float r1, float r2, float r3)
{
mCamera->setPosition(Vector3(x,z,-y));
// Rotation is probably not correct, but for now I have no reference
// point. Fix it later when we teleport from one cell to another, so
// we have something to compare against.
// Rotate around X axis
Quaternion xr(Radian(-r1), Vector3::UNIT_X);
// Rotate around Y axis
Quaternion yr(Radian(r3+3.14), Vector3::UNIT_Y);
// Rotate around Z axis
Quaternion zr(Radian(-r2), Vector3::UNIT_Z);
// Rotates first around z, then y, then x
mCamera->setOrientation(xr*yr*zr);
}
// Move camera relative to its own axis set
extern "C" void cpp_moveCameraRel(float x, float y, float z)
{
mCamera->moveRelative(Vector3(x,y,z));
}

@ -0,0 +1,755 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_interface.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
//-----------------------------------------------------------------------
// E X P O R T E D F U N C T I O N S
//-----------------------------------------------------------------------
extern "C" void cpp_cleanup()
{
// Kill the input systems. This will reset input options such as key
// repetition.
mInputManager->destroyInputObject(mKeyboard);
mInputManager->destroyInputObject(mMouse);
OIS::InputManager::destroyInputSystem(mInputManager);
// Kill OGRE
if (mRoot)
{
delete mRoot;
mRoot = 0;
}
}
extern "C" int cpp_configure()
{
mRoot = new Root();
// Add the BSA archive manager before reading the config file.
ArchiveManager::getSingleton().addArchiveFactory( &mBSAFactory );
// Load resource paths from config file
ConfigFile cf;
cf.load("resources.cfg");
// Go through all sections & settings in the file
ConfigFile::SectionIterator seci = cf.getSectionIterator();
String secName, typeName, archName;
while (seci.hasMoreElements())
{
secName = seci.peekNextKey();
ConfigFile::SettingsMultiMap *settings = seci.getNext();
ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
ResourceGroupManager::getSingleton().addResourceLocation(
archName, typeName, secName);
}
}
// Show the configuration dialog and initialise the system
// You can skip this and use root.restoreConfig() to load configuration
// settings if you were sure there are valid ones saved in ogre.cfg
//if(mRoot->showConfigDialog())
if(mRoot->restoreConfig())
return 0;
return 1;
}
// Initialize window. This will create and show the actual window.
extern "C" void cpp_initWindow()
{
std::cout << "cpp_initWindow()\n";
// Initialize OGRE.
mWindow = mRoot->initialise(true);
// Set up the input system
using namespace OIS;
size_t windowHnd;
mWindow->getCustomAttribute("WINDOW", &windowHnd);
std::ostringstream windowHndStr;
ParamList pl;
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
// Non-exclusive mouse and keyboard input
/*
#if defined OIS_WIN32_PLATFORM
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_FOREGROUND" )));
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_NONEXCLUSIVE")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_FOREGROUND")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
pl.insert(std::make_pair(std::string("x11_mouse_grab"),
std::string("true")));
pl.insert(std::make_pair(std::string("x11_mouse_hide"),
std::string("true")));
pl.insert(std::make_pair(std::string("x11_keyboard_grab"),
std::string("true")));
pl.insert(std::make_pair(std::string("XAutoRepeatOn"),
std::string("false")));
#endif
*/
mInputManager = InputManager::createInputSystem( pl );
const bool bufferedKeys = true;
const bool bufferedMouse = true;
// Create all devices
mKeyboard = static_cast<Keyboard*>(mInputManager->createInputObject
( OISKeyboard, bufferedKeys ));
mMouse = static_cast<Mouse*>(mInputManager->createInputObject
( OISMouse, bufferedMouse ));
unsigned int width, height, depth;
int left, top;
mWindow->getMetrics(width, height, depth, left, top);
// Set mouse region
const MouseState &ms = mMouse->getMouseState();
ms.width = width;
ms.height = height;
// Register the input listener
mKeyboard -> setEventCallback( &mInput );
mMouse -> setEventCallback( &mInput );
std::cout << "cpp_initWindow finished\n";
}
// Make a scene, set the given ambient light
extern "C" void cpp_makeScene()
{
// Get the SceneManager, in this case a generic one
mSceneMgr = mRoot->createSceneManager(ST_GENERIC);
// Create the camera
mCamera = mSceneMgr->createCamera("PlayerCam");
mCamera->setNearClipDistance(5);
// Create one viewport, entire window
vp = mWindow->addViewport(mCamera);
// Give the backround a healthy shade of green
vp->setBackgroundColour(ColourValue(0,0.1,0));
// Alter the camera aspect ratio to match the viewport
mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
// Set default mipmap level (NB some APIs ignore this)
TextureManager::getSingleton().setDefaultNumMipmaps(5);
// Load resources
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
// Add the frame listener
mRoot->addFrameListener(&mFrameListener);
// Turn the entire scene (represented by the 'root' node) -90
// degrees around the x axis. This makes Z go upwards, and Y go into
// the screen. This is the orientation that Morrowind uses, and it
// automagically makes everything work as it should.
SceneNode *rt = mSceneMgr->getRootSceneNode();
root = rt->createChildSceneNode();
root->pitch(Degree(-90));
}
extern "C" void cpp_makeSky()
{
mSceneMgr->setSkyDome( true, "Examples/CloudySky", 5, 8 );
}
extern "C" Light* cpp_attachLight(char *name, SceneNode* base,
float r, float g, float b,
float radius)
{
Light *l = mSceneMgr->createLight(name);
l->setDiffuseColour(r,g,b);
// Pulled these numbers out of nowhere. Looks OK-ish. I can polish
// it later.
l->setAttenuation(8*radius, 0, 0.008, 0.0);
// base might be null, sometimes lights don't have meshes
if(base) base->attachObject(l);
return l;
}
extern "C" void cpp_setAmbient(float r, float g, float b, // Ambient light
float rs, float gs, float bs) // "Sunlight"
{
ColourValue c = ColourValue(r, g, b);
mSceneMgr->setAmbientLight(c);
// Create a "sun" that shines light downwards. It doesn't look
// completely right, but leave it for now.
Light *l = mSceneMgr->createLight("Sun");
l->setDiffuseColour(rs, gs, bs);
l->setType(Light::LT_DIRECTIONAL);
l->setDirection(0,-1,0);
}
extern "C" void cpp_setFog(float rf, float gf, float bf, // Fog color
float flow, float fhigh) // Fog distance
{
ColourValue fogColor( rf, gf, bf );
mSceneMgr->setFog( FOG_LINEAR, fogColor, 0.0, flow, fhigh );
// Don't render what you can't see anyway
mCamera->setFarClipDistance(fhigh + 10);
// Leave this out for now
//vp->setBackgroundColour(fogColor);
}
extern "C" void cpp_startRendering()
{
mRoot->startRendering();
}
// Copy a scene node and all its children
void cloneNode(SceneNode *from, SceneNode *to, char* name)
{
to->setPosition(from->getPosition());
to->setOrientation(from->getOrientation());
to->setScale(from->getScale());
SceneNode::ObjectIterator it = from->getAttachedObjectIterator();
while(it.hasMoreElements())
{
// We can't handle non-entities. To be honest I have no idea
// what dynamic_cast does or if it's correct here. I used to be
// a C++ person but after discovering D I dropped C++ like it
// was red hot iron and never looked back.
Entity *e = dynamic_cast<Entity*> (it.getNext());
if(e)
{
e = e->clone(String(name) + ":" + e->getName());
to->attachObject(e);
}
}
// Recursively clone all child nodes
SceneNode::ChildNodeIterator it2 = from->getChildIterator();
while(it2.hasMoreElements())
{
cloneNode((SceneNode*)it2.getNext(), to->createChildSceneNode(), name);
}
}
// Supposed to insert a copy of the node, for now it just inserts the
// actual node.
extern "C" SceneNode *cpp_insertNode(SceneNode *base, char* name,
float *pos, float scale)
{
//std::cout << "cpp_insertNode(" << name << ")\n";
SceneNode *node = root->createChildSceneNode(name);
// Make a copy of the node
cloneNode(base, node, name);
// pos points to a Placement struct, which has the format
// float x, y, z; // position
// float r1, r2, r3; // rotation
node->setPosition(pos[0], pos[1], pos[2]);
// Rotate around X axis
Quaternion xr(Radian(-pos[3]), Vector3::UNIT_X);
// Rotate around Y axis
Quaternion yr(Radian(-pos[4]), Vector3::UNIT_Y);
// Rotate around Z axis
Quaternion zr(Radian(-pos[5]), Vector3::UNIT_Z);
// Rotates first around z, then y, then x
node->setOrientation(xr*yr*zr);
node->setScale(scale, scale, scale);
return node;
}
// Create the water plane. It doesn't really resemble "water" yet
// though.
extern "C" void cpp_createWater(float level)
{
// Create a plane aligned with the xy-plane.
MeshManager::getSingleton().createPlane("water",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Plane(Vector3::UNIT_Z, level),
150000,150000
//,20,20,true,1,5,5,Vector3::UNIT_Z
);
Entity *ent = mSceneMgr->createEntity( "WaterEntity", "water" );
root->createChildSceneNode()->attachObject(ent);
ent->setCastShadows(false);
}
// Manual loader for meshes. Reloading individual meshes is too
// difficult, and not worth the trouble. Later I should make one
// loader for each NIF file, and whenever it is invoked it should
// somehow reload the entire file. How this is to be done when some of
// the meshes might be loaded and in use already, I have no
// idea. Let's just ignore it for now.
class MeshLoader : public ManualResourceLoader
{
public:
void loadResource(Resource *resource)
{
}
} dummyLoader;
// TODO/FIXME/DEBUG (MURDER/DEATH/KILL)
String LASTNAME;
// Load the contents of a mesh
extern "C" void cpp_createMesh(
char* name, // Name of the mesh
int numVerts, // Number of vertices
float* vertices, // Vertex list
float* normals, // Normal list
float* colors, // Vertex colors
float* uvs, // Texture coordinates
int numFaces, // Number of faces*3
unsigned short* faces, // Faces
float radius, // Bounding sphere
char* material, // Material
// Bounding box
float minX,float minY,float minZ,
float maxX,float maxY,float maxZ,
SceneNode *owner
)
{
//std::cerr << "Creating mesh " << name << "\n";
MeshPtr msh = MeshManager::getSingleton().createManual(name, "Meshes",
&dummyLoader);
Entity *e = mSceneMgr->createEntity(name, name);
owner->attachObject(e);
//msh->setSkeletonName(name);
// Create vertex data structure
msh->sharedVertexData = new VertexData();
msh->sharedVertexData->vertexCount = numVerts;
/// Create declaration (memory format) of vertex data
VertexDeclaration* decl = msh->sharedVertexData->vertexDeclaration;
int nextBuf = 0;
// 1st buffer
decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION);
/// Allocate vertex buffer of the requested number of vertices (vertexCount)
/// and bytes per vertex (offset)
HardwareVertexBufferSharedPtr vbuf =
HardwareBufferManager::getSingleton().createVertexBuffer(
VertexElement::getTypeSize(VET_FLOAT3),
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
/// Upload the vertex data to the card
vbuf->writeData(0, vbuf->getSizeInBytes(), vertices, true);
/// Set vertex buffer binding so buffer 0 is bound to our vertex buffer
VertexBufferBinding* bind = msh->sharedVertexData->vertexBufferBinding;
bind->setBinding(nextBuf++, vbuf);
// The lists are read in the same order that they appear in NIF
// files, and likely in memory. Sequential reads might possibly
// avert an occational cache miss.
// normals
if(normals)
{
//std::cerr << "+ Adding normals\n";
decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL);
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
VertexElement::getTypeSize(VET_FLOAT3),
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
vbuf->writeData(0, vbuf->getSizeInBytes(), normals, true);
bind->setBinding(nextBuf++, vbuf);
}
// vertex colors
if(colors)
{
//std::cerr << "+ Adding vertex colors\n";
// Use render system to convert colour value since colour packing varies
RenderSystem* rs = Root::getSingleton().getRenderSystem();
RGBA colorsRGB[numVerts];
RGBA *pColour = colorsRGB;
for(int i=0; i<numVerts; i++)
{
rs->convertColourValue(ColourValue(colors[0],colors[1],colors[2], colors[3]),
pColour++);
colors += 4;
}
decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE);
/// Allocate vertex buffer of the requested number of vertices (vertexCount)
/// and bytes per vertex (offset)
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
VertexElement::getTypeSize(VET_COLOUR),
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
/// Upload the vertex data to the card
vbuf->writeData(0, vbuf->getSizeInBytes(), colorsRGB, true);
/// Set vertex buffer binding so buffer 1 is bound to our colour buffer
bind->setBinding(nextBuf++, vbuf);
}
if(uvs)
{
//std::cerr << "+ Adding texture coordinates\n";
decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES);
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
VertexElement::getTypeSize(VET_FLOAT2),
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
vbuf->writeData(0, vbuf->getSizeInBytes(), uvs, true);
bind->setBinding(nextBuf++, vbuf);
}
// Create the submesh that holds triangle data
SubMesh* sub = msh->createSubMesh(name);
sub->useSharedVertices = true;
if(numFaces)
{
//std::cerr << "+ Adding faces\n";
/// Allocate index buffer of the requested number of faces
HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton().
createIndexBuffer(
HardwareIndexBuffer::IT_16BIT,
numFaces,
HardwareBuffer::HBU_STATIC_WRITE_ONLY);
/// Upload the index data to the card
ibuf->writeData(0, ibuf->getSizeInBytes(), faces, true);
/// Set parameters of the submesh
sub->indexData->indexBuffer = ibuf;
sub->indexData->indexCount = numFaces;
sub->indexData->indexStart = 0;
}
// Create a material with the given texture, if any.
// If this mesh has a material, attach it.
if(material) sub->setMaterialName(name);
/*
// Assign this submesh to the given bone
VertexBoneAssignment v;
v.boneIndex = ((Bone*)bone)->getHandle();
v.weight = 1.0;
std::cerr << "+ Assigning bone index " << v.boneIndex << "\n";
for(int i=0; i < numVerts; i++)
{
v.vertexIndex = i;
sub->addBoneAssignment(v);
}
*/
/// Set bounding information (for culling)
msh->_setBounds(AxisAlignedBox(minX,minY,minZ,maxX,maxY,maxZ));
//std::cerr << "+ Radius: " << radius << "\n";
msh->_setBoundingSphereRadius(radius);
}
extern "C" void cpp_createMaterial(char *name, // Name to give
// resource
float *ambient, // Ambient RBG
// value
float *diffuse,
float *specular,
float *emissive, // Self
// illumination
float glossiness,// Same as
// shininess?
float alpha, // Use this in all
// alpha values?
char* texture) // Texture
{
MaterialPtr material = MaterialManager::getSingleton().create(
name,
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
// This assigns the texture to this material. If the texture
// name is a file name, and this file exists (in a resource
// directory), it will automatically be loaded when needed. If
// not, we should already have inserted a manual loader for the texture.
if(texture)
material->getTechnique(0)->getPass(0)->createTextureUnitState(texture);
// Set bells and whistles
material->setAmbient(ambient[0], ambient[1], ambient[2]);
material->setDiffuse(diffuse[0], diffuse[1], diffuse[2], alpha);
material->setSpecular(specular[0], specular[1], specular[2], alpha);
material->setSelfIllumination(emissive[0], emissive[1], emissive[2]);
material->setShininess(glossiness);
// Enable transparancy? This is just guesswork. Perhaps we
// should use NiAlphaProperty or something instead. This looks
// like crap so it's not enabled right now.
//material->setSceneBlending(SBT_TRANSPARENT_ALPHA);
// Temporary, just to store the name of one valid material.
LASTNAME = material->getName();
}
extern "C" SceneNode *cpp_getDetachedNode()
{
SceneNode *node = root->createChildSceneNode();
root->removeChild(node);
return node;
}
extern "C" SceneNode* cpp_createNode(
char *name,
float *trafo,
SceneNode *parent,
int noRot)
{
//std::cout << "cpp_createNode(" << name << ")";
SceneNode *node = parent->createChildSceneNode(name);
//std::cout << " ... done\n";
// First is the translation vector
// TODO should be "if(!noRot)" only for exterior cells!? Yay for
// consistency. Apparently, the displacement of the base node in NIF
// files must be ignored for meshes in interior cells, but not for
// exterior cells. Or at least that's my hypothesis, and it seems
// work. There might be some other NIF trickery going on though, you
// never know when you're reverse engineering someone else's file
// format. We will handle this later.
if(!noRot)
node->setPosition(trafo[0], trafo[1], trafo[2]);
// Then a 3x3 rotation matrix.
if(!noRot)
node->setOrientation(Quaternion(Matrix3(trafo[3], trafo[4], trafo[5],
trafo[6], trafo[7], trafo[8],
trafo[9], trafo[10], trafo[11]
)));
// Scale is at the end
node->setScale(trafo[12],trafo[12],trafo[12]);
return node;
}
// Callback to make D insert a texture.
extern "C" void d_loadTexture(void* tl, Resource *r);
// Manual loader for textures
class TextureLoader : public ManualResourceLoader
{
void *textureIndex;
public:
TextureLoader(char* name, void* ti) : textureIndex(ti)
{ createTexture(name); }
~TextureLoader() {}
void createTexture(char *name)
{
// Completely disable these textures for now.
return;
// Create a manually loaded texture. Dimensions etc are dummy
// values that will be overwritten later.
TexturePtr texture = TextureManager::getSingleton().createManual
(
name, // Name
"General", // Group
TEX_TYPE_2D, // Texture type
1, 1, // Dimensions
0, // Mipmaps
//PF_DXT1, // Pixel format
PF_UNKNOWN, // Pixel format
TU_DEFAULT,
this);
// Now unload this dummy image. This forces the image to be
// reloaded when needed.
texture->unload();
}
void loadResource(Resource *resource)
{
// This makes the D code insert the image.
d_loadTexture(textureIndex, resource);
}
};
// Create a new manually loaded texture, and return the manual loader
extern "C" TextureLoader *cpp_createTexture(
char *name,
void *textureIndex)
{
return new TextureLoader(name, textureIndex);
}
int hasLoad = 0;
extern "C" void cpp_loadMemImage(
char* name, // Name to give the resource
char* type, // Image type (eg. "dds")
void* data, // Pointer to file data
unsigned int size, // Size
Texture* txt)// Texture
{
DataStreamPtr m(new MemoryDataStream(name, data, size));
Image i;
i.load(m, type);
txt->setHeight(i.getHeight());
txt->setWidth(i.getWidth());
std::cout << "New width: " << txt->getWidth()
<< " source: " << txt->getSrcWidth() << "\n";
// _loadImages requires a list of image pointers
ConstImagePtrList lst(1, &i);
txt->_loadImages(lst);
/*
if(hasLoad != 15)
{
hasLoad = 15;
std::cout << "cpp_loadMemImage: name=" << name << " type=" << type
<< " data size=" << size << "\n";
std::cout << "Data stream size=" << m->size() << "\n";
std::cout << "Image: buffer size=" << i.getSize() << " dimensions="
<< i.getWidth() << "x" << i.getHeight() << " mipmaps="
<< i.getNumMipmaps() << "\n";
std::cout << "Image format: " << i.getFormat() << "\n";
//const char* filename = "output.png";
//i.save(filename);
//std::cout << "Saved file to " << filename << "\n";
}
*/
}
/* Code currently not in use
// We need this later for animated meshes. Boy will this be a mess.
extern "C" void* cpp_setupSkeleton(char* name)
{
SkeletonPtr skel = SkeletonManager::getSingleton().create(
name, "Closet", true);
skel->load();
// Create all bones at the origin and unrotated. This is necessary
// since our submeshes each have their own model space. We must
// move the bones after creating an entity, then copy this entity.
return (void*)skel->createBone();
}
// Use this later when loading textures directly from NIF files
extern "C" void cpp_createTexture(char* name, uint width, uint height)
{
TexturePtr texture = TextureManager::getSingleton().createManual(
name, // name
"ManualTexture", // group
TEX_TYPE_2D, // type
width, hight, // width & height
0, // number of mipmaps
PF_BYTE_BGRA, // pixel format
TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for
// textures updated very often (e.g. each frame)
// Get the pixel buffer
HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
// Lock the pixel buffer and get a pixel box
pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD!
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
uint8* pDest = static_cast<uint8*>(pixelBox.data);
// Fill in some pixel data. This will give a semi-transparent blue,
// but this is of course dependent on the chosen pixel format.
for (size_t j = 0; j < 256; j++)
for(size_t i = 0; i < 256; i++)
{
*pDest++ = 255; // B
*pDest++ = 0; // G
*pDest++ = 0; // R
*pDest++ = 127; // A
}
// Unlock the pixel buffer
pixelBuffer->unlock();
// Create a material using the texture
MaterialPtr material = MaterialManager::getSingleton().create(
"DynamicTextureMaterial", // name
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
material->getTechnique(0)->getPass(0)->createTextureUnitState("DynamicTexture");
material->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA);
}
extern "C" void *cpp_insertBone(char* name, void* rootBone, int index)
{
return (void*) ( ((Bone*)rootBone)->createChild(index) );
}
*/

@ -0,0 +1,58 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_ogre.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
#include <Ogre.h>
#include <iostream>
#include <Ogre.h>
#include <OgreConfigFile.h>
#include <OgreStringConverter.h>
#include <OgreException.h>
#include <OgreOverlayElementFactory.h>
#include <OgreArchive.h>
#include <OgreArchiveFactory.h>
#include <OIS/OIS.h>
using namespace Ogre;
RenderWindow* mWindow;
Root *mRoot;
SceneManager *mSceneMgr;
Camera *mCamera;
Viewport *vp;
OIS::InputManager *mInputManager;
OIS::Mouse *mMouse;
OIS::Keyboard *mKeyboard;
// Root node for all objects added to the scene. This is rotated so
// that the OGRE coordinate system matches that used internally in
// Morrowind.
SceneNode *root;
// Include the other parts of the code, and make one big object file.
#include "cpp_framelistener.cpp"
#include "cpp_bsaarchive.cpp"
#include "cpp_interface.cpp"
#include "cpp_overlay.cpp"

@ -0,0 +1,79 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_overlay.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
OverlayManager *om;
Overlay *owl;
PanelOverlayElement *cont;
int visible;
void crap(char *p, ColourValue v)
{
std::cerr << p << ": " << v.getAsRGBA() << "\n";
}
extern "C" void cpp_debug(int i)
{
std::cerr << "Running cpp_debug(" << i << ")\n";
if(om)
{
if(visible)
{
visible = 0;
owl->hide();
}
else
{
visible = 1;
owl->show();
}
return;
}
om = OverlayManager::getSingletonPtr();
owl = om->create("HUD");
OverlayElement *e = om->createOverlayElementFromFactory("Panel", "Hopp");
cont = (PanelOverlayElement*)e;
cont->setDimensions(0.3, 0.5);
cont->setPosition(0.1, 0.2);
cont->setMaterialName(LASTNAME);
/*
* I suggest creating a custom material based on a "live" texture,
* where we can specify alpha transparency and draw everything
* ourselves. (Reinvent the wheel at every chance! :)
*/
MaterialPtr material = MaterialManager::getSingleton().getByName(LASTNAME);
std::cerr << "Material " << LASTNAME << "\n";
Pass* pass = material->getTechnique(0)->getPass(0);
pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
owl->add2D(cont);
owl->show();
visible = 1;
}

@ -0,0 +1,309 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (meshloader.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module ogre.meshloader;
import std.stdio;
import std.stream;
import nif.nif;
import nif.record;
import core.resource;
import ogre.bindings;
import util.uniquename;
/*
There are some problems that will have to be looked into later:
- Some meshes crash Ogre when shadows are turned on. (Not tested in
newer versions of Ogre). Shadows are completely disabled for now.
- There are obviously some boundry problems, some times the mesh
disappears even though some part of it is inside the screen. This
is especially a problem with animated meshes, since the animation
might step outside the original bounding box.
*/
MeshLoader meshLoader;
struct MeshLoader
{
// Not sure how to handle the bounding box, just ignore it for now.
char[] baseName; // NIF file name. Used in scene node names etc. so
// that we can easier identify where they came
// from.
// Load a NIF mesh. Assumes nifMesh is already opened. This creates
// a "template" scene node containing this mesh, and removes it from
// the main scene. This node can later be "cloned" so that multiple
// instances of the object can be inserted into the world without
// inserting the mesh more than once.
NodePtr loadMesh(char[] name)
{
baseName = name;
// Check if the first record is a node
Node n = cast(Node) nifMesh.records[0];
if(n is null)
{
// TODO: Figure out what to do in this case, we should
// probably throw.
writefln("NIF '%s' IS NOT A MESH", name);
return null;
}
// Get a fresh SceneNode and detatch it from the root. We use this
// as the base for our mesh.
NodePtr base = cpp_getDetachedNode();
// Recursively insert nodes (don't rotate the first node)
insertNode(n, base, true);
return base;
}
private:
void insertNode(Node data, NodePtr parent, bool noRot = false)
{
// Skip hidden nodes for now.
if(data.flags & 0x01) return;
// Create a scene node, move and rotate it into place. The name
// must be unique, however we might have to recognize some special
// names later, in order to attach arms and legs on NPCs
// etc. Always ignore transformation of the first node? This is a
// problem, I don't know when to do this and when not to. Neither
// is always right. Update: It looks like we should always set
// noRot to false in exterior cells.
NodePtr node = cpp_createNode(UniqueName(data.name).ptr, &data.trafo,
parent, cast(int)noRot);
// Handle any general properties here
// Call functions that do node-specific things, like handleNiNode
// or handleNiTriShape.
{
NiNode n = cast(NiNode)data;
if(n !is null)
// Handle the NiNode, and any children it might have
handleNiNode(n, node);
}
{
NiTriShape n = cast(NiTriShape)data;
if(n !is null)
// Trishape, with a mesh
handleNiTriShape(n, node);
}
}
void handleNiNode(NiNode data, NodePtr node)
{
// Handle any effects here
// In the cases where meshes have skeletal animations, we must
// insert the children as bones in a skeleton instead, like we
// originally did for all nodes. Update: A much better way is to
// first insert the nodes normally, and then create the
// skeleton. The nodes can then be moved one by one over to the
// appropriate bones.
// Loop through children
foreach(Node n; data.children)
insertNode(n, node);
}
void handleNiTriShape(NiTriShape shape, NodePtr node)
{
char[] texture;
char[] material;
char[] newName = UniqueName(baseName);
NiMaterialProperty mp;
// Scan the property list for textures
foreach(Property p; shape.properties)
{
// NiTexturingProperty block
{
NiTexturingProperty t = cast(NiTexturingProperty) p;
if(t !is null && t.textures[0].inUse)
{
// Ignore all other options for now
NiSourceTexture st = t.textures[0].texture;
if(st.external)
{
// Find the resource for this texture
TextureIndex ti = resources.lookupTexture(st.filename);
// Insert a manual loader into OGRE
// ti.load();
// Get the resource name. We use getNewName to get
// the real texture name, not the lookup
// name. NewName has been converted to .dds if
// necessary, to match the file name in the bsa
// archives.
texture = ti.getNewName();
}
else
{
// Internal textures
texture = "BLAH";
writefln("Internal texture, cannot read this yet.");
writefln("Final resource name: '%s'", texture);
}
continue;
}
}
// NiMaterialProperty block
{
NiMaterialProperty tmp = cast(NiMaterialProperty) p;
if(tmp !is null)
{
if(mp !is null) writefln("WARNING: More than one material!");
mp = tmp;
material = newName;
continue;
}
//writefln("Unknown property found: ", p);
}
}
//if(!texture.length) writefln("No texture found");
// Get a pointer to the texture name
char* texturePtr;
if(texture.length) texturePtr = toStringz(texture);
else texturePtr = null;
// Create the material
if(material.length)
cpp_createMaterial(material.ptr, mp.ambient.array.ptr, mp.diffuse.array.ptr,
mp.specular.array.ptr, mp.emissive.array.ptr,
mp.glossiness, mp.alpha, texturePtr);
else if(texturePtr)
{
// Texture, but no material. Make a default one.
writefln("WARNING: Making default material for %s", texture);
float[3] zero;
float[3] one;
zero[] = 0.0;
one[] = 1.0;
cpp_createMaterial(newName.ptr, one.ptr, one.ptr, zero.ptr, zero.ptr, 0.0, 1.0,
texturePtr);
}
with(shape.data)
{
//writefln("Number of vertices: ", vertices.length);
float *normalsPtr;
float *colorsPtr;
float *uvsPtr;
short *facesPtr;
// Point pointers into the correct arrays, if they are present. If
// not, the pointers retain their values of null.
if(normals.length) normalsPtr = normals.ptr;
if(colors.length) colorsPtr = colors.ptr;
if(uvlist.length) uvsPtr = uvlist.ptr;
if(triangles.length) facesPtr = triangles.ptr;
float
minX = float.infinity,
minY = float.infinity,
minZ = float.infinity,
maxX = -float.infinity,
maxY = -float.infinity,
maxZ = -float.infinity;
// Calculate the bounding box. TODO: This is really a hack.
for( int i; i < vertices.length; i+=3 )
{
if( vertices[i] < minX ) minX = vertices[i];
if( vertices[i+1] < minY ) minY = vertices[i+1];
if( vertices[i+2] < minZ) minZ = vertices[i+2];
if( vertices[i] > maxX) maxX = vertices[i];
if( vertices[i+1] > maxY) maxY = vertices[i+1];
if( vertices[i+2] > maxZ) maxZ = vertices[i+2];
}
cpp_createMesh(newName.ptr, vertices.length, vertices.ptr,
normalsPtr, colorsPtr, uvsPtr, triangles.length, facesPtr,
radius, material.ptr, minX, minY, minZ, maxX, maxY, maxZ,
node);
}
}
}
/*
// Create a skeleton and get the root bone (index 0)
BonePtr bone = cpp_setupSkeleton(name);
// Reset the bone index. The next bone to be created has index 1.
boneIndex = 1;
// Create a mesh and assign the skeleton to it
MeshPtr mesh = cpp_setupMesh(name);
// Loop through the nodes, creating submeshes, materials and
// skeleton bones in the process.
handleNode(node, bone, mesh);
// Create the "template" entity
EntityPtr entity = cpp_createEntity(name);
// Loop through once again, this time to set the right
// transformations on the entity's SkeletonInstance. The order of
// children will be the same, allowing us to reference bones using
// their boneIndex.
int lastBone = boneIndex;
boneIndex = 1;
transformBones(node, entity);
if(lastBone != boneIndex) writefln("WARNING: Bone number doesn't match");
if(!hasBBox)
cpp_setMeshBoundingBox(mesh, minX, minY, minZ, maxX, maxY, maxZ);
return entity;
}
void handleNode(Node node, BonePtr root, MeshPtr mesh)
{
// Insert a new bone for this node
BonePtr bone = cpp_insertBone(node.name, root, boneIndex++);
}
void transformBones(Node node, EntityPtr entity)
{
cpp_transformBone(entity, &node.trafo, boneIndex++);
NiNode n = cast(NiNode)node;
if(n !is null)
foreach(Node nd; n.children)
transformBones(nd, entity);
}
*/

@ -0,0 +1,133 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (ogre.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module ogre.ogre;
import core.resource;
import ogre.bindings;
import util.uniquename;
import std.stdio;
import esm.defs;
class OgreException : Exception
{
this(char[] msg) {super("OgreException: " ~ msg);}
static void opCall(char[] msg)
{
throw new OgreException(msg);
}
}
// Place a mesh in the 3D scene graph, at the given
// location/scale. Returns a node pointer to the inserted object.
NodePtr placeObject(MeshIndex mesh, Placement *pos, float scale)
{
// Get a scene node for this object. mesh.getNode() will either load
// it from file or BSA archive, or give us a handle if it is already
// loaded.
// This must be called BEFORE UniqueName below, because it might
// possibly use UniqueName itself and overwrite the data
// there. (That was a fun bug to track down...)
NodePtr node = mesh.getNode();
// Let us insert a copy
char[] name = UniqueName(mesh.getName);
return cpp_insertNode(node, name.ptr, pos, scale);
}
NodePtr attachLight(NodePtr parent, Color c, float radius)
{
return cpp_attachLight(UniqueName("_light").ptr, parent,
c.red/255.0, c.green/255.0, c.blue/255.0,
radius);
}
// If 'true' then we must call cpp_cleanup() on exit.
bool ogreSetup = false;
// Make sure we clean up
static ~this()
{
cleanupOgre();
}
// Loads ogre configurations, creats the root, etc.
void setupOgre()
{
// Later we will send some config info from core.config along with
// this function
if(cpp_configure()) OgreException("Configuration abort");
cpp_initWindow();
// We set up the scene manager in a separate function, since we
// might have to do that for every new cell later on, and handle
// exterior cells differently, etc.
cpp_makeScene();
ogreSetup = true;
}
void setAmbient(Color amb, Color sun, Color fog, float density)
{
cpp_setAmbient(amb.red/255.0, amb.green/255.0, amb.blue/255.0,
sun.red/255.0, sun.green/255.0, sun.blue/255.0);
// Calculate fog distance
// TODO: Mesh with absolute view distance later
float fhigh = 4500 + 9000*(1-density);
float flow = 200 + 2000*(1-density);
cpp_setFog(fog.red/255.0, fog.green/255.0, fog.blue/255.0, 200, fhigh);
}
// Jump into the OGRE rendering loop. Everything should be loaded and
// done before calling this.
void startRendering()
{
// Kick OGRE into gear
cpp_startRendering();
}
// Cleans up after OGRE. Resets things like screen resolution and
// mouse control.
void cleanupOgre()
{
if(ogreSetup)
cpp_cleanup();
ogreSetup = false;
}
// Gives the placement of an item in the scene (position and
// orientation). It must have this exact structure since we also use
// it when reading ES files.
align(1) struct Placement
{
float[3] position;
float[3] rotation;
}

@ -0,0 +1,13 @@
# Defines plugins to load
# Define plugin folder
PluginFolder=/usr/lib/OGRE
# Define plugins
Plugin=RenderSystem_GL
Plugin=Plugin_ParticleFX
Plugin=Plugin_BSPSceneManager
Plugin=Plugin_OctreeSceneManager
# Plugin=Plugin_CgProgramManager

@ -0,0 +1,23 @@
# Resource locations to be added to the 'boostrap' path
# This also contains the minimum you need to use the Ogre example framework
[Bootstrap]
#Zip=ogre/media/packs/OgreCore.zip
# Resource locations to be added to the default path
[General]
#FileSystem=media
#FileSystem=ogre/media/fonts
#FileSystem=ogre/media/materials/programs
#FileSystem=ogre/media/materials/scripts
#FileSystem=ogre/media/materials/textures
#FileSystem=ogre/media/models
#FileSystem=ogre/media/overlays
#FileSystem=ogre/media/particle
#FileSystem=ogre/media/gui
#Zip=ogre/media/packs/cubemap.zip
#Zip=ogre/media/packs/cubemapsJS.zip
#Zip=ogre/media/packs/dragon.zip
#Zip=ogre/media/packs/fresneldemo.zip
#Zip=ogre/media/packs/ogretestmap.zip
#Zip=ogre/media/packs/skybox.zip
BSA=internal

@ -0,0 +1,597 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (celldata.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module scene.celldata;
import std.stdio;
public import esm.esmmain;
import core.memory;
import util.reglist;
import ogre.ogre;
import ogre.bindings;
import sound.audio;
import scene.player;
// Base properties common to all live objects. Currently extremely
// sketchy.
struct LiveObjectBase
{
// Should this stuff be in here?
bool disabled; // Disabled in game
bool deleted; // Deleted relative to plugin file
// Used for objects created in-game, like custom potions or
// enchanted items. These can be completely deleted. It is also used
// for creatures created from leveled lists.
bool transient;
// Is this a door that teleports to another cell?
bool teleport;
// Scale, 1.0 is normal.
float scale;
// Owner of an object / activator
char[] owner;
// A global variable? Don't know what it's used for.
char[] global;
// Reference to a soul trapped creature?
char[] soul;
// Faction owner? Rank?
char[] cnam;
int indx;
// Magic value / health / uses of an item?
float xchg;
// ?? See comment below
int intv, nam9;
// Destination for a door
Placement destPos;
char[] destCell;
// Lock level?
int fltv;
// For locked doors and containers
char[] key, trap;
// Position in 3D world
Placement pos;
// ??
byte unam;
// Don't even get me started on script-related issues
}
// Generic version of a "live" object
struct GenLive(T)
{
// This HAS to be a pointer, since we load the LOB after copying the
// LiveWhatever into the linked list.
LiveObjectBase *base;
T *m;
}
alias GenLive!(Static) LiveStatic;
alias GenLive!(NPC) LiveNPC;
alias GenLive!(Activator) LiveActivator;
alias GenLive!(Potion) LivePotion;
alias GenLive!(Apparatus) LiveApparatus;
alias GenLive!(Ingredient) LiveIngredient;
alias GenLive!(Armor) LiveArmor;
alias GenLive!(Weapon) LiveWeapon;
alias GenLive!(Book) LiveBook;
alias GenLive!(Clothing) LiveClothing;
alias GenLive!(Tool) LiveTool;
alias GenLive!(Creature) LiveCreature;
alias GenLive!(Door) LiveDoor;
alias GenLive!(Misc) LiveMisc;
alias GenLive!(Container) LiveContainer;
struct LiveLight
{
LiveObjectBase *base;
Light *m;
NodePtr lightNode;
SoundInstance *loopSound;
// Lifetime left, in seconds?
float time;
}
class CellData
{
private:
RegionManager reg;
public:
InteriorCell *inCell;
ExteriorCell *exCell;
// Ambient light data
AMBIStruct ambi;
// Water height
int water;
// Linked lists that hold references to all the objects in the cell
RegionList!(LiveStatic) statics;
RegionList!(LiveMisc) miscItems;
RegionList!(LiveLight) lights;
RegionList!(LiveLight) statLights;
RegionList!(LiveNPC) npcs;
RegionList!(LiveContainer) containers;
RegionList!(LiveDoor) doors;
RegionList!(LiveActivator) activators;
RegionList!(LivePotion) potions;
RegionList!(LiveApparatus) appas;
RegionList!(LiveIngredient) ingredients;
RegionList!(LiveArmor) armors;
RegionList!(LiveWeapon) weapons;
RegionList!(LiveBook) books;
RegionList!(LiveTool) tools;
RegionList!(LiveClothing) clothes;
RegionList!(LiveCreature) creatures;
this(RegionManager r)
{
reg = r;
killCell(); // Make sure all data is initialized.
}
// Kills all data and initialize the object for reuse.
void killCell()
{
inCell = null;
exCell = null;
// Reset the lists
statics.init(reg);
miscItems.init(reg);
lights.init(reg);
statLights.init(reg);
npcs.init(reg);
containers.init(reg);
doors.init(reg);
activators.init(reg);
potions.init(reg);
appas.init(reg);
ingredients.init(reg);
armors.init(reg);
weapons.init(reg);
books.init(reg);
tools.init(reg);
clothes.init(reg);
creatures.init(reg);
// Write some statistics
//writefln(reg);
reg.freeAll();
}
// Load an exterior cell
void loadExtCell(int x, int y)
{
exCell = cells.getExt(x, y);
writefln("Name: %s", exCell.name);
writefln("Region: %s", exCell.region.id);
esFile.restoreContext(exCell.context, reg);
// Always for exterior cells
water = 0;
Color mapColor;
if(esFile.isNextSub("NAM5"))
esFile.readHExact(&mapColor, mapColor.sizeof);
loadReferences();
const float cellWidth = 8192;
// TODO/FIXME: This is temporary
playerData.position.position[0] = x*cellWidth;
playerData.position.position[1] = y*cellWidth;
playerData.position.position[2] = 6000;
playerData.position.rotation[] = 0;
}
// Load an interior cell
void loadIntCell(char[] cName)
{
inCell = cells.getInt(cName);
/*
writefln("Cell id: '%s'", cName);
writefln("Cell name: '%s'", cell.id);
*/
esFile.restoreContext(inCell.context, reg);
// TODO: Read this crap in loadcell.d
if(esFile.isNextSub("INTV") || esFile.isNextSub("WHGT"))
water = esFile.getHInt();
//writefln("Water height: ", water);
if(inCell.flags & CellFlags.QuasiExt)
{
Region* reg = esFile.getHNOPtr!(Region)("RGNN", regions);
if(reg) writefln("Cell has region %s", reg.id);
else writefln("No region");
// Determine weather from this region
}
else
{
// Only for interior cells
esFile.readHNExact(&ambi, ambi.sizeof, "AMBI");
}
/*
writefln("Ambient light: ", ambi.ambient.array);
writefln("Sunlight: ", ambi.sunlight.array);
writefln("Fog color: ", ambi.ambient.array);
writefln("Fog density: ", ambi.fogDensity);
*/
loadReferences();
}
private void loadReferences()
{
with(esFile)
{
// Now read all the references
while(hasMoreSubs)
{
// Number of references in the cell? Maximum once in each
// cell, but not always at the beginning, and not always
// right. In other words, completely useless. Strange
// people...
getHNOInt("NAM0", 0);
int refnum = getHNInt("FRMR"); // Reference number
char[] refr = getHNString("NAME"); // ID of object
// Used internally for optimizing
bool container;
bool door;
bool stat;
bool activator;
// Identify the referenced object by looking up the id
// string 'ref' in the global database of all
// cell-referencable objects.
Item itm = cellRefs.lookup(refr);
ItemT it = ItemT(itm);
// Create a new base object that holds all our reference
// data.
LiveObjectBase *base = reg.newT!(LiveObjectBase)();
// These should be ordered according to how commonly they
// occur and how large the reference lists are.
// Static mesh - probably the most common
if(Static *s = it.getStatic())
{
LiveStatic ls;
ls.m = s;
ls.base = base;
statics.insert(ls);
stat = true;
}
// Misc items are also pretty common
else if(Misc *m = it.getMisc())
{
LiveMisc ls;
ls.m = m;
ls.base = base;
miscItems.insert(ls);
}
// Lights and containers too
else if(Light *m = it.getLight())
{
LiveLight ls;
ls.m = m;
ls.base = base;
ls.time = m.data.time;
if(m.data.flags&Light.Flags.Carry)
lights.insert(ls);
else
statLights.insert(ls);
}
else if(Container *c = it.getContainer())
{
LiveContainer ls;
ls.m = c;
ls.base = base;
containers.insert(ls);
container = true;
}
// From here on I doubt the order will matter much
else if(Door *d = it.getDoor())
{
LiveDoor ls;
ls.m = d;
ls.base = base;
doors.insert(ls);
door = true;
}
// Activator?
else if(Activator *a = it.getActivator())
{
LiveActivator ls;
ls.m = a;
ls.base = base;
activators.insert(ls);
activator = true;
}
// NPC?
else if(NPC *n = it.getNPC())
{
LiveNPC ls;
ls.m = n;
ls.base = base;
npcs.insert(ls);
}
else if(Potion *p = it.getPotion())
{
LivePotion ls;
ls.m = p;
ls.base = base;
potions.insert(ls);
}
else if(Apparatus *m = it.getApparatus())
{
LiveApparatus ls;
ls.m = m;
ls.base = base;
appas.insert(ls);
}
else if(Ingredient *m = it.getIngredient())
{
LiveIngredient ls;
ls.m = m;
ls.base = base;
ingredients.insert(ls);
}
else if(Armor *m = it.getArmor())
{
LiveArmor ls;
ls.m = m;
ls.base = base;
armors.insert(ls);
}
else if(Weapon *m = it.getWeapon())
{
LiveWeapon ls;
ls.m = m;
ls.base = base;
weapons.insert(ls);
}
else if(Book *m = it.getBook())
{
LiveBook ls;
ls.m = m;
ls.base = base;
books.insert(ls);
}
else if(Clothing *m = it.getClothing())
{
LiveClothing ls;
ls.m = m;
ls.base = base;
clothes.insert(ls);
}
else if(Tool *m = it.getPick())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Tool *m = it.getProbe())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Tool *m = it.getRepair())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Creature *c = it.getCreature())
{
LiveCreature ls;
ls.m = c;
ls.base = base;
creatures.insert(ls);
}
else if(LeveledCreatures *l = it.getCreatureList)
{
// Create a creature, based on current player level.
LiveCreature ls;
ls.m = l.instCreature(playerData.level);
if(ls.m != null)
{
ls.base = base;
creatures.insert(ls);
}
}
else fail(format(" UNKNOWN REFERENCE! Type ", cast(int)it.i.type));
// Now that the object has found it's place, load data
// into base.
with(*base)
{
// ALL variables must be initialized here
disabled = false;
deleted = false;
transient = false;
teleport = false;
// Scale
scale = getHNOFloat("XSCL", 1.0);
// Statics only need the position data. Skip the
// unneeded calls to isNextSub() as an optimization.
if(stat) goto readpos;
// An NPC that owns this object (and will get angry if
// you steal it)
owner = getHNOString("ANAM");
// ??? I have no idea, link to a global variable
global = getHNOString("BNAM");
// ID of creature trapped in a soul gem (?)
soul = getHNOString("XSOL");
// ?? CNAM has a faction name, might be for
// objects/beds etc belonging to a faction.
cnam = getHNOString("CNAM");
// INDX might be PC faction rank required to use the
// item? Sometimes is -1.
if(cnam.length) indx = getHNInt("INDX");
// Possibly weapon health, number of uses left or
// weapon magic charge?
xchg = getHNOFloat("XCHG", 0.0);
// I have no idea, these are present some times, often
// along with owner (ANAM) and sometimes otherwise. Is
// NAM9 is always 1? INTV is usually one, but big for
// lights. Perhaps something to do with remaining
// light "charge". I haven't tried reading it as a
// float in those cases.
intv = getHNOInt("INTV", 0);
nam9 = getHNOInt("NAM9", 0);
// Present for doors that teleport you to another
// cell.
if(door && isNextSub("DODT"))
{
teleport = true;
readHExact(&destPos, destPos.sizeof);
// Destination cell (optitional?)
destCell = getHNOString("DNAM");
}
if(door || container)
{
// Lock level (I think)
fltv = getHNOInt("FLTV", 0);
// For locked doors and containers
key = getHNOString("KNAM");
trap = getHNOString("TNAM");
}
if(activator)
{
// Occurs ONCE in Morrowind.esm, for an activator.
unam = getHNOByte("UNAM", 0);
// Occurs in Tribunal.esm, eg. in the cell
// "Mournhold, Plaza Brindisi Dorom", where it has
// the value 100.
fltv = getHNOInt("FLTV", 0);
}
readpos:
// Position of this object within the cell
readHNExact(&pos, Placement.sizeof, "DATA");
// TODO/FIXME: Very temporary. Set player position at
// the first door we find.
if(door && !playerData.posSet)
{
playerData.posSet = true;
playerData.position = pos;
}
}
}
// Skip this? Large chance that the same file will be used for
// the next cell load, and the rest of our system can handle
// that without closing or reopening the file.
//close(); // Close the file
}
}
}
CellFreelist cellList;
// Structure used as a free list for cell data objects and their
// respective regions.
struct CellFreelist
{
// We love static arrays! And 100 cells should be enough for
// everybody :)
CellData[100] list;
uint next;
// TODO: Figure out a good size to use here as well.
CellData get()
{
if(next) return list[--next];
// Since these are reused, there's no waste in allocating on the
// stack here. Also, this is only semi-runtime (executed when
// loading a cell), thus a rare GC slow down is non-critical.
return new CellData(new RegionManager("CELL"));
}
void release(CellData r)
{
assert(next < list.length);
r.killCell();
list[next++] = r;
}
}

@ -0,0 +1,48 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (player.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module scene.player;
import ogre.ogre;
/*
* Contains essential data about the player and other current game
* data. This will be moved to script code.
*/
PlayerData playerData;
struct PlayerData
{
// Position and rotation. The rotation is not updated continuously,
// only the position.
Placement position;
// Just an example value, we use it for resolving leveled lists
// (lists that determine eg. what creatures to put in a cave based
// on what level the player is.)
short level = 5;
// Temp. way of selecting start point - used in celldata
bool posSet = false;
}

@ -0,0 +1,72 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (soundlist.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module scene.soundlist;
import esm.loadsoun;
import sound.audio;
SoundList soundScene;
// Has a list over all sounds currently playing in the
// scene. Currently only holds static, looping sounds, mainly
// torches. Later I will have to decide what to do with play-once
// sounds. Do I bother to update their position, or do I assume that
// once it starts playing, it is short enough that it doesn't matter?
// The last option is probably not viable, since some sounds can be
// very long.
struct SoundList
{
// TODO: This is really just a test, a hack. Will be replaced by a
// list or similar later.
SoundInstance list[50];
int index = 0;
// Get a sound instance from a Sound struct
static SoundInstance getInstance(Sound *s, bool loop=false)
{
const distFactor = 40.0; // Just guessing, really.
SoundInstance inst = s.sound.getInstance();
inst.setParams(s.data.volume/255.0,
s.data.minRange*distFactor,
s.data.maxRange*distFactor,
loop);
return inst;
}
SoundInstance *insert(Sound *snd, bool loop=false)
{
if(index == 50) return null;
SoundInstance *s = &list[index++];
*s = getInstance(snd, loop);
return s;
}
void update(float x, float y, float z)
{
foreach(ref s; list[0..index])
s.setPlayerPos(x,y,z);
}
}

@ -0,0 +1,72 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (audiere.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// This file exposes the C++ functions that deal directly with the
// sound library. The functions are implemented in cpp_audiere.cpp
module sound.audiere;
// A sound resource is the handle that represents the resource,
// ie. the file.
typedef void* AudiereResource;
// A sound instance is an instance of this resource. We can have
// several instances of the same file playing at once. Each instance
// has its own volume, file position, pitch, etc.
typedef void* AudiereInstance;
extern(C)
{
// Open the music device. Returns 0 on success, 1 on failure.
int cpp_openDevice();
// Open a new sound resource. Returns null on failure.
AudiereResource cpp_openSound(char* filename);
// Close a resource.
void cpp_closeSound(AudiereResource sound);
// Create an instance of a sound.
AudiereInstance cpp_createInstance(AudiereResource sound);
// Create an instance by streaming directly from file, and play it.
AudiereInstance cpp_playStream(char* filename, float volume);
// Destroy a previously created instance
void cpp_destroyInstance(AudiereInstance instance);
// Is this instance currently playing?
int cpp_isPlaying(AudiereInstance sound);
// Adjust parameters for this instance.
void cpp_setParams(AudiereInstance sound, float volume, float pan);
// Play a sound.
void cpp_playSound(AudiereInstance sound);
// Set repeat mode on
void cpp_setRepeat(AudiereInstance sound);
// Stop a sound
void cpp_stopSound(AudiereInstance sound);
}

@ -0,0 +1,46 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (audio.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module sound.audio;
public import sound.sfx;
public import sound.music;
import sound.audiere;
class SoundException : Exception
{
this(char[] caller, char[] msg) { super(caller ~ " SoundException: " ~ msg); }
}
MusicManager jukebox;
MusicManager battleMusic;
void initializeSound()
{
if(cpp_openDevice())
throw new SoundException("initializeSound()",
"Failed to initialize music device");
jukebox.initialize("Main");
battleMusic.initialize("Battle");
}

@ -0,0 +1,118 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_audiere.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
#include <audiere.h>
#include <iostream>
using namespace audiere;
using namespace std;
/*
* Sound, using Audiere. Creates a very simple C interface for
* opening, closing and manipulating sounds.
*/
AudioDevicePtr device;
extern "C" int cpp_openDevice()
{
device = OpenDevice("");
if (!device) return 1;
return 0;
}
// Opens a new sample buffer from a file
extern "C" SampleBuffer *cpp_openSound(char* filename)
{
SampleSourcePtr sample = OpenSampleSource(filename);
if(!sample) return NULL;
SampleBufferPtr buf = CreateSampleBuffer(sample);
buf->ref();
return buf.get();
}
// Delete a sample buffer
extern "C" void cpp_closeSound(SampleBuffer *buf)
{
buf->unref();
}
// Get an output stream from a sample buffer.
extern "C" OutputStream *cpp_createInstance(SampleBuffer *buf)
{
SampleSourcePtr sample = buf->openStream();
if(!sample) return NULL;
OutputStreamPtr sound = OpenSound(device, sample, false);
sound->ref();
return sound.get();
}
// Stream a file directly. Used for music.
extern "C" OutputStream *cpp_playStream(char* filename, float volume)
{
OutputStreamPtr sound = OpenSound(device, filename, true);
if(sound)
{
sound->ref();
sound->setVolume(volume);
sound->play();
}
return sound.get();
}
extern "C" void cpp_destroyInstance(OutputStream *sound)
{
sound->unref();
}
extern "C" int cpp_isPlaying(OutputStream *sound)
{
if(sound && sound->isPlaying()) return 1;
return 0;
}
extern "C" void cpp_setParams(OutputStream *sound, float vol, float pan)
{
if(sound)
{
sound->setVolume(vol);
sound->setPan(pan);
}
}
extern "C" void cpp_playSound(OutputStream *sound)
{
sound->play();
}
extern "C" void cpp_setRepeat(OutputStream *sound)
{
sound->setRepeat(true);
}
// Stop the sound
extern "C" void cpp_stopSound(OutputStream *sound)
{
if(sound) sound->stop();
}

@ -0,0 +1,277 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (music.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module sound.music;
import sound.audiere;
import sound.audio;
import core.config;
import core.resource;
// Simple music player, has a playlist and can pause/resume music.
struct MusicManager
{
private:
// How often we check if the music has died.
const float pollInterval = 2.0;
// Max file size to load. Files larger than this are streamed.
const int loadSize = 1024*1024;
// How much to add to the volume each second when fading
const float fadeInRate = 0.10;
const float fadeOutRate = 0.35;
// Volume
float volume, maxVolume;
// Time since last time we polled
float sumTime;
char[] name;
void fail(char[] msg)
{
throw new SoundException(name ~ " Jukebox", msg);
}
// Used when randomizing playlist
struct rndListStruct
{
char[] entry;
bool used;
}
rndListStruct[] rndList;
// TODO: How do we handle the play list? Randomize should be an
// option. We could also support things like M3U files, etc.
char[][] playlist;
int index; // Index of next song to play
bool musicOn;
AudiereInstance music;
// Which direction are we currently fading, if any
enum Fade { None = 0, In, Out }
Fade fading;
public:
// Initialize the jukebox
void initialize(char[] name)
{
this.name = name;
musicOn = false;
updateVolume();
}
// Get the new volume setting.
void updateVolume()
{
maxVolume = config.calcMusicVolume();
if(!musicOn) return;
// Adjust volume up to new setting, unless we are in the middle of
// a fade. Even if we are fading, though, the volume should never
// be over the max.
if(fading == Fade.None || volume > maxVolume) volume = maxVolume;
cpp_setParams(music, volume, 0.0);
}
// Give a music play list
void setPlaylist(char[][] pl)
{
playlist = pl;
index = 0;
randomize();
}
// Randomize playlist. An N^2 algorithm, but our current playlists
// are small. Improve it later if you really have to. If the
// argument is true, then we don't want the old last to be the new
// first.
private void randomize(bool checklast = false)
{
if(playlist.length < 2) return;
char[] last = playlist[0];
int left = playlist.length;
rndList.length = left;
foreach(int i, ref s; rndList)
{
s.used = false;
s.entry = playlist[i];
}
while(left--)
{
int index = rnd.randInt(0,left);
int i = 0;
foreach(ref s; rndList)
{
// Skip the ones we have used already
if(s.used) continue;
// Is this the correct index?
if(i == index)
{
s.used = true;
playlist[left] = s.entry;
break;
}
i++;
}
}
// Check that we don't replay the previous song, if the caller
// requested this.
if(checklast && playlist[0] == last)
{
playlist[0] = playlist[$-1];
playlist[$-1] = last;
}
}
// Skip to the next track
void playNext()
{
// If music is disabled, do nothing
if(!musicOn) return;
// Kill current track
if(music) cpp_destroyInstance(music);
music = null;
// No tracks to play?
if(!playlist.length) return;
// End of list? Randomize and start over
if(index == playlist.length)
{
randomize(true);
index = 0;
}
// Make sure the string is null terminated
assert(*(playlist[index].ptr+playlist[index].length) == 0);
music = cpp_playStream(playlist[index].ptr, volume);
if(!music) fail("Unable to start music track " ~ playlist[index]);
index++;
}
// Start playing the jukebox
void enableMusic()
{
if(!config.useMusic) return;
sumTime = 0;
musicOn = true;
volume = maxVolume;
fading = Fade.None;
playNext();
}
// Disable music
void disableMusic()
{
if(music) cpp_destroyInstance(music);
music = null;
musicOn = false;
}
// Pause current track
void pauseMusic()
{
fading = Fade.Out;
}
// Resume. Can also be called in place of enableMusic for fading in.
void resumeMusic()
{
if(!config.useMusic) return;
sumTime = 0;
volume = 0.0;
fading = Fade.In;
musicOn = true;
if(music) cpp_playSound(music);
else playNext();
}
// Add time since last frame to the counter. If the accumulated time
// has passed the polling interval, then check if the music has
// died. The Audiere library has a callback functionality, but it
// turned out not to be terribly reliable. Sometimes it was never
// called at all. So we have to poll at regular intervals .. :( This
// function is also used for fading.
void addTime(float time)
{
if(!musicOn) return;
sumTime += time;
if(sumTime > pollInterval)
{
sumTime = 0;
if(!cpp_isPlaying(music)) playNext();
}
if(fading)
{
// Fade the volume
if(fading == Fade.In)
{
volume += fadeInRate * time;
if(volume >= maxVolume)
{
fading = Fade.None;
volume = maxVolume;
}
}
else
{
assert(fading == Fade.Out);
volume -= fadeOutRate * time;
if(volume <= 0.0)
{
fading = Fade.None;
volume = 0.0;
// We are done fading out, disable music.
cpp_stopSound(music);
musicOn = false;
}
}
// Set the new volume
cpp_setParams(music, volume, 0.0);
}
}
}

@ -0,0 +1,202 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (sfx.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module sound.sfx;
import sound.audiere;
import sound.audio;
import core.config;
import core.resource;
import std.string;
// Handle for a sound resource. This struct represents one sound
// effect file (not a music file, those are handled differently
// because of their size.) From this handle we may get instances,
// which may be played and managed independently of each other. TODO:
// Let the resource manager worry about only opening one resource per
// file, when to kill resources, etc.
struct SoundFile
{
AudiereResource res;
char[] name;
bool loaded;
private int refs;
private void fail(char[] msg)
{
throw new SoundException(format("SoundFile '%s'", name), msg);
}
// Load a sound resource.
void load(char[] file)
{
// Make sure the string is null terminated
assert(*(file.ptr+file.length) == 0);
name = file;
loaded = true;
refs = 0;
res = cpp_openSound(file.ptr);
if(!res) fail("Failed to open sound file " ~ file);
}
// Get an instance of this resource.
SoundInstance getInstance()
{
SoundInstance si;
si.owner = this;
si.inst = cpp_createInstance(res);
if(!si.inst) fail("Failed to instantiate sound resource");
refs++;
return si;
}
// Return the sound instance when you're done with it
private void returnInstance(AudiereInstance inst)
{
refs--;
cpp_destroyInstance(inst);
if(refs == 0) unload();
}
// Unload the resource.
void unload()
{
loaded = false;
cpp_closeSound(res);
}
}
struct SoundInstance
{
AudiereInstance inst;
SoundFile *owner;
float volume, min, max;
float xx, yy, zz; // 3D position
bool playing;
bool repeat;
// Return this instance to the owner
void kill()
{
owner.returnInstance(inst);
}
// Start playing a sound.
void play()
{
playing = true;
cpp_playSound(inst);
if(repeat) cpp_setRepeat(inst);
}
// Go buy a cookie
void stop()
{
cpp_stopSound(inst);
playing = false;
}
// Set parameters such as max volume and range
void setParams(float volume, float minRange, float maxRange, bool repeat=false)
in
{
assert(volume >= 0 && volume <= 1.0, "Volume out of range");
}
body
{
this.volume = volume;
min = minRange;
max = maxRange;
this.repeat = repeat;
playing = false;
}
// Set 3D position of sound
void setPos(float x, float y, float z)
{
xx = x;
yy = y;
zz = z;
}
// Currently VERY experimental, panning disabled. At some point we
// will likely switch to OpenAL with built-in 3D sound and dump this
// entirely.
void setPlayerPos(float x, float y, float z)
{
//writef("{%s %s %s} ", x, y, z);
// Distance squared
x -= xx;
y -= yy;
z -= zz;
//writef("[%s %s %s] ", x, y, z);
float r2 = (x*x + y*y + z*z);
//writefln(r2, " (%s)", max*max);
// If outside range, disable
if(r2 > max*max)
{
// We just moved out of range
if(playing)
{
//writefln("Out of range");
stop();
}
}
else
{
// We just moved into range
if(!playing)
{
//writefln("In range!");
play();
}
}
if(!playing) return;
// Invert distance
if(r2 < 1) r2 = 1;
else r2 = 1/r2;
float vol = 2*r2*min*min;
float pan = 0;//80*x*r2;
//writefln("x=%s, vol=%s, pan=%s", x, vol, pan);
if(vol>1.0) vol = 1.0;
if(pan<-1.0) pan = -1.0;
else if(pan > 1.0) pan = 1.0;
//writefln("vol=", vol, " volume=", vol*volume*config.calcSfxVolume());
cpp_setParams(inst, vol*volume*config.calcSfxVolume(), pan);
}
}

@ -0,0 +1,155 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (random.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module util.random;
private import std.date;
private import std.random;
abstract class Random
{
static const double scale = 1.0/uint.max;
// Initialize from current time
this() { initialize(); }
// Initialize from parameter
this(long seed) { initialize(seed); }
// Reinitialize with seed
abstract void initialize(long newSeed);
// Produce random numbers between 0 and uint.max
abstract uint rand();
// Default is to initialize using current time as seed
void initialize() { initialize(getUTCtime()); }
// Produce a uniform random number between 0 and 1
double random()
{
return rand() * scale;
}
// Return a uniform random number between a and b. Works for the
// both the cases a < b and a > b.
double random(double a, double b)
in
{
// We only disallow nan parameters
assert(a <>= b);
}
out(result)
{
// Result must be in range m < result < M, where m=min(a,b) and M=max(a,b)
if(b > a) assert( (a < result) && (result < b) );
else if(a > b) assert( (b < result) && (result < a) );
}
body
{ return random()*(b - a) + a; }
// Return a random integer between a and b, inclusive.
int randInt(int a, int b)
out(result)
{
// Result must be in range m <= result <= M, where m=min(a,b) and M=max(a,b)
if(b >= a) assert( (a <= result) && (result <= b) );
else if(a > b) assert( (b <= result) && (result <= a) );
}
body
{
if(a>b) return cast(int)(rand() % (a-b+1)) + b;
else if(b>a) return cast(int)(rand() % (b-a+1)) + a;
else return a;
}
// Allow using "function call" syntax:
//
// Random ran = new Random1;
// double d = ran(); // Calls ran.random()
// d = ran(a,b); // Calls ran.random(a,b);
double opCall() { return random(); }
double opCall(double a, double b) { return random(a, b); }
// Return the seed originally given the object
long getSeed() {return origSeed;}
protected:
long origSeed; // Value used to seed the generator
}
// Uses the standard library generator
class DRand : Random
{
// Initialize from current time
this() { super(); }
// Initialize from parameter
this(long seed) { super(seed); }
uint rand() { return std.random.rand(); }
void initialize(long newSeed)
{
origSeed = newSeed;
rand_seed(newSeed, 0);
}
alias Random.initialize initialize;
unittest
{
struct tmp { import std.stdio; }
alias tmp.writef writef;
alias tmp.writefln writefln;
writefln("Unittest for class DRand");
DRand ran = new DRand;
writefln("Seed (from time) = ", ran.getSeed());
// Take a look at some numbers on screen
writefln("Some random numbers in [0,1]:");
for(int i=0; i<10; i++)
writef(" ", ran());
ran = new DRand(0);
writefln("\nNew seed (preset) = ", ran.getSeed());
// Take a look at some numbers on screen
writefln("Some random numbers in [0,1]:");
for(int i=0; i<10; i++)
writef(" ", ran());
// Check that all interfaces work (compile time)
ran();
ran(1,2);
ran.random();
ran.random(3,4);
ran.initialize();
ran.initialize(10);
ran.randInt(-3,5);
writefln("\nEnd of unittest for class DRand\n");
}
}

@ -0,0 +1,644 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (regions.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module util.regions;
private import std.gc;
private import std.string;
private import std.c.stdlib;
private import monster.util.string;
alias std.c.stdlib.malloc malloc;
class RegionManagerException : Exception
{
this(char[] msg, char[] name)
{ super( format("Memory Region Manager '%s': %s", name, msg ) ); }
}
// I tried this as an inner class, and it didn't even remotely work. I
// think inner class support in DMD sucks quite a bit. Works ok now
// (but still had some problems - template support in general is still
// pretty patchy.)
// UPDATE: The above comment was written a long time ago, it might
// work now. But if it ain't broke, don't fix it.
// A resizable array using a region for memory allocation. These can
// safely be resized back and forth without wasting large amounts of
// memory.
class RegionBuffer(T)
{
final:
private:
T[] buffer;
T[] inUse;
int reserve = 20;
RegionManager reg;
public:
// Size is initial size, reserve gives an indicator of the minimum
// amount to increase the array with at once.
this(RegionManager m, int size = 0, int reserve = 0)
{
reg = m;
if(reserve) this.reserve = reserve;
if(size) alloc(size);
}
new(uint size, RegionManager r)
{
return r.allocate(size).ptr;
}
delete(void *p) { assert(0); }
// Check if the buffer can hold 'size' more elements. If not,
// increase it. NOTE: This works even if data = null, and inUse
// is not. This allows us to make copy-on-resize slices.
private void alloc(uint size)
{
if(inUse.length + size <= buffer.length)
{
inUse = buffer[0..inUse.length+size];
return;
}
// Allocate a new array, with more entries than requested
buffer = reg.allocateT!(T)(buffer.length + size + reserve);
// Copy the old data
buffer[0..inUse.length] = inUse;
// Set up the array in use
inUse = buffer[0..(inUse.length+size)];
}
T opIndex(int i)
{
return inUse[i];
}
T opIndexAssign(T val, int i) { return inUse[i] = val; }
RegionBuffer opSlice(int x, int y)
{
RegionBuffer b = new(reg) RegionBuffer(reg,0,reserve);
b.inUse = inUse[x..y];
return b;
}
RegionBuffer opSlice() { return opSlice(0,inUse.length); }
RegionBuffer opCatAssign(T t)
{
alloc(1);
inUse[$-1] = t;
return this;
}
RegionBuffer opCatAssign(T[] t)
{
alloc(t.length);
inUse[($-t.length)..$] = t;
return this;
}
RegionBuffer opCatAssign(RegionBuffer t)
{
alloc(t.inUse.length);
inUse[($-t.inUse.length)..$] = t.inUse;
return this;
}
RegionBuffer opCat(T t) { return this.dup() ~= t; }
RegionBuffer opCat(T[] t) { return this.dup() ~= t; }
RegionBuffer opCat(RegionBuffer t) { return this.dup() ~= t; }
RegionBuffer dup()
{
RegionBuffer b = new(reg) RegionBuffer(reg, inUse.length, reserve);
b.inUse[] = inUse[];
return b;
}
uint length() { return inUse.length; }
void length(uint size)
{
// Grow array
if(size > inUse.length) alloc(size - inUse.length);
// Shrink array
else inUse = inUse[0..size];
}
// For direct access
T[] array() { return inUse; }
}
alias RegionBuffer!(char) RegionCharBuffer;
alias RegionBuffer!(int) RegionIntBuffer;
/*
* Region manager, a mark-sweep memory manager. You use it to allocate
* a lot of buffers, and when you are done with them you deallocate
* them all in one fell sweep.
*
*/
class RegionManager
{
final:
private:
// Identifying name, used for error messages.
char[] name;
// Use a default buffer size of one meg. Might change later.
const uint defaultBufferSize = 1024*1024;
// The size to use for new buffer.
uint bufferSize;
// Current amount of space that is 'lost' in unused end-of-buffer
// areas. Since we have proceeded to other buffers, this space will
// remain unused until freeAll is called.
uint lost;
ubyte[][] buffers; // Actual memory buffers
void *gcRanges[]; // List of ranges added to gc
ubyte[] left; // Slice of what is left of the current buffer
int currentBuffer; // Index into the next unused buffer
int currentRange; // Index into the next unused gcRanges entry
void fail(char[] msg)
{
throw new RegionManagerException(msg, name);
}
// We have run out of space, add a new buffer. I want this to be an
// exception rather than the rule, the default bufferSize should be
// large enough to prevent this from being necessary in most cases.
void nextBuffer()
{
/*
if(currentBuffer != 0)
writefln("WARNING: adding new buffer, number ", currentBuffer);
*/
// We should never have to increase the number of buffers!
if(currentBuffer >= buffers.length) fail("Out of buffers");
// Set up the buffer (if it is not already allocated.)
//buffers[currentBuffer].length = bufferSize;
if(buffers[currentBuffer].length != bufferSize)
{
assert(buffers[currentBuffer].length == 0);
ubyte *p = cast(ubyte*)malloc(bufferSize);
if(!p) fail("Malloc failed");
buffers[currentBuffer] = p[0..bufferSize];
}
// Remember the amount of space we just lost
lost += left.length;
// The entire buffer is available to us
left = buffers[currentBuffer];
// Point to the next unused buffer
currentBuffer++;
}
public:
this(char[] name = "", uint bufferSize = defaultBufferSize)
{
this.name = name;
this.bufferSize = bufferSize;
// Pointers are cheap. Let's preallocate these arrays big enough
// from the start. It's inflexible, but on purpose. We shouldn't
// NEED to grow them later, so we don't.
buffers.length = 100;
gcRanges.length = 10000;
freeAll();
}
~this()
{
// Don't leave any loose ends dangeling for the GC.
freeAll();
// Kill everything
foreach(ubyte[] arr; buffers)
free(arr.ptr);
delete buffers;
delete gcRanges;
}
// Allocates an array from the region.
ubyte[] allocate(uint size)
{
if(size > bufferSize)
fail(format("Tried to allocate %d, but maximum allowed allocation size is %d",
size, bufferSize));
// If the array cannot fit inside this buffer, get a new one.
if(size > left.length) nextBuffer();
ubyte[] ret = left[0..size];
left = left[size..$];
//writefln("Allocated %d, %d left in buffer", size, left.length);
return ret;
}
// Allocate an array and add it to the GC as a root region. This
// should be used for classes and other data that might contain
// pointers / class references to GC-managed data.
ubyte[] allocateGC(uint size)
{
if(currentRange >= gcRanges.length)
fail("No more available GC ranges");
ubyte[] ret = allocate(size);
// Add it to the GC
void *p = ret.ptr;
std.gc.addRange(p, p+ret.length);
gcRanges[currentRange++] = p;
return ret;
}
// Allocate an array of a specific type, eg. to allocate 4 ints, do
// int[] array = allocateT!(int)(4);
template allocateT(T)
{
T[] allocateT(uint number)
{
return cast(T[])allocate(number * T.sizeof);
}
}
alias allocateT!(int) getInts;
alias allocateT!(char) getString;
template newT(T)
{
T* newT()
{
return cast(T*)allocate(T.sizeof);
}
}
template allocateGCT(T)
{
T[] allocateGCT(uint number)
{
return cast(T[])allocateGC(number * T.sizeof);
}
}
// Copies an array of a given type
template copyT(T)
{
T[] copyT(T[] input)
{
T[] output = cast(T[]) allocate(input.length * T.sizeof);
output[] = input[];
return output;
}
}
alias copyT!(int) copy;
alias copyT!(char) copy;
// Copies a string and ensures that it is null-terminated
char[] copyz(char[] str)
{
char[] res = cast(char[]) allocate(str.length+1);
res[$-1] = 0;
res = res[0..$-1];
res[] = str[];
return res;
}
// Resets the region manager, but does not deallocate the
// buffers. To do that, delete the object.
void freeAll()
{
lost = 0;
currentBuffer = 0;
left = null;
// Free the ranges from the clutch of the GC's hand.
foreach(inout void *p; gcRanges[0..currentRange])
if(p)
{
std.gc.removeRange(p);
p = null;
}
currentRange = 0;
}
// Number of used buffers, including the current one
uint usedBuffers() { return currentBuffer; }
// Total number of allocated buffers
uint totalBuffers()
{
uint i;
// Count number of allocated buffers
while(i < buffers.length && buffers[i].length) i++;
return i;
}
// Total number of allocated bytes
uint poolSize()
{
return bufferSize * totalBuffers();
}
// Total number of bytes that are unavailable for use. (They might
// not be used, as such, if they are at the end of a buffer but the
// next buffer is in use.)
uint usedSize()
{
return currentBuffer*bufferSize - left.length;
}
// The total size of data that the user has requested.
uint dataSize()
{
return usedSize() - lostSize();
}
// Number of lost bytes
uint lostSize()
{
return lost;
}
// Total amount of allocated space that is not used
uint wastedSize()
{
return poolSize() - dataSize();
}
// Give some general info and stats
char[] toString()
{
return format("Memory Region Manager '%s':", name,
"\n pool %s (%d blocks)", comma(poolSize), totalBuffers,
"\n used %s", comma(usedSize),
"\n data %s", comma(dataSize),
"\n wasted %s (%.2f%%)", comma(wastedSize),
poolSize()?100.0*wastedSize()/poolSize():0,
"\n lost %s (%.2f%%)", comma(lost), usedSize()?100.0*lost/usedSize:0);
}
// Get a RegionBuffer of a given type
template getBuffer(T)
{
RegionBuffer!(T) getBuffer(int size = 0, int reserve = 0)
{
return new(this) RegionBuffer!(T)(this,size,reserve);
}
}
alias getBuffer!(char) getCharBuffer;
alias getBuffer!(int) getIntBuffer;
}
unittest
{
RegionManager r = new RegionManager("UT", 100);
// Test normal allocations first
assert(r.poolSize == 0);
assert(r.usedSize == 0);
assert(r.dataSize == 0);
assert(r.lostSize == 0);
assert(r.wastedSize == 0);
ubyte [] arr = r.allocate(30);
void *p = arr.ptr;
assert(p == r.buffers[0].ptr);
assert(arr.length == 30);
assert(r.poolSize == 100);
assert(r.usedSize == 30);
assert(r.dataSize == 30);
assert(r.lostSize == 0);
assert(r.wastedSize == 70);
arr = r.allocate(70);
assert(arr.ptr == p + 30);
assert(arr.length == 70);
assert(r.poolSize == 100);
assert(r.usedSize == 100);
assert(r.dataSize == 100);
assert(r.lostSize == 0);
assert(r.wastedSize == 0);
// Overflow the buffer
p = r.allocate(2).ptr;
assert(p == r.buffers[1].ptr);
assert(r.poolSize == 200);
assert(r.usedSize == 102);
assert(r.dataSize == 102);
assert(r.lostSize == 0);
assert(r.wastedSize == 98);
// Overflow the buffer and leave lost space behind
r.freeAll();
assert(r.poolSize == 200);
assert(r.usedSize == 0);
assert(r.dataSize == 0);
assert(r.lostSize == 0);
assert(r.wastedSize == 200);
r.allocate(1);
r.allocate(100);
assert(r.poolSize == 200);
assert(r.usedSize == 200);
assert(r.dataSize == 101);
assert(r.lostSize == 99);
assert(r.wastedSize == 99);
// Try to allocate a buffer that is too large
bool threw = false;
try r.allocate(101);
catch(RegionManagerException e)
{
threw = true;
}
assert(threw);
// The object should still be in a valid state.
// Try an allocation with roots
assert(r.currentRange == 0);
arr = r.allocateGC(50);
assert(r.poolSize == 300);
assert(r.usedSize == 250);
assert(r.dataSize == 151);
assert(r.lostSize == 99);
assert(r.wastedSize == 149);
assert(r.currentRange == 1);
assert(r.gcRanges[0] == arr.ptr);
int[] i1 = r.allocateGCT!(int)(10);
assert(i1.length == 10);
assert(r.poolSize == 300);
assert(r.usedSize == 290);
assert(r.dataSize == 191);
assert(r.lostSize == 99);
assert(r.currentRange == 2);
assert(r.gcRanges[1] == i1.ptr);
r.freeAll();
assert(r.currentRange == 0);
assert(r.poolSize == 300);
assert(r.usedSize == 0);
assert(r.dataSize == 0);
assert(r.lostSize == 0);
// Allocate some floats
float[] fl = r.allocateT!(float)(24);
assert(fl.length == 24);
assert(r.poolSize == 300);
assert(r.usedSize == 96);
assert(r.dataSize == 96);
assert(r.lostSize == 0);
// Copy an array
r.freeAll();
char[] stat = "hello little guy";
assert(r.dataSize == 0);
char[] copy = r.copy(stat);
assert(copy == stat);
assert(copy.ptr != stat.ptr);
copy[0] = 'a';
copy[$-1] = 'a';
assert(stat != copy);
assert(stat == "hello little guy");
assert(r.dataSize == stat.length);
// Test copyz()
r.freeAll();
stat = "ABC";
char *pp = cast(char*) r.copyz(stat).ptr;
assert(pp[2] == 'C');
assert(pp[3] == 0);
copy = r.copyz(stat);
assert(cast(char*)copy.ptr - pp == 4);
assert(pp[4] == 'A');
copy[0] = 'F';
assert(pp[3] == 0);
assert(pp[4] == 'F');
// Test of the buffer function
r.freeAll();
RegionBuffer!(int) b = r.getBuffer!(int)();
assert(b.inUse.length == 0);
assert(b.reserve == 20);
b.reserve = 5;
assert(b.length == 0);
b ~= 10;
b ~= 13;
assert(b.length == 2);
assert(b[0] == 10);
assert(b[1] == 13);
assert(b.buffer.length == 6);
p = b.buffer.ptr;
b.length = 0;
b.length = 6;
assert(p == b.buffer.ptr);
assert(b.length == 6);
b.length = 3;
assert(p == b.buffer.ptr);
assert(b.length == 3);
b[2] = 167;
b.length = 7;
assert(p != b.buffer.ptr); // The buffer was reallocated
assert(b.length == 7);
assert(b[2] == 167);
i1 = new int[5];
foreach(int v, inout int i; i1)
i = v;
p = b.buffer.ptr;
RegionBuffer!(int) a = b ~ i1;
assert(p != a.buffer.ptr); // A new buffer has been allocated
assert(p == b.buffer.ptr); // B should be unchanged
assert(a.length == b.length + i1.length);
for(int i=0; i < b.length; i++)
assert(a[i] == b[i]);
for(int i=0; i < i1.length; i++)
assert(a[i+7] == i);
// Make sure the arrays are truly different
a[5] = b[5] + 2;
assert(a[5] != b[5]);
// Make a slice
a = b[2..5];
assert(a.inUse.ptr == b.inUse.ptr+2);
a[1] = 4;
assert(a[1] == b[3]);
b[3] = -12;
assert(a[1] == b[3]);
a.length = a.length + 1;
assert(a.inUse.ptr != b.inUse.ptr+2);
a[1] = 4;
assert(a[1] != b[3]);
b[3] = -12;
assert(a[1] != b[3]);
r.freeAll();
}

@ -0,0 +1,215 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (reglist.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module util.reglist;
import util.regions;
import std.string;
class RegionListException : Exception
{
this(char[] msg)
{
super("RegionListException: " ~ msg);
}
}
/*
* Internal structure used below
*/
struct _aaNode(Value)
{
Value value;
_aaNode* next; // Next node
_aaNode* prev; // Previous node
}
/*
* Very simple linked list that uses a specified region for
* allocation.
*/
struct RegionList(Value)
{
private:
alias _aaNode!(Value) Node;
Node *head; // This is the head of the linked list (first element)
Node *tail; // New nodes are inserted here
uint totalNum; // Number of elements
RegionManager reg;
// Throw an exception
void fail(char[] msg)
{
msg = format("RegionList!(%s) exception: %s", typeid(Value).toString, msg);
throw new RegionListException(msg);
}
public:
alias Node* Iterator;
// Equivalent to calling remove() on all elements
void clear()
{
head = tail = null;
totalNum = 0;
}
// Reset the object and assign a new region manager
void init(RegionManager reg)
{
clear();
this.reg = reg;
}
Iterator insert(Value v)
{
Node* p = createNode();
if(tail)
{
// Insert node at the end of the list
assert(head != null);
tail.next = p;
}
else
{
// This is the first element to be inserted
assert(head == null);
head = p;
}
p.prev = tail;
tail = p;
p.value = v;
return p;
}
void remove(Iterator p)
{
// Remove from the list
if(p.next) p.next.prev = p.prev;
else // We're the tail
{
assert(tail == p);
tail = p.prev;
}
if(p.prev) p.prev.next = p.next;
else // We're head
{
assert(head == p);
head = p.next;
}
totalNum--;
}
// Create a new node and return it's pointer
private Node* createNode()
{
Node *p = cast(Node*) reg.allocate(Node.sizeof).ptr;
// Initialize pointers
p.next = null;
p.prev = null;
totalNum++;
return p;
}
// Loop through the nodes in the order they were inserted
int opApply(int delegate(ref Value v) del)
{
Node *p = head;
uint safeGuard = 0;
while(p != null)
{
assert(safeGuard++ < totalNum);
int i = del(p.value);
if(i) return i;
p = p.next;
}
return 0;
}
// Loop through the nodes in the order they were inserted
int opApply(int delegate(ref int ind, ref Value v) del)
{
Node *p = head;
int ind = 0;
while(p != null)
{
assert(ind < totalNum);
int i = del(ind, p.value);
ind++;
if(i) return i;
p = p.next;
}
return 0;
}
// Number of elements
uint length() { return totalNum; }
}
/*
unittest
{
RegionManager r = new RegionManager();
RegionList!(float) ll;
ll.reg = r;
assert(ll.length == 0);
ll.Iterator it = ll.insert(10.4);
writefln(r);
assert(ll.length == 1);
ll.insert(23);
it = ll.insert(6.3);
ll.insert(-1000);
writefln(r);
assert(ll.length == 4);
foreach(float f; ll) writefln(f);
ll.remove(it);
assert(ll.length == 3);
assert(r.dataSize() == 12*4);
foreach(int i, float f; ll) writefln(i, " ", f);
}
import std.stdio;
*/

@ -0,0 +1,96 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (uniquename.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module util.uniquename;
import std.stdio;
// Simple auxiliary code for creating unique names in succession,
// eg. "Screenshot_0000000001.png", "Screenshot_0000000002.png", etc..
// WARNING: Uses static storage, so do NOT use persistent references
// or slices to the strings produced here. Intended only for OGRE
// calls, where copies are always made anyway.
struct UniqueName
{
static this()
{
buffer.length = 100;
number = buffer[0..10];
number[] = "0000000000";
}
static char[] opCall(char[] addon = "")
{
// Point p at last digit
char *p = &number[$-1];
char *pbeg = number.ptr;
while(p != pbeg-1)
{
if(*p == '9')
{
// Carry over
*p = '0';
p--;
}
else
{
// Increase this digit and exit
(*p)++;
break;
}
}
assert(p != pbeg-1); // Overflow
int totLen = 10 + addon.length;
if(totLen >= buffer.length) totLen = buffer.length - 1;
if(totLen > 10) buffer[10..totLen] = addon;
buffer[totLen] = 0; // String must null-terminate
//writefln("UniqueName result=%s", buffer[0..totLen]);
return buffer[0..totLen];
}
private:
static char[] buffer;
static char[] number;
}
unittest
{
assert(UniqueName() == "0000000001");
assert(UniqueName() == "0000000002");
assert(UniqueName() == "0000000003");
assert(UniqueName() == "0000000004");
assert(UniqueName() == "0000000005");
assert(UniqueName() == "0000000006");
assert(UniqueName("blah") == "0000000007blah");
assert(UniqueName() == "0000000008");
assert(UniqueName() == "0000000009");
assert(UniqueName() == "0000000010");
assert(UniqueName() == "0000000011");
assert(UniqueName(" bottles of beer on the wall") ==
"0000000012 bottles of beer on the wall");
}
Loading…
Cancel
Save