Added trunk
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@4 ea6a568a-9f4f-0410-981a-c910a81bb256actorid
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;
|
@ -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(§ion) + 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…
Reference in New Issue