File freenukum-0.3.5.obscpio of Package freenukum

07070100000000000081A40000000000000000000000015FD8FA6800000046000000000000000000000000000000000000001B00000000freenukum-0.3.5/.gitignore/build
/apidoc
/target
**/*.rs.bk
/transdl/target
/transdl/Cargo.lock
07070100000001000081A40000000000000000000000015FD8FA68000000B8000000000000000000000000000000000000001800000000freenukum-0.3.5/AUTHORSThe following people have contributd to the existance of this game:

Wolfgang Silbermayr - wolfgang@silbermayr.at
  * programmer

George Vlahavas - vlahavas@gmail.com
  * icon creator
07070100000002000081A40000000000000000000000015FD8FA6800001205000000000000000000000000000000000000001D00000000freenukum-0.3.5/CHANGELOG.md# Freenukum Changelog

## [0.3.5] - 2020-12-15

### Added
- Implement horizontal electric arc

### Fixed
- Track and process controller x-axis events with value of 0, so that the
  hero movement stops when that event occurs

## [0.3.4] - 2020-12-13

### Added
- The hero can do somersaults when jumping with boots
- When the hero gets hurt while jumping, the jump is interrupted and the
  hero starts to fall down

### Fixed
- Shots collide with other actors slightly later, preventing things from
  being shot that are actually behind it
- Stop interaction with items when releasing the interaction button on a
  game controller
- Move surveillance screen into background
- Make the elevator act while invisible so that it can fully descend while
  not seen
- Prohibit hero from shooting while playing death animation


## [0.3.3] - 2020-12-10

### Added
- Implement basic handling of hero death

### Fixed
- Prevent drawing outside of the level size range which caused a crash
  at the range assertion


## [0.3.2] - 2020-12-09

### Fixed
- Fix loading of shootable wall
- Properly absorb shots when hitting actors
- Make score, particles and singleanimation act while invisible, so they
  don't reappear when they went out of view
- Make jumping mines absorb the shot


## [0.3.1] - 2020-12-08

### Added
- Show a disclaimer about the game version being under development
- Initial controller support (tested with 8BitDo SN30 Pro and Pro+)

### Fixed
- Fix fan range for pushing away the hero
- Fix a whitespace problem in the episode switch message
- Look for a TTF font on in the windows system font path when running on
  Windows

## [0.3.0] - 2020-12-07

### Added
- Extra `freenukum-data-tool` command-line application for managing the
  original data files
- Menu can now be controlled by cursor keys and mouse
- Improved collision detection system
- Implemented rocket (LP: #257550)
- Implemented rotating mill (LP: #242120)

### Changed
- Relicensed under AGPL-3.0
- Transitioned implementation to Rust
- Url is now https://gitlab.com/silwol/freenukum/

### Removed
- Built-in assistant for downloading the shareware was removed


## [0.2.10] - 2008-07-31

### Added
- Assistant which can download the shareware episode
- Fire wheel bot (LP: #242134)


## [0.2.9] - 2008-07-06

### Added
- Implemented fan wheels (LP: #242128)
- No more solid parts behind cameras (LP: #242133)
- Added fullscreen mode (LP: #245274)
- Implemented glove slot which can expand floor
- Soda can fly away when shot by hero (LP: #242118)
- Freenukum can now also load lowercase named data files (LP: #245269)


## [0.2.8] - 2008-07-03

### Fixed
- Compile bug on AMD64 (LP: #244763)

## [0.2.7] - 2008-07-01

### Added
- Implemented shots by enemies (LP: #243990)
- Implemented bombs (LP: #242116)
- Added background (LP: #240832)
- Added particle fireworks (LP: #242117)
- Added menu shortcut .desktop file
- Added tankbot (LP: #241105)

### Changed
- Removed dependency on glib (LP: #243956)


## [0.2.6] - 2008-06-28

### Changed
- Using BitsPerPixel and SDL-Flags from initial screen (LP: #243352)
- Calling SDL_Quit on exit of the program (LP: #243597)
- Distribution tarball includes manpage (LP: #243296)
- `fn_test_*` programs can now be compiled using
  `./configure --enable-testprograms`


## [0.2.5] - 2008-06-23

### Fixed
- Made freenukum compile on AMD64 (LP: #242228)

## [0.2.4] - 2008-06-22

### Added
- Implemented flamethrower
- Added ACME stone
- Added Manpage
- Implemented mines
- Implemented floor which crashes when hero steps upon it twice

### Fixed
- Shots can no longer go through solid parts


## [0.2.3] - 2008-06-18

### Added
- Implemented balloons
- Implemented wall-crawling green bots
- Made it possible to change to other episodes (if installed).

### Changed
- When hero gets hurt he has an immunity phase before he is hurt again


## [0.2.2] - 2008-06-15

### Added
- Implemented simple robot
- Made hero motion model more similar to original game.
- Elevators implemented


## [0.2.1] - 2008-06-14

### Added
- Hero can fetch guns for more firepower.
- Hero can fetch boots and then jump higher.
- Hero can use teleporters.
- Hero can shoot boxes.
- Hero can walk through opened doors immediately instead of having
  to wait until the animation finishes.
- Hero earns score for fetching keys.
- Keys can now be used.

### Fixed
- Keyhole stops blinking after key insert.
- After finishing a level, we go on to the next instead of returning
  to the title screen


## [0.2] - 2008-06-11

### Fixed
- Exit doors work now


## [0.1] - 2008-03-18

### Added
- Initial development release
07070100000003000081A40000000000000000000000015FD8FA68000086DB000000000000000000000000000000000000001800000000freenukum-0.3.5/COPYING                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are 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.

  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.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  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 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 work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  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 AGPL, see
<https://www.gnu.org/licenses/>.
07070100000004000081A40000000000000000000000015FD8FA680000383B000000000000000000000000000000000000001B00000000freenukum-0.3.5/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"

[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
 "winapi",
]

[[package]]
name = "anyhow"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"

[[package]]
name = "arraydeque"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0ffd3d69bd89910509a5d31d1f1353f38ccffdd116dd0099bbd6627f7bd8ad8"

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
 "hermit-abi",
 "libc",
 "winapi",
]

[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"

[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"

[[package]]
name = "bzip2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
dependencies = [
 "bzip2-sys",
 "libc",
]

[[package]]
name = "bzip2-sys"
version = "0.1.9+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad3b39a260062fca31f7b0b12f207e8f2590a67d32ec7d59c20484b07ea7285e"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
]

[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"

[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
 "ansi_term",
 "atty",
 "bitflags",
 "strsim",
 "textwrap",
 "unicode-width",
 "vec_map",
]

[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
 "cfg-if 1.0.0",
]

[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
 "cfg-if 1.0.0",
 "dirs-sys-next",
]

[[package]]
name = "dirs-sys-next"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
dependencies = [
 "libc",
 "redox_users",
 "winapi",
]

[[package]]
name = "explode"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46695dd0a1b7a2a6c3e0800604bd9738c2699682b9fc325d4886b6c1a78025ac"
dependencies = [
 "arraydeque",
]

[[package]]
name = "flate2"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
dependencies = [
 "cfg-if 0.1.10",
 "crc32fast",
 "libc",
 "miniz_oxide",
]

[[package]]
name = "freenukum"
version = "0.3.5"
dependencies = [
 "anyhow",
 "directories-next",
 "explode",
 "log",
 "rand",
 "sdl2",
 "serde",
 "serde_derive",
 "structopt",
 "toml",
 "zip",
]

[[package]]
name = "getrandom"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [
 "cfg-if 0.1.10",
 "libc",
 "wasi 0.9.0+wasi-snapshot-preview1",
]

[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
 "unicode-segmentation",
]

[[package]]
name = "hermit-abi"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
 "libc",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"

[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
 "cfg-if 0.1.10",
]

[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
 "adler32",
]

[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"

[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"

[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
 "proc-macro-error-attr",
 "proc-macro2",
 "quote",
 "syn",
 "version_check",
]

[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
 "proc-macro2",
 "quote",
 "version_check",
]

[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
 "unicode-xid",
]

[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
 "getrandom",
 "libc",
 "rand_chacha",
 "rand_core",
 "rand_hc",
]

[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
 "ppv-lite86",
 "rand_core",
]

[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
 "getrandom",
]

[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
 "rand_core",
]

[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"

[[package]]
name = "redox_users"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
 "getrandom",
 "redox_syscall",
]

[[package]]
name = "sdl2"
version = "0.34.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcbb85f4211627a7291c83434d6bbfa723e28dcaa53c7606087e3c61929e4b9c"
dependencies = [
 "bitflags",
 "lazy_static",
 "libc",
 "sdl2-sys",
]

[[package]]
name = "sdl2-sys"
version = "0.34.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d81feded049b9c14eceb4a4f6d596a98cebbd59abdba949c5552a015466d33"
dependencies = [
 "cfg-if 0.1.10",
 "libc",
 "version-compare",
]

[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"

[[package]]
name = "serde_derive"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"

[[package]]
name = "structopt"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
dependencies = [
 "clap",
 "lazy_static",
 "structopt-derive",
]

[[package]]
name = "structopt-derive"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
dependencies = [
 "heck",
 "proc-macro-error",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "syn"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-xid",
]

[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
 "unicode-width",
]

[[package]]
name = "thiserror"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
 "libc",
 "wasi 0.10.0+wasi-snapshot-preview1",
 "winapi",
]

[[package]]
name = "toml"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
 "serde",
]

[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"

[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"

[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"

[[package]]
name = "version-compare"
version = "0.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1"

[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"

[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"

[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

[[package]]
name = "zip"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2896475a242c41366941faa27264df2cb935185a92e059a004d0048feb2ac5"
dependencies = [
 "byteorder",
 "bzip2",
 "crc32fast",
 "flate2",
 "thiserror",
 "time",
]
07070100000005000081A40000000000000000000000015FD8FA6800000267000000000000000000000000000000000000001B00000000freenukum-0.3.5/Cargo.toml[package]
name = "freenukum"
version = "0.3.5"
authors = ["Wolfgang Silbermayr <wolfgang@silbermayr.at>"]
edition = "2018"
default-run = "freenukum"
categories = ["games"]
license = "AGPL-3.0-or-later"
description = "A clone of the 1991 DOS game Duke Nukem 1"
readme = "README.md"
homepage = "https://gitlab.com/silwol/freenukum"
repository = "https://gitlab.com/silwol/freenukum"

[dependencies]
anyhow = "1.0.33"
directories-next = "2"
sdl2 = { version = "0.34", features = ["ttf"] }
serde = "1"
serde_derive = "1"
toml = "0.5.6"
rand = "0.7.3"
explode = "0.1.2"
structopt = "0.3.20"
zip = "0.5.8"
log = "0.4.11"
07070100000006000081A40000000000000000000000015FD8FA68000008C3000000000000000000000000000000000000001A00000000freenukum-0.3.5/README.md# Freenukum

A clone of the 1991 DOS game *Duke Nukem 1*.

The game is still work in progress. Expect many things to not function yet,
such as opponents and some other items missing from the levels.

## Screenshots

[Screenshots can be found here](https://gitlab.com/silwol/freenukum/-/wikis/FreeNukum-Screenshots).

## Changelog

[Changelog can be found here](https://gitlab.com/silwol/freenukum/-/blob/main/CHANGELOG.md)

## How to install

For now, no compiled executable files are built or distributed by the
developers of this project, instead the source must be compiled by the
users.

The concrete building steps may vary depending on the environment. Here
is a list of a few systems and how to install it there. The generic
information can be found in [the Rust
book](https://doc.rust-lang.org/book/ch14-04-installing-binaries.html).

If your desired system is not listed and you'd like to contribute the guide
for it, please either submit
[an issue](https://gitlab.com/silwol/freenukum/-/issues) or
[a merge request](https://gitlab.com/silwol/freenukum/-/merge_requests).

### Debian GNU/Linux or derived distributions such as Ubuntu or Mint

* Install the required development libraries and the Rust compiler:
  `sudo apt install rustc libsdl2-dev libsdl2-ttf-dev`.
* Build and install the game with `cargo install freenukum`.
* The files are installed to `~/.cargo/bin`. You can add this to your
  `PATH` environment variable, or run the data tool and the game with
  `~/.cargo/bin/freenukum-data-tool` and `~/.cargo/bin/freenukum`.

## Game data

Currently the game requires the installation of the original game graphics
data. The project includes the `freenukum-data-tool` executable which can
be used for installation.

The game data can be obtained:
* Shareware episode from ftp://ftp.3drealms.com/share/1duke.zip (free of
  charge)
* Search on https://archive.org/ for it.
* The Duke Nukem 3D CD contains a copy of the full version.
* Buy it from an online store if you find it. It used to be available on
  [GOG.com](https://www.gog.com/news/release_duke_nukem_12), but that is no
  longer the case. Maybe it will be available some time in the future
  again.

The home of FreeNukum is <https://gitlab.com/silwol/freenukum>.
07070100000007000041ED0000000000000000000000035FD8FA6800000000000000000000000000000000000000000000001500000000freenukum-0.3.5/data07070100000008000041ED0000000000000000000000035FD8FA6800000000000000000000000000000000000000000000001B00000000freenukum-0.3.5/data/icons07070100000009000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000002400000000freenukum-0.3.5/data/icons/scalable0707010000000A000081A40000000000000000000000015FD8FA6800001651000000000000000000000000000000000000003200000000freenukum-0.3.5/data/icons/scalable/freenukum.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="256" height="256" id="svg2466" sodipodi:version="0.32" inkscape:version="0.46" version="1.0" sodipodi:docname="freenukum.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape">
  <defs id="defs2468">
    <inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 300 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="800 : 300 : 1" inkscape:persp3d-origin="400 : 200 : 1" id="perspective2474"/>
  </defs>
  <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2" inkscape:cx="113.33333" inkscape:cy="124.97001" inkscape:current-layer="layer1" inkscape:document-units="px" showgrid="false" inkscape:window-width="1225" inkscape:window-height="717" inkscape:window-x="47" inkscape:window-y="29"/>
  <metadata id="metadata2471">
    <rdf:RDF>
      <cc:Work rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
        <dc:title>freenukum</dc:title>
        <dc:creator>
          <cc:Agent>
            <dc:title>George Vlahavas (vlahavas~at~gmail.com)</dc:title>
          </cc:Agent>
        </dc:creator>
        <dc:description>Licenced under the GPL v2 or newer.</dc:description>
        <cc:license rdf:resource=""/>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer" transform="translate(-232.886, -254.985)">
    <path sodipodi:type="arc" style="fill: rgb(252, 251, 64); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(255, 255, 0); stroke-width: 5.07071; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;" id="path2477" sodipodi:cx="156.75" sodipodi:cy="127.75" sodipodi:rx="96.75" sodipodi:ry="96.75" d="M 253.5,127.75 A 96.75,96.75 0 1 1 60,127.75 A 96.75,96.75 0 1 1 253.5,127.75 z" transform="matrix(1.1873, 0, 0, 1.17179, 174.278, 232.789)"/>
    <path style="fill: rgb(0, 0, 0);" d="M 346.93294,505.13971 C 298.81005,499.75991 258.07689,467.94873 241.98726,423.18076 C 218.83487,358.76125 254.21449,287.40127 320.26974,265.2866 C 333.61153,260.81989 344.89345,259.05023 360.43294,258.98669 C 382.03037,258.89837 399.3857,263.02068 418.43294,272.76308 C 452.70302,290.29175 475.84461,320.61909 483.99332,358.68076 C 486.35332,369.704 486.38087,394.77347 484.04472,405.44815 C 473.04148,455.7259 435.38235,493.10965 385.66661,503.10687 C 373.79526,505.49405 357.67187,506.34024 346.93294,505.13971 z M 383.04304,495.70723 C 405.95161,491.32767 427.56315,479.815 444.16322,463.14793 C 484.14818,423.00158 489.39473,361.3521 456.73423,315.43244 C 446.77118,301.42469 429.49145,286.67797 413.93294,278.90533 C 406.49296,275.1885 393.04013,270.64862 383.43294,268.61259 C 373.33317,266.47217 350.43181,266.17967 340.43294,268.06337 C 314.15726,273.0135 292.91578,284.48919 274.80639,303.51804 C 234.32024,346.05977 231.87066,408.68385 268.88725,454.84248 C 287.07103,477.51714 315.74651,493.26665 345.93294,497.15848 C 355.12457,498.34352 372.88649,497.64891 383.04304,495.70723 z M 340.93294,490.71794 C 325.54688,487.8994 303.43294,479.04568 303.43294,475.70414 C 303.43294,474.21966 341.82412,412.67507 343.33112,411.74369 C 343.73366,411.4949 346.45511,411.99257 349.37877,412.84961 C 356.93646,415.06509 371.2626,414.30734 377.87049,411.3426 C 381.86361,409.55103 383.04364,409.3715 384.03976,410.40403 C 385.79766,412.22618 423.43294,471.32688 423.43294,472.26525 C 423.43294,474.52487 407.74798,482.65182 395.93294,486.51402 C 379.2752,491.95923 357.0023,493.66166 340.93294,490.71794 z M 352.93294,401.51913 C 345.32139,397.50642 341.43294,391.13668 341.43294,382.68076 C 341.43294,373.80713 345.82304,367.00485 353.66447,363.72849 C 370.30906,356.77394 387.24003,375.87473 378.88461,392.18076 C 377.09962,395.66427 371.47967,400.85197 368.11784,402.11943 C 364.04778,403.6539 356.40954,403.35194 352.93294,401.51913 z M 278.43294,385.17253 C 262.20794,384.86027 248.82044,384.50938 248.68294,384.39278 C 247.90371,383.73194 248.64533,372.20675 249.95901,364.56175 C 253.99806,341.05647 264.6653,320.90468 282.03981,303.95696 C 290.05749,296.13623 293.43287,293.52766 302.74587,287.95485 C 308.53774,284.48905 309.75571,284.08808 310.87264,285.27941 C 312.55773,287.07676 343.43294,348.33864 343.43294,349.88482 C 343.43294,350.52427 341.2101,353.32956 338.49329,356.1188 C 332.80989,361.95373 327.88536,371.31279 326.44944,379.0081 C 325.9027,381.93814 325.19877,384.75064 324.88514,385.2581 C 324.57151,385.76556 320.62896,386.08165 316.12392,385.96051 C 311.61888,385.83937 294.65794,385.48478 278.43294,385.17253 z M 397.91228,381.91527 C 397.66698,381.23629 396.77083,378.67497 395.92083,376.22345 C 392.92326,367.57805 386.23954,359.26116 376.96597,352.63698 L 374.10477,350.59321 L 389.3992,316.38698 C 400.4225,291.73323 405.14594,282.18469 406.31329,282.19483 C 408.5591,282.21433 421.23035,289.23854 428.17151,294.31175 C 452.79922,312.31187 470.98847,345.04419 471.72092,372.68076 L 471.93294,380.68076 L 461.93294,381.30457 C 456.43294,381.64767 439.87864,382.2032 425.14561,382.53908 C 404.34988,383.01318 398.25852,382.87365 397.91228,381.91527 z" id="path2453"/>
  </g>
</svg>0707010000000B000041ED0000000000000000000000035FD8FA6800000000000000000000000000000000000000000000001400000000freenukum-0.3.5/doc0707010000000C000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000002300000000freenukum-0.3.5/doc/dukefileformat0707010000000D000081A40000000000000000000000015FD8FA6800004901000000000000000000000000000000000000003100000000freenukum-0.3.5/doc/dukefileformat/dukefiles.xml<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<article>
    <!-- $Id:$ -->
    <articleinfo>
        <title>The Duke Nukem file format</title>
        <author>
            <personname>
                <firstname>Wolfgang</firstname>
                <surname>Silbermayr</surname>
            </personname>
            <email>wosi@gmx.at</email>
        </author>
    </articleinfo>
    <para>
        This document describes the information that I could
        find out about the file format of the original Duke
        Nukem game files. For reverse engineering the file
        formats, I only used the shareware version of the game
        because I don't have any other version of it. This
        version can be downloaded from
        <ulink url="http://www.apogee1.com/">http://www.apogee1.com/</ulink>.
    </para>

    <sect1>
        <title>The files</title>
        <para>
            The following files are contained in the original shareware
            version of the game:
            <variablelist>
                <varlistentry>
                    <term><filename>ANIM0.DN1</filename></term>
                    <term><filename>ANIM1.DN1</filename></term>
                    <term><filename>ANIM2.DN1</filename></term>
                    <term><filename>ANIM3.DN1</filename></term>
                    <term><filename>ANIM4.DN1</filename></term>
                    <term><filename>ANIM5.DN1</filename></term>
                    <listitem>
                        <para>
                            The animated sprites of the opponents in the
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>BACK0.DN1</filename></term>
                    <term><filename>BACK1.DN1</filename></term>
                    <term><filename>BACK2.DN1</filename></term>
                    <term><filename>BACK3.DN1</filename></term>
                    <listitem>
                        <para>
                            The background sprites of the levels in the
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>BADGUY.DN1</filename></term>
                    <listitem>
                        <para>
                            The graphic shoing the opponent before starting the game
                            in the <link linkend="PictureFileFormat">Picture File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>BORDER.DN1</filename></term>
                    <listitem>
                        <para>
                            The graphics of which the borders consist in the
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>CREDITS.DN1</filename></term>
                    <listitem>
                        <para>
                            The graphic showing the credits in the
                            <link linkend="PictureFileFormat">Picture File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DN1.EXE</filename></term>
                    <listitem>
                        <para>
                            The game executable in DOS EXE format.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DN.DN1</filename></term>
                    <listitem>
                        <para>
                            The background image of the main menu. In
                            <link linkend="PictureFileFormat">Picture File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DROP0.DN1</filename></term>
                    <term><filename>DRPP1.DN1</filename></term>
                    <term><filename>DROP2.DN1</filename></term>
                    <term><filename>DROP3.DN1</filename></term>
                    <term><filename>DROP5.DN1</filename></term>
                    <term><filename>DROP7.DN1</filename></term>
                    <term><filename>DROP9.DN1</filename></term>
                    <term><filename>DROP11.DN1</filename></term>
                    <term><filename>DROP13.DN1</filename></term>
                    <listitem>
                        <para>
                            The background graphics in the
                            <link linkend="BackdropFileFormat">Backdrop File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DUKE1-B.DN1</filename></term>
                    <listitem>
                        <para>
                            Don't know much about this file yet, but it seems to contain some
                            settings like if the sound is on, the highscore and so on.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DUKE1.DN1</filename></term>
                    <listitem>
                        <para>
                            Don't know much about this file either, but it also seems to contain
                            some configuration information, maybe key configuration and so on.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>DUKE.DN1</filename></term>
                    <listitem>
                        <para>
                            The image of Duke's face that is shown before entering
                            level 1. In <link linkend="PictureFileFormat">Picture File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>END.DN1</filename></term>
                    <listitem>
                        <para>
                            The image shown when you finish the game.
                            In <link linkend="PictureFileFormat">Picture File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>FONT1.DN1</filename></term>
                    <term><filename>FONT2.DN1</filename></term>
                    <listitem>
                        <para>
                            The fonts that are used inside the game to display
                            messages and other text in
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>MAN0.DN1</filename></term>
                    <term><filename>MAN1.DN1</filename></term>
                    <term><filename>MAN2.DN1</filename></term>
                    <term><filename>MAN3.DN1</filename></term>
                    <term><filename>MAN4.DN1</filename></term>
                    <listitem>
                        <para>
                            The files containing the animations of Duke himself
                            in <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>MY_DEMO.DN1</filename></term>
                    <listitem>
                        <para>
                            The file containing the original recorded demo.
                            Didn't check anything about the file format yet.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>NUKUM.TXT</filename></term>
                    <listitem>
                        <para>
                            A text file containing some information about the
                            name of the Duke Nukem game.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>NUMBERS.DN1</filename></term>
                    <listitem>
                        <para>
                            The numbers that indicate the sores in the game as well
                            as the boxes showing the bonuses you got inside a level
                            in <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>OBJECT0.DN1</filename></term>
                    <term><filename>OBJECT1.DN1</filename></term>
                    <term><filename>OBJECT2.DN1</filename></term>
                    <listitem>
                        <para>
                            Several active and passive objects like the
                            key switches, rockets, flames, chicken,
                            shots, elevators, boxes etc. in
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>ORDER.FRM</filename></term>
                    <listitem>
                        <para>
                            A Fax form for ordering games from Apogee.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>SOLID0.DN1</filename></term>
                    <term><filename>SOLID1.DN1</filename></term>
                    <term><filename>SOLID2.DN1</filename></term>
                    <term><filename>SOLID3.DN1</filename></term>
                    <listitem>
                        <para>
                            The solid parts of the levels in
                            <link linkend="SpritesFileFormat">Sprites File Format</link>.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>VENDOR.DOC</filename></term>
                    <listitem>
                        <para>
                            A text file containing the information about
                            redistributing the files.
                        </para>
                    </listitem>
                </varlistentry>
                <varlistentry>
                    <term><filename>WORLDAL1.DN1</filename></term>
                    <term><filename>WORLDAL2.DN1</filename></term>
                    <term><filename>WORLDAL3.DN1</filename></term>
                    <term><filename>WORLDAL4.DN1</filename></term>
                    <term><filename>WORLDAL5.DN1</filename></term>
                    <term><filename>WORLDAL6.DN1</filename></term>
                    <term><filename>WORLDAL7.DN1</filename></term>
                    <term><filename>WORLDAL8.DN1</filename></term>
                    <term><filename>WORLDAL9.DN1</filename></term>
                    <term><filename>WORLDALA.DN1</filename></term>
                    <term><filename>WORLDALB.DN1</filename></term>
                    <term><filename>WORLDALC.DN1</filename></term>
                    <listitem>
                        <para>
                            The levels in
                            <link linkend="LevelFileFormat">Level File Format</link>
                            Be careful here, the numeration is not straight-forward.
                            <filename>WORLDAL1.DN1</filename> contains the first level,
                            <filename>WORLDAL2.DN1</filename> contains the short level
                            that is shown between two usual levels. From
                            <filename>WORLDAL3.DN1</filename> to
                            <filename>WORLDALB.DN1</filename> there are the levels
                            from 2 to 10. The file <filename>WORLDALC.DN1</filename>
                            contains the level that is shown in the demo.
                        </para>
                    </listitem>
                </varlistentry>
            </variablelist>
        </para>
    </sect1>

    <!-- ****************************************************************** -->

    <sect1>
        <title id="PictureFileFormat">The Picture File Format</title>
        <para>
            The Picture File Format is rather simple. It consists of three
            parts: one for the blue components, one for the green components
            and one for the red components and one for the brightening
            component. Each of them has a length of 9600
            Bytes. Each color contains 320x240 pixels, stored in 40x320 Bytes.
            The lines are stored one after the other, with a Byte for 8 Pixels.
        </para>
    </sect1>

    <!-- ****************************************************************** -->

    <sect1>
        <title id="SpritesFileFormat">The Sprites File Format</title>
        <para>
            The Sprite files consist of three main parts described
            as follows.
        </para>

            <sect2>
                <title>Header</title>
                <para>
                    The header only consists of three bytes:
                    The first byte contains the number of sprites (in the following
                    called <emphasis>NumSprites</emphasis>) that are stored in the
                    file. The second byte contains the width of the sprites in pixels/8
                    (in the following called <emphasis>SpriteWidth</emphasis>) and the
                    third byte contains the height of the sprites in pixels (in the
                    following called <emphasis>SpriteHeight</emphasis>).
                </para>
            </sect2>

            <sect2>
                <title>Body</title>
                <para>
                    The body contains <emphasis>NumSprites</emphasis> sprites.
                    A sprite contains of <emphasis>SpriteWidth</emphasis> *
                    <emphasis>SpriteHeight</emphasis> parts. Each part has
                    5 bytes. These represent eight pixels in a row which is
                    composed of one bit at the same position of each of the
                    5 bytes.
                    <variablelist>
                        <varlistentry>
                            <term>Transparency</term>
                            <listitem>
                                <para>
                                    If this bit has value 1, the pixel is visible,
                                    otherwise it is transparent. This bit is ignored
                                    for solid sprites and background sprites.
                                </para>
                            </listitem>
                        </varlistentry>
                        <varlistentry>
                            <term>Blue</term>
                            <listitem>
                                <para>
                                    If this bit has value 1, the blue component
                                    is on, otherwise it is off.
                                </para>
                            </listitem>
                        </varlistentry>
                        <varlistentry>
                            <term>Green</term>
                            <listitem>
                                <para>
                                    If this bit has value 1, the green component
                                    is on, otherwise it is off.
                                 </para>
                            </listitem>
                        </varlistentry>
                        <varlistentry>
                            <term>Red</term>
                            <listitem>
                                <para>
                                    If this bit has value 1, the red component
                                    is on, otherwise it is off.
                                 </para>
                            </listitem>
                        </varlistentry>
                        <varlistentry>
                            <term>Brightness</term>
                            <listitem>
                                <para>
                                    If this bit has value 1, the pixel is
                                    brightened, otherwise it keeps the dark color.
                                </para>
                            </listitem>
                        </varlistentry>
                    </variablelist>
                </para>
            </sect2>

            <sect2>
                <title>Footer</title>
                <para>
                    <!-- TODO -->
                    I don't know what yet what data is stored here.
                </para>
            </sect2>
    </sect1>

    <!-- ****************************************************************** -->

    <sect1>
        <title id="BackdropFileFormat">The Backdrop File
            Format</title>
        <para>
            <!-- TODO -->
            To be written...
        </para>
    </sect1>

    <!-- ****************************************************************** -->

    <sect1>
        <title id="LevelFileFormat">The Level File Format</title>
        <para>
            <!-- TODO -->
            To be written...
        </para>
    </sect1>

</article>

0707010000000E000081A40000000000000000000000015FD8FA6800000B74000000000000000000000000000000000000002000000000freenukum-0.3.5/doc/freenukum.6.TH freenukum 6

.SH NAME
freenukum \- Jump'n Run game

.SH SYNOPSIS
.B freenukum

.SH DESCRIPTION
The \fBFreenukum\fR game is a free software implementation of the
Duke Nukum 1 game. The player must go through the levels, shoot
enemy robots and find keys and access cards in order to unlock
barriers and reach the exit door.

.SH DATA FILES
The original level files
of the Duke Nukum 1 game need to be copied to
\fB~/.local/share/freenukum/data/original/\fR all in lowercase file names.
This can either be the shareware episode
only which is available for download free of charge from
ftp://ftp.3drealms.com/share/1duke.zip or the
full version which contains three episodes and can also be
acquired from this website. In order to unpack the data
from the original game, either use the accompanied
\fBfreenukum-data-tool\fR or a DOS environment such as
\fBdosbox\fR, \fBfreedos\fR or
an installation of \fBDOS\fR or \fBWindows\fR.

.SH ITEMS
The hero can also collect several different items, which are
listed below. The collected items are shown in the right
section of the screen.

The more \fBfirepower\fR the hero has,
the more shots can stay inside the window simultaneously.
This means the hero can shoot faster and doesn not have
to wait until a shot leaves the visible area.

\fBKeys\fR exist in several different colors. Every key
fits into the keyhole of the same color as the key is.
The activation of the keyhole unlocks the door which
is linked to it.

The \fBAccess Card\fR work just the same way as the keys.
Once used on the access card slot, it opens the
laser beam which keeps the hero from passing on.

The \fBBoot\fR allows the hero to jump higher and
reach areas he wouldn't be able to access otherwise.

The \fBGlove\fR allows the hero to activate a special switch
which enables a floor where there would be none. This way
the hero can walk over the floor in order to finish his
goals.

The \fBClamps\fR allow the hero to attach to some special ceilings.

.SH CONTROL
Freenukum is controlled by the keyboard. The \fBleft arrow\fR
and the \fBright arrow\fR are used for walking left and
right. The \fBleft CTRL key\fR is used for jumping, and the
\fBleft ALT key\fR is used for shooting. In order to
activate something (keyhole, exit door, elevator etc.) the
\fBup arrow\fR is used.

You can switch the game to fullscreen by pressing the
\fBf key\fR.

.SH OPTIONS
No options are currently available to freenukum.

.SH AUTHOR
Written by Wolfgang Silbermayr.
This manual page was originally written by Wolfgang Silbermayr <wolfgang@silbermayr.at>
.br
Visit official homepage: http://gitlab.com/silwol/freenukum/

.SH COPYRIGHT
Copyright \(co 2007-2020 The Freenukum Team.
.br
This is Free Software; this software is licensed under the Affero GPL
version 3, as published by the Free Software Foundation, or any later
version.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0707010000000F000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000001900000000freenukum-0.3.5/examples07070100000010000081A40000000000000000000000015FD8FA6800000917000000000000000000000000000000000000002500000000freenukum-0.3.5/examples/backdrop.rsuse anyhow::{anyhow, Result};
use freenukum::backdrop;
use freenukum::settings::Settings;
use freenukum::tile::TileHeader;
use freenukum::{
    game, BACKDROP_HEIGHT, BACKDROP_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
};
use std::fs::File;
use std::path::PathBuf;
use structopt::StructOpt;

/// Show an original Duke Nukem 1 backdrop.
#[derive(StructOpt, Debug)]
struct Arguments {
    /// The path to the file that should be shown.
    /// The file is usually named `drop1.dn1` or similar.
    filename: PathBuf,
}

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let args = Arguments::from_args();

    let mut file = File::open(&args.filename)?;

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        BACKDROP_WIDTH * TILE_WIDTH,
        BACKDROP_HEIGHT * TILE_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} backdrop example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    TileHeader::load_from(&mut file)?;
    let backdrop = backdrop::load(&mut file)?;
    canvas
        .copy(&backdrop.as_texture(&texture_creator)?, None, None)
        .map_err(|s| anyhow!(s))?;
    canvas.present();

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
07070100000011000081A40000000000000000000000015FD8FA6800000905000000000000000000000000000000000000002400000000freenukum-0.3.5/examples/borders.rsuse anyhow::{anyhow, Result};
use freenukum::borders::Borders;
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::rendering::CanvasRenderer;
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
};

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} borders example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let borders = Borders {};
    let mut border_renderer = CanvasRenderer {
        canvas: &mut canvas,
        texture_creator: &texture_creator,
        tileprovider: &tilecache,
    };
    borders.render(&mut border_renderer)?;
    canvas.present();

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
07070100000012000081A40000000000000000000000015FD8FA6800001D34000000000000000000000000000000000000002100000000freenukum-0.3.5/examples/hero.rsuse anyhow::{anyhow, Result};
use freenukum::{
    actor::ActorQueue,
    data::original_data_dir,
    game,
    graphics::load_default_font,
    hero::{HeroData, Motion},
    level::solids::LevelSolids,
    rendering::{CanvasRenderer, Renderer},
    settings::Settings,
    tilecache::TileCache,
    HorizontalDirection, UserEvent, BACKDROP_HEIGHT, BACKDROP_WIDTH,
    GAME_INTERVAL, TILE_HEIGHT, TILE_WIDTH,
};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    mouse::MouseButton,
    pixels::Color,
};
use std::collections::HashSet;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let event_subsystem = sdl_context.event().map_err(|s| anyhow!(s))?;
    let timer_subsystem = sdl_context.timer().map_err(|s| anyhow!(s))?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    event_subsystem
        .register_custom_event::<UserEvent>()
        .map_err(|s| anyhow!(s))?;

    let win_width = BACKDROP_WIDTH * TILE_WIDTH;
    let win_height = BACKDROP_HEIGHT * TILE_HEIGHT;
    let window = game::create_window(
        win_width,
        win_height,
        settings.fullscreen,
        &format!("Freenukum {} hero example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let mut hero = HeroData::new();

    hero.position.geometry.x =
        (win_width as i32 - hero.position.geometry.width() as i32) / 2;
    hero.position.geometry.y =
        (win_height as i32 - hero.position.geometry.height() as i32) / 2;

    let mut renderer = CanvasRenderer {
        canvas: &mut canvas,
        texture_creator: &texture_creator,
        tileprovider: &tilecache,
    };

    let solids = LevelSolids::new_all_solid();
    hero.render(&mut renderer, &solids, settings.draw_collision_bounds)?;
    renderer.canvas.present();

    let mut directions = HashSet::new();

    let event_sender = event_subsystem.event_sender();

    let timer = timer_subsystem.add_timer(
        GAME_INTERVAL,
        Box::new(move || {
            event_sender.push_custom_event(UserEvent::Timer).unwrap();
            GAME_INTERVAL
        }),
    );

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::KeyDown {
                keycode: Some(Keycode::Right),
                ..
            } => {
                directions.insert(HorizontalDirection::Right);
                if directions.contains(&HorizontalDirection::Left) {
                    hero.motion = Motion::NotMoving;
                } else {
                    hero.motion = Motion::Walking;
                    hero.direction = HorizontalDirection::Right;
                }
                hero.update_animation();
            }
            Event::KeyDown {
                keycode: Some(Keycode::Left),
                ..
            } => {
                directions.insert(HorizontalDirection::Left);
                if directions.contains(&HorizontalDirection::Right) {
                    hero.motion = Motion::NotMoving;
                } else {
                    hero.motion = Motion::Walking;
                    hero.direction = HorizontalDirection::Left;
                }
                hero.update_animation();
            }
            Event::KeyDown {
                keycode: Some(Keycode::LAlt),
                ..
            }
            | Event::MouseButtonDown {
                mouse_btn: MouseButton::Left,
                ..
            } => {
                hero.is_shooting = true;
                hero.update_animation();
            }
            Event::KeyUp {
                keycode: Some(Keycode::LAlt),
                ..
            }
            | Event::MouseButtonUp {
                mouse_btn: MouseButton::Left,
                ..
            } => {
                hero.is_shooting = false;
                hero.update_animation();
            }
            Event::KeyDown {
                keycode: Some(Keycode::LCtrl),
                ..
            }
            | Event::MouseButtonDown {
                mouse_btn: MouseButton::Right,
                ..
            } => {
                hero.jump();
                hero.update_animation();
            }
            Event::KeyUp {
                keycode: Some(Keycode::LCtrl),
                ..
            }
            | Event::MouseButtonUp {
                mouse_btn: MouseButton::Right,
                ..
            } => {
                hero.land();
                hero.update_animation();
            }
            Event::KeyUp {
                keycode: Some(Keycode::Right),
                ..
            } => {
                directions.remove(&HorizontalDirection::Right);
                if directions.contains(&HorizontalDirection::Left) {
                    hero.motion = Motion::Walking;
                    hero.direction = HorizontalDirection::Left;
                } else {
                    hero.motion = Motion::NotMoving;
                }
                hero.update_animation();
            }
            Event::KeyUp {
                keycode: Some(Keycode::Left),
                ..
            } => {
                directions.remove(&HorizontalDirection::Left);
                if directions.contains(&HorizontalDirection::Right) {
                    hero.motion = Motion::Walking;
                    hero.direction = HorizontalDirection::Right;
                } else {
                    hero.motion = Motion::NotMoving;
                }
                hero.update_animation();
            }
            e if e.is_user_event() => {
                if e.as_user_event_type::<UserEvent>()
                    == Some(UserEvent::Timer)
                {
                    let mut actor_adder = ActorQueue::new();
                    renderer.fill(Color::RGB(0, 0, 0))?;
                    hero.next_frame();
                    hero.update_animation();
                    hero.act(&solids, &mut actor_adder)?;
                    hero.render(
                        &mut renderer,
                        &solids,
                        settings.draw_collision_bounds,
                    )?;
                    renderer.canvas.present();
                }
            }
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => {
                renderer.canvas.present();
            }
            _ => {}
        }
    }
    drop(timer);
    Ok(())
}
07070100000013000081A40000000000000000000000015FD8FA6800000689000000000000000000000000000000000000002400000000freenukum-0.3.5/examples/infobox.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::infobox;
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::pixels::Color;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} backdrop example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    infobox::show(&mut canvas, &tilecache, "This is...", &mut event_pump)?;
    infobox::show(
        &mut canvas,
        &tilecache,
        "...the great\nInfobox example.\n",
        &mut event_pump,
    )?;
    infobox::show(
        &mut canvas,
        &tilecache,
        "now\nwith\neven\nmore\nlines.",
        &mut event_pump,
    )?;

    Ok(())
}
07070100000014000081A40000000000000000000000015FD8FA6800000679000000000000000000000000000000000000002500000000freenukum-0.3.5/examples/inputbox.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::inputbox::{self, Answer};
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::pixels::Color;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} backdrop example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    match inputbox::show(
        &mut canvas,
        &tilecache,
        "Please enter your name:",
        30,
        &mut event_pump,
    )? {
        Answer::Ok(name) => {
            println!("OK, your name is {:?}", name);
        }
        Answer::Quit => {
            println!("Quit");
        }
    }

    Ok(())
}
07070100000015000081A40000000000000000000000015FD8FA6800001E55000000000000000000000000000000000000002900000000freenukum-0.3.5/examples/level_loader.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::hero::HeroData;
use freenukum::level::raw::LevelRaw;
use freenukum::level::LevelData;
use freenukum::rendering::{CanvasRenderer, MovePositionRenderer};
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::UserEvent;
use freenukum::{
    game, LEVEL_HEIGHT, LEVEL_WIDTH, TILE_HEIGHT, TILE_WIDTH,
    WINDOW_HEIGHT, WINDOW_WIDTH,
};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    mouse::MouseButton,
    pixels::Color,
    rect::Rect,
};
use std::fs::File;
use std::num::NonZeroUsize;
use std::num::ParseIntError;
use structopt::StructOpt;

/// Show an original Duke Nukem 1 level.
#[derive(StructOpt, Debug)]
struct Arguments {
    /// The number of the level in hexadecimal format.
    /// This is usually in the range from 1 to 'c'.
    #[structopt(parse(try_from_str=parse_hex))]
    level_number: usize,

    /// The episode number. Usually in the range 1 to 3.
    #[structopt(default_value = "1", long, name = "EPISODE_NUMBER")]
    episode: NonZeroUsize,
}

fn parse_hex(src: &str) -> Result<usize, ParseIntError> {
    usize::from_str_radix(src, 16)
}

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let args = Arguments::from_args();

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let event_subsystem = sdl_context.event().map_err(|s| anyhow!(s))?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;
    event_subsystem
        .register_custom_event::<UserEvent>()
        .map_err(|s| anyhow!(s))?;

    let event_sender = event_subsystem.event_sender();

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} level loader example", VERSION),
        &video_subsystem,
    )?;
    let (win_w, win_h) = window.size();

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    let mut episodes = game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;

    let tilecache = TileCache::load_from_path(&original_data_dir())?;
    episodes.switch_to(args.episode.get() - 1)?;

    let level_file = format!(
        "worldal{:1x}.{}",
        args.level_number,
        episodes.file_extension()
    );

    let mut file = File::open(&original_data_dir().join(level_file))?;

    let mut level_raw = LevelRaw::new();
    let mut hero = HeroData::new();
    let mut level_data =
        LevelData::load(&mut file, &mut hero, &mut Some(&mut level_raw))?;

    let mut r = Rect::new(0, 0, win_w, win_h);
    let level_rect = Rect::new(
        0,
        0,
        TILE_WIDTH * LEVEL_WIDTH,
        TILE_HEIGHT * LEVEL_HEIGHT,
    );

    event_sender
        .push_custom_event(UserEvent::Redraw)
        .map_err(|s| anyhow!(s))?;

    let mut multiply = 10;
    'event_loop: loop {
        match event_pump.wait_event() {
            Event::KeyDown {
                keycode: Some(Keycode::Up),
                ..
            } => {
                let (x, y) = (0, -1);
                scroll(x * multiply, y * multiply, &mut r, level_rect);
                event_sender
                    .push_custom_event(UserEvent::Redraw)
                    .map_err(|s| anyhow!(s))?;
            }
            Event::KeyDown {
                keycode: Some(Keycode::Down),
                ..
            } => {
                let (x, y) = (0, 1);
                scroll(x * multiply, y * multiply, &mut r, level_rect);
                event_sender
                    .push_custom_event(UserEvent::Redraw)
                    .map_err(|s| anyhow!(s))?;
            }
            Event::KeyDown {
                keycode: Some(Keycode::Left),
                ..
            } => {
                let (x, y) = (-1, 0);
                scroll(x * multiply, y * multiply, &mut r, level_rect);
                event_sender
                    .push_custom_event(UserEvent::Redraw)
                    .map_err(|s| anyhow!(s))?;
            }
            Event::KeyDown {
                keycode: Some(Keycode::Right),
                ..
            } => {
                let (x, y) = (1, 0);
                scroll(x * multiply, y * multiply, &mut r, level_rect);
                event_sender
                    .push_custom_event(UserEvent::Redraw)
                    .map_err(|s| anyhow!(s))?;
            }
            Event::KeyDown {
                keycode: Some(Keycode::LShift),
                ..
            } => {
                multiply = 50;
            }
            Event::KeyUp {
                keycode: Some(Keycode::LShift),
                ..
            } => {
                multiply = 10;
            }
            Event::MouseButtonDown {
                mouse_btn: MouseButton::Left,
                x,
                y,
                ..
            } => {
                let global_x = r.x() + x;
                let global_y = r.y() + y;
                let tile_x = global_x as u32 / TILE_WIDTH;
                let tile_y = global_y as u32 / TILE_HEIGHT;

                let tilenr = level_raw.get(tile_x, tile_y);
                let is_solid = level_data.solids.get(tile_x, tile_y);

                println!(
                    "Tile at (x={}, y={}): 0x{:04x}. Solid: {}",
                    tile_x, tile_y, tilenr, is_solid
                );
            }
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            e if e.is_user_event() => {
                if e.as_user_event_type::<UserEvent>()
                    == Some(UserEvent::Redraw)
                {
                    let mut renderer = CanvasRenderer {
                        canvas: &mut canvas,
                        texture_creator: &texture_creator,
                        tileprovider: &tilecache,
                    };
                    let mut renderer = MovePositionRenderer {
                        offset_x: -r.x(),
                        offset_y: -r.y(),
                        upstream: &mut renderer,
                    };
                    level_data.render(
                        &mut renderer,
                        &mut hero,
                        settings.draw_collision_bounds,
                        r,
                        None,
                        None,
                    )?;
                }
                canvas.present()
            }
            _ => {}
        }
    }
    Ok(())
}

fn scroll(x_dist: i32, y_dist: i32, r: &mut Rect, level_rect: Rect) {
    r.offset(x_dist, y_dist);

    if r.left() < 0 {
        r.set_x(0);
    }
    if r.right() > level_rect.right() {
        r.set_right(level_rect.right());
    }

    if r.top() < 0 {
        r.set_y(0);
    }
    if r.bottom() > level_rect.bottom() {
        r.set_bottom(level_rect.bottom());
    }
}
07070100000016000081A40000000000000000000000015FD8FA6800000892000000000000000000000000000000000000002100000000freenukum-0.3.5/examples/menu.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::menu::{Menu, MenuEntry};
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, UserEvent, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::pixels::Color;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let event_subsystem = sdl_context.event().map_err(|s| anyhow!(s))?;
    let timer_subsystem = sdl_context.timer().map_err(|s| anyhow!(s))?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    event_subsystem
        .register_custom_event::<UserEvent>()
        .map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} menu example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let mut menu = Menu::new("Testmenu\nTest\nTest".to_string());
    menu.append(MenuEntry {
        shortcut: 's',
        name: "S)tart game".to_string(),
    });
    menu.append(MenuEntry {
        shortcut: 'h',
        name: "H)ello".to_string(),
    });
    menu.append(MenuEntry {
        shortcut: 'd',
        name: "D)emo game".to_string(),
    });

    println!(
        "Menu choice: {:?}",
        menu.get_choice(
            &mut canvas,
            &tilecache,
            &mut event_pump,
            &event_subsystem.event_sender(),
            &timer_subsystem
        )
    );

    Ok(())
}
07070100000017000081A40000000000000000000000015FD8FA6800000A59000000000000000000000000000000000000002700000000freenukum-0.3.5/examples/messagebox.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::messagebox::messagebox;
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
    rect::{Point, Rect},
};

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} backdrop example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let msg = r" FREENUKUM MAIN MENU
 ------------------- 

S)tart a new game
R)estore an old game
I)nstructions
O)rdering information
G)ame setup
H)igh scores
P)reviews/Main Demo!
V)iew user demo
T)itle screen
C)redits
Q)it to DOS";

    let msgbox = messagebox(&msg, &tilecache, &texture_creator)?;
    let destrect = Rect::from_center(
        Point::new(WINDOW_WIDTH as i32 / 2, WINDOW_HEIGHT as i32 / 2),
        msgbox.width(),
        msgbox.height(),
    );
    canvas
        .copy(&msgbox.as_texture(&texture_creator)?, None, destrect)
        .map_err(|s| anyhow!(s))?;

    canvas.present();

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
07070100000018000081A40000000000000000000000015FD8FA68000008C2000000000000000000000000000000000000002400000000freenukum-0.3.5/examples/picture.rsuse anyhow::{anyhow, Result};
use freenukum::picture;
use freenukum::settings::Settings;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
};
use std::fs::File;
use std::path::PathBuf;
use structopt::StructOpt;

/// Show an original Duke Nukem 1 game picture.
#[derive(StructOpt, Debug)]
struct Arguments {
    /// The path to the file that should be shown.
    /// The file is usually named one of: `badguy.dn1`, `credits.dn1`,
    /// `dn.dn1`, `duke.dn1`, `end.dn1`.
    filename: PathBuf,
}

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let args = Arguments::from_args();

    let mut file = File::open(&args.filename)?;

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} picture example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    let picture = picture::load(&mut file)?;

    canvas
        .copy(&picture.as_texture(&texture_creator)?, None, None)
        .map_err(|s| anyhow!(s))?;
    canvas.present();

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
07070100000019000081A40000000000000000000000015FD8FA68000008EC000000000000000000000000000000000000002300000000freenukum-0.3.5/examples/splash.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::picture;
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::pixels::Color;
use std::fs::File;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} borders example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    let episodes = game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let items = vec![
        ("badguy", "I am the\nBAD GUY!", 50, 144),
        ("duke", "Hello BAD GUY!\nI'm the GOOD GUY.", 100, 144),
        ("dn", "", 0, 0),
        ("end", "The end.", 200, 144),
    ];

    for item in items {
        let (file, text, x, y) = item;

        let filename = format!("{}.{}", file, episodes.file_extension());
        let filepath = original_data_dir().join(filename);
        let mut file = File::open(filepath)?;

        if text == "" {
            picture::show_splash(
                &mut canvas,
                &tilecache,
                &mut file,
                &mut event_pump,
            )?;
        } else {
            picture::show_splash_with_message(
                &mut canvas,
                &tilecache,
                &mut file,
                &mut event_pump,
                Some(text),
                x,
                y,
            )?;
        }
    }
    Ok(())
}
0707010000001A000081A40000000000000000000000015FD8FA6800000A2D000000000000000000000000000000000000002100000000freenukum-0.3.5/examples/tile.rsuse anyhow::{anyhow, Result};
use freenukum::game;
use freenukum::settings::Settings;
use freenukum::tile::{self, TileHeader};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
    rect::Rect,
};
use std::fs::File;
use std::path::PathBuf;
use structopt::StructOpt;

/// Show tiles from a Duke Nukem 1 grame graphics file.
#[derive(StructOpt, Debug)]
struct Arguments {
    /// The path to the file that should be shown.
    /// The file is usually named one of: `anim0.dn1` to `anim5.dn1`,
    /// `border.dn1`, `font1.dn1`, `font2.dn1`, `numbers.dn1`,
    /// `object0.dn1` to `object2.dn1` or `solid0.dn1` to `solid3.dn1`.
    filename: PathBuf,
}

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let args = Arguments::from_args();

    let settings = Settings::load_or_create();

    let mut file = File::open(&args.filename)?;
    let header = TileHeader::load_from(&mut file)?;

    let mut r =
        Rect::new(0, 0, header.width as u32 * 8, header.height as u32);

    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let window = game::create_window(
        r.width() * header.tiles as u32,
        r.height(),
        settings.fullscreen,
        &format!("Freenukum {} tile example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    for _ in 0..header.tiles {
        let tile = tile::load(&mut file, header, false)?;
        canvas
            .copy(&tile.as_texture(&texture_creator)?, None, r)
            .map_err(|s| anyhow!(s))?;
        r.set_x(r.x() + header.width as i32 * 8);
    }
    canvas.present();

    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
0707010000001B000081A40000000000000000000000015FD8FA6800000E80000000000000000000000000000000000000002600000000freenukum-0.3.5/examples/tilecache.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::rendering::{
    CanvasRenderer, MovePositionRenderer, Renderer,
};
use freenukum::settings::Settings;
use freenukum::text;
use freenukum::tilecache::{FileProperties, TileCache};
use freenukum::{game, TILE_HEIGHT, TILE_WIDTH};
use sdl2::{
    event::{Event, WindowEvent},
    keyboard::Keycode,
    pixels::Color,
    rect::Point,
};

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let file_properties = FileProperties::get_all();
    let max_tiles =
        file_properties.iter().map(|p| p.num_tiles).max().unwrap();

    let settings = Settings::load_or_create();
    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        (max_tiles + 2) as u32 * TILE_WIDTH,
        (file_properties.len() as u32 + 2) * TILE_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {} tilecache example", VERSION),
        &video_subsystem,
    )?;

    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let mut dest = Point::new(TILE_WIDTH as i32, 0);

    let mut renderer = CanvasRenderer {
        canvas: &mut canvas,
        texture_creator: &texture_creator,
        tileprovider: &tilecache,
    };

    let blithex = |value: usize,
                   dest: Point,
                   renderer: &mut dyn Renderer|
     -> Result<()> {
        let mut renderer = MovePositionRenderer {
            offset_x: dest.x(),
            offset_y: dest.y(),
            upstream: renderer,
        };
        text::render(&mut renderer, &format!("{:02X}", value))
    };

    for x in 0..max_tiles {
        blithex(x, dest, &mut renderer)?;
        dest.x += TILE_WIDTH as i32;
    }
    dest.x = 0;
    dest.y += TILE_HEIGHT as i32;

    let mut i = 0;
    for (row, file) in FileProperties::get_all().iter().enumerate() {
        blithex(row, dest, &mut renderer)?;
        dest.x += TILE_WIDTH as i32;
        for _ in 0..file.num_tiles {
            renderer.place_tile(i, dest)?;
            dest.x += TILE_WIDTH as i32;
            i += 1;
        }
        dest.x = (TILE_WIDTH * (max_tiles as u32 + 1)) as i32;
        blithex(row, dest, &mut renderer)?;
        dest.x = 0;
        dest.y += TILE_HEIGHT as i32;
    }
    dest.x += TILE_WIDTH as i32;
    for x in 0..max_tiles {
        blithex(x, dest, &mut renderer)?;
        dest.x += TILE_WIDTH as i32;
    }

    canvas.present();

    'event_loop: loop {
        match event_pump.wait_event() {
            Event::Quit { .. }
            | Event::KeyDown {
                keycode: Some(Keycode::Escape),
                ..
            }
            | Event::KeyDown {
                keycode: Some(Keycode::Q),
                ..
            } => break 'event_loop,
            Event::Window {
                win_event: WindowEvent::Exposed,
                ..
            }
            | Event::Window {
                win_event: WindowEvent::Shown,
                ..
            } => canvas.present(),
            _ => {}
        }
    }
    Ok(())
}
0707010000001C000081A40000000000000000000000015FD8FA68000000D4000000000000000000000000000000000000002500000000freenukum-0.3.5/freenukum.desktop.in[Desktop Entry]
Version=@version@
Name=FreeNukum
Type=Application
Icon=freenukum
Comment=Clone of the original Duke Nukum 1 Jump'n Run game
Exec=freenukum
TryExec=freenukum
Categories=Game;ArcadeGame;ActionGame;
0707010000001D000081A40000000000000000000000015FD8FA680000000F000000000000000000000000000000000000001D00000000freenukum-0.3.5/rustfmt.tomlmax_width = 75
0707010000001E000041ED0000000000000000000000055FD8FA6800000000000000000000000000000000000000000000001400000000freenukum-0.3.5/src0707010000001F000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000001A00000000freenukum-0.3.5/src/actor07070100000020000081A40000000000000000000000015FD8FA68000005FE000000000000000000000000000000000000002D00000000freenukum-0.3.5/src/actor/accesscard_door.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ReceiveMessageParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_LASERBEAM, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {
            tile: OBJECT_LASERBEAM,
            current_frame: 0,
            num_frames: 4,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn receive_message(&mut self, p: ReceiveMessageParameters) {
        if p.message != ActorMessageType::OpenDoor {
            return;
        }
        let x = p.general.position.x() as u32 / TILE_WIDTH;
        let y = p.general.position.y() as u32 / TILE_HEIGHT;
        p.solids.set(x, y, false);
        p.general.is_alive = false;
    }
}
07070100000021000081A40000000000000000000000015FD8FA680000078B000000000000000000000000000000000000002D00000000freenukum-0.3.5/src/actor/accesscard_slot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ActorType, HeroInteractStartParameters,
        RenderParameters,
    },
    hero::InventoryItem,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_ACCESS_CARD_SLOT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {
            tile: OBJECT_ACCESS_CARD_SLOT,
            current_frame: 0,
            num_frames: 8,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        if p.hero_data.inventory.is_set(InventoryItem::AccessCard) {
            p.actor_message_queue.push_back(
                ActorType::AccessCardDoor,
                ActorMessageType::OpenDoor,
            );
            self.current_frame = 0;
            self.num_frames = 1;
            self.tile = OBJECT_ACCESS_CARD_SLOT + 8;
            p.hero_data.inventory.unset(InventoryItem::AccessCard);
        } else {
            p.info_message_queue
                .push_back("You don't have the access card\n".to_string());
        }
    }

    fn act(&mut self, _p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }
}
07070100000022000081A40000000000000000000000015FD8FA6800000F8E000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/acme.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
        ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_FALLINGBLOCK, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    counter: usize,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH * 2, TILE_HEIGHT);
        general.is_in_foreground = true;

        Specific {
            tile: OBJECT_FALLINGBLOCK,
            counter: 0,
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        let hero_geometry = p.hero_data.position.geometry;

        match self.counter {
            0 => {
                let xl = p.general.position.left();
                let xr = p.general.position.right();
                let y = p.general.position.y();
                let hxl = hero_geometry.left();
                let hxr = hero_geometry.right();
                let hy = hero_geometry.y();

                if y < hy && xl < hxr && xr > hxl {
                    let mut solid_between = false;
                    for i in (y as u32 / TILE_HEIGHT) + 1
                        ..hy as u32 / TILE_HEIGHT
                    {
                        let x = xl as u32 / TILE_WIDTH;
                        if p.solids.get(x, i) || p.solids.get(x + 1, i) {
                            solid_between = true;
                            break;
                        }
                    }
                    if !solid_between {
                        self.counter += 1;
                    }
                }
            }
            c if c <= 10 && c % 2 == 0 => {
                p.general.position.y -= 1;
                self.counter += 1;
            }
            c if c <= 10 && c % 2 == 1 => {
                p.general.position.y += 1;
                self.counter += 1;
            }
            _ => {
                if p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH,
                    p.general.position.y() as u32 / TILE_HEIGHT + 1,
                ) {
                    p.actor_adder.add_actor(
                        ActorType::Steam,
                        p.general.position.top_left(),
                    );
                    p.actor_adder.add_particle_firework(
                        p.general.position.top_left(),
                        4,
                    );
                    p.general.is_alive = false;
                } else {
                    p.general.position.offset(0, TILE_HEIGHT as i32);
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        p.renderer.place_tile(self.tile, pos)?;
        pos.x += TILE_WIDTH as i32;
        p.renderer.place_tile(self.tile + 1, pos)?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        if self.counter > 0 {
            p.hero_data.score.add(500);
            p.actor_adder.add_actor(
                ActorType::Score500,
                p.general.position.top_left(),
            );
            p.actor_adder
                .add_particle_firework(p.general.position.top_left(), 4);

            p.general.is_alive = false;
        }
        ShotProcessing::Absorb
    }

    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        if self.counter > 10 && !self.touching_hero {
            self.touching_hero = true;
            p.general.hurts_hero = true;
        }
    }
}
07070100000023000081A40000000000000000000000015FD8FA68000009F1000000000000000000000000000000000000002500000000freenukum-0.3.5/src/actor/balloon.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
        ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_BALLOON, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    destroyed: bool,
    current_frame: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT * 2);

        Specific {
            destroyed: false,
            current_frame: 0,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        if !self.destroyed {
            p.general.is_alive = false;
            p.hero_data.score.add(10000);
            p.actor_adder.add_actor(
                ActorType::Score10000,
                p.general.position.top_left(),
            );
        }
    }

    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= 9;

        if self.destroyed {
            p.general.is_alive = false;
        } else {
            p.general.position.y -= 1;
            if p.solids.get(
                p.general.position.x() as u32 / TILE_WIDTH,
                p.general.position.y() as u32 / TILE_WIDTH,
            ) {
                // balloon bumps against wall
                self.destroyed = true;
                p.actor_adder.add_actor(
                    ActorType::Steam,
                    p.general.position.top_left(),
                );
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();

        let tile = if self.destroyed {
            OBJECT_BALLOON + 4
        } else {
            OBJECT_BALLOON
        };
        p.renderer.place_tile(tile, pos)?;

        pos.y += TILE_HEIGHT as i32;
        p.renderer.place_tile(
            OBJECT_BALLOON + 1 + self.current_frame / 3,
            pos,
        )?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        self.destroyed = true;
        p.actor_adder
            .add_actor(ActorType::Steam, p.general.position.top_left());
        ShotProcessing::Absorb
    }
}
07070100000024000081A40000000000000000000000015FD8FA6800000EAE000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/bomb.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_BOMB, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug, PartialEq)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    counter: u32,
    explode_left: bool,
    explode_right: bool,
    explode_threshold: u32,
    num_flames: u32,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.acts_while_invisible = true;

        Specific {
            tile: ANIMATION_BOMB,
            current_frame: 0,
            num_frames: 2,
            counter: 0,
            explode_left: true,
            explode_right: true,
            explode_threshold: 12,
            num_flames: 4,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;

        self.counter += 1;

        if self.counter < self.explode_threshold {
        } else if self.counter < self.explode_threshold + self.num_flames {
            let distance = self.counter - self.explode_threshold;
            if self.explode_left {
                // explode to the left if possible
                let space_is_free = !p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH - distance,
                    p.general.position.y() as u32 / TILE_HEIGHT,
                );
                let space_has_solid_below = p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH - distance,
                    p.general.position.y() as u32 / TILE_HEIGHT + 1,
                );
                if space_is_free && space_has_solid_below {
                    p.actor_adder.add_actor(
                        ActorType::BombFire,
                        p.general
                            .position
                            .top_left()
                            .offset(-((distance * TILE_WIDTH) as i32), 0),
                    );
                } else {
                    self.explode_left = false;
                }
            }
            if self.explode_right {
                // explode to the right if possible
                let space_is_free = !p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH + distance,
                    p.general.position.y() as u32 / TILE_HEIGHT,
                );
                let space_has_solid_below = p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH + distance,
                    p.general.position.y() as u32 / TILE_HEIGHT + 1,
                );
                if space_is_free && space_has_solid_below {
                    p.actor_adder.add_actor(
                        ActorType::BombFire,
                        p.general
                            .position
                            .top_left()
                            .offset((distance * TILE_WIDTH) as i32, 0),
                    );
                } else {
                    self.explode_right = false;
                }
            }
        } else {
            p.general.is_alive = false;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        if self.counter < self.explode_threshold {
            p.renderer.place_tile(
                self.tile + self.current_frame,
                p.general.position.top_left(),
            )?;
        }
        Ok(())
    }
}
07070100000025000081A40000000000000000000000015FD8FA6800000791000000000000000000000000000000000000002400000000freenukum-0.3.5/src/actor/camera.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_CAMERA_CENTER, ANIMATION_CAMERA_LEFT,
    ANIMATION_CAMERA_RIGHT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Self {
        general.is_in_foreground = false;
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        let x = general.position.x() as u32 / TILE_WIDTH;
        let y = general.position.y() as u32 / TILE_HEIGHT;
        tiles.copy_from_to(x, y + 1, x, y);

        Specific {
            tile: ANIMATION_CAMERA_CENTER,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        let x = p.hero_data.position.geometry.x;
        self.tile = if x - 1 > p.general.position.x {
            ANIMATION_CAMERA_RIGHT
        } else if x + 1 < p.general.position.x {
            ANIMATION_CAMERA_LEFT
        } else {
            ANIMATION_CAMERA_CENTER
        };
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        p.general.is_alive = false;
        p.hero_data.score.add(100);
        p.actor_adder
            .add_actor(ActorType::Score100, p.general.position.top_left());
        p.actor_adder.add_actor(
            ActorType::Explosion,
            p.general.position.top_left(),
        );
        ShotProcessing::Absorb
    }
}
07070100000026000081A40000000000000000000000015FD8FA6800000ED8000000000000000000000000000000000000002600000000freenukum-0.3.5/src/actor/conveyor.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, HALFTILE_WIDTH, SOLID_BLACK,
    SOLID_CONVEYORBELT_CENTER, SOLID_CONVEYORBELT_LEFTEND,
    SOLID_CONVEYORBELT_RIGHTEND, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    current_frame: usize,
    num_frames: usize,
    direction: HorizontalDirection,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Self {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        let direction = match general.actor_type {
            ActorType::ConveyorLeftMovingRightEnd => {
                HorizontalDirection::Left
            }
            ActorType::ConveyorRightMovingRightEnd => {
                HorizontalDirection::Right
            }
            _ => unreachable!(),
        };

        // find the beginning of the conveyor belt
        let mut found_begin = false;
        let mut tile;
        while !found_begin {
            general.position.offset(-(TILE_WIDTH as i32), 0);
            general
                .position
                .set_width(general.position.width() + TILE_WIDTH);
            tile = tiles.get(
                general.position.x() as u32 / TILE_WIDTH,
                general.position.y() as u32 / TILE_HEIGHT,
            );
            if tile as usize == SOLID_CONVEYORBELT_LEFTEND
                || general.position.x() <= 0
                || tile == 0
            {
                found_begin = true;
                tiles.set(
                    general.position.x() as u32 / TILE_WIDTH,
                    general.position.y() as u32 / TILE_HEIGHT,
                    SOLID_BLACK as u16,
                );
            }
        }

        Specific {
            current_frame: 0,
            num_frames: 4,
            direction,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        let hero_push_offset = match self.direction {
            HorizontalDirection::Left => {
                if self.current_frame == 0 {
                    self.current_frame = self.num_frames;
                }
                self.current_frame -= 1;
                -(HALFTILE_WIDTH as i32)
            }
            HorizontalDirection::Right => {
                self.current_frame += 1;
                self.current_frame %= self.num_frames;
                HALFTILE_WIDTH as i32
            }
        };

        let hero_geometry = p.hero_data.position.geometry;

        if hero_geometry.right() > p.general.position.left()
            && hero_geometry.left() < p.general.position.right()
            && hero_geometry.bottom() == p.general.position.top()
        {
            p.hero_data
                .position
                .push_horizontally(p.solids, hero_push_offset);
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut tile = SOLID_CONVEYORBELT_LEFTEND + self.current_frame;
        let mut pos = p.general.position.top_left();

        let num_elements = p.general.position.width() / TILE_WIDTH;
        for i in 0..num_elements {
            if i == num_elements - 1 {
                // right end of the conveyor
                tile = SOLID_CONVEYORBELT_RIGHTEND + self.current_frame;
            } else if i == 1 {
                // center parts of the conveyor
                tile = SOLID_CONVEYORBELT_CENTER + self.current_frame % 2;
            }
            p.renderer.place_tile(tile, pos)?;
            pos.x += TILE_WIDTH as i32;
        }
        Ok(())
    }
}
07070100000027000081A40000000000000000000000015FD8FA680000078A000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/door.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ReceiveMessageParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_DOOR, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug, PartialEq, Eq)]
enum State {
    Closed,
    Opening,
    Open,
}

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    counter: usize,
    state: State,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            tile: OBJECT_DOOR,
            counter: 0,
            state: State::Closed,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        match self.state {
            State::Closed => {}
            State::Opening => {
                if self.counter == 0 {
                    p.solids.set(
                        p.general.position.x() as u32 / TILE_WIDTH,
                        p.general.position.y() as u32 / TILE_HEIGHT,
                        false,
                    );
                }
                self.counter += 1;
                if self.counter == 8 {
                    self.state = State::Open;
                    p.general.is_alive = false;
                }
            }
            State::Open => {}
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.counter,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn receive_message(&mut self, p: ReceiveMessageParameters) {
        if p.message != ActorMessageType::OpenDoor {
            return;
        }
        self.state = State::Opening;
    }
}
07070100000028000081A40000000000000000000000015FD8FA68000006FF000000000000000000000000000000000000002A00000000freenukum-0.3.5/src/actor/electric_arc.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, HeroTouchEndParameters,
        HeroTouchStartParameters, ReceiveMessageParameters,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_ELECTRIC_ARC, OBJECT_ELECTRIC_ARC_HURTING, TILE_HEIGHT,
    TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.is_in_foreground = true;
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            tile: OBJECT_ELECTRIC_ARC,
            current_frame: 0,
            num_frames: 4,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        self.tile = OBJECT_ELECTRIC_ARC_HURTING;
        p.general.hurts_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        self.tile = OBJECT_ELECTRIC_ARC;
        p.general.hurts_hero = false;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn receive_message(&mut self, p: ReceiveMessageParameters) {
        if p.message != ActorMessageType::Remove {
            return;
        }
        p.general.is_alive = false;
    }
}
07070100000029000081A40000000000000000000000015FD8FA68000012DE000000000000000000000000000000000000002600000000freenukum-0.3.5/src/actor/elevator.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        HeroInteractEndParameters, HeroInteractStartParameters,
        RenderParameters,
    },
    geometry::RectExt,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, HALFTILE_HEIGHT, OBJECT_ELEVATOR_TOP, SOLID_ELEVATOR,
    TILE_HEIGHT, TILE_WIDTH,
};

#[derive(PartialEq, Eq, Debug)]
enum State {
    Idle,
    Ascending,
    Descending,
}

#[derive(Debug)]
pub(crate) struct Specific {
    state: State,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        // TODO: check whether we *really* need the double width
        general.position.resize(TILE_WIDTH * 2, TILE_HEIGHT);
        general.is_in_foreground = true;
        general.acts_while_invisible = true;

        Specific { state: State::Idle }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        let hero_geometry = p.hero_data.position.geometry;

        if self.state == State::Ascending
            || self.state == State::Idle
                && p.general.position.height() as u32 > TILE_HEIGHT
        {
            // check if hero leaves elevator
            if !hero_geometry.touches(p.general.position)
                || p.general.position.x != hero_geometry.x
            {
                self.state = State::Descending;
            }
        }

        match self.state {
            State::Ascending => {
                if p.solids.get(
                    p.general.position.x() as u32 / TILE_WIDTH,
                    p.general.position.y() as u32 / TILE_HEIGHT - 3,
                ) {
                    // hero touches solid with head
                    self.state = State::Idle;
                } else {
                    let offset = p
                        .hero_data
                        .position
                        .push_vertically(&p.solids, -(TILE_HEIGHT as i32));
                    if -offset < TILE_HEIGHT as i32 {
                        p.hero_data
                            .position
                            .push_vertically(&p.solids, -offset);
                        self.state = State::Idle;
                    } else {
                        p.general.position.offset(0, offset);
                        p.general.position.set_height(
                            p.general.position.height() + (-offset) as u32,
                        );

                        p.solids.set(
                            p.general.position.x() as u32 / TILE_WIDTH,
                            p.general.position.y() as u32 / TILE_HEIGHT,
                            true,
                        );
                    }
                }
            }
            State::Descending => {
                for _ in 0..2 {
                    if p.general.position.height() as u32 > TILE_HEIGHT {
                        p.solids.set(
                            p.general.position.x() as u32 / TILE_WIDTH,
                            p.general.position.y() as u32 / TILE_HEIGHT,
                            false,
                        );
                        p.general.position.offset(0, TILE_HEIGHT as i32);
                        p.general.position.set_height(
                            p.general.position.height() - TILE_HEIGHT,
                        );
                    } else {
                        self.state = State::Idle;
                    }
                }
            }
            State::Idle => {}
        }
    }

    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        if p.hero_data.position.geometry.touches(p.general.position)
            && p.hero_data.position.geometry.bottom()
                == p.general.position.top()
        {
            self.state = State::Ascending;
        }
    }

    fn hero_interact_end(&mut self, p: HeroInteractEndParameters) {
        if p.hero_data.position.geometry.touches(p.general.position)
            && p.hero_data.position.geometry.x == p.general.position.x
        {
            self.state = State::Idle;
        } else {
            self.state = State::Descending;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = SOLID_ELEVATOR;
        let mut pos = p.general.position.top_left();
        for _ in 0..(p.general.position.height() / TILE_HEIGHT - 1) * 2 {
            pos.y += HALFTILE_HEIGHT as i32;
            p.renderer.place_tile(tile, pos)?;
        }
        pos = p.general.position.top_left();
        let tile = OBJECT_ELEVATOR_TOP;
        p.renderer.place_tile(tile, pos)?;
        Ok(())
    }
}
0707010000002A000081A40000000000000000000000015FD8FA6800000982000000000000000000000000000000000000002600000000freenukum-0.3.5/src/actor/exitdoor.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        HeroInteractStartParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles, PlayState},
    Result, ANIMATION_EXITDOOR, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(PartialEq, Eq, Debug)]
enum State {
    Closed,
    Opening,
    Closing,
}

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    counter: usize,
    state: State,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH * 2, TILE_HEIGHT * 2);
        general.is_in_foreground = false;

        Specific {
            tile: ANIMATION_EXITDOOR,
            counter: 0,
            state: State::Closed,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, _p: HeroInteractStartParameters) {
        if self.state == State::Closed {
            self.state = State::Opening;
        }
    }

    fn act(&mut self, p: ActParameters) {
        match self.state {
            State::Closed => {}
            State::Opening => {
                self.counter += 1;
                if self.counter >= 4 {
                    p.hero_data.hidden = true;
                    self.state = State::Closing;
                    self.counter -= 1;
                }
            }
            State::Closing => {
                if self.counter == 0 {
                    *p.play_state = PlayState::LevelFinished;
                    p.hero_data.hidden = false;
                } else {
                    self.counter -= 1;
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        p.renderer.place_tile(self.tile + self.counter * 4, pos)?;
        pos.x += TILE_WIDTH as i32;
        p.renderer
            .place_tile(self.tile + self.counter * 4 + 1, pos)?;
        pos.x -= TILE_WIDTH as i32;
        pos.y += TILE_HEIGHT as i32;
        p.renderer
            .place_tile(self.tile + self.counter * 4 + 2, pos)?;
        pos.x += TILE_WIDTH as i32;
        p.renderer
            .place_tile(self.tile + self.counter * 4 + 3, pos)?;
        Ok(())
    }
}
0707010000002B000081A40000000000000000000000015FD8FA6800000783000000000000000000000000000000000000002C00000000freenukum-0.3.5/src/actor/expandingfloor.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ReceiveMessageParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, SOLID_EXPANDINGFLOOR, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    expanding: bool,
    finished: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            expanding: false,
            finished: false,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        if self.expanding {
            let x = p.general.position.right() as u32 / TILE_WIDTH;
            let y = p.general.position.top() as u32 / TILE_HEIGHT;
            let can_expand = !p.solids.get(x, y);
            if can_expand {
                p.solids.set(x, y, true);
                p.general
                    .position
                    .set_width(p.general.position.width() + TILE_WIDTH);
            } else {
                self.expanding = false;
                self.finished = true;
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = SOLID_EXPANDINGFLOOR;
        let mut pos = p.general.position.top_left();
        for _ in 0..p.general.position.width() as u32 / TILE_WIDTH {
            p.renderer.place_tile(tile, pos)?;
            pos.x += TILE_WIDTH as i32;
        }
        Ok(())
    }

    fn receive_message(&mut self, p: ReceiveMessageParameters) {
        if p.message != ActorMessageType::Expand {
            return;
        }
        if !self.expanding && !self.finished {
            self.expanding = true;
        }
    }
}
0707010000002C000081A40000000000000000000000015FD8FA6800000CF2000000000000000000000000000000000000002100000000freenukum-0.3.5/src/actor/fan.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters, ShotParameters, ShotProcessing,
    },
    geometry::RectExt,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_FAN, HALFTILE_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    running: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.offset(0, -(TILE_HEIGHT as i32));
        general.position.resize(TILE_WIDTH, TILE_HEIGHT * 2);

        Specific {
            tile: ANIMATION_FAN,
            current_frame: 0,
            num_frames: 4,
            running: 10,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        match self.running {
            0 => {}
            1 => {
                self.current_frame += 1;
            }
            2 => {}
            3 => {}
            4 => {}
            5 => {
                self.current_frame += 1;
            }
            6 => {}
            7 => {}
            8 => {
                self.current_frame += 1;
            }
            9 => {}
            10 => {
                self.current_frame += 1;
            }
            _ => unreachable!(),
        }
        self.current_frame %= self.num_frames;
        if self.running < 10 && self.running > 0 {
            self.running -= 1;
        } else if self.running == 10
            && p.hero_data
                .position
                .geometry
                .overlaps_vertically(p.general.position)
        {
            let mut hdistance = p
                .hero_data
                .position
                .geometry
                .horizontal_distance(p.general.position);

            let fan_direction = match p.general.actor_type {
                ActorType::FanLeft => -1,
                ActorType::FanRight => 1,
                _ => unreachable!(),
            };

            if (fan_direction * hdistance) < 0 {
                return;
            }

            if hdistance == 0 {
                hdistance = HALFTILE_WIDTH as i32 * fan_direction;
            }

            let range = HALFTILE_WIDTH as i32 * 8;
            if hdistance.abs() < range {
                p.hero_data.position.push_horizontally(
                    &p.solids,
                    fan_direction * TILE_WIDTH as i32,
                );
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        p.renderer
            .place_tile(self.tile + self.current_frame * 2, pos)?;
        pos.y += TILE_HEIGHT as i32;
        p.renderer
            .place_tile(self.tile + self.current_frame * 2 + 1, pos)?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        self.running = 9;
        p.actor_adder
            .add_actor(ActorType::Steam, p.general.position.top_left());
        ShotProcessing::Absorb
    }
}
0707010000002D000081A40000000000000000000000015FD8FA68000012C2000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/fire.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, LEVEL_WIDTH, OBJECT_FIRELEFT,
    OBJECT_FIRERIGHT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug, PartialEq, Eq)]
enum State {
    Off,
    Ignition,
    Burning,
}

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    direction: HorizontalDirection,
    state: State,
    counter: usize,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        let x = general.position.x() as u32 / TILE_WIDTH;
        let y = general.position.y() as u32 / TILE_HEIGHT;

        let (tile, direction) = match general.actor_type {
            ActorType::FireRight => {
                if x < LEVEL_WIDTH + 1 {
                    tiles.copy_from_to(x + 1, y, x, y);
                }
                (OBJECT_FIRERIGHT, HorizontalDirection::Right)
            }
            ActorType::FireLeft => {
                if x > 0 {
                    tiles.copy_from_to(x - 1, y, x, y);
                }
                general.position.offset(-2 * TILE_WIDTH as i32, 0);
                (OBJECT_FIRELEFT, HorizontalDirection::Left)
            }
            _ => unreachable!(),
        };

        Specific {
            tile,
            direction,
            state: State::Off,
            counter: 0,
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = self.state == State::Burning;
        self.touching_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
        self.touching_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        match self.state {
            State::Off => {
                if self.counter == 40 {
                    self.counter = 0;
                    self.state = State::Ignition;
                }
            }
            State::Ignition => {
                if self.counter == 20 {
                    self.counter = 0;
                    self.state = State::Burning;
                    if self.touching_hero {
                        p.general.hurts_hero = true;
                    }
                }
            }
            State::Burning => {
                if self.counter == 20 {
                    self.counter = 0;
                    self.state = State::Off;
                    if self.touching_hero {
                        p.general.hurts_hero = false;
                    }
                }
            }
        }

        self.counter += 1;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let (tile0, tile1, tile2) = match self.state {
            State::Off => (None, None, None),
            State::Ignition => {
                if (self.counter % 2) > 0 {
                    match self.direction {
                        HorizontalDirection::Left => {
                            (None, None, Some(self.tile))
                        }
                        HorizontalDirection::Right => {
                            (Some(self.tile), None, None)
                        }
                    }
                } else {
                    (None, None, None)
                }
            }
            State::Burning => {
                let offset = self.counter % 2;
                match self.direction {
                    HorizontalDirection::Left => (
                        Some(self.tile + 3 + offset),
                        Some(self.tile + 1 + offset),
                        Some(self.tile + 1 + offset),
                    ),
                    HorizontalDirection::Right => (
                        Some(self.tile + 1 + offset),
                        Some(self.tile + 1 + offset),
                        Some(self.tile + 3 + offset),
                    ),
                }
            }
        };

        let mut pos = p.general.position.top_left();
        if let Some(tile) = tile0 {
            p.renderer.place_tile(tile, pos)?;
        }
        pos.x += TILE_WIDTH as i32;
        if let Some(tile) = tile1 {
            p.renderer.place_tile(tile, pos)?;
        }
        pos.x += TILE_WIDTH as i32;
        if let Some(tile) = tile2 {
            p.renderer.place_tile(tile, pos)?;
        }
        Ok(())
    }
}
0707010000002E000081A40000000000000000000000015FD8FA6800001259000000000000000000000000000000000000002A00000000freenukum-0.3.5/src/actor/firewheelbot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, ANIMATION_FIREWHEEL_OFF,
    ANIMATION_FIREWHEEL_ON, HALFTILE_HEIGHT, HALFTILE_WIDTH, TILE_HEIGHT,
    TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    direction: HorizontalDirection,
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    was_shot: usize,
    fire_is_on: bool,
    counter: usize,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            direction: HorizontalDirection::Left,
            tile: ANIMATION_FIREWHEEL_OFF,
            counter: 0,
            touching_hero: false,
            current_frame: 0,
            num_frames: 4,
            was_shot: 0,
            fire_is_on: false,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        if self.was_shot < 2 {
            self.touching_hero = true;
            p.general.hurts_hero = true;
        }
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        self.touching_hero = false;
        p.general.hurts_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        if self.was_shot == 2 {
            p.general.is_alive = false;
            p.actor_adder.add_actor(
                ActorType::Explosion,
                p.general
                    .position
                    .top_left()
                    .offset(HALFTILE_WIDTH as i32, 0),
            );
            p.actor_adder
                .add_particle_firework(p.general.position.top_left(), 8);
            p.hero_data.score.add(2500);
        } else {
            self.counter += 1;
            if self.counter % 2 == 1 {
                self.current_frame += 1;
                self.current_frame %= self.num_frames;
            }

            if self.counter == 50 {
                self.counter = 0;
                self.fire_is_on = !self.fire_is_on;
                if self.fire_is_on {
                    self.tile = ANIMATION_FIREWHEEL_ON;
                } else {
                    self.tile = ANIMATION_FIREWHEEL_OFF;
                }
            }

            let direction = self.direction.as_factor_i32();

            if !p.solids.push_rect_standing_on_ground(
                &mut p.general.position,
                direction * HALFTILE_WIDTH as i32 / 2,
                HALFTILE_HEIGHT as u8,
            ) {
                // push was not successful, so we reverse the direction
                self.direction.reverse();
            }

            if self.was_shot == 1 {
                // create steam clouds
                if self.current_frame == 0 {
                    p.actor_adder.add_actor(
                        ActorType::Steam,
                        p.general.position.top_left().offset(
                            HALFTILE_WIDTH as i32,
                            -(TILE_HEIGHT as i32),
                        ),
                    );
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let r = p.general.position;
        let mut pos = r.top_left();
        pos.x = pos.x + r.width() as i32 / 2 - TILE_WIDTH as i32;
        pos.y -= TILE_HEIGHT as i32;

        p.renderer
            .place_tile(self.tile + self.current_frame * 4, pos)?;
        pos.x += TILE_WIDTH as i32;
        p.renderer
            .place_tile(self.tile + self.current_frame * 4 + 1, pos)?;
        pos.x -= TILE_WIDTH as i32;
        pos.y += TILE_HEIGHT as i32;
        p.renderer
            .place_tile(self.tile + self.current_frame * 4 + 2, pos)?;
        pos.x += TILE_WIDTH as i32;
        p.renderer
            .place_tile(self.tile + self.current_frame * 4 + 3, pos)?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        if !self.fire_is_on {
            if self.was_shot == 1 && self.touching_hero {
                p.general.hurts_hero = false;
                self.touching_hero = false;
            }
            if self.was_shot != 2 {
                self.was_shot += 1;
            }
        }
        ShotProcessing::Absorb
    }
}
0707010000002F000081A40000000000000000000000015FD8FA6800000CA5000000000000000000000000000000000000002800000000freenukum-0.3.5/src/actor/glove_slot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ActorType, HeroInteractStartParameters,
        RenderParameters,
    },
    hero::InventoryItem,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_GLOVE_SLOT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
enum State {
    Idle,
    Shooting,
    Expanded,
}

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    state: State,
    countdown: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {
            tile: OBJECT_GLOVE_SLOT,
            current_frame: 0,
            num_frames: 4,
            state: State::Idle,
            countdown: 0,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        match self.state {
            State::Idle => {
                if p.hero_data.inventory.is_set(InventoryItem::Glove) {
                    p.actor_message_queue.push_back(
                        ActorType::ExpandingFloor,
                        ActorMessageType::Expand,
                    );
                    self.state = State::Expanded;
                } else {
                    self.state = State::Shooting;
                    self.countdown = 20;
                }
            }
            State::Shooting => {}
            State::Expanded => {}
        }
    }

    fn act(&mut self, p: ActParameters) {
        match self.state {
            State::Idle => {
                self.current_frame += 1;
                self.current_frame %= self.num_frames;
            }
            State::Shooting => {
                self.current_frame += 1;
                self.current_frame %= self.num_frames;
                self.countdown -= 1;
                if self.countdown % 4 == 0 {
                    p.actor_adder.add_actor(
                        ActorType::HostileShotRight,
                        p.general.position.top_left(),
                    );
                } else if self.countdown % 4 == 2 {
                    p.actor_adder.add_actor(
                        ActorType::HostileShotLeft,
                        p.general.position.top_left(),
                    );
                }
                if self.countdown == 0 {
                    self.state = State::Idle;
                }
            }
            State::Expanded => {}
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let adder = if self.current_frame == 0 { 0 } else { 1 };
        let mut pos = p.general.position.top_left();
        p.renderer.place_tile(self.tile + adder, pos)?;

        pos.x -= TILE_WIDTH as i32;
        p.renderer.place_tile(self.tile + 2, pos)?;

        pos.x += 2 * TILE_WIDTH as i32;
        p.renderer.place_tile(self.tile + 3, pos)?;
        Ok(())
    }
}
07070100000030000081A40000000000000000000000015FD8FA68000009BC000000000000000000000000000000000000002900000000freenukum-0.3.5/src/actor/hostileshot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_HOSTILESHOT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    touching_hero: bool,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.acts_while_invisible = true;

        let tile = match general.actor_type {
            ActorType::HostileShotLeft => OBJECT_HOSTILESHOT,
            ActorType::HostileShotRight => OBJECT_HOSTILESHOT + 2,
            _ => unreachable!(
                "Passed actor type {:?} to hostile shot actor \
                which is not a shot actor id",
                general.actor_type
            ),
        };

        Specific {
            tile,
            touching_hero: false,
            current_frame: 0,
            num_frames: 2,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        self.touching_hero = true;
        p.general.hurts_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        self.touching_hero = false;
        p.general.hurts_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        let offset = match p.general.actor_type {
            ActorType::HostileShotLeft => -(TILE_WIDTH as i32),
            ActorType::HostileShotRight => TILE_WIDTH as i32,
            _ => unreachable!(),
        };
        p.general.position.offset(offset, 0);

        if p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT,
        ) {
            p.general.is_alive = false;
            if self.touching_hero {
                p.general.hurts_hero = false;
            }
        }

        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }
}
07070100000031000081A40000000000000000000000015FD8FA6800004693000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/item.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
        ShotParameters, ShotProcessing,
    },
    hero::{FetchedLetter, InventoryItem},
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_SODA, HALFTILE_HEIGHT, OBJECT_ACCESS_CARD,
    OBJECT_BOOT, OBJECT_BOX_BLUE, OBJECT_BOX_GREY, OBJECT_BOX_RED,
    OBJECT_CHICKEN_DOUBLE, OBJECT_CHICKEN_SINGLE, OBJECT_CLAMP,
    OBJECT_DISK, OBJECT_FLAG, OBJECT_FOOTBALL, OBJECT_GLOVE, OBJECT_GUN,
    OBJECT_JOYSTICK, OBJECT_LETTER_D, OBJECT_LETTER_E, OBJECT_LETTER_K,
    OBJECT_LETTER_U, OBJECT_NUCLEARMOLECULE, OBJECT_RADIO, TILE_HEIGHT,
    TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        let (tile, num_frames) = match general.actor_type {
            ActorType::BoxRedSoda | ActorType::BoxRedChicken => {
                (OBJECT_BOX_RED, 1)
            }
            ActorType::BoxBlueFootball
            | ActorType::BoxBlueJoystick
            | ActorType::BoxBlueDisk
            | ActorType::BoxBlueBalloon
            | ActorType::BoxBlueFlag
            | ActorType::BoxBlueRadio => (OBJECT_BOX_BLUE, 1),
            ActorType::BoxGreyEmpty
            | ActorType::BoxGreyBoots
            | ActorType::BoxGreyClamps
            | ActorType::BoxGreyGun
            | ActorType::BoxGreyBomb
            | ActorType::BoxGreyGlove
            | ActorType::BoxGreyFullLife
            | ActorType::BoxGreyAccessCard
            | ActorType::BoxGreyLetterD
            | ActorType::BoxGreyLetterU
            | ActorType::BoxGreyLetterK
            | ActorType::BoxGreyLetterE => (OBJECT_BOX_GREY, 1),
            ActorType::Joystick => (OBJECT_JOYSTICK, 1),
            ActorType::Football => (OBJECT_FOOTBALL, 1),
            ActorType::Flag => (OBJECT_FLAG, 3),
            ActorType::Disk => (OBJECT_DISK, 1),
            ActorType::Radio => (OBJECT_RADIO, 3),
            ActorType::Soda => (ANIMATION_SODA, 4),
            ActorType::Boots => (OBJECT_BOOT, 1),
            ActorType::Gun => (OBJECT_GUN, 1),
            ActorType::FullLife => (OBJECT_NUCLEARMOLECULE, 8),
            ActorType::ChickenSingle => (OBJECT_CHICKEN_SINGLE, 1),
            ActorType::ChickenDouble => (OBJECT_CHICKEN_DOUBLE, 1),
            ActorType::LetterD => (OBJECT_LETTER_D, 1),
            ActorType::LetterU => (OBJECT_LETTER_U, 1),
            ActorType::LetterK => (OBJECT_LETTER_K, 1),
            ActorType::LetterE => (OBJECT_LETTER_E, 1),
            ActorType::AccessCard => (OBJECT_ACCESS_CARD, 1),
            ActorType::Glove => (OBJECT_GLOVE, 1),
            ActorType::Clamps => (OBJECT_CLAMP, 1),
            _ => unreachable!(
                "Attempted to load actor type {:?} as item",
                general.actor_type
            ),
        };

        Specific {
            tile,
            current_frame: 0,
            num_frames,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        match p.general.actor_type {
            ActorType::LetterD => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::D);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterU => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::U);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterK => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::K);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterE => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::E);
                p.hero_data.score.add(500);
                if p.hero_data.fetched_letter_state.succeeded() {
                    p.actor_adder.add_actor(
                        ActorType::Score10000,
                        p.general.position.top_left(),
                    );
                    p.hero_data.score.add(10000);
                } else {
                    p.actor_adder.add_actor(
                        ActorType::Score500,
                        p.general.position.top_left(),
                    );
                    p.hero_data.score.add(500);
                }
            }
            ActorType::FullLife => {
                p.hero_data.health.fill_max();
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Gun => {
                p.hero_data.firepower.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::AccessCard => {
                p.hero_data.inventory.set(InventoryItem::AccessCard);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Glove => {
                p.hero_data.inventory.set(InventoryItem::Glove);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Boots => {
                p.hero_data.inventory.set(InventoryItem::Boot);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Clamps => {
                p.hero_data.inventory.set(InventoryItem::Clamp);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Football => {
                p.general.is_alive = false;
                p.hero_data.score.add(100);
                p.actor_adder.add_actor(
                    ActorType::Score100,
                    p.general.position.top_left(),
                );
            }
            ActorType::Disk => {
                p.general.is_alive = false;
                p.hero_data.score.add(5000);
                p.actor_adder.add_actor(
                    ActorType::Score5000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Joystick => {
                p.general.is_alive = false;
                p.hero_data.score.add(2000);
                p.actor_adder.add_actor(
                    ActorType::Score2000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Radio | ActorType::Flag => {
                p.general.is_alive = false;
                match self.current_frame {
                    0 => {
                        p.hero_data.score.add(100);
                        p.actor_adder.add_actor(
                            ActorType::Score100,
                            p.general.position.top_left(),
                        );
                    }
                    1 => {
                        p.hero_data.score.add(2000);
                        p.actor_adder.add_actor(
                            ActorType::Score2000,
                            p.general.position.top_left(),
                        );
                    }
                    2 => {
                        p.hero_data.score.add(5000);
                        p.actor_adder.add_actor(
                            ActorType::Score5000,
                            p.general.position.top_left(),
                        );
                    }
                    _ => unreachable!(),
                }
            }
            ActorType::Soda => {
                p.hero_data.health.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(200);
                p.actor_adder.add_actor(
                    ActorType::Score200,
                    p.general.position.top_left(),
                );
            }
            ActorType::ChickenSingle => {
                p.hero_data.health.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(100);
                p.actor_adder.add_actor(
                    ActorType::Score100,
                    p.general.position.top_left(),
                );
            }
            ActorType::ChickenDouble => {
                p.hero_data.health.increase(2);
                p.general.is_alive = false;
                p.hero_data.score.add(200);
                p.actor_adder.add_actor(
                    ActorType::Score200,
                    p.general.position.top_left(),
                );
            }
            _ => {}
        }
    }

    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;

        if !p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) {
            // fall down until the actor lands on solid ground
            p.general.position.offset(0, HALFTILE_HEIGHT as i32);
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn can_get_shot(&self, general: &ActorData) -> bool {
        matches!(
            general.actor_type,
            ActorType::BoxBlueFootball
                | ActorType::BoxBlueJoystick
                | ActorType::BoxBlueDisk
                | ActorType::BoxBlueBalloon
                | ActorType::BoxBlueFlag
                | ActorType::BoxBlueRadio
                | ActorType::BoxRedSoda
                | ActorType::BoxRedChicken
                | ActorType::BoxGreyEmpty
                | ActorType::BoxGreyBoots
                | ActorType::BoxGreyClamps
                | ActorType::BoxGreyGun
                | ActorType::BoxGreyBomb
                | ActorType::BoxGreyGlove
                | ActorType::BoxGreyFullLife
                | ActorType::BoxGreyAccessCard
                | ActorType::BoxGreyLetterD
                | ActorType::BoxGreyLetterU
                | ActorType::BoxGreyLetterK
                | ActorType::BoxGreyLetterE
                | ActorType::ChickenSingle
                | ActorType::Soda
        )
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        let pos = p.general.position.top_left();
        match p.general.actor_type {
            ActorType::BoxBlueFootball => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Football, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueJoystick => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Joystick, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueDisk => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Disk, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueBalloon => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(
                    ActorType::Balloon,
                    pos.offset(0, -(TILE_HEIGHT as i32)),
                );
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueFlag => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Flag, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueRadio => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Radio, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxRedSoda => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Soda, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxRedChicken => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::ChickenSingle, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyEmpty => {
                p.general.is_alive = false;
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyBoots => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Boots, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyClamps => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Clamps, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyGun => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Gun, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyBomb => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Bomb, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyGlove => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Glove, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyFullLife => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::FullLife, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyAccessCard => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::AccessCard, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterD => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterD, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterU => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterU, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterK => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterK, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterE => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterE, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::ChickenSingle => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::ChickenDouble, pos);
                ShotProcessing::Absorb
            }
            ActorType::Soda => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::SodaFlying, pos);
                ShotProcessing::Absorb
            }
            _ => ShotProcessing::Ignore,
        }
    }
}
07070100000032000081A40000000000000000000000015FD8FA680000074E000000000000000000000000000000000000002100000000freenukum-0.3.5/src/actor/key.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
    },
    hero::InventoryItem,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_KEY_BLUE, OBJECT_KEY_GREEN, OBJECT_KEY_PINK,
    OBJECT_KEY_RED, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {}
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        let item = match p.general.actor_type {
            ActorType::KeyRed => InventoryItem::KeyRed,
            ActorType::KeyBlue => InventoryItem::KeyBlue,
            ActorType::KeyPink => InventoryItem::KeyPink,
            ActorType::KeyGreen => InventoryItem::KeyGreen,
            _ => unreachable!(),
        };

        p.hero_data.inventory.set(item);
        p.hero_data.score.add(1000);
        p.actor_adder.add_actor(
            ActorType::Score1000,
            p.general.position.top_left(),
        );
        p.general.is_alive = false;
    }

    fn act(&mut self, _p: ActParameters) {}

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = match p.general.actor_type {
            ActorType::KeyRed => OBJECT_KEY_RED,
            ActorType::KeyBlue => OBJECT_KEY_BLUE,
            ActorType::KeyPink => OBJECT_KEY_PINK,
            ActorType::KeyGreen => OBJECT_KEY_GREEN,
            _ => unreachable!(),
        };
        p.renderer.place_tile(tile, p.general.position.top_left())?;
        Ok(())
    }
}
07070100000033000081A40000000000000000000000015FD8FA6800000BF7000000000000000000000000000000000000002500000000freenukum-0.3.5/src/actor/keyhole.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ActorType, HeroInteractStartParameters,
        RenderParameters,
    },
    hero::InventoryItem,
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_KEYHOLE_BLACK, OBJECT_KEYHOLE_BLUE,
    OBJECT_KEYHOLE_GREEN, OBJECT_KEYHOLE_PINK, OBJECT_KEYHOLE_RED,
    TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    counter: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {
            tile: OBJECT_KEYHOLE_BLACK,
            counter: 0,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {
        if self.counter < 5 {
            self.counter += 1;
            self.counter %= 4;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = match (self.counter, p.general.actor_type) {
            (0, _) => self.tile,
            (_, ActorType::KeyholeRed) => OBJECT_KEYHOLE_RED,
            (_, ActorType::KeyholeBlue) => OBJECT_KEYHOLE_BLUE,
            (_, ActorType::KeyholePink) => OBJECT_KEYHOLE_PINK,
            (_, ActorType::KeyholeGreen) => OBJECT_KEYHOLE_GREEN,
            _ => unreachable!(),
        };

        p.renderer.place_tile(tile, p.general.position.top_left())?;
        Ok(())
    }

    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        let (required_item, door_actor_type) = match p.general.actor_type {
            ActorType::KeyholeRed => {
                (InventoryItem::KeyRed, ActorType::DoorRed)
            }
            ActorType::KeyholeBlue => {
                (InventoryItem::KeyBlue, ActorType::DoorBlue)
            }
            ActorType::KeyholePink => {
                (InventoryItem::KeyPink, ActorType::DoorPink)
            }
            ActorType::KeyholeGreen => {
                (InventoryItem::KeyGreen, ActorType::DoorGreen)
            }
            _ => unreachable!(),
        };

        if p.hero_data.inventory.is_set(required_item) {
            p.actor_message_queue
                .push_back(door_actor_type, ActorMessageType::OpenDoor);
            self.counter = 5;
            p.hero_data.inventory.unset(required_item);
        } else if self.counter < 5 {
            let color = match required_item {
                InventoryItem::KeyRed => "red",
                InventoryItem::KeyBlue => "blue",
                InventoryItem::KeyPink => "pink",
                InventoryItem::KeyGreen => "green",
                _ => unreachable!(),
            };
            p.info_message_queue
                .push_back(format!("You don't have the {} key.", color));
        }
    }
}
07070100000034000081A40000000000000000000000015FD8FA6800000C8F000000000000000000000000000000000000002200000000freenukum-0.3.5/src/actor/mill.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ActorType, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_ROTATINGCYLINDER, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    lives: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        while general.position.y > 0
            && !solids.get(
                general.position.x() as u32 / TILE_WIDTH,
                general.position.y() as u32 / TILE_HEIGHT - 1,
            )
        {
            general.position.offset(0, -(TILE_HEIGHT as i32));
            general
                .position
                .set_height(general.position.height() + TILE_HEIGHT);
        }

        Specific {
            tile: OBJECT_ROTATINGCYLINDER,
            current_frame: 0,
            num_frames: 5,
            lives: 10,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.hero_data.health.kill();
    }

    fn act(&mut self, _p: ActParameters) {
        if self.lives > 0 {
            self.current_frame += 1;
            self.current_frame %= self.num_frames;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();

        for _ in 0..p.general.position.height() / TILE_WIDTH {
            p.renderer.place_tile(self.tile + self.current_frame, pos)?;
            pos.y += TILE_HEIGHT as i32;
        }
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        self.lives -= 1;
        if self.lives > 0 {
            p.actor_adder
                .add_particle_firework(p.general.position.center(), 4);
        } else {
            // TODO: add removal animation (destroyed body)
            p.general.is_alive = false;
            p.actor_message_queue.push_back(
                ActorType::ElectricArc,
                ActorMessageType::Remove,
            );
            p.hero_data.score.add(20000);
            p.actor_adder
                .add_particle_firework(p.general.position.center(), 20);
            p.actor_adder.add_actor(
                ActorType::Score10000,
                p.general.position.top_left().offset(
                    0,
                    (p.general.position.height() / 2 - TILE_HEIGHT) as i32,
                ),
            );
            p.actor_adder.add_actor(
                ActorType::Score10000,
                p.general
                    .position
                    .top_left()
                    .offset(0, p.general.position.height() as i32 / 2),
            );
        }
        ShotProcessing::Absorb
    }
}
07070100000035000081A40000000000000000000000015FD8FA6800008645000000000000000000000000000000000000002100000000freenukum-0.3.5/src/actor/mod.rsmod accesscard_door;
mod accesscard_slot;
mod acme;
mod balloon;
mod bomb;
mod camera;
mod conveyor;
mod door;
mod electric_arc;
mod elevator;
mod exitdoor;
mod expandingfloor;
mod fan;
mod fire;
mod firewheelbot;
mod glove_slot;
mod hostileshot;
mod item;
mod key;
mod keyhole;
mod mill;
mod notebook;
mod particle;
mod placeholder;
mod redball_jumping;
mod redball_lying;
mod robot;
mod rocket;
mod score;
mod shootable_wall;
mod simpleanimation;
mod singleanimation;
mod soda_flying;
mod spikes;
mod surveillancescreen;
mod tankbot;
mod teleporter;
mod unstablefloor;
mod wallcrawler;

use crate::{
    geometry::RectExt,
    hero::HeroData,
    infobox::InfoMessageQueue,
    level::{solids::LevelSolids, tiles::LevelTiles, PlayState},
    rendering::Renderer,
    Result,
};
use sdl2::rect::{Point, Rect};

#[derive(Debug)]
pub struct ActorsList {
    actors: Vec<Actor>,
    interaction_target: Option<usize>,
}

impl Default for ActorsList {
    fn default() -> Self {
        Self::new()
    }
}

impl ActorsList {
    pub fn new() -> Self {
        ActorsList {
            actors: Vec::new(),
            interaction_target: None,
        }
    }

    pub fn count(&self) -> usize {
        self.actors.len()
    }

    pub fn get_mut(&mut self, index: usize) -> Option<&mut Actor> {
        self.actors.get_mut(index)
    }

    pub fn remove_dead(&mut self) {
        let mut i = 0;
        while i < self.actors.len() {
            match self.actors.get(i) {
                Some(a) if !a.general.is_alive => {
                    self.actors.remove(i);
                    self.interaction_target = match self.interaction_target
                    {
                        Some(index) if index == i => None,
                        Some(index) if index > i => Some(index - 1),
                        Some(index) => Some(index),
                        None => None,
                    };
                }
                _ => {
                    i += 1;
                }
            }
        }
    }

    pub fn send_message(
        &mut self,
        receivers: ActorType,
        message: ActorMessageType,
        hero_data: &mut HeroData,
        solids: &mut LevelSolids,
    ) {
        for actor in self
            .actors
            .iter_mut()
            .filter(|a| a.general.actor_type == receivers)
        {
            let p = ReceiveMessageParameters {
                general: &mut actor.general,
                message,
                hero_data,
                solids,
            };
            actor.specific.receive_message(p);
        }
    }

    pub fn process_shot(
        &mut self,
        shot_position: Rect,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        actor_adder: &mut dyn ActorAdder,
        hero_data: &mut HeroData,
        actor_message_queue: &mut ActorMessageQueue,
    ) -> bool {
        for actor in self.actors.iter_mut() {
            if actor.can_get_shot()
                && shot_position.touches(actor.position())
                && actor.shot(
                    solids,
                    tiles,
                    actor_adder,
                    hero_data,
                    actor_message_queue,
                ) == ShotProcessing::Absorb
            {
                return true;
            }
        }
        false
    }

    pub fn start_interaction(
        &mut self,
        play_state: &mut PlayState,
        hero_data: &mut HeroData,
        info_message_queue: &mut InfoMessageQueue,
        actor_message_queue: &mut ActorMessageQueue,
    ) {
        let new_interactor = self.actors.iter().position(|actor| {
            actor.hero_can_interact(hero_data)
                && hero_data
                    .position
                    .geometry
                    .touches(actor.general.position)
        });

        if let Some(i) = new_interactor {
            self.end_interaction(play_state, hero_data);

            let actor = self.actors.get_mut(i).unwrap();
            let p = HeroInteractStartParameters {
                general: &mut actor.general,
                play_state,
                hero_data,
                info_message_queue,
                actor_message_queue,
            };
            self.interaction_target = Some(i);
            actor.specific.hero_interact_start(p);
        }
    }

    pub fn end_interaction(
        &mut self,
        play_state: &mut PlayState,
        hero_data: &mut HeroData,
    ) {
        if let Some(i) = self.interaction_target.take() {
            if let Some(actor) = self.actors.get_mut(i) {
                let p = HeroInteractEndParameters {
                    general: &mut actor.general,
                    play_state,
                    hero_data,
                };
                actor.specific.hero_interact_end(p);
            }
        }
    }

    pub fn act(
        &mut self,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        hero_data: &mut HeroData,
        actor_queue: &mut ActorQueue,
        play_state: &mut PlayState,
    ) {
        let mut actors_hurting_hero = 0usize;
        for actor in self.actors.iter_mut() {
            if actor.general.acts_while_invisible
                || actor.general.is_visible
            {
                actor.act(
                    solids,
                    tiles,
                    hero_data,
                    actor_queue,
                    play_state,
                );
                if actor.general.is_alive && actor.general.hurts_hero {
                    actors_hurting_hero += 1;
                }
            }
        }

        let mut adder = LevelActorAdder {
            solids,
            tiles,
            actors: self,
        };

        actor_queue.process(&mut adder);
        self.remove_dead();
        hero_data.gets_hurt = actors_hurting_hero > 0;
    }

    pub fn update_visibility(&mut self, visible_rect: Rect) {
        self.actors.iter_mut().for_each(|actor| {
            actor.general.is_visible =
                actor.general.position.has_intersection(visible_rect);
        });
    }

    pub fn render_background_actors(
        &mut self,
        renderer: &mut dyn Renderer,
        draw_collision_bounds: bool,
    ) -> Result<()> {
        Ok(self
            .actors
            .iter_mut()
            .filter(|actor| {
                actor.general.is_visible && !actor.general.is_in_foreground
            })
            .map(|actor| actor.render(renderer, draw_collision_bounds))
            .collect::<Result<_>>()?)
    }

    pub fn render_foreground_actors(
        &mut self,
        renderer: &mut dyn Renderer,
        draw_collision_bounds: bool,
    ) -> Result<()> {
        Ok(self
            .actors
            .iter_mut()
            .filter(|actor| {
                actor.general.is_visible && actor.general.is_in_foreground
            })
            .map(|actor| actor.render(renderer, draw_collision_bounds))
            .collect::<Result<_>>()?)
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ShotProcessing {
    Absorb,
    Ignore,
}

#[derive(Debug)]
pub struct Actor {
    pub(crate) general: ActorData,
    pub(crate) specific: Box<dyn ActorInterface>,
}

impl Actor {
    fn act(
        &mut self,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        hero_data: &mut HeroData,
        actor_adder: &mut dyn ActorAdder,
        play_state: &mut PlayState,
    ) -> bool {
        self.check_hero_touch(hero_data, actor_adder);

        let p = ActParameters {
            general: &mut self.general,
            solids,
            tiles,
            hero_data,
            actor_adder,
            play_state,
        };
        self.specific.act(p);
        self.general.is_alive
    }

    fn check_hero_touch(
        &mut self,
        hero_data: &mut HeroData,
        actor_adder: &mut dyn ActorAdder,
    ) {
        let touching_hero = self
            .general
            .position
            .has_intersection(hero_data.position.geometry);

        if touching_hero {
            if !self.general.touches_hero {
                self.general.touches_hero = true;

                let p = HeroTouchStartParameters {
                    general: &mut self.general,
                    hero_data,
                    actor_adder,
                };

                self.specific.hero_touch_start(p);
            }
        } else if self.general.touches_hero {
            self.general.touches_hero = false;

            let p = HeroTouchEndParameters {
                general: &mut self.general,
                hero_data,
            };
            self.specific.hero_touch_end(p);
        }
    }

    fn hero_can_interact(&self, hero_data: &HeroData) -> bool {
        if self.general.actor_type == ActorType::Lift {
            /* This check needs to be done for elevator only because
             * if there are two elevators next to each other, the mostleft
             * elevator would be chosen for interaction instead of the one on
             * which the hero stands.
             */
            // TODO: this should be moved into ActorInterface::hero_can_interact
            self.general.position.x == hero_data.position.geometry.x
        } else {
            self.specific.hero_can_interact()
        }
    }

    pub fn can_get_shot(&self) -> bool {
        self.specific.can_get_shot(&self.general)
    }

    pub fn shot(
        &mut self,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        actor_adder: &mut dyn ActorAdder,
        hero_data: &mut HeroData,
        actor_message_queue: &mut ActorMessageQueue,
    ) -> ShotProcessing {
        let p = ShotParameters {
            general: &mut self.general,
            solids,
            tiles,
            actor_adder,
            hero_data,
            actor_message_queue,
        };
        self.specific.shot(p)
    }

    pub fn is_alive(&self) -> bool {
        self.general.is_alive
    }

    pub fn position(&self) -> Rect {
        self.general.position
    }

    pub fn render(
        &mut self,
        renderer: &mut dyn Renderer,
        draw_collision_bounds: bool,
    ) -> Result<()> {
        let p = RenderParameters {
            general: &mut self.general,
            renderer,
        };
        self.specific.render(p)?;

        if draw_collision_bounds {
            let color = crate::collision_bounds_color();
            renderer.draw_rect(self.general.position, color)?;
        }
        Ok(())
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ActorType {
    FireWheelBot,
    FlameGnomeBot,
    FlyingBot,
    FootBot,
    HelicopterBot,
    RabbitoidBot,
    RedBallJumping,
    RedBallLying,
    Robot,
    RobotDisappearing,
    SnakeBot,
    TankBot,
    WallCrawlerBotLeft,
    WallCrawlerBotRight,
    DrProton,
    Camera,
    Explosion,
    DustCloud,
    Steam,
    ParticlePink,
    ParticleBlue,
    ParticleWhite,
    ParticleGreen,
    Rocket,
    Bomb,
    BombFire,
    Water,
    ExitDoor,
    Notebook,
    SurveillanceScreen,
    HostileShotLeft,
    HostileShotRight,
    Soda,
    SodaFlying,
    UnstableFloor,
    ExpandingFloor,
    ConveyorLeftMovingRightEnd,
    ConveyorRightMovingRightEnd,
    FanLeft,
    FanRight,
    BrokenWallBackground,
    StoneBackground,
    Teleporter1,
    Teleporter2,
    FenceBackground,
    StoneWindowBackground,
    WindowLeftBackground,
    WindowRightBackground,
    Screen,
    BoxGreyEmpty,
    BoxGreyBoots,
    Boots,
    BoxGreyClamps,
    Clamps,
    BoxGreyGun,
    Gun,
    BoxGreyBomb,
    BoxRedSoda,
    BoxRedChicken,
    ChickenSingle,
    ChickenDouble,
    BoxBlueFootball,
    Football,
    Flag,
    BoxBlueJoystick,
    Joystick,
    BoxBlueDisk,
    Disk,
    BoxBlueBalloon,
    Balloon,
    BoxGreyGlove,
    Glove,
    BoxGreyFullLife,
    FullLife,
    BoxBlueFlag,
    BlueFlag,
    BoxBlueRadio,
    Radio,
    BoxGreyAccessCard,
    AccessCard,
    BoxGreyLetterD,
    LetterD,
    BoxGreyLetterU,
    LetterU,
    BoxGreyLetterK,
    LetterK,
    BoxGreyLetterE,
    LetterE,
    AccessCardSlot,
    GloveSlot,
    KeyRed,
    KeyholeRed,
    DoorRed,
    KeyBlue,
    KeyholeBlue,
    DoorBlue,
    KeyPink,
    KeyholePink,
    DoorPink,
    KeyGreen,
    KeyholeGreen,
    DoorGreen,
    ShootableWall,
    Lift,
    Acme,
    FireRight,
    FireLeft,
    Mill,
    ElectricArc,
    AccessCardDoor,
    SpikesUp,
    SpikesDown,
    Spike,
    Score100,
    Score200,
    Score500,
    Score1000,
    Score2000,
    Score5000,
    Score10000,
    ScoreBonus1Left,
    ScoreBonus1Right,
    ScoreBonus2Left,
    ScoreBonus2Right,
    ScoreBonus3Left,
    ScoreBonus3Right,
    ScoreBonus4Left,
    ScoreBonus4Right,
    ScoreBonus5Left,
    ScoreBonus5Right,
    ScoreBonus6Left,
    ScoreBonus6Right,
    ScoreBonus7Left,
    ScoreBonus7Right,
    BlueLightBackground1,
    BlueLightBackground2,
    BlueLightBackground3,
    BlueLightBackground4,
    TextOnScreenBackground,
    HighVoltageFlashBackground,
    RedFlashlightBackground,
    BlueFlashlightBackground,
    KeypanelBackground,
    RedRotationLightBackground,
    UpArrowBackground,
    GreenPoisonBackground,
    LavaBackground,
}

impl ActorType {
    pub(crate) fn create_actor_interface(
        &self,
        g: &mut ActorData,
        s: &mut LevelSolids,
        t: &mut LevelTiles,
    ) -> Box<dyn ActorInterface> {
        match self {
            ActorType::FireWheelBot => {
                firewheelbot::Specific::create_boxed(g, s, t)
            }
            ActorType::FlameGnomeBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::FlyingBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::FootBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::HelicopterBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::RabbitoidBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::RedBallJumping => {
                redball_jumping::Specific::create_boxed(g, s, t)
            }
            ActorType::RedBallLying => {
                redball_lying::Specific::create_boxed(g, s, t)
            }
            ActorType::Robot => robot::Specific::create_boxed(g, s, t),
            ActorType::RobotDisappearing => {
                singleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::SnakeBot => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::TankBot => tankbot::Specific::create_boxed(g, s, t),
            ActorType::WallCrawlerBotLeft => {
                wallcrawler::Specific::create_boxed(g, s, t)
            }
            ActorType::WallCrawlerBotRight => {
                wallcrawler::Specific::create_boxed(g, s, t)
            }
            ActorType::DrProton => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::Camera => camera::Specific::create_boxed(g, s, t),
            ActorType::Explosion => {
                singleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::DustCloud => {
                singleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::Steam => {
                singleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::ParticlePink => {
                particle::Specific::create_boxed(g, s, t)
            }
            ActorType::ParticleBlue => {
                particle::Specific::create_boxed(g, s, t)
            }
            ActorType::ParticleWhite => {
                particle::Specific::create_boxed(g, s, t)
            }
            ActorType::ParticleGreen => {
                particle::Specific::create_boxed(g, s, t)
            }
            ActorType::Rocket => rocket::Specific::create_boxed(g, s, t),
            ActorType::Bomb => bomb::Specific::create_boxed(g, s, t),
            ActorType::BombFire => {
                singleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::Water => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::ExitDoor => {
                exitdoor::Specific::create_boxed(g, s, t)
            }
            ActorType::Notebook => {
                notebook::Specific::create_boxed(g, s, t)
            }
            ActorType::SurveillanceScreen => {
                surveillancescreen::Specific::create_boxed(g, s, t)
            }
            ActorType::HostileShotLeft => {
                hostileshot::Specific::create_boxed(g, s, t)
            }
            ActorType::HostileShotRight => {
                hostileshot::Specific::create_boxed(g, s, t)
            }
            ActorType::Soda => item::Specific::create_boxed(g, s, t),
            ActorType::SodaFlying => {
                soda_flying::Specific::create_boxed(g, s, t)
            }
            ActorType::UnstableFloor => {
                unstablefloor::Specific::create_boxed(g, s, t)
            }
            ActorType::ExpandingFloor => {
                expandingfloor::Specific::create_boxed(g, s, t)
            }
            ActorType::ConveyorLeftMovingRightEnd => {
                conveyor::Specific::create_boxed(g, s, t)
            }
            ActorType::ConveyorRightMovingRightEnd => {
                conveyor::Specific::create_boxed(g, s, t)
            }
            ActorType::FanLeft => fan::Specific::create_boxed(g, s, t),
            ActorType::FanRight => fan::Specific::create_boxed(g, s, t),
            ActorType::BrokenWallBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::StoneBackground => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::Teleporter1 => {
                teleporter::Specific::create_boxed(g, s, t)
            }
            ActorType::Teleporter2 => {
                teleporter::Specific::create_boxed(g, s, t)
            }
            ActorType::FenceBackground => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::StoneWindowBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::WindowLeftBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::WindowRightBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::Screen => {
                placeholder::Specific::create_boxed(g, s, t)
            }
            ActorType::BoxGreyEmpty => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::BoxGreyBoots => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Boots => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyClamps => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Clamps => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyGun => item::Specific::create_boxed(g, s, t),
            ActorType::Gun => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyBomb => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::BoxRedSoda => item::Specific::create_boxed(g, s, t),
            ActorType::BoxRedChicken => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::ChickenSingle => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::ChickenDouble => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::BoxBlueFootball => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Football => item::Specific::create_boxed(g, s, t),
            ActorType::Flag => item::Specific::create_boxed(g, s, t),
            ActorType::BoxBlueJoystick => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Joystick => item::Specific::create_boxed(g, s, t),
            ActorType::BoxBlueDisk => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Disk => item::Specific::create_boxed(g, s, t),
            ActorType::BoxBlueBalloon => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Balloon => balloon::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyGlove => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Glove => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyFullLife => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::FullLife => item::Specific::create_boxed(g, s, t),
            ActorType::BoxBlueFlag => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueFlag => item::Specific::create_boxed(g, s, t),
            ActorType::BoxBlueRadio => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::Radio => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyAccessCard => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::AccessCard => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyLetterD => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::LetterD => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyLetterU => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::LetterU => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyLetterK => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::LetterK => item::Specific::create_boxed(g, s, t),
            ActorType::BoxGreyLetterE => {
                item::Specific::create_boxed(g, s, t)
            }
            ActorType::LetterE => item::Specific::create_boxed(g, s, t),
            ActorType::AccessCardSlot => {
                accesscard_slot::Specific::create_boxed(g, s, t)
            }
            ActorType::GloveSlot => {
                glove_slot::Specific::create_boxed(g, s, t)
            }
            ActorType::KeyRed => key::Specific::create_boxed(g, s, t),
            ActorType::KeyholeRed => {
                keyhole::Specific::create_boxed(g, s, t)
            }
            ActorType::DoorRed => door::Specific::create_boxed(g, s, t),
            ActorType::KeyBlue => key::Specific::create_boxed(g, s, t),
            ActorType::KeyholeBlue => {
                keyhole::Specific::create_boxed(g, s, t)
            }
            ActorType::DoorBlue => door::Specific::create_boxed(g, s, t),
            ActorType::KeyPink => key::Specific::create_boxed(g, s, t),
            ActorType::KeyholePink => {
                keyhole::Specific::create_boxed(g, s, t)
            }
            ActorType::DoorPink => door::Specific::create_boxed(g, s, t),
            ActorType::KeyGreen => key::Specific::create_boxed(g, s, t),
            ActorType::KeyholeGreen => {
                keyhole::Specific::create_boxed(g, s, t)
            }
            ActorType::DoorGreen => door::Specific::create_boxed(g, s, t),
            ActorType::ShootableWall => {
                shootable_wall::Specific::create_boxed(g, s, t)
            }
            ActorType::Lift => elevator::Specific::create_boxed(g, s, t),
            ActorType::Acme => acme::Specific::create_boxed(g, s, t),
            ActorType::FireRight => fire::Specific::create_boxed(g, s, t),
            ActorType::FireLeft => fire::Specific::create_boxed(g, s, t),
            ActorType::Mill => mill::Specific::create_boxed(g, s, t),
            ActorType::ElectricArc => {
                electric_arc::Specific::create_boxed(g, s, t)
            }
            ActorType::AccessCardDoor => {
                accesscard_door::Specific::create_boxed(g, s, t)
            }
            ActorType::SpikesUp => spikes::Specific::create_boxed(g, s, t),
            ActorType::SpikesDown => {
                spikes::Specific::create_boxed(g, s, t)
            }
            ActorType::Spike => spikes::Specific::create_boxed(g, s, t),
            ActorType::Score100 => score::Specific::create_boxed(g, s, t),
            ActorType::Score200 => score::Specific::create_boxed(g, s, t),
            ActorType::Score500 => score::Specific::create_boxed(g, s, t),
            ActorType::Score1000 => score::Specific::create_boxed(g, s, t),
            ActorType::Score2000 => score::Specific::create_boxed(g, s, t),
            ActorType::Score5000 => score::Specific::create_boxed(g, s, t),
            ActorType::Score10000 => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus1Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus1Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus2Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus2Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus3Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus3Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus4Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus4Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus5Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus5Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus6Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus6Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus7Left => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::ScoreBonus7Right => {
                score::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueLightBackground1 => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueLightBackground2 => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueLightBackground3 => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueLightBackground4 => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::TextOnScreenBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::HighVoltageFlashBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::RedFlashlightBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::BlueFlashlightBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::KeypanelBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::RedRotationLightBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::UpArrowBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::GreenPoisonBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
            ActorType::LavaBackground => {
                simpleanimation::Specific::create_boxed(g, s, t)
            }
        }
    }
}

#[derive(Debug)]
pub struct ActorData {
    pub actor_type: ActorType,
    pub position: Rect,
    pub is_in_foreground: bool,
    pub hurts_hero: bool,
    pub is_alive: bool,
    pub touches_hero: bool,
    pub is_visible: bool,
    pub acts_while_invisible: bool,
}

impl ActorData {
    pub fn new(actor_type: ActorType) -> Self {
        ActorData {
            actor_type,
            position: Rect::new(0, 0, 0, 0),
            is_in_foreground: true,
            hurts_hero: false,
            is_alive: true,
            touches_hero: false,
            is_visible: false,
            acts_while_invisible: false,
        }
    }
}

pub struct ActorQueueItem {
    pub actor_type: ActorType,
    pub pos: Point,
}

pub trait ActorAdder {
    fn add_actor(&mut self, actor_type: ActorType, pos: Point);

    fn add_particle_firework(&mut self, pos: Point, count: usize) {
        for i in 0..count {
            let actor_type = match i % 4 {
                0 => ActorType::ParticlePink,
                1 => ActorType::ParticleBlue,
                2 => ActorType::ParticleWhite,
                3 => ActorType::ParticleGreen,
                _ => unreachable!(),
            };
            self.add_actor(actor_type, pos);
        }
    }
}

#[derive(Default)]
pub struct ActorQueue {
    pub actors: Vec<ActorQueueItem>,
}

impl ActorAdder for ActorQueue {
    fn add_actor(&mut self, actor_type: ActorType, pos: Point) {
        self.push_back(actor_type, pos);
    }
}

impl ActorQueue {
    pub fn new() -> Self {
        Self { actors: Vec::new() }
    }

    pub fn push_back(&mut self, actor_type: ActorType, pos: Point) {
        self.actors.push(ActorQueueItem { actor_type, pos });
    }

    pub(crate) fn process(&mut self, destination: &mut dyn ActorAdder) {
        for ActorQueueItem { actor_type, pos } in self.actors.drain(..) {
            destination.add_actor(actor_type, pos);
        }
    }
}

pub struct LevelActorAdder<'a> {
    pub solids: &'a mut LevelSolids,
    pub tiles: &'a mut LevelTiles,
    pub actors: &'a mut ActorsList,
}
impl<'a> ActorAdder for LevelActorAdder<'a> {
    fn add_actor(&mut self, actor_type: ActorType, pos: Point) {
        let mut general = ActorData::new(actor_type);
        general.position.reposition(pos);
        let specific = actor_type.create_actor_interface(
            &mut general,
            self.solids,
            self.tiles,
        );
        self.actors.actors.push(Actor { general, specific });
    }
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum ActorMessageType {
    OpenDoor,
    Teleport,
    Expand,
    Remove,
}

pub struct ActorMessage {
    pub receivers: ActorType,
    pub message: ActorMessageType,
}

#[derive(Default)]
pub struct ActorMessageQueue {
    pub messages: Vec<ActorMessage>,
}

impl ActorMessageQueue {
    pub fn new() -> Self {
        ActorMessageQueue {
            messages: Vec::new(),
        }
    }

    pub fn push_back(
        &mut self,
        receivers: ActorType,
        message: ActorMessageType,
    ) {
        self.messages.push(ActorMessage { receivers, message });
    }
}

pub(crate) trait ActorCreateInterface: Sized {
    fn create(
        general: &mut ActorData,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Self;

    fn create_boxed(
        general: &mut ActorData,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Box<Self> {
        Box::new(Self::create(general, solids, tiles))
    }
}

pub struct ActParameters<'a> {
    pub general: &'a mut ActorData,
    pub solids: &'a mut LevelSolids,
    pub tiles: &'a mut LevelTiles,
    pub hero_data: &'a mut HeroData,
    pub actor_adder: &'a mut dyn ActorAdder,
    pub play_state: &'a mut PlayState,
}

pub struct ShotParameters<'a> {
    pub general: &'a mut ActorData,
    pub solids: &'a mut LevelSolids,
    pub tiles: &'a mut LevelTiles,
    pub actor_adder: &'a mut dyn ActorAdder,
    pub hero_data: &'a mut HeroData,
    pub actor_message_queue: &'a mut ActorMessageQueue,
}

pub struct RenderParameters<'a> {
    pub general: &'a mut ActorData,
    pub renderer: &'a mut dyn Renderer,
}

pub struct ReceiveMessageParameters<'a> {
    pub general: &'a mut ActorData,
    pub message: ActorMessageType,
    pub hero_data: &'a mut HeroData,
    pub solids: &'a mut LevelSolids,
}

pub struct HeroInteractStartParameters<'a> {
    pub general: &'a mut ActorData,
    pub play_state: &'a mut PlayState,
    pub hero_data: &'a mut HeroData,
    pub info_message_queue: &'a mut InfoMessageQueue,
    pub actor_message_queue: &'a mut ActorMessageQueue,
}

pub struct HeroInteractEndParameters<'a> {
    pub general: &'a mut ActorData,
    pub hero_data: &'a mut HeroData,
    pub play_state: &'a mut PlayState,
}

pub struct HeroTouchStartParameters<'a> {
    pub general: &'a mut ActorData,
    pub hero_data: &'a mut HeroData,
    pub actor_adder: &'a mut dyn ActorAdder,
}

pub struct HeroTouchEndParameters<'a> {
    pub general: &'a mut ActorData,
    pub hero_data: &'a mut HeroData,
}

pub(crate) trait ActorInterface: std::fmt::Debug {
    fn hero_touch_start(&mut self, _p: HeroTouchStartParameters) {}

    fn hero_touch_end(&mut self, _p: HeroTouchEndParameters) {}

    fn hero_can_interact(&self) -> bool {
        false
    }

    fn hero_interact_start(&mut self, _p: HeroInteractStartParameters) {}

    fn hero_interact_end(&mut self, _p: HeroInteractEndParameters) {}

    fn act(&mut self, p: ActParameters);

    fn render(&mut self, p: RenderParameters) -> Result<()>;

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        false
    }

    fn shot(&mut self, _p: ShotParameters) -> ShotProcessing {
        ShotProcessing::Ignore
    }

    fn receive_message(&mut self, _p: ReceiveMessageParameters) {}
}
07070100000036000081A40000000000000000000000015FD8FA6800000483000000000000000000000000000000000000002600000000freenukum-0.3.5/src/actor/notebook.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        HeroInteractStartParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_NOTEBOOK, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;
        Specific {}
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {}

    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        // TODO: implement functionality.
        p.info_message_queue
            .push_back("Not implemented yet.".to_string());
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(OBJECT_NOTEBOOK, p.general.position.top_left())?;
        Ok(())
    }
}
07070100000037000081A40000000000000000000000015FD8FA6800000748000000000000000000000000000000000000002600000000freenukum-0.3.5/src/actor/particle.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, HALFTILE_HEIGHT, HALFTILE_WIDTH, OBJECT_SPARK_BLUE,
    OBJECT_SPARK_GREEN, OBJECT_SPARK_PINK, OBJECT_SPARK_WHITE,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    countdown: usize,
    hspeed: i32,
    vspeed: i32,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.is_in_foreground = true;
        general.acts_while_invisible = true;
        general.position.resize(HALFTILE_WIDTH, HALFTILE_HEIGHT);

        use rand::Rng;
        let mut rng = rand::thread_rng();
        let vspeed = rng.gen_range(-12, 5);
        let hspeed = rng.gen_range(-8, 9);

        let tile = match general.actor_type {
            ActorType::ParticlePink => OBJECT_SPARK_PINK,
            ActorType::ParticleBlue => OBJECT_SPARK_BLUE,
            ActorType::ParticleWhite => OBJECT_SPARK_WHITE,
            ActorType::ParticleGreen => OBJECT_SPARK_GREEN,
            _ => unreachable!(),
        };

        Specific {
            tile,
            countdown: 20,
            hspeed,
            vspeed,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        if self.countdown > 0 {
            self.countdown -= 1;
            p.general.position.offset(self.hspeed, self.vspeed);
            self.vspeed += 2;
        } else {
            p.general.is_alive = false;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }
}
07070100000038000081A40000000000000000000000015FD8FA680000032B000000000000000000000000000000000000002900000000freenukum-0.3.5/src/actor/placeholder.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        println!(
            "Warning: creating placeholder for unimplemented \
                actor type {:?}",
            general.actor_type
        );
        general.is_alive = false;
        Specific {}
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {}

    fn render(&mut self, _p: RenderParameters) -> Result<()> {
        Ok(())
    }
}
07070100000039000081A40000000000000000000000015FD8FA68000007AA000000000000000000000000000000000000002D00000000freenukum-0.3.5/src/actor/redball_jumping.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_MINE, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    counter: u16,
    base_y: i32,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            tile: ANIMATION_MINE,
            counter: 0,
            base_y: general.position.y(),
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        let distance = match self.counter {
            0 => 0,
            1 | 11 => 16,
            2 | 10 => 28,
            3 | 9 => 36,
            4 | 8 => 40,
            5 | 7 => 41,
            6 => 42,
            _ => unreachable!(),
        };
        p.general.position.set_y(self.base_y - distance);

        self.counter += 1;
        self.counter %= 12;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        /*
         * We don't need to do anything, this is just to absorb
         * the bullet when the actor is shot.
         */
        true
    }

    fn shot(&mut self, _p: ShotParameters) -> ShotProcessing {
        ShotProcessing::Absorb
    }
}
0707010000003A000081A40000000000000000000000015FD8FA68000006C6000000000000000000000000000000000000002B00000000freenukum-0.3.5/src/actor/redball_lying.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_MINE, HALFTILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    touching_hero: u8,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        Specific {
            tile: ANIMATION_MINE,
            touching_hero: 0,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
        self.touching_hero = 1;
    }

    fn act(&mut self, p: ActParameters) {
        if !p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) {
            p.general.position.offset(0, HALFTILE_HEIGHT as i32);
        }

        match self.touching_hero {
            1 => self.touching_hero += 1,
            2 => {
                p.general.hurts_hero = false;
                p.general.is_alive = false;
                p.actor_adder.add_actor(
                    ActorType::BombFire,
                    p.general.position.top_left(),
                );
            }
            _ => {}
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }
}
0707010000003B000081A40000000000000000000000015FD8FA6800000FBB000000000000000000000000000000000000002300000000freenukum-0.3.5/src/actor/robot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, ANIMATION_ROBOT, HALFTILE_HEIGHT,
    HALFTILE_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    direction: HorizontalDirection,
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        Specific {
            direction: HorizontalDirection::Left,
            tile: ANIMATION_ROBOT,
            current_frame: 0,
            num_frames: 3,
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
        self.touching_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
        self.touching_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;

        if !p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) {
            // In the air, falling down.
            p.general.position.offset(0, HALFTILE_HEIGHT as i32);
        } else {
            // On the floor, walking.
            if self.current_frame == 0 {
                let mut direction = match self.direction {
                    HorizontalDirection::Left => -1,
                    HorizontalDirection::Right => 2,
                };
                // Check if the place next to the bot is free
                if !p.solids.get(
                (
                    p.general.position.x() +
                    direction * HALFTILE_WIDTH as i32
                ) as u32/ TILE_WIDTH,
                p.general.position.y() as u32 / TILE_HEIGHT
            ) &&
            // Check if the tile below this free place is solid
            p.solids.get(
                (
                    p.general.position.x() +
                    direction * HALFTILE_WIDTH as i32
                ) as u32 / TILE_WIDTH,
                (p.general.position.y() as u32 + TILE_HEIGHT) / TILE_HEIGHT
            ) {
                    if direction == 2 {
                        direction = 1;
                    }
                    p.general
                        .position
                        .offset(direction * HALFTILE_WIDTH as i32, 0);
                } else {
                    self.direction.reverse();
                    if direction == 2 {
                        direction = 1
                    };
                    direction *= -1;
                    p.general
                        .position
                        .offset(direction * HALFTILE_WIDTH as i32, 0);
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        p.hero_data.score.add(100);
        if self.touching_hero {
            p.general.hurts_hero = false;
            self.touching_hero = false;
        }
        p.actor_adder.add_actor(
            ActorType::RobotDisappearing,
            p.general.position.top_left(),
        );
        p.general.is_alive = false;
        ShotProcessing::Absorb
    }
}
0707010000003C000081A40000000000000000000000015FD8FA6800000DCC000000000000000000000000000000000000002400000000freenukum-0.3.5/src/actor/rocket.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, HALFTILE_HEIGHT, OBJECT_ROCKET, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug, PartialEq)]
enum State {
    Idle,
    Flying,
}

#[derive(Debug, PartialEq)]
pub(crate) struct Specific {
    state: State,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        let tile_x = general.position.x() as u32 / TILE_WIDTH;
        let tile_y = general.position.y() as u32 / TILE_HEIGHT;
        tiles.copy_from_to(tile_x, tile_y - 1, tile_x, tile_y);

        Specific { state: State::Idle }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        match self.state {
            State::Idle => {}
            State::Flying => {
                p.general.position.offset(0, -(HALFTILE_HEIGHT as i32));
                if p.solids.collides(p.general.position) {
                    let tile_x =
                        p.general.position.x() as u32 / TILE_WIDTH;
                    let tile_y =
                        p.general.position.y() as u32 / TILE_HEIGHT;
                    p.solids.set(tile_x, tile_y + 1, false);
                    // TODO: trigger a re-rendering of the affected tiles
                    p.tiles.copy_from_to(
                        tile_x,
                        tile_y - 1,
                        tile_x,
                        tile_y,
                    );
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p
            .general
            .position
            .top_left()
            .offset(0, -(TILE_HEIGHT as i32 * 3));

        let tile = OBJECT_ROCKET;
        p.renderer.place_tile(tile, pos)?;

        let tile = OBJECT_ROCKET + 1;
        for _ in 0..2 {
            pos.y += TILE_HEIGHT as i32;
            p.renderer.place_tile(tile, pos)?;
        }

        let tile = OBJECT_ROCKET + 2;
        pos.y += TILE_HEIGHT as i32;
        p.renderer.place_tile(tile, pos)?;

        let tile = OBJECT_ROCKET + 3;
        pos.x -= TILE_WIDTH as i32;
        p.renderer.place_tile(tile, pos)?;

        let tile = OBJECT_ROCKET + 4;
        pos.x += 2 * TILE_WIDTH as i32;
        p.renderer.place_tile(tile, pos)?;

        if self.state == State::Flying {
            let tile = OBJECT_ROCKET + 6;
            pos.x -= TILE_WIDTH as i32;
            pos.y += TILE_HEIGHT as i32;
            p.renderer.place_tile(tile, pos)?;
        }
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        if self.state == State::Idle {
            // TODO: create animation
            self.state = State::Flying;
            let tile_x = p.general.position.x() as u32 / TILE_WIDTH;
            let tile_y = (p.general.position.y() as u32
                + p.general.position.height())
                / TILE_HEIGHT;

            p.solids.set(tile_x, tile_y, false);
            // TODO: trigger a re-rendering of the affected tiles
            p.tiles.copy_from_to(tile_x, tile_y + 1, tile_x, tile_y);
        }
        ShotProcessing::Absorb
    }
}
0707010000003D000081A40000000000000000000000015FD8FA6800000C3E000000000000000000000000000000000000002300000000freenukum-0.3.5/src/actor/score.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, NUMBER_100, NUMBER_1000, NUMBER_10000, NUMBER_200,
    NUMBER_2000, NUMBER_500, NUMBER_5000, NUMBER_BONUS_1_LEFT,
    NUMBER_BONUS_1_RIGHT, NUMBER_BONUS_2_LEFT, NUMBER_BONUS_2_RIGHT,
    NUMBER_BONUS_3_LEFT, NUMBER_BONUS_3_RIGHT, NUMBER_BONUS_4_LEFT,
    NUMBER_BONUS_4_RIGHT, NUMBER_BONUS_5_LEFT, NUMBER_BONUS_5_RIGHT,
    NUMBER_BONUS_6_LEFT, NUMBER_BONUS_6_RIGHT, NUMBER_BONUS_7_LEFT,
    NUMBER_BONUS_7_RIGHT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    countdown: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.is_in_foreground = true;
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.acts_while_invisible = true;

        let tile = match general.actor_type {
            ActorType::Score100 => NUMBER_100,
            ActorType::Score200 => NUMBER_200,
            ActorType::Score500 => NUMBER_500,
            ActorType::Score1000 => NUMBER_1000,
            ActorType::Score2000 => NUMBER_2000,
            ActorType::Score5000 => NUMBER_5000,
            ActorType::Score10000 => NUMBER_10000,
            ActorType::ScoreBonus1Left => NUMBER_BONUS_1_LEFT,
            ActorType::ScoreBonus1Right => NUMBER_BONUS_1_RIGHT,
            ActorType::ScoreBonus2Left => NUMBER_BONUS_2_LEFT,
            ActorType::ScoreBonus2Right => NUMBER_BONUS_2_RIGHT,
            ActorType::ScoreBonus3Left => NUMBER_BONUS_3_LEFT,
            ActorType::ScoreBonus3Right => NUMBER_BONUS_3_RIGHT,
            ActorType::ScoreBonus4Left => NUMBER_BONUS_4_LEFT,
            ActorType::ScoreBonus4Right => NUMBER_BONUS_4_RIGHT,
            ActorType::ScoreBonus5Left => NUMBER_BONUS_5_LEFT,
            ActorType::ScoreBonus5Right => NUMBER_BONUS_5_RIGHT,
            ActorType::ScoreBonus6Left => NUMBER_BONUS_6_LEFT,
            ActorType::ScoreBonus6Right => NUMBER_BONUS_6_RIGHT,
            ActorType::ScoreBonus7Left => NUMBER_BONUS_7_LEFT,
            ActorType::ScoreBonus7Right => NUMBER_BONUS_7_RIGHT,
            _ => {
                unreachable!(
                    "Actor type {:?} added as an score \
                    which is not a score id",
                    general.actor_type
                );
            }
        };

        Specific {
            tile,
            countdown: 40,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        self.countdown -= 1;
        p.general.position.y -= 1;
        if self.countdown == 0
            || p.general.position.y() == -(TILE_HEIGHT as i32)
        {
            p.general.is_alive = false;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer
            .place_tile(self.tile, p.general.position.top_left())?;
        Ok(())
    }
}
0707010000003E000081A40000000000000000000000015FD8FA680000065C000000000000000000000000000000000000002C00000000freenukum-0.3.5/src/actor/shootable_wall.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, BACKGROUND_LIGHT_GREY, SOLID_SHOOTABLE_WALL_BRICKS,
    TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = false;

        Specific {}
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {}

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        p.hero_data.score.add(10);
        p.actor_adder.add_actor(
            ActorType::Explosion,
            p.general.position.top_left(),
        );
        p.general.is_alive = false;
        p.solids.set(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT,
            false,
        );
        ShotProcessing::Absorb
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            BACKGROUND_LIGHT_GREY,
            p.general.position.top_left(),
        )?;
        p.renderer.place_tile(
            SOLID_SHOOTABLE_WALL_BRICKS,
            p.general.position.top_left(),
        )?;
        Ok(())
    }
}
0707010000003F000081A40000000000000000000000015FD8FA6800000A5D000000000000000000000000000000000000002D00000000freenukum-0.3.5/src/actor/simpleanimation.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_BROKENWALLBG, ANIMATION_STONEWINDOWBG,
    ANIMATION_WINDOWBG, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.is_in_foreground = false;
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);

        let (tile, num_frames) = match general.actor_type {
            ActorType::TextOnScreenBackground => (0x0004, 4),
            ActorType::HighVoltageFlashBackground => (0x0008, 4),
            ActorType::RedFlashlightBackground => (0x000C, 4),
            ActorType::BlueFlashlightBackground => (0x0010, 4),
            ActorType::KeypanelBackground => (0x0014, 4),
            ActorType::RedRotationLightBackground => (0x0018, 4),
            ActorType::UpArrowBackground => (0x001C, 4),
            ActorType::BlueLightBackground1 => (0x0020, 4),
            ActorType::BlueLightBackground2 => (0x0021, 4),
            ActorType::BlueLightBackground3 => (0x0022, 4),
            ActorType::BlueLightBackground4 => (0x0023, 4),
            ActorType::GreenPoisonBackground => (0x0028, 4),
            ActorType::LavaBackground => (0x002C, 4),
            ActorType::WindowLeftBackground => (ANIMATION_WINDOWBG, 1),
            ActorType::WindowRightBackground => {
                (ANIMATION_WINDOWBG + 1, 1)
            }
            ActorType::StoneWindowBackground => {
                (ANIMATION_STONEWINDOWBG, 1)
            }
            ActorType::BrokenWallBackground => (ANIMATION_BROKENWALLBG, 1),
            _ => {
                unreachable!(
                    "Actor type {:?} added as an animation \
                    which is not an animation id",
                    general.actor_type
                );
            }
        };

        Specific {
            tile,
            current_frame: 0,
            num_frames,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }
}
07070100000040000081A40000000000000000000000015FD8FA6800000A84000000000000000000000000000000000000002D00000000freenukum-0.3.5/src/actor/singleanimation.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_BOMBFIRE, ANIMATION_EXPLOSION, ANIMATION_ROBOT,
    OBJECT_DUSTCLOUD, OBJECT_STEAM, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    can_hurt_hero: bool,
    replaced_by: Option<ActorType>,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.is_in_foreground = false;
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.acts_while_invisible = true;

        let (tile, num_frames, can_hurt_hero, replaced_by) = match general
            .actor_type
        {
            ActorType::BombFire => (ANIMATION_BOMBFIRE, 6, true, None),
            ActorType::Explosion => (ANIMATION_EXPLOSION, 6, false, None),
            ActorType::DustCloud => (OBJECT_DUSTCLOUD, 5, false, None),
            ActorType::Steam => (OBJECT_STEAM, 5, false, None),
            ActorType::RobotDisappearing => {
                (ANIMATION_ROBOT + 3, 7, false, Some(ActorType::Explosion))
            }
            _ => {
                unreachable!(
                    "Actor type {:?} added as an animation \
                    which is not an animation id",
                    general.actor_type
                );
            }
        };

        Specific {
            tile,
            current_frame: 0,
            num_frames,
            can_hurt_hero,
            replaced_by,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        if self.current_frame == self.num_frames {
            p.general.is_alive = false;
            if let Some(successor) = self.replaced_by {
                p.actor_adder
                    .add_actor(successor, p.general.position.top_left());
            }
        }
    }

    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        if self.can_hurt_hero {
            p.general.hurts_hero = true;
        }
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }
}
07070100000041000081A40000000000000000000000015FD8FA6800000684000000000000000000000000000000000000002900000000freenukum-0.3.5/src/actor/soda_flying.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_SODAFLY, HALFTILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        Specific {}
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.hero_data.score.add(1000);
        p.actor_adder.add_actor(
            ActorType::Score1000,
            p.general.position.top_left(),
        );
        p.general.is_alive = false;
    }

    fn act(&mut self, p: ActParameters) {
        p.general.position.offset(0, -(HALFTILE_HEIGHT as i32));
        if p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT,
        ) {
            p.actor_adder.add_actor(
                ActorType::Explosion,
                p.general.position.top_left(),
            );
            p.general.is_alive = false;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = ANIMATION_SODAFLY
            + ((p.general.position.y() as usize
                / HALFTILE_HEIGHT as usize)
                % 4);
        p.renderer.place_tile(tile, p.general.position.top_left())?;
        Ok(())
    }
}
07070100000042000081A40000000000000000000000015FD8FA68000007F7000000000000000000000000000000000000002400000000freenukum-0.3.5/src/actor/spikes.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, OBJECT_SPIKE, OBJECT_SPIKES_DOWN, OBJECT_SPIKES_UP,
    TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        let x = general.position.x() as u32 / TILE_WIDTH;
        let y = general.position.y() as u32 / TILE_HEIGHT;

        match general.actor_type {
            ActorType::SpikesUp | ActorType::Spike => {
                tiles.copy_from_to(x, y - 1, x, y);
            }
            ActorType::SpikesDown => {
                tiles.copy_from_to(x, y + 1, x, y);
            }
            _ => unreachable!(),
        }

        Specific {
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, _p: ActParameters) {}

    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
        self.touching_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
        self.touching_hero = false;
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let tile = match p.general.actor_type {
            ActorType::SpikesUp => OBJECT_SPIKES_UP,
            ActorType::SpikesDown => OBJECT_SPIKES_DOWN,
            ActorType::Spike if self.touching_hero => OBJECT_SPIKE + 1,
            ActorType::Spike => OBJECT_SPIKE,
            _ => unreachable!(),
        };

        p.renderer.place_tile(tile, p.general.position.top_left())?;
        Ok(())
    }
}
07070100000043000081A40000000000000000000000015FD8FA6800000514000000000000000000000000000000000000003000000000freenukum-0.3.5/src/actor/surveillancescreen.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        HeroInteractStartParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_BADGUYSCREEN, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH * 2, TILE_HEIGHT);
        general.is_in_foreground = false;
        Specific {}
    }
}

impl ActorInterface for Specific {
    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        // TODO: implement functionality.
        p.info_message_queue
            .push_back("Not implemented yet.".to_string());
    }

    fn act(&mut self, _p: ActParameters) {}

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        p.renderer.place_tile(ANIMATION_BADGUYSCREEN, pos)?;
        pos = pos.offset(TILE_WIDTH as i32, 0);
        p.renderer.place_tile(ANIMATION_BADGUYSCREEN + 1, pos)?;
        Ok(())
    }
}
07070100000044000081A40000000000000000000000015FD8FA6800001666000000000000000000000000000000000000002500000000freenukum-0.3.5/src/actor/tankbot.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, ANIMATION_CARBOT, HALFTILE_HEIGHT,
    HALFTILE_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    orientation: HorizontalDirection,
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    was_shot: usize,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH * 2, TILE_HEIGHT);
        general.is_in_foreground = true;

        Specific {
            orientation: HorizontalDirection::Left,
            tile: ANIMATION_CARBOT,
            current_frame: 0,
            num_frames: 4,
            was_shot: 0,
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
        self.touching_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
        self.touching_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;

        if self.was_shot == 2 {
            p.general.is_alive = false;
            p.actor_adder.add_actor(
                ActorType::Explosion,
                p.general
                    .position
                    .top_left()
                    .offset(HALFTILE_WIDTH as i32, 0),
            );
            p.actor_adder
                .add_particle_firework(p.general.position.top_left(), 4);
            p.hero_data.score.add(2500);
        } else if p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) && !p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH + 1,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) {
            // still in the air, falling down
            p.general.position.offset(0, HALFTILE_HEIGHT as i32);
        } else {
            // on the floor, walking
            let mut direction = match self.orientation {
                HorizontalDirection::Left => -1,
                HorizontalDirection::Right => 4,
            };

            if !p.solids.get(
                // check if the place next ot the bot is free
                (p.general.position.x()
                    + direction * HALFTILE_WIDTH as i32)
                    as u32
                    / TILE_WIDTH,
                p.general.position.y() as u32 / TILE_HEIGHT,
            ) && p.solids.get(
                // check if the tile below is solid
                (p.general.position.x() as i32
                    + direction * HALFTILE_WIDTH as i32)
                    as u32
                    / TILE_WIDTH,
                (p.general.position.y() as u32 + TILE_HEIGHT)
                    / TILE_HEIGHT,
            ) {
                if direction > 0 {
                    direction = 1;
                }
                p.general.position.offset(
                    (direction as f64 * HALFTILE_WIDTH as f64 * 0.7)
                        as i32,
                    0,
                );
            } else {
                // reached the end, turning around
                self.orientation.reverse();
                if direction > 0 {
                    direction = 1;
                }
                direction *= -1;
                p.general
                    .position
                    .offset(direction * HALFTILE_WIDTH as i32, 0);
                self.tile = (self.tile as i32 + 4 * direction) as usize;

                if direction > 0 {
                    p.actor_adder.add_actor(
                        ActorType::HostileShotRight,
                        p.general.position.top_left().offset(0, -6),
                    );
                } else {
                    p.actor_adder.add_actor(
                        ActorType::HostileShotLeft,
                        p.general.position.top_left().offset(0, -6),
                    );
                }
            }
        }
        if self.was_shot == 1 {
            // create steam clouds
            if self.current_frame == 0 {
                p.actor_adder.add_actor(
                    ActorType::Steam,
                    p.general.position.top_left().offset(
                        HALFTILE_WIDTH as i32,
                        -(TILE_HEIGHT as i32),
                    ),
                );
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        let tile = self.tile + (self.current_frame / 2) * 2;
        p.renderer.place_tile(tile, pos)?;

        let tile = self.tile + (self.current_frame / 2) * 2 + 1;
        pos = pos.offset(TILE_WIDTH as i32, 0);
        p.renderer.place_tile(tile, pos)?;
        Ok(())
    }

    fn can_get_shot(&self, _gerenal: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        if self.was_shot == 1 && self.touching_hero {
            p.general.hurts_hero = false;
            self.touching_hero = false;
        }
        if self.was_shot != 2 {
            self.was_shot += 1;
        }
        ShotProcessing::Absorb
    }
}
07070100000045000081A40000000000000000000000015FD8FA68000007AF000000000000000000000000000000000000002800000000freenukum-0.3.5/src/actor/teleporter.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorMessageType, ActorType, HeroInteractStartParameters,
        ReceiveMessageParameters, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_TELEPORTER1, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        Specific {}
    }
}

impl ActorInterface for Specific {
    fn hero_can_interact(&self) -> bool {
        true
    }

    fn hero_interact_start(&mut self, p: HeroInteractStartParameters) {
        let other = if p.general.actor_type == ActorType::Teleporter1 {
            ActorType::Teleporter2
        } else {
            ActorType::Teleporter1
        };

        p.actor_message_queue
            .push_back(other, ActorMessageType::Teleport);
    }

    fn act(&mut self, _p: ActParameters) {}

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        for i in 0..3 {
            for j in 0..3 {
                let pos = p.general.position.top_left().offset(
                    (j - 1) * TILE_WIDTH as i32,
                    (i - 2) * TILE_HEIGHT as i32,
                );
                let tile =
                    ANIMATION_TELEPORTER1 + i as usize * 3 + j as usize;
                p.renderer.place_tile(tile, pos)?;
            }
        }
        Ok(())
    }

    fn receive_message(&mut self, p: ReceiveMessageParameters) {
        if p.message != ActorMessageType::Teleport {
            return;
        }

        p.hero_data.position.move_to(
            p.general.position.x(),
            p.general.position.y() - TILE_HEIGHT as i32,
        );
    }
}
07070100000046000081A40000000000000000000000015FD8FA6800000BB9000000000000000000000000000000000000002B00000000freenukum-0.3.5/src/actor/unstablefloor.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, RenderParameters,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, SOLID_START, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    touch_count: usize,
    touching_hero: bool,
    floor_length: u32,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        let mut floor_length = 0;
        while !solids.get(
            general.position.x() as u32 / TILE_WIDTH + floor_length,
            general.position.y() as u32 / TILE_HEIGHT,
        ) {
            solids.set(
                general.position.x() as u32 / TILE_WIDTH + floor_length,
                general.position.y() as u32 / TILE_HEIGHT,
                true,
            );
            floor_length += 1;
        }

        general
            .position
            .resize(TILE_WIDTH * floor_length, TILE_HEIGHT);

        Specific {
            tile: SOLID_START + 77,
            touch_count: 0,
            touching_hero: false,
            floor_length,
        }
    }
}

impl ActorInterface for Specific {
    fn act(&mut self, p: ActParameters) {
        // Detect whether the hero is standing upon the floor.
        // We can't use the hero_touch_start functionality here
        // because it only gets triggered when the hero geometry
        // overlaps with the part, which is not the case here.
        let hero_geometry = p.hero_data.position.geometry;
        let hero_center = hero_geometry.x() + (hero_geometry.w as i32) / 2;
        let stands_upon = hero_center >= p.general.position.left()
            && hero_center <= p.general.position.right()
            && hero_geometry.bottom() == p.general.position.top();

        if stands_upon {
            if !self.touching_hero {
                self.touching_hero = true;
                self.touch_count += 1;
            }
        } else {
            self.touching_hero = false;
        }

        if self.touch_count >= 2 {
            let mut r = p.general.position;
            for _ in 0..self.floor_length {
                p.solids.set(
                    r.x() as u32 / TILE_WIDTH,
                    r.y() as u32 / TILE_HEIGHT,
                    false,
                );
                p.actor_adder
                    .add_actor(ActorType::Explosion, r.top_left());
                p.actor_adder.add_particle_firework(r.center(), 4);
                r.offset(TILE_WIDTH as i32, 0);
            }
            p.general.is_alive = false;
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = p.general.position.top_left();
        for _ in 0..self.floor_length {
            p.renderer.place_tile(self.tile, pos)?;
            pos.x += TILE_WIDTH as i32;
        }
        Ok(())
    }
}
07070100000047000081A40000000000000000000000015FD8FA6800001554000000000000000000000000000000000000002900000000freenukum-0.3.5/src/actor/wallcrawler.rsuse crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchEndParameters, HeroTouchStartParameters,
        RenderParameters, ShotParameters, ShotProcessing,
    },
    level::{solids::LevelSolids, tiles::LevelTiles},
    HorizontalDirection, Result, VerticalDirection,
    ANIMATION_WALLCRAWLERBOT_LEFT, ANIMATION_WALLCRAWLERBOT_RIGHT,
    LEVEL_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    direction: VerticalDirection,
    orientation: HorizontalDirection,
    tile: usize,
    current_frame: usize,
    num_frames: usize,
    was_shot: bool,
    touching_hero: bool,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.set_width(TILE_WIDTH);
        general.position.set_height(TILE_HEIGHT);
        general.is_in_foreground = true;

        let x = general.position.x() as u32 / TILE_WIDTH;
        let y = general.position.y() as u32 / TILE_HEIGHT;

        let (tile, orientation) = match general.actor_type {
            ActorType::WallCrawlerBotLeft => {
                if x < LEVEL_WIDTH + 1 {
                    tiles.copy_from_to(x + 1, y, x, y);
                }
                (ANIMATION_WALLCRAWLERBOT_LEFT, HorizontalDirection::Left)
            }
            ActorType::WallCrawlerBotRight => {
                if x > 0 {
                    tiles.copy_from_to(x - 1, y, x, y);
                }
                (
                    ANIMATION_WALLCRAWLERBOT_RIGHT,
                    HorizontalDirection::Right,
                )
            }
            _ => unreachable!(),
        };

        Specific {
            direction: VerticalDirection::Up,
            orientation,
            tile,
            current_frame: 0,
            num_frames: 4,
            was_shot: false,
            touching_hero: false,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        p.general.hurts_hero = true;
        self.touching_hero = true;
    }

    fn hero_touch_end(&mut self, p: HeroTouchEndParameters) {
        p.general.hurts_hero = false;
        self.touching_hero = false;
    }

    fn act(&mut self, p: ActParameters) {
        let orientation = self.orientation.as_factor_i32();

        match self.direction {
            VerticalDirection::Up => {
                // going up
                self.current_frame += 1;
                self.current_frame %= self.num_frames;

                if
                // bot collides with solid tile
                p.solids.get(
                p.general.position.x as u32 / TILE_WIDTH,
                (p.general.position.y as u32 - 1) / TILE_WIDTH) ||
            // bot has no more wall to stick upon
            !p.solids.get(
                (
                    p.general.position.x +
                    orientation *
                    TILE_WIDTH as i32
                ) as u32 / TILE_WIDTH,
                (p.general.position.y - 1) as u32 / TILE_HEIGHT)
                {
                    p.general.position.y += 1;
                    self.direction = VerticalDirection::Down;
                } else {
                    p.general.position.y -= 1;
                }
            }
            VerticalDirection::Down => {
                // going down
                if self.current_frame == 0 {
                    self.current_frame = self.num_frames;
                }
                self.current_frame -= 1;

                if
                // bot collides with solid tile
                p.solids.get(
                    p.general.position.x as u32 / TILE_WIDTH,
                    (
                        p.general.position.y as u32 + TILE_HEIGHT
                    ) / TILE_HEIGHT) ||
            // bot has no more wall to stick upon
            !p.solids.get(
                (
                    p.general.position.x +
                    orientation *
                    TILE_WIDTH as i32) as u32 /
                TILE_WIDTH,
                (p.general.position.y as u32 + TILE_HEIGHT) / TILE_HEIGHT)
                {
                    p.general.position.y -= 1;
                    self.direction = VerticalDirection::Up;
                } else {
                    p.general.position.y += 1;
                }
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn can_get_shot(&self, _general: &ActorData) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        if !self.was_shot {
            if self.touching_hero {
                p.general.hurts_hero = false;
                self.touching_hero = false;
                self.current_frame = 0;
            }
            p.general.is_alive = false;

            p.hero_data.score.add(100);
            p.actor_adder.add_actor(
                ActorType::Steam,
                p.general.position.top_left(),
            );
            p.actor_adder.add_actor(
                ActorType::Explosion,
                p.general.position.top_left(),
            );
        }
        ShotProcessing::Absorb
    }
}
07070100000048000081A40000000000000000000000015FD8FA6800000521000000000000000000000000000000000000002000000000freenukum-0.3.5/src/backdrop.rsuse super::tile::{self, TileHeader};
use crate::{
    Result, BACKDROP_HEIGHT, BACKDROP_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};
use anyhow::anyhow;
use sdl2::{
    pixels::PixelFormatEnum, rect::Rect, render::Canvas, surface::Surface,
};
use std::io::Read;

pub fn load<'t, R: Read>(r: &mut R) -> Result<Surface<'t>> {
    let surface = Surface::new(
        BACKDROP_WIDTH * TILE_WIDTH,
        BACKDROP_HEIGHT * TILE_HEIGHT,
        PixelFormatEnum::RGB888,
    )
    .map_err(|s| anyhow!(s))?;

    let mut geometry = Rect::new(0, 0, TILE_WIDTH, TILE_HEIGHT);

    let header = TileHeader {
        width: 2,
        height: 16,
        tiles: 0,
    };

    let mut canvas =
        Canvas::from_surface(surface).map_err(|s| anyhow!(s))?;
    {
        let texture_creator = canvas.texture_creator();

        for _ in 0..BACKDROP_WIDTH * BACKDROP_HEIGHT {
            let tile = tile::load(r, header, false)?;
            canvas
                .copy(&tile.as_texture(&texture_creator)?, None, geometry)
                .map_err(|s| anyhow!(s))?;

            geometry.x += 16;
            if geometry.x == 16 * BACKDROP_WIDTH as i32 {
                geometry.x = 0;
                geometry.y += 16;
            }
        }
    }
    canvas.present();
    let surface = canvas.into_surface();
    Ok(surface)
}
07070100000049000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000001800000000freenukum-0.3.5/src/bin0707010000004A000081A40000000000000000000000015FD8FA6800001100000000000000000000000000000000000000002F00000000freenukum-0.3.5/src/bin/freenukum-data-tool.rsuse anyhow::Context;
use explode::explode;
use freenukum::data_dir;
use std::fs::{create_dir_all, File};
use std::io::ErrorKind;
use std::io::{Read, Write};
use std::iter::Iterator;
use std::ops::Shl;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use zip::read::ZipArchive;

trait Skip {
    fn skip(&mut self, count: usize) -> std::io::Result<()>;
}

impl<R: Read> Skip for R {
    fn skip(&mut self, count: usize) -> std::io::Result<()> {
        for _ in 0..count {
            let mut buffer = [0; 1];
            self.read_exact(&mut buffer)?;
        }
        Ok(())
    }
}

#[derive(StructOpt, Debug)]
struct InstallFromShrArguments {
    /// Path of the input SHR file (usually named "DN1SW20.SHR")
    input: PathBuf,
}

#[derive(StructOpt, Debug)]
struct InstallFromZipArguments {
    /// The input Zip file
    input: PathBuf,
}

#[derive(StructOpt, Debug)]
enum Command {
    /// Install the data from a SHR file.
    InstallFromShr(InstallFromShrArguments),

    /// Install the data from a ZIP file.
    InstallFromZip(InstallFromZipArguments),
}

/// FreeNukum data tool
///
/// Manages game data for the FreeNukum game
///
/// The shareware episode of the original game can be downloaded from the
/// official FTP server at
/// ftp://ftp.3drealms.com/share/1duke.zip
/// and then be installed by this tool:
///
/// `freenukum-data-tool install-from-zip /path/to/downloaded/1duke.zip`
///
/// After the game data has been successfully installed, the FreeNukum
/// game can use the data.
#[derive(StructOpt, Debug)]
struct Arguments {
    #[structopt(subcommand)]
    command: Command,
}

fn read_file_entry<R: Read>(
    reader: &mut R,
) -> Result<Option<(String, Vec<u8>)>, anyhow::Error> {
    let mut filename = [0; 16];

    match reader.read_exact(&mut filename) {
        Ok(_) => {}
        Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
        Err(e) => return Err(e.into()),
    }

    let mut filename = std::str::from_utf8(
        filename
            .split(|c| *c == 0)
            .next()
            .context("Couldn't extract filename")?,
    )?
    .to_string();
    filename.make_ascii_lowercase();

    reader.skip(120)?;

    let compressed_size: usize = {
        let mut b = [0; 4];
        reader.read_exact(&mut b)?;
        (b[3] as usize).shl(24)
            | (b[2] as usize).shl(16)
            | (b[1] as usize).shl(8)
            | (b[0] as usize).shl(0)
    };

    reader.skip(28)?;

    let mut buffer = Vec::new();
    buffer.resize_with(compressed_size, Default::default);

    reader.read_exact(&mut buffer)?;

    Ok(Some((filename, explode(&buffer)?)))
}

fn extract_shr_data_to_dir<R: Read>(
    reader: &mut R,
    directory: &Path,
) -> Result<(), anyhow::Error> {
    // skip header
    reader.skip(58)?;

    create_dir_all(directory)?;

    while let Some((filename, data)) = read_file_entry(reader)? {
        println!("Extracting {}", filename);
        let path = directory.join(filename);
        let mut file = File::create(path)?;
        file.write_all(&data)?;
    }
    Ok(())
}

fn extract_shr_file_to_dir(
    file: &Path,
    directory: &Path,
) -> Result<(), anyhow::Error> {
    println!(
        "Extracting data from {:?} to directory {:?}",
        file, directory
    );

    let mut file = File::open(file)?;
    extract_shr_data_to_dir(&mut file, directory)
}

fn extract_zip_nested_shr_file_to_dir(
    file: &Path,
    directory: &Path,
) -> Result<(), anyhow::Error> {
    println!(
        "Extracting data from {:?} to directory {:?}",
        file, directory
    );

    let file = File::open(file)?;
    let mut zip = ZipArchive::new(file)?;
    let filename = "DN1SW20.SHR";
    let mut zip_file = zip
        .by_name(filename)
        .context(format!("Couldn't extract file {:?}", filename))?;
    extract_shr_data_to_dir(&mut zip_file, directory)
}

fn main() -> Result<(), anyhow::Error> {
    let arguments = Arguments::from_args();

    let data_path = data_dir().join("data").join("original");

    match arguments.command {
        Command::InstallFromShr(args) => {
            extract_shr_file_to_dir(&args.input, &data_path).unwrap();
        }
        Command::InstallFromZip(args) => {
            extract_zip_nested_shr_file_to_dir(&args.input, &data_path)
                .unwrap();
        }
    }
    Ok(())
}
0707010000004B000081A40000000000000000000000015FD8FA6800001E54000000000000000000000000000000000000002500000000freenukum-0.3.5/src/bin/freenukum.rsuse anyhow::{anyhow, Result};
use freenukum::data::original_data_dir;
use freenukum::graphics::load_default_font;
use freenukum::hero::HeroData;
use freenukum::infobox;
use freenukum::mainmenu::{mainmenu, MainMenuEntry};
use freenukum::picture::show_splash;
use freenukum::settings::Settings;
use freenukum::tilecache::TileCache;
use freenukum::{game, UserEvent};
use freenukum::{WINDOW_HEIGHT, WINDOW_WIDTH};
use sdl2::pixels::Color;
use std::fs::File;

fn main() -> Result<()> {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let mut settings = Settings::load_or_create();

    let sdl_context = sdl2::init().map_err(|s| anyhow!(s))?;
    let video_subsystem = sdl_context.video().map_err(|s| anyhow!(s))?;
    let ttf_context = sdl2::ttf::init()?;
    let event_subsystem = sdl_context.event().map_err(|s| anyhow!(s))?;
    let timer_subsystem = sdl_context.timer().map_err(|s| anyhow!(s))?;
    let controller_subsystem =
        sdl_context.game_controller().map_err(|s| anyhow!(s))?;
    controller_subsystem.set_event_state(true);

    let mut controllers = Vec::new();
    for i in 0..controller_subsystem
        .num_joysticks()
        .map_err(|s| anyhow!(s))?
    {
        if controller_subsystem.is_game_controller(i) {
            controllers.push(controller_subsystem.open(i).unwrap());
        }
    }
    let mut event_pump =
        sdl_context.event_pump().map_err(|s| anyhow!(s))?;
    let event_sender = event_subsystem.event_sender();

    event_subsystem
        .register_custom_event::<UserEvent>()
        .map_err(|s| anyhow!(s))?;

    let window = game::create_window(
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        settings.fullscreen,
        &format!("Freenukum {}", VERSION),
        &video_subsystem,
    )?;
    let mut canvas = window.into_canvas().present_vsync().build()?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();
    let texture_creator = canvas.texture_creator();

    let mut episodes = game::check_episodes(
        &mut canvas,
        &load_default_font(&ttf_context)?,
        &texture_creator,
        &mut event_pump,
    )?;
    let tilecache = TileCache::load_from_path(&original_data_dir())?;

    let mut bg_filepath = original_data_dir().join("dn.dn1");
    {
        let mut file = File::open(&bg_filepath)?;
        show_splash(&mut canvas, &tilecache, &mut file, &mut event_pump)?;
    }

    infobox::show(
        &mut canvas,
        &tilecache,
        "DISCLAIMER\n\
        \n\
        This is an early development\n\
        version of the game.\n\
        \n\
        Expect functionality to be\n\
        broken or missing.\
        ",
        &mut event_pump,
    )?;

    let mut hero = HeroData::new();

    'menu_loop: loop {
        match mainmenu(
            &mut canvas,
            &tilecache,
            &mut event_pump,
            &event_sender,
            &timer_subsystem,
        )? {
            MainMenuEntry::Start => {
                game::start(
                    &mut canvas,
                    &tilecache,
                    &mut hero,
                    &mut settings,
                    &episodes,
                    &mut event_pump,
                    &event_sender,
                    &timer_subsystem,
                )?;
                let mut file = File::open(&bg_filepath)?;
                show_splash(
                    &mut canvas,
                    &tilecache,
                    &mut file,
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::Restore => {
                infobox::show(
                    &mut canvas,
                    &tilecache,
                    "Restore not implemented yet",
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::Instructions => {
                infobox::show(
                    &mut canvas,
                    &tilecache,
                    "Instructions not implemented yet",
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::OrderingInfo => {
                infobox::show(
                    &mut canvas,
                    &tilecache,
                    "Ordering info not implemented yet",
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::FullScreenToggle => {
                use sdl2::video::FullscreenType;
                // TODO: this causes the window to show artifacts
                // that are lying around in memory, will need to
                // fix that (and the scaling as well).
                match canvas.window().fullscreen_state() {
                    FullscreenType::Off => {
                        settings.fullscreen = true;
                        canvas
                            .window_mut()
                            .set_fullscreen(FullscreenType::Desktop)
                            .map_err(|s| anyhow!(s))?;
                    }
                    FullscreenType::True | FullscreenType::Desktop => {
                        settings.fullscreen = false;
                        canvas
                            .window_mut()
                            .set_fullscreen(FullscreenType::Off)
                            .map_err(|s| anyhow!(s))?;
                    }
                }
                settings.save();
            }
            MainMenuEntry::EpisodeChange => {
                let old = episodes.current();
                let new = episodes.switch_next();

                bg_filepath = original_data_dir()
                    .join(format!("dn.{}", episodes.file_extension()));

                if old == new {
                    infobox::show(
                        &mut canvas,
                        &tilecache,
                        "\
                             You don't have another\n\
                             episode installed.\n\
                             \n\
                             We stay in this episode",
                        &mut event_pump,
                    )?;
                }
                let mut file = File::open(&bg_filepath)?;
                show_splash(
                    &mut canvas,
                    &tilecache,
                    &mut file,
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::HighScores => {
                infobox::show(
                    &mut canvas,
                    &tilecache,
                    "Highscores not implemented yet",
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::Previews => {
                infobox::show(
                    &mut canvas,
                    &tilecache,
                    "Previews not implemented yet",
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::ViewUserDemo => infobox::show(
                &mut canvas,
                &tilecache,
                "Userdemo not implemented yet",
                &mut event_pump,
            )?,
            MainMenuEntry::TitleScreen => {
                let mut file = File::open(&bg_filepath)?;
                show_splash(
                    &mut canvas,
                    &tilecache,
                    &mut file,
                    &mut event_pump,
                )?;
            }
            MainMenuEntry::Credits => infobox::show(
                &mut canvas,
                &tilecache,
                "Credits not implemented yet",
                &mut event_pump,
            )?,
            MainMenuEntry::Quit => {
                break 'menu_loop;
            }
            MainMenuEntry::Invalid => {}
        }
    }
    Ok(())
}
0707010000004C000081A40000000000000000000000015FD8FA68000025CB000000000000000000000000000000000000001F00000000freenukum-0.3.5/src/borders.rsuse super::hero::{Firepower, Inventory, InventoryItem};
use super::text;
use crate::rendering::{MovePositionRenderer, Renderer, TileIndex};
use crate::{
    Result, BORDER_GREY_START, FONT_HEIGHT, FONT_WIDTH, HALFTILE_HEIGHT,
    HALFTILE_WIDTH, MAX_LIFE, OBJECT_ACCESS_CARD, OBJECT_BOOT,
    OBJECT_CLAMP, OBJECT_GLOVE, OBJECT_GUN, OBJECT_HEALTH,
    OBJECT_KEY_BLUE, OBJECT_KEY_GREEN, OBJECT_KEY_PINK, OBJECT_KEY_RED,
    OBJECT_NONHEALTH, OBJECT_SHOT, SCORE_DIGITS, TILE_HEIGHT, TILE_WIDTH,
    WINDOW_HEIGHT, WINDOW_WIDTH,
};
use sdl2::rect::Point;

pub struct Borders {}

#[rustfmt::skip]
const BORDERS:
[i32; (2 * WINDOW_HEIGHT / TILE_HEIGHT) as usize * (2 * WINDOW_WIDTH / TILE_WIDTH) as usize] =
[
     4,-1, 2,-1, 2,-1, 2,-1, 2,-1, 2,-1, 2,-1, 2,-1, 2,-1, 2,-1,
     2,-1, 2,-1, 2,-1, 2,-1, 5,-1, 8,-1,38,-1,39,-1, 8,-1, 9,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,14,-1, 8,-1,36,-1,37,-1, 8,-1,15,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,14,-1, 8,33,-1,34,-1,35,-1, 8,15,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,14,-1, 8,30,-1,31,-1,32,-1, 8,15,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

     7,-1, 3,-1, 3,-1, 3, -1,3,-1, 3,26,-1,27,-1,28,-1,29,-1, 3,
     3,-1, 3,-1, 3,-1, 3,-1, 6,-1, 8,-1, 8,-1, 8,-1, 8,-1,11,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
];

impl Borders {
    fn render_tile(
        &self,
        x: i32,
        y: i32,
        renderer: &mut dyn Renderer,
        tile: TileIndex,
    ) -> Result<()> {
        renderer.place_tile(tile, Point::new(x, y))?;
        Ok(())
    }

    fn render_iter<I>(
        &self,
        columns: u8,
        grid_x: u16,
        grid_y: u16,
        renderer: &mut dyn Renderer,
        borders: I,
    ) -> Result<()>
    where
        I: Iterator<Item = Option<usize>>,
    {
        for (i, border) in borders.enumerate() {
            if let Some(border) = border {
                self.render_tile(
                    (i as i32 % columns as i32) * (grid_x as i32),
                    (i as i32 / columns as i32) * (grid_y as i32),
                    renderer,
                    border,
                )?;
            }
        }
        Ok(())
    }

    pub fn render(&self, renderer: &mut dyn Renderer) -> Result<()> {
        self.render_iter(
            (2 * WINDOW_WIDTH / TILE_WIDTH) as u8,
            HALFTILE_WIDTH as u16,
            HALFTILE_HEIGHT as u16,
            renderer,
            BORDERS.iter().map(|i| {
                if *i < 0 {
                    None
                } else {
                    Some(*i as usize + BORDER_GREY_START)
                }
            }),
        )
    }

    pub fn render_life(
        &self,
        health: u8,
        renderer: &mut dyn Renderer,
    ) -> Result<()> {
        let health = std::cmp::min(health as usize, MAX_LIFE);
        let iter = (0..MAX_LIFE).map(|i| {
            if i < health {
                Some(OBJECT_HEALTH)
            } else {
                Some(OBJECT_NONHEALTH)
            }
        });
        let mut move_renderer = MovePositionRenderer {
            offset_x: 30 * HALFTILE_WIDTH as i32,
            offset_y: (15 * HALFTILE_HEIGHT as i32) / 2,
            upstream: renderer,
        };
        self.render_iter(
            MAX_LIFE as u8,
            HALFTILE_WIDTH as u16,
            HALFTILE_HEIGHT as u16,
            &mut move_renderer,
            iter,
        )
    }

    pub fn render_score(
        &self,
        score: u128,
        renderer: &mut dyn Renderer,
    ) -> Result<()> {
        let score = std::cmp::min(99999999, score);
        let score_string =
            format!("{0:0width$}", score, width = SCORE_DIGITS);

        let mut position_renderer = MovePositionRenderer {
            offset_x: 30 * FONT_WIDTH as i32,
            offset_y: 3 * FONT_HEIGHT as i32,
            upstream: renderer,
        };

        text::render(&mut position_renderer, &score_string)
    }

    pub fn render_firepower(
        &self,
        firepower: &Firepower,
        renderer: &mut dyn Renderer,
    ) -> Result<()> {
        const GUN: Option<usize> = Some(OBJECT_GUN);
        const SHOT: Option<usize> = Some(OBJECT_SHOT);

        let shots = firepower.num_shots();

        let shot0 = if shots > 0 { SHOT } else { None };
        let shot1 = if shots > 1 { SHOT } else { None };
        let shot2 = if shots > 2 { SHOT } else { None };
        let shot3 = if shots > 3 { SHOT } else { None };

        #[rustfmt::skip]
        let tiles = vec![
            None,  None, None,  GUN,  None,  None, None,  None,
            None,  None, None,  None, None,  None, None,  None,
            shot0, None, shot1, None, shot2, None, shot3, None,
            None,  None, None,  None, None,  None, None,  None,
        ];

        let mut move_renderer = MovePositionRenderer {
            offset_x: 15 * TILE_WIDTH as i32,
            offset_y: 6 * TILE_HEIGHT as i32,
            upstream: renderer,
        };
        self.render_iter(
            MAX_LIFE as u8,
            HALFTILE_WIDTH as u16,
            HALFTILE_HEIGHT as u16,
            &mut move_renderer,
            tiles.into_iter(),
        )
    }

    pub fn render_inventory(
        &self,
        inventory: &Inventory,
        renderer: &mut dyn Renderer,
    ) -> Result<()> {
        let red_key = if inventory.is_set(InventoryItem::KeyRed) {
            Some(OBJECT_KEY_RED)
        } else {
            None
        };
        let green_key = if inventory.is_set(InventoryItem::KeyGreen) {
            Some(OBJECT_KEY_GREEN)
        } else {
            None
        };
        let blue_key = if inventory.is_set(InventoryItem::KeyBlue) {
            Some(OBJECT_KEY_BLUE)
        } else {
            None
        };
        let pink_key = if inventory.is_set(InventoryItem::KeyPink) {
            Some(OBJECT_KEY_PINK)
        } else {
            None
        };
        let boot = if inventory.is_set(InventoryItem::Boot) {
            Some(OBJECT_BOOT)
        } else {
            None
        };
        let glove = if inventory.is_set(InventoryItem::Glove) {
            Some(OBJECT_GLOVE)
        } else {
            None
        };
        let clamp = if inventory.is_set(InventoryItem::Clamp) {
            Some(OBJECT_CLAMP)
        } else {
            None
        };
        let access_card = if inventory.is_set(InventoryItem::AccessCard) {
            Some(OBJECT_ACCESS_CARD)
        } else {
            None
        };

        #[rustfmt::skip]
        let tiles = vec![
            red_key, green_key, blue_key, pink_key,
            boot, glove, clamp, access_card,
        ];

        let mut move_renderer = MovePositionRenderer {
            offset_x: 15 * TILE_WIDTH as i32,
            offset_y: 9 * TILE_HEIGHT as i32,
            upstream: renderer,
        };
        self.render_iter(
            4,
            TILE_WIDTH as u16,
            TILE_HEIGHT as u16,
            &mut move_renderer,
            tiles.into_iter(),
        )
    }
}
0707010000004D000081A40000000000000000000000015FD8FA6800000679000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/data.rsuse crate::data_dir;
use anyhow::anyhow;
use sdl2::{
    pixels::Color,
    rect::Rect,
    render::{Canvas, RenderTarget, TextureCreator, TextureQuery},
    ttf::Font,
};
use std::path::PathBuf;

pub fn required_file_names() -> Vec<&'static str> {
    vec![
        "anim0", "anim1", "anim2", "anim3", "anim4", "anim5", "back0",
        "back1", "back2", "back3", "badguy", "border", "credits", "dn",
        "drop0", "duke", "duke1-b", "duke1", "end", "font1", "font2",
        "man0", "man1", "man2", "man3", "man4", "numbers", "object0",
        "object1", "object2", "solid0", "solid1", "solid2", "solid3",
        "worldal1", "worldal2", "worldal3", "worldal4", "worldal5",
        "worldal6", "worldal7", "worldal8", "worldal9", "worldala",
        "worldalb", "worldalc",
    ]
}

pub fn original_data_dir() -> PathBuf {
    data_dir().join("data").join("original")
}

pub fn display_text<RT: RenderTarget, T>(
    canvas: &mut Canvas<RT>,
    x: i32,
    y: i32,
    font: &Font,
    message: &str,
    texture_creator: &TextureCreator<T>,
) -> crate::Result<()> {
    let mut destrect = Rect::new(x, y, 0, 0);
    let color = Color::RGB(255, 255, 255);

    canvas.set_draw_color(color);
    for line in message.lines() {
        let text = font.render(line).blended(color)?;
        let text = texture_creator.create_texture_from_surface(&text)?;
        let TextureQuery { width, height, .. } = text.query();
        destrect.set_y(destrect.y() + height as i32);
        destrect.set_width(width);
        destrect.set_height(height);
        canvas.copy(&text, None, destrect).map_err(|s| anyhow!(s))?;
    }
    canvas.present();
    Ok(())
}
0707010000004E000081A40000000000000000000000015FD8FA6800000665000000000000000000000000000000000000002000000000freenukum-0.3.5/src/episodes.rsuse super::data::original_data_dir;
use anyhow::{anyhow, Result};

#[derive(Debug)]
pub struct Episodes {
    current: usize,
    count: usize,
}

impl Episodes {
    pub fn find_installed() -> Self {
        let count = Self::count_installed();
        Self {
            current: 0usize,
            count,
        }
    }

    fn count_installed() -> usize {
        let mut count = 0;
        for i in 0..9 {
            if Self::is_installed(i) {
                count = i;
            }
        }
        count
    }

    fn is_installed(number: usize) -> bool {
        let data_path = original_data_dir();
        for f in super::data::required_file_names() {
            let f = format!("{}.dn{}", f, number);
            let file_path = data_path.join(f);
            match std::fs::File::open(file_path) {
                Ok(_) => {}
                Err(_) => {
                    return false;
                }
            }
        }
        true
    }

    pub fn switch_next(&mut self) -> usize {
        self.current += 1;
        self.current %= self.count;
        self.current
    }

    pub fn switch_to(&mut self, episode: usize) -> Result<()> {
        if self.count > episode {
            self.current = episode;
            Ok(())
        } else {
            Err(anyhow!(
                "Episode with number {} not installed",
                episode + 1
            ))
        }
    }

    pub fn current(&self) -> usize {
        self.current
    }

    pub fn count(&self) -> usize {
        self.count
    }

    pub fn file_extension(&self) -> String {
        format!("dn{}", self.current + 1)
    }
}
0707010000004F000081A40000000000000000000000015FD8FA6800005BA8000000000000000000000000000000000000001D00000000freenukum-0.3.5/src/event.rsuse crate::hero::InventoryItem;
use crate::HorizontalDirection;
use crate::UserEvent;
use crate::{HALFTILE_HEIGHT, HALFTILE_WIDTH};
use anyhow::{anyhow, Error, Result};
use sdl2::{
    controller::{Axis, Button},
    event::{Event, WindowEvent},
    keyboard::{Keycode, Mod},
    mouse::MouseButton,
    EventPump,
};
use std::{collections::BTreeSet, convert::TryFrom, iter::FromIterator};

#[must_use]
pub enum GameEvent {
    Escape,
    GetInventoryItem(InventoryItem),
    IncreaseLife,
    FinishLevel,
    ToggleFullscreen,
    MoveViewPoint {
        x: i32,
        y: i32,
    },
    HeroInteractionStart,
    HeroInteractionEnd,
    HeroSetWalkingDirectionEnabled {
        directions: BTreeSet<HorizontalDirection>,
        context: InputContext,
        enabled: bool,
    },
    RefreshScreen,
    HeroJump,
    HeroStartFiring,
    HeroStopFiring,
    TimerTriggered,
}

#[must_use]
pub enum ConfirmEvent {
    Confirmed,
    Aborted,
    RefreshScreen,
}

#[must_use]
pub enum InputEvent {
    DeleteLeft,
    DeleteRight,
    MoveCursorLeft,
    MoveCursorRight,
    Confirm,
    Abort,
    Letter(char),
    RefreshScreen,
}

#[must_use]
pub enum MenuEvent {
    ChooseCurrentEntry,
    Abort,
    NextEntry {
        context: InputContext,
        enabled: bool,
    },
    PreviousEntry {
        context: InputContext,
        enabled: bool,
    },
    ChooseShortcutEntry(char),
    MoveMouse {
        x: i32,
        y: i32,
    },
    ClickMouse,
    RefreshScreen,
    TimerTriggered,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum InputContext {
    ControllerDPad,
    ControllerAxis,
    Keyboard,
}

pub trait OnOffTracking {
    type Context;

    fn set_enabled(
        &mut self,
        context: Self::Context,
        enable: bool,
        off_to_on: &mut dyn FnMut(),
        on_to_off: &mut dyn FnMut(),
    );
}

impl OnOffTracking for BTreeSet<InputContext> {
    type Context = InputContext;

    fn set_enabled(
        &mut self,
        context: Self::Context,
        enable: bool,
        off_to_on: &mut dyn FnMut(),
        on_to_off: &mut dyn FnMut(),
    ) {
        let was_enabled = !self.is_empty();
        if enable {
            self.insert(context);
        } else {
            self.remove(&context);
        }
        let is_enabled = !self.is_empty();

        match (was_enabled, is_enabled) {
            (false, true) => off_to_on(),
            (true, false) => on_to_off(),
            (false, false) | (true, true) => {}
        }
    }
}

pub trait WaitEvent: Sized {
    fn wait(event_pump: &mut EventPump) -> Result<Self>;
}

impl<T: TryFrom<Event>> WaitEvent for T {
    fn wait(event_pump: &mut EventPump) -> Result<Self> {
        loop {
            let event = event_pump.wait_event();
            if let Ok(e) = T::try_from(event) {
                return Ok(e);
            }
        }
    }
}

impl TryFrom<Event> for GameEvent {
    type Error = Error;

    fn try_from(e: Event) -> Result<GameEvent> {
        use Button as B;
        use Event as E;
        use Keycode as K;
        use WindowEvent as W;
        match e {
            E::Quit { .. }
            | E::KeyDown {
                keycode: Some(K::Escape),
                ..
            }
            | E::KeyDown {
                keycode: Some(K::Q),
                ..
            }
            | E::ControllerButtonDown {
                button: B::Start, ..
            } => Ok(GameEvent::Escape),
            E::KeyDown {
                keycode: Some(K::Num1),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::KeyRed)),
            E::KeyDown {
                keycode: Some(K::Num2),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::KeyGreen)),
            E::KeyDown {
                keycode: Some(K::Num3),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::KeyBlue)),
            E::KeyDown {
                keycode: Some(K::Num4),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::KeyPink)),
            E::KeyDown {
                keycode: Some(K::Num5),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::Boot)),
            E::KeyDown {
                keycode: Some(K::Num6),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::Glove)),
            E::KeyDown {
                keycode: Some(K::Num7),
                ..
            } => Ok(GameEvent::GetInventoryItem(InventoryItem::Clamp)),
            E::KeyDown {
                keycode: Some(K::Num8),
                ..
            } => {
                Ok(GameEvent::GetInventoryItem(InventoryItem::AccessCard))
            }
            E::KeyDown {
                keycode: Some(K::Num9),
                ..
            } => Ok(GameEvent::IncreaseLife),
            E::KeyDown {
                keycode: Some(K::Num0),
                ..
            } => Ok(GameEvent::FinishLevel),
            E::KeyDown {
                keycode: Some(K::F),
                ..
            }
            | E::KeyDown {
                keycode: Some(K::F11),
                ..
            } => Ok(GameEvent::ToggleFullscreen),
            E::KeyDown {
                keycode: Some(K::Down),
                keymod,
                ..
            } if keymod.contains(Mod::LSHIFTMOD)
                || keymod.contains(Mod::RSHIFTMOD) =>
            {
                Ok(GameEvent::MoveViewPoint {
                    x: HALFTILE_HEIGHT as i32,
                    y: 0,
                })
            }
            E::KeyDown {
                keycode: Some(K::Up),
                keymod,
                ..
            } if keymod.contains(Mod::LSHIFTMOD)
                || keymod.contains(Mod::RSHIFTMOD) =>
            {
                Ok(GameEvent::MoveViewPoint {
                    x: -(HALFTILE_HEIGHT as i32),
                    y: 0,
                })
            }
            E::KeyDown {
                keycode: Some(K::Right),
                keymod,
                ..
            } if keymod.contains(Mod::LSHIFTMOD)
                || keymod.contains(Mod::RSHIFTMOD) =>
            {
                Ok(GameEvent::MoveViewPoint {
                    x: 0,
                    y: HALFTILE_WIDTH as i32,
                })
            }
            E::KeyDown {
                keycode: Some(K::Left),
                keymod,
                ..
            } if keymod.contains(Mod::LSHIFTMOD)
                || keymod.contains(Mod::RSHIFTMOD) =>
            {
                Ok(GameEvent::MoveViewPoint {
                    x: 0,
                    y: -(HALFTILE_WIDTH as i32),
                })
            }
            E::KeyDown {
                keycode: Some(K::Up),
                ..
            }
            | E::ControllerButtonDown { button: B::Y, .. }
            | E::MouseButtonDown {
                mouse_btn: MouseButton::Middle,
                ..
            } => Ok(GameEvent::HeroInteractionStart),
            E::ControllerAxisMotion {
                axis: Axis::LeftX,
                value,
                ..
            } if value < -20000 => {
                Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                    directions: BTreeSet::from_iter(
                        Some(HorizontalDirection::Left).into_iter(),
                    ),
                    context: InputContext::ControllerAxis,
                    enabled: true,
                })
            }
            E::ControllerAxisMotion {
                axis: Axis::LeftX,
                value,
                ..
            } if value > 20000 => {
                Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                    directions: BTreeSet::from_iter(
                        Some(HorizontalDirection::Right).into_iter(),
                    ),
                    context: InputContext::ControllerAxis,
                    enabled: true,
                })
            }
            E::ControllerAxisMotion {
                axis: Axis::LeftX, ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    vec![
                        HorizontalDirection::Left,
                        HorizontalDirection::Right,
                    ]
                    .into_iter(),
                ),
                context: InputContext::ControllerAxis,
                enabled: false,
            }),
            E::KeyDown {
                keycode: Some(K::Right),
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Right).into_iter(),
                ),
                context: InputContext::Keyboard,
                enabled: true,
            }),
            E::ControllerButtonDown {
                button: B::DPadRight,
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Right).into_iter(),
                ),
                context: InputContext::ControllerDPad,
                enabled: true,
            }),
            E::KeyDown {
                keycode: Some(K::Left),
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Left).into_iter(),
                ),
                context: InputContext::Keyboard,
                enabled: true,
            }),
            E::ControllerButtonDown {
                button: B::DPadLeft,
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Left).into_iter(),
                ),
                context: InputContext::ControllerDPad,
                enabled: true,
            }),
            E::KeyDown {
                keycode: Some(K::LCtrl),
                ..
            }
            | E::ControllerButtonDown { button: B::A, .. }
            | E::MouseButtonDown {
                mouse_btn: MouseButton::Right,
                ..
            } => Ok(GameEvent::HeroJump),
            E::KeyDown {
                keycode: Some(K::LAlt),
                ..
            }
            | E::ControllerButtonDown {
                button: B::LeftShoulder,
                ..
            }
            | E::ControllerButtonDown {
                button: B::RightShoulder,
                ..
            }
            | E::MouseButtonDown {
                mouse_btn: MouseButton::Left,
                ..
            } => Ok(GameEvent::HeroStartFiring),
            E::KeyUp {
                keycode: Some(K::Up),
                ..
            }
            | E::ControllerButtonUp { button: B::Y, .. }
            | E::MouseButtonUp {
                mouse_btn: MouseButton::Middle,
                ..
            } => Ok(GameEvent::HeroInteractionEnd),
            E::KeyUp {
                keycode: Some(K::Right),
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Right).into_iter(),
                ),
                context: InputContext::Keyboard,
                enabled: false,
            }),
            E::ControllerButtonUp {
                button: B::DPadRight,
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Right).into_iter(),
                ),
                context: InputContext::ControllerDPad,
                enabled: false,
            }),
            E::KeyUp {
                keycode: Some(K::Left),
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Left).into_iter(),
                ),
                context: InputContext::Keyboard,
                enabled: false,
            }),
            E::ControllerButtonUp {
                button: B::DPadLeft,
                ..
            } => Ok(GameEvent::HeroSetWalkingDirectionEnabled {
                directions: BTreeSet::from_iter(
                    Some(HorizontalDirection::Left).into_iter(),
                ),
                context: InputContext::ControllerDPad,
                enabled: false,
            }),
            E::KeyUp {
                keycode: Some(K::LAlt),
                ..
            }
            | E::ControllerButtonUp {
                button: B::LeftShoulder,
                ..
            }
            | E::ControllerButtonUp {
                button: B::RightShoulder,
                ..
            }
            | E::MouseButtonUp {
                mouse_btn: MouseButton::Left,
                ..
            } => Ok(GameEvent::HeroStopFiring),
            E::Window {
                win_event: W::Exposed,
                ..
            }
            | E::Window {
                win_event: W::Shown,
                ..
            } => Ok(GameEvent::RefreshScreen),
            e if e.is_user_event() => {
                match e.as_user_event_type::<UserEvent>() {
                    Some(UserEvent::Timer) => {
                        Ok(GameEvent::TimerTriggered)
                    }
                    Some(UserEvent::Redraw) => {
                        Ok(GameEvent::RefreshScreen)
                    }
                    None => unreachable!("Unknown user event"),
                }
            }

            _ => Err(anyhow!("Event not handled")),
        }
    }
}

impl TryFrom<Event> for ConfirmEvent {
    type Error = Error;

    fn try_from(e: Event) -> Result<ConfirmEvent> {
        use Button as B;
        use Event as E;
        use Keycode as K;
        use WindowEvent as W;
        match e {
            E::KeyDown {
                keycode: Some(K::Return),
                ..
            }
            | E::ControllerButtonDown { button: B::A, .. } => {
                Ok(ConfirmEvent::Confirmed)
            }
            E::KeyDown {
                keycode: Some(K::Escape),
                ..
            }
            | E::ControllerButtonDown { button: B::B, .. }
            | E::Quit { .. }
            | E::Window {
                win_event: W::Close,
                ..
            } => Ok(ConfirmEvent::Aborted),
            E::Window {
                win_event: W::Exposed,
                ..
            }
            | E::Window {
                win_event: W::Shown,
                ..
            } => Ok(ConfirmEvent::RefreshScreen),
            _ => Err(anyhow!("Event not handled")),
        }
    }
}

impl TryFrom<Event> for InputEvent {
    type Error = Error;

    fn try_from(e: Event) -> Result<InputEvent> {
        use Button as B;
        use Event as E;
        use Keycode as K;
        use Mod as M;
        use WindowEvent as W;
        match e {
            E::ControllerButtonDown {
                button: B::DPadLeft,
                ..
            } => Ok(InputEvent::MoveCursorLeft),
            E::ControllerButtonDown {
                button: B::DPadRight,
                ..
            } => Ok(InputEvent::MoveCursorRight),
            E::ControllerButtonDown { button: B::A, .. } => {
                Ok(InputEvent::Confirm)
            }
            E::ControllerButtonDown { button: B::B, .. } => {
                Ok(InputEvent::Abort)
            }

            E::KeyDown {
                keycode, keymod, ..
            } => match keycode {
                Some(K::Backspace) => Ok(InputEvent::DeleteLeft),
                Some(K::Delete) => Ok(InputEvent::DeleteRight),
                Some(K::Left) => Ok(InputEvent::MoveCursorLeft),
                Some(K::Right) => Ok(InputEvent::MoveCursorRight),
                Some(K::Return) => Ok(InputEvent::Confirm),
                Some(K::Escape) => Ok(InputEvent::Abort),
                Some(code)
                    if code == K::Space
                        || code == K::Exclaim
                        || code == K::Quotedbl
                        || code == K::Hash
                        || code == K::Dollar
                        || code == K::Ampersand
                        || code == K::Quote
                        || code == K::LeftParen
                        || code == K::RightParen
                        || code == K::Asterisk
                        || code == K::Plus
                        || code == K::Comma
                        || code == K::Minus
                        || code == K::Period
                        || code == K::Slash
                        || code == K::Num0
                        || code == K::Num1
                        || code == K::Num2
                        || code == K::Num3
                        || code == K::Num4
                        || code == K::Num5
                        || code == K::Num6
                        || code == K::Num7
                        || code == K::Num8
                        || code == K::Num9
                        || code == K::Colon
                        || code == K::Semicolon
                        || code == K::Less
                        || code == K::Equals
                        || code == K::Greater
                        || code == K::Question
                        || code == K::At
                        || code == K::A
                        || code == K::B
                        || code == K::C
                        || code == K::D
                        || code == K::E
                        || code == K::F
                        || code == K::G
                        || code == K::H
                        || code == K::I
                        || code == K::J
                        || code == K::K
                        || code == K::L
                        || code == K::M
                        || code == K::N
                        || code == K::O
                        || code == K::P
                        || code == K::Q
                        || code == K::R
                        || code == K::S
                        || code == K::T
                        || code == K::U
                        || code == K::V
                        || code == K::W
                        || code == K::X
                        || code == K::Y
                        || code == K::Z =>
                {
                    let mut c = code as u8 as char;
                    if keymod.contains(M::LSHIFTMOD)
                        || keymod.contains(M::RSHIFTMOD)
                    {
                        c.make_ascii_uppercase();
                    }
                    Ok(InputEvent::Letter(c))
                }
                _ => Err(anyhow!("Event not handled")),
            },
            E::Window {
                win_event: W::Exposed,
                ..
            }
            | E::Window {
                win_event: W::Shown,
                ..
            } => Ok(InputEvent::RefreshScreen),
            _ => Err(anyhow!("Event not handled")),
        }
    }
}

impl TryFrom<Event> for MenuEvent {
    type Error = Error;

    fn try_from(e: Event) -> Result<MenuEvent> {
        use Button as B;
        use Event as E;
        use Keycode as K;
        use MouseButton as M;
        use WindowEvent as W;
        match e {
            E::KeyDown {
                keycode: Some(K::Return),
                ..
            }
            | E::ControllerButtonDown { button: B::A, .. } => {
                Ok(MenuEvent::ChooseCurrentEntry)
            }
            E::KeyDown {
                keycode: Some(K::Escape),
                ..
            }
            | E::ControllerButtonDown { button: B::B, .. } => {
                Ok(MenuEvent::Abort)
            }
            E::ControllerAxisMotion {
                axis: Axis::LeftY,
                value,
                ..
            } if value < 0 => Ok(MenuEvent::PreviousEntry {
                context: InputContext::ControllerAxis,
                enabled: value < -20000,
            }),
            E::ControllerAxisMotion {
                axis: Axis::LeftY,
                value,
                ..
            } if value > 0 => Ok(MenuEvent::NextEntry {
                context: InputContext::ControllerAxis,
                enabled: value > 20000,
            }),
            E::KeyDown {
                keycode: Some(K::Down),
                ..
            } => Ok(MenuEvent::NextEntry {
                context: InputContext::Keyboard,
                enabled: true,
            }),
            E::ControllerButtonDown {
                button: B::DPadDown,
                ..
            } => Ok(MenuEvent::NextEntry {
                context: InputContext::ControllerDPad,
                enabled: true,
            }),
            E::KeyUp {
                keycode: Some(K::Down),
                ..
            } => Ok(MenuEvent::NextEntry {
                context: InputContext::Keyboard,
                enabled: true,
            }),
            E::ControllerButtonUp {
                button: B::DPadDown,
                ..
            } => Ok(MenuEvent::NextEntry {
                context: InputContext::ControllerDPad,
                enabled: false,
            }),
            E::ControllerButtonUp {
                button: B::DPadUp, ..
            } => Ok(MenuEvent::PreviousEntry {
                context: InputContext::ControllerDPad,
                enabled: false,
            }),
            E::KeyDown {
                keycode: Some(K::Up),
                ..
            }
            | E::ControllerButtonDown {
                button: B::DPadUp, ..
            } => Ok(MenuEvent::PreviousEntry {
                context: InputContext::ControllerDPad,
                enabled: true,
            }),
            E::KeyDown {
                keycode: Some(key), ..
            } => {
                let c = key as u8 as char;
                Ok(MenuEvent::ChooseShortcutEntry(c))
            }
            E::MouseMotion { x, y, .. } => {
                Ok(MenuEvent::MoveMouse { x, y })
            }
            E::MouseButtonDown { mouse_btn, .. } => {
                if mouse_btn == M::Left {
                    Ok(MenuEvent::ClickMouse)
                } else {
                    Err(anyhow!("Event not handled"))
                }
            }
            E::Window {
                win_event: W::Exposed,
                ..
            }
            | E::Window {
                win_event: W::Shown,
                ..
            } => Ok(MenuEvent::RefreshScreen),
            e if e.is_user_event() => {
                if e.as_user_event_type::<UserEvent>()
                    == Some(UserEvent::Timer)
                {
                    Ok(MenuEvent::TimerTriggered)
                } else {
                    // Ignore other events
                    Err(anyhow!("Event not handled"))
                }
            }
            _ => {
                // Ignore other events
                Err(anyhow!("Event not handled"))
            }
        }
    }
}
07070100000050000081A40000000000000000000000015FD8FA680000446F000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/game.rsuse crate::actor::{ActorMessageQueue, ActorQueue};
use crate::borders::Borders;
use crate::data::original_data_dir;
use crate::episodes::Episodes;
use crate::event::{ConfirmEvent, GameEvent, InputContext, WaitEvent};
use crate::hero::{HeroData, Motion};
use crate::infobox::{self, InfoMessageQueue};
use crate::level::{LevelData, PlayState};
use crate::picture::show_splash_with_message;
use crate::rendering::{CanvasRenderer, MovePositionRenderer};
use crate::settings::Settings;
use crate::tile::TileHeader;
use crate::{backdrop, HorizontalDirection, TileProvider, UserEvent};
use crate::{
    Result, GAME_INTERVAL, LEVELWINDOW_HEIGHT, LEVELWINDOW_WIDTH,
    LEVEL_HEIGHT, LEVEL_WIDTH, TILE_HEIGHT, TILE_WIDTH,
};

use anyhow::anyhow;
use sdl2::{
    event::EventSender,
    pixels::Color,
    rect::Rect,
    render::{Canvas, RenderTarget, TextureCreator, WindowCanvas},
    ttf::Font,
    video::Window,
    EventPump, TimerSubsystem, VideoSubsystem,
};
use std::collections::BTreeSet;
use std::fs::File;

#[derive(PartialEq, Eq)]
enum NextAction {
    RestartLevel,
    NextLevel,
    GoToMainScreen,
}

#[allow(clippy::too_many_arguments)]
fn start_in_level(
    level_number: usize,
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    hero: &mut HeroData,
    settings: &mut Settings,
    episodes: &Episodes,
    borders: &Borders,
    event_pump: &mut EventPump,
    event_sender: &EventSender,
    timer_subsystem: &TimerSubsystem,
) -> Result<NextAction> {
    let backdrop = {
        let backdrop_number = match level_number {
            1 | 3 => 0,
            4 => 7,
            5 | 8 => 3,
            6 => 2,
            7 | 10 => 1,
            9 => 5,
            _ => 1,
        };

        let filename = format!(
            "drop{}.{}",
            backdrop_number,
            episodes.file_extension()
        );
        let filepath = original_data_dir().join(filename);
        let mut file = File::open(filepath)?;
        TileHeader::load_from(&mut file)?;
        backdrop::load(&mut file)?
    };

    let mut level_data = {
        let filename = format!(
            "worldal{:x}.{}",
            level_number,
            episodes.file_extension()
        );
        let filepath = original_data_dir().join(filename);
        let mut file = File::open(filepath)?;
        LevelData::load(&mut file, hero, &mut None)?
    };

    let destrect = Rect::new(
        TILE_WIDTH as i32,
        TILE_HEIGHT as i32,
        (LEVELWINDOW_WIDTH) * TILE_WIDTH,
        (LEVELWINDOW_HEIGHT) * TILE_HEIGHT,
    );
    let heropos = hero.position.geometry;
    let mut srcrect = Rect::new(
        (heropos.x() as u32 + TILE_WIDTH)
            .saturating_sub(destrect.width() / 2) as i32,
        (heropos.y() as u32).saturating_sub(destrect.height() / 2) as i32,
        LEVELWINDOW_WIDTH * TILE_WIDTH,
        LEVELWINDOW_HEIGHT * TILE_HEIGHT,
    );

    let timer = timer_subsystem.add_timer(
        GAME_INTERVAL,
        Box::new(move || {
            event_sender.push_custom_event(UserEvent::Timer).unwrap();
            GAME_INTERVAL
        }),
    );

    let mut actor_queue = ActorQueue::new();
    let mut info_message_queue = InfoMessageQueue::new();
    let mut actor_message_queue = ActorMessageQueue::new();

    let mut do_update = true;
    let mut walking_left = BTreeSet::new();
    let mut walking_right = BTreeSet::new();

    while level_data.play_state.keep_acting() {
        let texture_creator = canvas.texture_creator();
        canvas.set_draw_color(Color::RGB(0, 0, 0));
        canvas.clear();
        let mut renderer = CanvasRenderer {
            canvas,
            texture_creator: &texture_creator,
            tileprovider,
        };

        if do_update {
            let heropos = hero.position.geometry;

            match level_data.play_state {
                PlayState::KilledPlayingAnimation(0) => {
                    level_data.play_state = PlayState::RestartLevel;
                    info_message_queue.push_back(
                        "You died.\nRestarting level.".to_string(),
                    );
                }
                PlayState::KilledPlayingAnimation(i) => {
                    hero.hidden = true;

                    if i > 30 && i % 2 == 0 {
                        use crate::actor::ActorAdder;
                        actor_queue
                            .add_particle_firework(heropos.center(), 4);
                    }
                    level_data.play_state =
                        PlayState::KilledPlayingAnimation(i - 1);
                }
                _ => {}
            }

            srcrect.x = std::cmp::min(
                (heropos.center().x as u32)
                    .saturating_sub(LEVELWINDOW_WIDTH * TILE_WIDTH / 2),
                LEVEL_WIDTH * TILE_WIDTH - srcrect.width(),
            ) as i32;
            srcrect.y = std::cmp::min(
                (heropos.y() as u32)
                    .saturating_sub(LEVELWINDOW_HEIGHT * TILE_HEIGHT / 2),
                LEVEL_HEIGHT * TILE_HEIGHT - srcrect.height(),
            ) as i32;

            borders.render(&mut renderer)?;
            borders.render_life(
                hero.health.life().unwrap_or(0),
                &mut renderer,
            )?;
            borders.render_firepower(&hero.firepower, &mut renderer)?;
            borders.render_inventory(&hero.inventory, &mut renderer)?;
            borders.render_score(hero.score.value(), &mut renderer)?;

            renderer.canvas.set_clip_rect(destrect);
            let mut level_renderer = MovePositionRenderer {
                offset_x: -srcrect.x() + TILE_WIDTH as i32,
                offset_y: -srcrect.y() + TILE_HEIGHT as i32,
                upstream: &mut renderer,
            };

            level_data.render(
                &mut level_renderer,
                hero,
                settings.draw_collision_bounds,
                srcrect,
                Some(&backdrop),
                None,
            )?;
            canvas.set_clip_rect(None);
            canvas.present();
            do_update = false;
        }

        info_message_queue.process(canvas, tileprovider, event_pump)?;

        match GameEvent::wait(event_pump)? {
            GameEvent::Escape => {
                level_data.play_state = PlayState::GoToMainScreen;
            }
            GameEvent::GetInventoryItem(item) => {
                hero.inventory.set(item);
            }
            GameEvent::IncreaseLife => {
                hero.firepower.increase(1);
            }
            GameEvent::FinishLevel => {
                level_data.play_state = PlayState::LevelFinished
            }
            GameEvent::ToggleFullscreen => {
                use sdl2::video::FullscreenType;
                match canvas.window().fullscreen_state() {
                    FullscreenType::Off => {
                        settings.fullscreen = true;
                        canvas
                            .window_mut()
                            .set_fullscreen(FullscreenType::Desktop)
                            .map_err(|s| anyhow!(s))?;
                    }
                    FullscreenType::True | FullscreenType::Desktop => {
                        settings.fullscreen = false;
                        canvas
                            .window_mut()
                            .set_fullscreen(FullscreenType::Off)
                            .map_err(|s| anyhow!(s))?;
                    }
                }
                settings.save();
            }
            GameEvent::MoveViewPoint { x, y } => {
                srcrect.offset(x, y);

                if srcrect.x() < 0 {
                    srcrect.set_x(0);
                }
                if srcrect.y() < 0 {
                    srcrect.set_y(0);
                }
                if srcrect.right() > (LEVEL_WIDTH * TILE_WIDTH) as i32 {
                    srcrect.set_right((LEVEL_WIDTH * TILE_WIDTH) as i32);
                }
                if srcrect.bottom() > (LEVEL_HEIGHT * TILE_HEIGHT) as i32 {
                    srcrect
                        .set_bottom((LEVEL_HEIGHT * TILE_HEIGHT) as i32);
                }
                do_update = true;
            }
            GameEvent::HeroInteractionStart => {
                level_data.hero_interact_start(
                    hero,
                    &mut info_message_queue,
                    &mut actor_message_queue,
                );
                do_update = true;
            }
            GameEvent::HeroInteractionEnd => {
                level_data.hero_interact_end(hero);
                do_update = true;
            }
            GameEvent::HeroSetWalkingDirectionEnabled {
                directions,
                context,
                enabled,
            } => {
                for direction in directions {
                    match direction {
                        HorizontalDirection::Left => {
                            if enabled {
                                walking_left.insert(context);
                                if context == InputContext::ControllerAxis
                                {
                                    walking_right.remove(
                                        &InputContext::ControllerAxis,
                                    );
                                }
                            } else {
                                walking_left.remove(&context);
                            }
                        }
                        HorizontalDirection::Right => {
                            if enabled {
                                walking_right.insert(context);
                                if context == InputContext::ControllerAxis
                                {
                                    walking_left.remove(
                                        &InputContext::ControllerAxis,
                                    );
                                }
                            } else {
                                walking_right.remove(&context);
                            }
                        }
                    }
                }

                match (!walking_left.is_empty(), !walking_right.is_empty())
                {
                    (true, true) | (false, false) => {
                        hero.motion = Motion::NotMoving
                    }
                    (true, false) => {
                        hero.motion = Motion::Walking;
                        hero.direction = HorizontalDirection::Left;
                    }
                    (false, true) => {
                        hero.motion = Motion::Walking;
                        hero.direction = HorizontalDirection::Right;
                    }
                }
                hero.update_animation();
            }
            GameEvent::RefreshScreen => {
                canvas.present();
            }
            GameEvent::HeroJump => {
                hero.jump();
                hero.update_animation();
            }
            GameEvent::HeroStartFiring => {
                hero.is_shooting = true;
                level_data.fire_shot(
                    hero,
                    &mut actor_queue,
                    &mut actor_message_queue,
                );
                hero.update_animation();
            }
            GameEvent::HeroStopFiring => {
                hero.is_shooting = false;
                hero.update_animation();
            }
            GameEvent::TimerTriggered => {
                level_data.act(
                    hero,
                    &mut actor_queue,
                    &mut actor_message_queue,
                )?;
                do_update = true;
            }
        }
    }
    drop(timer);

    let next_action = match level_data.play_state {
        PlayState::LevelFinished => NextAction::NextLevel,
        PlayState::GoToMainScreen => NextAction::GoToMainScreen,
        PlayState::RestartLevel => NextAction::RestartLevel,
        _ => unreachable!(),
    };
    Ok(next_action)
}

#[allow(clippy::too_many_arguments)]
pub fn start(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    hero: &mut HeroData,
    settings: &mut Settings,
    episodes: &Episodes,
    event_pump: &mut EventPump,
    event_sender: &EventSender,
    timer_subsystem: &TimerSubsystem,
) -> Result<()> {
    {
        let filename = format!("badguy.{}", episodes.file_extension());
        let filepath = original_data_dir().join(filename);
        let mut file = File::open(filepath)?;
        let message = "\
            So you're the pitiful\n\
            hero they sent to stop\n\
            me. I, Dr. Proton, will\n\
            soon rule the world!";
        show_splash_with_message(
            canvas,
            tileprovider,
            &mut file,
            event_pump,
            Some(message),
            0,
            144,
        )?;
    }
    {
        let filename = format!("duke.{}", episodes.file_extension());
        let filepath = original_data_dir().join(filename);
        let mut file = File::open(filepath)?;
        let message = "\
            You're wrong, Proton\n\
            breath. I'll be done\n\
            with you and still have\n\
            time to watch Oprah!";
        show_splash_with_message(
            canvas,
            tileprovider,
            &mut file,
            event_pump,
            Some(message),
            79,
            144,
        )?;
    }

    hero.reset();

    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();

    let borders = Borders {};

    // start the game itself
    let mut level = 1;
    let mut interlevel = false;

    infobox::show(
        canvas,
        tileprovider,
        "Get ready FreeNukum,\nyou are going in.\n",
        event_pump,
    )?;
    let mut finished = false;
    let mut initial_lives = hero.health.life().unwrap();
    let mut initial_score = hero.score.value();
    let mut initial_inventory = hero.inventory.get_items();

    'level_loop: loop {
        if interlevel {
            match start_in_level(
                2,
                canvas,
                tileprovider,
                hero,
                settings,
                episodes,
                &borders,
                event_pump,
                event_sender,
                timer_subsystem,
            )? {
                NextAction::NextLevel => {
                    initial_lives = hero.health.life().unwrap();
                    initial_score = hero.score.value();
                    initial_inventory = hero.inventory.get_items();
                    level = if level == 1 { level + 2 } else { level + 1 };
                    interlevel = false;
                }
                NextAction::RestartLevel => unreachable!(),
                NextAction::GoToMainScreen => break 'level_loop,
            }
        } else {
            hero.reset_for_level();
            hero.health.set(initial_lives);
            hero.score.set_value(initial_score);
            hero.inventory.set_items(initial_inventory.clone());
            match start_in_level(
                level,
                canvas,
                tileprovider,
                hero,
                settings,
                episodes,
                &borders,
                event_pump,
                event_sender,
                timer_subsystem,
            )? {
                NextAction::NextLevel => {
                    if level == 13 {
                        finished = true;
                        break 'level_loop;
                    } else {
                        interlevel = true;
                    }
                }
                NextAction::RestartLevel => {
                    // Restart the level
                }
                NextAction::GoToMainScreen => break 'level_loop,
            }
        }
    }

    if finished {
        // TODO: the player finished, so we should show the end sequence
    }

    Ok(())
}

pub fn check_episodes<RT: RenderTarget, T>(
    target: &mut Canvas<RT>,
    font: &Font,
    texture_creator: &TextureCreator<T>,
    event_pump: &mut EventPump,
) -> Result<Episodes> {
    let episodes = Episodes::find_installed();
    if episodes.count() == 0 {
        show_missing_data_information(
            target,
            font,
            texture_creator,
            event_pump,
        )?;
    }
    Ok(episodes)
}

fn show_missing_data_information<RT: RenderTarget, T>(
    target: &mut Canvas<RT>,
    font: &Font,
    texture_creator: &TextureCreator<T>,
    event_pump: &mut EventPump,
) -> Result<()> {
    let msg = "Could not load data level and graphics files.\n\
    Please use the accompanied freenukum-data-tool\n\
    for installing the game data files";
    println!("{}", msg);

    super::data::display_text(target, 0, 0, font, msg, texture_creator)?;

    loop {
        match ConfirmEvent::wait(event_pump)? {
            ConfirmEvent::Confirmed | ConfirmEvent::Aborted => {
                return Ok(())
            }
            ConfirmEvent::RefreshScreen => {
                target.present();
            }
        }
    }
}

pub fn create_window(
    w: u32,
    h: u32,
    fullscreen: bool,
    title: &str,
    video_subsystem: &VideoSubsystem,
) -> Result<Window> {
    let mut builder = video_subsystem.window(title, w, h);
    if fullscreen {
        builder.fullscreen();
    }
    Ok(builder.build()?)
}
07070100000051000081A40000000000000000000000015FD8FA68000003DB000000000000000000000000000000000000002000000000freenukum-0.3.5/src/geometry.rsuse sdl2::rect::Rect;

pub trait RectExt {
    fn touches(&self, other: Self) -> bool;
    fn overlaps_vertically(&self, other: Self) -> bool;
    fn horizontal_distance(&self, other: Self) -> i32;
}

impl RectExt for Rect {
    fn touches(&self, other: Self) -> bool {
        let mut r1 = *self;
        r1.w += 1;
        r1.h += 1;
        let mut r2 = other;
        r2.w += 1;
        r2.h += 1;
        r1.has_intersection(r2)
    }

    fn overlaps_vertically(&self, other: Self) -> bool {
        if self.y as i32 + self.h as i32 <= other.y as i32 {
            return false;
        }
        if other.y as i32 + other.h as i32 <= self.y as i32 {
            return false;
        }
        true
    }

    fn horizontal_distance(&self, other: Self) -> i32 {
        if self.right() < other.left() {
            self.right() - other.left()
        } else if other.right() < self.left() {
            self.left() - other.right()
        } else {
            0
        }
    }
}
07070100000052000081A40000000000000000000000015FD8FA6800000679000000000000000000000000000000000000002000000000freenukum-0.3.5/src/graphics.rsuse crate::Result;
use anyhow::anyhow;
use sdl2::{
    pixels::Color,
    rect::Point,
    render::{Canvas, RenderTarget},
    ttf::{Font, FontStyle, Sdl2TtfContext},
};

pub fn load_default_font(ttf_context: &Sdl2TtfContext) -> Result<Font> {
    #[cfg(target_os = "windows")]
    let font_path = std::path::Path::new(&std::env::var("WINDIR")?)
        .join("Fonts")
        .join("Arial.ttf");

    #[cfg(target_os = "linux")]
    let font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";

    let mut font = ttf_context
        .load_font(font_path, 10)
        .map_err(|s| anyhow!(s))?;
    font.set_style(FontStyle::BOLD);
    Ok(font)
}

pub trait SurfaceExt {
    fn set_data(
        &mut self,
        data: &[u8],
        width: u32,
        height: u32,
    ) -> Result<()>;
}

impl<RT: RenderTarget> SurfaceExt for Canvas<RT> {
    fn set_data(
        &mut self,
        data: &[u8],
        width: u32,
        height: u32,
    ) -> Result<()> {
        let mut iter = data.iter();

        for i in 0..height {
            for j in 0..width {
                let red = *iter.next().unwrap();
                let green = *iter.next().unwrap();
                let blue = *iter.next().unwrap();
                let opaque = *iter.next().unwrap();

                let color = if opaque == 0 {
                    Color::RGBA(0, 0, 0, 0)
                } else {
                    Color::RGB(red, green, blue)
                };

                self.set_draw_color(color);
                self.draw_point(Point::new(j as i32, i as i32))
                    .map_err(|s| anyhow!(s))?;
            }
        }
        Ok(())
    }
}
07070100000053000081A40000000000000000000000015FD8FA68000051C3000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/hero.rsuse crate::{
    actor::{ActorAdder, ActorType},
    level::solids::LevelSolids,
    rendering::Renderer,
    HorizontalDirection, Result, HALFTILE_HEIGHT, HALFTILE_WIDTH,
    HERO_FALLING_LEFT, HERO_FALLING_RIGHT, HERO_JUMPING_LEFT,
    HERO_JUMPING_LEFT_SOMERSAULT, HERO_JUMPING_RIGHT,
    HERO_JUMPING_RIGHT_SOMERSAULT, HERO_NUM_FALLING, HERO_NUM_JUMPING,
    HERO_NUM_STANDING, HERO_NUM_WALKING, HERO_SKELETON_LEFT,
    HERO_SKELETON_RIGHT, HERO_STANDING_LEFT, HERO_STANDING_RIGHT,
    HERO_WALKING_LEFT, HERO_WALKING_RIGHT, LEVEL_HEIGHT, LEVEL_WIDTH,
    TILE_HEIGHT, TILE_WIDTH,
};
use sdl2::rect::{Point, Rect};
use std::convert::TryFrom;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Motion {
    NotMoving,
    Walking,
}

#[derive(Debug)]
pub struct HeroData {
    pub position: Position,
    pub score: Score,
    pub health: Health,
    pub firepower: Firepower,
    pub inventory: Inventory,
    pub fetched_letter_state: FetchedLetterState,
    pub immunity: Immunity,
    pub hidden: bool,
    pub direction: HorizontalDirection,
    just_turned_around: bool,
    pub motion: Motion,
    is_in_the_air: bool,
    pub is_shooting: bool,
    counter: usize,
    somersault: Option<usize>,
    base_tile_number: usize,
    current_frame: usize,
    num_frames: usize,
    vertical_speed: usize,
    pub gets_hurt: bool,
}

impl Default for HeroData {
    fn default() -> Self {
        Self::new()
    }
}

impl HeroData {
    pub fn new() -> Self {
        HeroData {
            position: Position::new(),
            score: Score::new(),
            health: Health::new(),
            firepower: Firepower::new(),
            inventory: Inventory::new(),
            fetched_letter_state: FetchedLetterState::new(),
            immunity: Immunity::new(),
            hidden: false,
            direction: HorizontalDirection::Right,
            just_turned_around: false,
            motion: Motion::NotMoving,
            is_in_the_air: false,
            is_shooting: false,
            counter: 0,
            somersault: None,
            base_tile_number: HERO_STANDING_RIGHT,
            current_frame: 0,
            num_frames: 1,
            vertical_speed: 0,
            gets_hurt: false,
        }
    }

    pub fn reset(&mut self) {
        self.position.reset();
        self.score.reset();
        self.health.reset();
        self.firepower.reset();
        self.inventory.reset();
        self.fetched_letter_state.reset();
        self.immunity.reset();
        self.hidden = false;
        self.direction = HorizontalDirection::Right;
        self.just_turned_around = false;
        self.motion = Motion::NotMoving;
        self.is_in_the_air = false;
        self.is_shooting = false;
        self.counter = 0;
        self.base_tile_number = HERO_STANDING_RIGHT;
        self.current_frame = 0;
        self.num_frames = 1;
        self.vertical_speed = 0;
        self.gets_hurt = false;
    }

    pub fn reset_for_level(&mut self) {
        self.direction = HorizontalDirection::Right;
        self.inventory.unset(InventoryItem::KeyRed);
        self.inventory.unset(InventoryItem::KeyGreen);
        self.inventory.unset(InventoryItem::KeyBlue);
        self.inventory.unset(InventoryItem::KeyPink);
        self.fetched_letter_state.reset();
        self.immunity.reset();
        self.hidden = false;
        self.just_turned_around = false;
        self.motion = Motion::NotMoving;
        self.is_in_the_air = false;
        self.is_shooting = false;
        self.counter = 0;
        self.base_tile_number = HERO_STANDING_RIGHT;
        self.current_frame = 0;
        self.num_frames = 1;
        self.vertical_speed = 0;
        self.gets_hurt = false;
    }

    pub fn next_frame(&mut self) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;
    }

    pub fn render(
        &self,
        renderer: &mut dyn Renderer,
        solids: &LevelSolids,
        draw_collision_bounds: bool,
    ) -> Result<()> {
        if self.hidden || self.immunity.hero_invisible() {
            return Ok(());
        }

        let mut point = self.position.geometry.top_left();
        point.x -= HALFTILE_WIDTH as i32;

        let base_tile_number = if self.immunity.hero_skeleton() {
            match self.direction {
                HorizontalDirection::Left => HERO_SKELETON_LEFT,
                HorizontalDirection::Right => HERO_SKELETON_RIGHT,
            }
        } else {
            self.base_tile_number
        };

        renderer.place_tile(base_tile_number, point)?;
        point.x += TILE_WIDTH as i32;
        renderer.place_tile(base_tile_number + 1, point)?;
        point.x -= TILE_WIDTH as i32;
        point.y += TILE_HEIGHT as i32;
        renderer.place_tile(base_tile_number + 2, point)?;
        point.x += TILE_WIDTH as i32;
        renderer.place_tile(base_tile_number + 3, point)?;

        if draw_collision_bounds {
            let color = crate::collision_bounds_color();
            let g = self.position.geometry;

            renderer.draw_rect(g, color)?;

            for i in (g.x / TILE_WIDTH as i32) - 1
                ..(g.x / TILE_WIDTH as i32) + 2
            {
                for j in (g.y / TILE_HEIGHT as i32) - 1
                    ..(g.y / TILE_HEIGHT as i32) + 3
                {
                    if i > 0 && j > 0 && solids.get(i as u32, j as u32) {
                        let obstacle = Rect::new(
                            i * TILE_WIDTH as i32,
                            j * TILE_HEIGHT as i32,
                            TILE_WIDTH,
                            TILE_HEIGHT,
                        );
                        renderer.draw_rect(obstacle, color)?;
                    }
                }
            }
        }
        Ok(())
    }

    pub fn enter_level(&mut self, x: i32, y: i32) {
        self.position.geometry.x = x;
        self.position.geometry.y = y;
        self.reset_for_level();
    }

    pub fn would_collide(
        &self,
        solids: &LevelSolids,
        x: i32,
        y: i32,
    ) -> bool {
        let mut destination = self.position.geometry;
        destination.x = x;
        destination.y = y;
        solids.collides(destination)
    }

    pub fn update_animation(&mut self) {
        self.base_tile_number = if self.is_in_the_air {
            // hero is jumping or falling
            if self.counter > 0 {
                // hero is jumping
                self.num_frames = HERO_NUM_JUMPING;
                if let Some(frame) = self.somersault {
                    self.direction.map(
                        HERO_JUMPING_LEFT_SOMERSAULT,
                        HERO_JUMPING_RIGHT_SOMERSAULT,
                    ) + 4 * (frame / 2)
                } else {
                    self.direction
                        .map(HERO_JUMPING_LEFT, HERO_JUMPING_RIGHT)
                }
            } else {
                // hero is falling
                self.num_frames = HERO_NUM_FALLING;
                if let Some(frame) = self.somersault {
                    self.direction.map(
                        HERO_JUMPING_LEFT_SOMERSAULT,
                        HERO_JUMPING_RIGHT_SOMERSAULT,
                    ) + 4 * (frame / 2)
                } else {
                    self.direction
                        .map(HERO_FALLING_LEFT, HERO_FALLING_RIGHT)
                }
            }
        } else {
            // hero is standing or walking on ground
            if self.motion == Motion::NotMoving {
                // hero is standing
                self.num_frames = HERO_NUM_STANDING;
                if self.is_shooting {
                    self.direction
                        .map(HERO_WALKING_LEFT, HERO_WALKING_RIGHT)
                        + 12
                } else {
                    self.direction
                        .map(HERO_STANDING_LEFT, HERO_STANDING_RIGHT)
                }
            } else {
                // hero is walking
                self.num_frames = HERO_NUM_WALKING;
                self.direction.map(HERO_WALKING_LEFT, HERO_WALKING_RIGHT)
                    + 4 * self.current_frame
            }
        };
    }

    pub fn jump(&mut self) {
        if !self.is_in_the_air {
            let (counter, somersault) =
                if self.inventory.is_set(InventoryItem::Boot) {
                    use rand::Rng;
                    let mut rng = rand::thread_rng();
                    (
                        7,
                        if self.motion == Motion::Walking
                            && rng.gen_range(0, 5) == 0
                        {
                            Some(0)
                        } else {
                            None
                        },
                    )
                } else {
                    (6, None)
                };
            self.counter = counter;
            self.somersault = somersault;
            self.vertical_speed = 2;
            self.is_in_the_air = true;
        }
    }

    pub fn land(&mut self) {
        self.vertical_speed = 0;
        self.is_in_the_air = false;
        self.somersault = None;
        self.counter = 0;
    }

    fn fall(&mut self) {
        self.is_in_the_air = true;
        self.counter = 0;
    }

    /// Returns the remaining health
    pub fn act(
        &mut self,
        solids: &LevelSolids,
        actor_adder: &mut dyn ActorAdder,
    ) -> Result<()> {
        self.immunity.count_down();
        if !self.immunity.hero_is_protected() && self.gets_hurt {
            self.immunity.enable();
            self.health.decrease(1);
            // when jumping, this jump should be interrupted
            // just as if the hero had bumped against a ceiling
            self.counter = 0;
        }

        self.somersault = match self.somersault {
            Some(frame) if frame == 13 => None,
            Some(frame) => Some(frame + 1),
            None => None,
        };

        if self.motion == Motion::Walking {
            // the hero is moving
            if self.just_turned_around {
                self.just_turned_around = false;
            } else {
                let mut new_position = self.position.geometry;
                match self.direction {
                    HorizontalDirection::Left => {
                        new_position.x -= HALFTILE_WIDTH as i32;
                    }
                    HorizontalDirection::Right => {
                        new_position.x += HALFTILE_WIDTH as i32;
                    }
                }
                if !self.would_collide(
                    solids,
                    new_position.x,
                    new_position.y,
                ) {
                    self.position
                        .move_to(new_position.x(), new_position.y());
                }
            }
        }

        if !self.is_in_the_air {
            // the hero is standing or walking
            self.vertical_speed = 0;
        } else {
            // the hero is jumping or falling
            if self.counter > 0 {
                // the hero is jumping
                self.counter -= 1;
                self.vertical_speed = match self.counter {
                    3 | 2 => 1,
                    1 | 0 => 0,
                    _ => 2,
                };

                for _ in 0..self.vertical_speed {
                    let geometry = self.position.geometry;
                    if !self.would_collide(
                        solids,
                        geometry.x,
                        geometry.y - HALFTILE_HEIGHT as i32,
                    ) {
                        self.position.move_y_to(
                            geometry.y() - HALFTILE_HEIGHT as i32,
                        );
                    } else {
                        // hero bumped against the ceiling
                        self.counter = 0;
                    }
                }
            } else {
                // the hero is falling
                self.vertical_speed =
                    std::cmp::min(self.vertical_speed + 1, 6);

                for _ in 0..self.vertical_speed / 2 {
                    let geometry = self.position.geometry;
                    if !self.would_collide(
                        solids,
                        geometry.x,
                        geometry.y + HALFTILE_HEIGHT as i32,
                    ) {
                        self.position.move_y_to(
                            geometry.y() + HALFTILE_HEIGHT as i32,
                        );
                    }
                }
            }
        }

        let geometry = self.position.geometry;
        if self.would_collide(
            solids,
            geometry.x,
            geometry.y + HALFTILE_HEIGHT as i32,
        ) {
            if self.is_in_the_air {
                actor_adder.add_actor(
                    ActorType::DustCloud,
                    Point::new(
                        self.position.geometry.x(),
                        self.position.geometry.y() + TILE_HEIGHT as i32,
                    ),
                );
            }
            // the hero is standing on solid ground
            self.land();
            self.counter = 0;
        } else {
            // the hero is falling down
            if self.counter == 0 {
                self.fall();
            }
        }

        Ok(())
    }
}

#[derive(Debug)]
pub struct Position {
    pub geometry: Rect,
}

impl Default for Position {
    fn default() -> Self {
        Position {
            geometry: Self::default_geometry(),
        }
    }
}

impl Position {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn reset(&mut self) {
        self.geometry = Self::default_geometry();
    }

    fn default_geometry() -> Rect {
        Rect::new(0, 0, TILE_WIDTH, TILE_HEIGHT * 2)
    }

    pub fn move_to(&mut self, x: i32, y: i32) {
        self.geometry.x =
            std::cmp::min(x, LEVEL_WIDTH as i32 * TILE_WIDTH as i32);
        self.geometry.y =
            std::cmp::min(y, LEVEL_HEIGHT as i32 * TILE_HEIGHT as i32);
    }

    pub fn move_x_to(&mut self, x: i32) {
        self.move_to(x, self.geometry.y())
    }

    pub fn move_y_to(&mut self, y: i32) {
        self.move_to(self.geometry.x(), y)
    }

    pub fn push_vertically(
        &mut self,
        solids: &LevelSolids,
        offset: i32,
    ) -> i32 {
        if offset == 0 {
            return 0;
        }
        let mut geometry = self.geometry;
        geometry.y += offset;

        if !solids.collides(geometry) {
            self.move_y_to(geometry.y());
            return offset;
        }

        let offset_absolute = offset.abs();
        let direction = offset / offset_absolute;

        for i in 0..offset_absolute {
            geometry.offset(0, -direction);
            if !solids.collides(geometry) {
                self.move_y_to(geometry.y());
                return i * direction;
            }
        }
        0
    }

    pub fn push_horizontally(
        &mut self,
        solids: &LevelSolids,
        offset: i32,
    ) -> i32 {
        if offset == 0 {
            return 0;
        }
        let mut geometry = self.geometry;
        geometry.offset(offset, 0);

        if !solids.collides(geometry) {
            self.move_x_to(geometry.x());
            return offset;
        }

        let offset_absolute = offset.abs();
        let direction = offset / offset_absolute;

        for i in 0..offset_absolute {
            geometry.offset(-direction, 0);
            if !solids.collides(geometry) {
                self.move_x_to(geometry.x());
                return i * direction;
            }
        }
        0
    }
}

#[derive(Debug)]
pub struct Immunity {
    countdown: usize,
}

impl Immunity {
    const DURATION: usize = 16;

    fn new() -> Self {
        Immunity { countdown: 0 }
    }

    fn reset(&mut self) {
        self.countdown = 0;
    }

    fn enable(&mut self) {
        self.countdown = Self::DURATION;
    }

    fn hero_invisible(&self) -> bool {
        self.countdown % 2 > 0
    }

    fn hero_skeleton(&self) -> bool {
        self.countdown == Self::DURATION
    }

    fn hero_is_protected(&self) -> bool {
        self.countdown > 0
    }

    fn count_down(&mut self) {
        if self.countdown > 0 {
            self.countdown -= 1;
        }
    }
}

#[derive(Debug)]
pub struct Score {
    count: u128,
}

impl Default for Score {
    fn default() -> Self {
        Self::new()
    }
}

impl Score {
    pub fn new() -> Self {
        Score { count: 0 }
    }

    pub fn add(&mut self, amount: u128) {
        self.count = self.count.saturating_add(amount);
    }

    pub fn reset(&mut self) {
        self.count = 0;
    }

    pub fn set_value(&mut self, value: u128) {
        self.count = value;
    }

    pub fn value(&self) -> u128 {
        self.count
    }
}

#[derive(Debug)]
pub struct Health {
    life: Option<u8>,
}

impl Default for Health {
    fn default() -> Self {
        Health { life: Some(8u8) }
    }
}

impl Health {
    pub const MAX: u8 = 8;

    pub fn new() -> Self {
        Health::default()
    }

    pub fn reset(&mut self) {
        self.life = Some(Self::MAX);
    }

    pub fn set(&mut self, count: u8) {
        self.life = Some(std::cmp::min(Self::MAX, count));
    }

    pub fn increase(&mut self, count: u8) {
        self.life = match self.life {
            Some(life) => Some(std::cmp::min(Self::MAX, life + count)),
            None => None,
        }
    }

    pub fn decrease(&mut self, count: u8) {
        self.life = match self.life {
            Some(life) if life < count => None,
            Some(life) => Some(life - count),
            None => None,
        };
    }

    pub fn fill_max(&mut self) {
        self.life = Some(Self::MAX);
    }

    pub fn kill(&mut self) {
        self.life = None
    }

    pub fn life(&self) -> Option<u8> {
        self.life
    }
}

#[derive(Debug)]
pub struct Firepower {
    shots: u8,
}

impl Default for Firepower {
    fn default() -> Self {
        Firepower { shots: 1u8 }
    }
}

impl Firepower {
    pub const MAX: u8 = 4;

    pub fn new() -> Firepower {
        Firepower::default()
    }

    pub fn increase(&mut self, count: u8) {
        self.shots = std::cmp::min(Self::MAX, self.shots + count);
    }

    pub fn reset(&mut self) {
        self.shots = 1;
    }

    pub fn num_shots(&self) -> u8 {
        self.shots
    }
}

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum InventoryItem {
    KeyRed,
    KeyGreen,
    KeyBlue,
    KeyPink,
    Boot,
    Glove,
    Clamp,
    AccessCard,
}

#[derive(Default, Debug)]
pub struct Inventory {
    items: std::collections::BTreeSet<InventoryItem>,
}

impl Inventory {
    pub fn new() -> Self {
        Inventory {
            items: Default::default(),
        }
    }

    pub fn reset(&mut self) {
        self.items.clear();
    }

    pub fn clear(&mut self) {
        self.items.clear();
    }

    pub fn get_items(&self) -> std::collections::BTreeSet<InventoryItem> {
        self.items.clone()
    }

    pub fn set_items(
        &mut self,
        items: std::collections::BTreeSet<InventoryItem>,
    ) {
        self.items = items;
    }

    pub fn set(&mut self, item: InventoryItem) {
        self.items.insert(item);
    }

    pub fn unset(&mut self, item: InventoryItem) {
        self.items.remove(&item);
    }

    pub fn is_set(&self, item: InventoryItem) -> bool {
        self.items.contains(&item)
    }
}

#[derive(Debug)]
pub struct FetchedLetterState {
    last_fetched: Option<FetchedLetter>,
}

impl Default for FetchedLetterState {
    fn default() -> Self {
        Self::new()
    }
}

impl FetchedLetterState {
    pub fn new() -> Self {
        FetchedLetterState { last_fetched: None }
    }

    pub fn picked(&mut self, letter: FetchedLetter) {
        use FetchedLetter as L;
        self.last_fetched = match (self.last_fetched, letter) {
            (_, L::D) => Some(L::D),
            (Some(L::D), L::U) => Some(L::U),
            (Some(L::U), L::K) => Some(L::K),
            (Some(L::K), L::E) => Some(L::E),
            _ => None,
        }
    }

    pub fn succeeded(&self) -> bool {
        self.last_fetched == Some(FetchedLetter::E)
    }

    pub fn reset(&mut self) {
        self.last_fetched = None;
    }
}

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum FetchedLetter {
    D,
    U,
    K,
    E,
}

impl TryFrom<char> for FetchedLetter {
    type Error = String;

    fn try_from(c: char) -> Result<Self, Self::Error> {
        match c {
            'D' | 'd' => Ok(FetchedLetter::D),
            'U' | 'u' => Ok(FetchedLetter::U),
            'K' | 'k' => Ok(FetchedLetter::K),
            'E' | 'e' => Ok(FetchedLetter::E),
            c => Err(format!("Unknown hero letter {:?}", c)),
        }
    }
}
07070100000054000081A40000000000000000000000015FD8FA6800000982000000000000000000000000000000000000001F00000000freenukum-0.3.5/src/infobox.rsuse super::messagebox::messagebox;
use crate::event::{ConfirmEvent, WaitEvent};
use crate::{Result, TileProvider};
use anyhow::anyhow;
use sdl2::{
    rect::{Point, Rect},
    render::WindowCanvas,
    surface::Surface,
    EventPump,
};

pub fn show(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    text: &str,
    event_pump: &mut EventPump,
) -> Result<()> {
    let texture_creator = canvas.texture_creator();
    let messagebox = messagebox(text, tileprovider, &texture_creator)?;
    let surface = canvas
        .window()
        .surface(event_pump)
        .map_err(|s| anyhow!(s))?;
    let destrect = Rect::from_center(
        Point::new(
            surface.width() as i32 / 2,
            surface.height() as i32 / 2,
        ),
        messagebox.width(),
        messagebox.height(),
    );

    let mut background_backup = Surface::new(
        destrect.width(),
        destrect.height(),
        surface.pixel_format_enum(),
    )
    .map_err(|s| anyhow!(s))?;
    surface
        .blit(destrect, &mut background_backup, None)
        .map_err(|s| anyhow!(s))?;

    canvas
        .copy(&messagebox.as_texture(&texture_creator)?, None, destrect)
        .map_err(|s| anyhow!(s))?;
    canvas.present();

    loop {
        match ConfirmEvent::wait(event_pump)? {
            ConfirmEvent::Confirmed | ConfirmEvent::Aborted => {
                canvas
                    .copy(
                        &background_backup.as_texture(&texture_creator)?,
                        None,
                        destrect,
                    )
                    .map_err(|s| anyhow!(s))?;
                canvas.present();
                return Ok(());
            }
            ConfirmEvent::RefreshScreen => {
                canvas.present();
            }
        }
    }
}

#[derive(Default)]
pub struct InfoMessageQueue {
    messages: Vec<String>,
}

impl InfoMessageQueue {
    pub fn new() -> Self {
        InfoMessageQueue {
            messages: Vec::new(),
        }
    }

    pub fn process(
        &mut self,
        canvas: &mut WindowCanvas,
        tileprovider: &dyn TileProvider,
        event_pump: &mut EventPump,
    ) -> Result<()> {
        for message in self.messages.drain(..) {
            show(canvas, tileprovider, &message, event_pump)?;
        }
        Ok(())
    }

    pub fn push_back(&mut self, msg: String) {
        self.messages.push(msg);
    }
}
07070100000055000081A40000000000000000000000015FD8FA6800001006000000000000000000000000000000000000002000000000freenukum-0.3.5/src/inputbox.rsuse super::inputfield::InputField;
use super::messagebox::messagebox;
use crate::event::{InputEvent, WaitEvent};
use crate::rendering::{CanvasRenderer, MovePositionRenderer};
use crate::{TileProvider, FONT_HEIGHT, FONT_WIDTH};
use anyhow::{anyhow, Result};
use sdl2::{
    rect::{Point, Rect},
    render::WindowCanvas,
    surface::Surface,
    EventPump,
};

pub enum Answer {
    Ok(String),
    Quit,
}

pub fn show(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    msg: &str,
    max_length: usize,
    event_pump: &mut EventPump,
) -> Result<Answer> {
    let placeholder_msg = format!(
        "{}\n{}\n\nOk (Enter)   Abort (Esc)\n",
        msg,
        " ".repeat(max_length)
    );
    let texture_creator = canvas.texture_creator();
    let messagebox =
        messagebox(&placeholder_msg, tileprovider, &texture_creator)?;
    let surface = canvas
        .window()
        .surface(event_pump)
        .map_err(|s| anyhow!(s))?;

    let destrect = Rect::from_center(
        Point::new(
            surface.width() as i32 / 2,
            surface.height() as i32 / 2,
        ),
        messagebox.width(),
        messagebox.height(),
    );

    let mut background_backup = Surface::new(
        destrect.width(),
        destrect.height(),
        surface.pixel_format_enum(),
    )
    .map_err(|s| anyhow!(s))?;
    surface
        .blit(destrect, &mut background_backup, None)
        .map_err(|s| anyhow!(s))?;

    canvas
        .copy(&messagebox.as_texture(&texture_creator)?, None, destrect)
        .map_err(|s| anyhow!(s))?;

    let offset_x = destrect.left() + FONT_WIDTH as i32;
    let offset_y = destrect.bottom() - FONT_HEIGHT as i32 * 4;
    let mut input_field = InputField::new(max_length);
    {
        let mut input_field_renderer = CanvasRenderer {
            canvas,
            texture_creator: &texture_creator,
            tileprovider,
        };
        let mut input_field_renderer = MovePositionRenderer {
            offset_x,
            offset_y,
            upstream: &mut input_field_renderer,
        };

        input_field.render(&mut input_field_renderer)?;
    }
    canvas.present();

    loop {
        match InputEvent::wait(event_pump)? {
            InputEvent::DeleteLeft => {
                input_field.backspace_pressed();
            }
            InputEvent::DeleteRight => {
                input_field.delete_pressed();
            }
            InputEvent::MoveCursorLeft => {
                input_field.left_pressed();
            }
            InputEvent::MoveCursorRight => {
                input_field.right_pressed();
            }
            InputEvent::Confirm => {
                canvas
                    .copy(
                        &background_backup.as_texture(&texture_creator)?,
                        None,
                        destrect,
                    )
                    .map_err(|s| anyhow!(s))?;
                canvas.present();
                let text = input_field.get_text();
                return Ok(Answer::Ok(text.to_string()));
            }
            InputEvent::Abort => {
                canvas
                    .copy(
                        &background_backup.as_texture(&texture_creator)?,
                        None,
                        destrect,
                    )
                    .map_err(|s| anyhow!(s))?;
                canvas.present();
                return Ok(Answer::Quit);
            }
            InputEvent::Letter(c) => {
                input_field.symbol_pressed(c);
            }
            InputEvent::RefreshScreen => {}
        }

        {
            let mut input_field_renderer = CanvasRenderer {
                canvas,
                texture_creator: &texture_creator,
                tileprovider,
            };
            let mut input_field_renderer = MovePositionRenderer {
                offset_x,
                offset_y,
                upstream: &mut input_field_renderer,
            };
            input_field.render(&mut input_field_renderer)?;
        }
        canvas.present();
    }
}
07070100000056000081A40000000000000000000000015FD8FA6800000878000000000000000000000000000000000000002200000000freenukum-0.3.5/src/inputfield.rsuse super::text;
use crate::rendering::Renderer;
use crate::Result;
use crate::{FONT_HEIGHT, FONT_WIDTH};
use sdl2::{pixels::Color, rect::Rect};

pub struct InputField {
    text: String,
    max_length: usize,
    cursor_position: usize,
}

impl InputField {
    pub fn new(max_length: usize) -> Self {
        InputField {
            text: String::new(),
            max_length,
            cursor_position: 0,
        }
    }

    pub fn backspace_pressed(&mut self) {
        if self.cursor_position > 0
            && self.cursor_position <= self.text.len()
        {
            self.cursor_position -= 1;
            self.text.remove(self.cursor_position);
        }
    }

    pub fn delete_pressed(&mut self) {
        if !self.text.is_empty()
            && self.cursor_position < self.text.len() - 1
        {
            self.text.remove(self.cursor_position);
        }
    }

    pub fn left_pressed(&mut self) {
        if self.cursor_position > 0 {
            self.cursor_position -= 1;
        }
    }

    pub fn right_pressed(&mut self) {
        if self.cursor_position < self.text.len() {
            self.cursor_position += 1;
        }
    }

    pub fn symbol_pressed(&mut self, symbol: char) {
        if self.text.len() < self.max_length {
            if self.cursor_position == self.text.len() {
                self.text.push(symbol);
            } else {
                self.text.insert(self.cursor_position, symbol);
            }
            self.cursor_position += 1;
        }
    }

    pub fn render(&self, renderer: &mut dyn Renderer) -> Result<()> {
        let bgrect = Rect::new(
            0,
            0,
            FONT_WIDTH * self.max_length as u32,
            FONT_HEIGHT,
        );
        renderer.fill_rect(bgrect, Color::RGB(0, 0, 0))?;
        text::render(renderer, &self.text)?;
        let cursorrect = Rect::new(
            self.cursor_position as i32 * FONT_WIDTH as i32,
            1,
            1,
            FONT_HEIGHT,
        );
        renderer.fill_rect(cursorrect, Color::RGB(0x88, 0x88, 0x88))?;
        Ok(())
    }

    pub fn get_text(&self) -> &str {
        self.text.as_ref()
    }
}
07070100000057000041ED0000000000000000000000025FD8FA6800000000000000000000000000000000000000000000001A00000000freenukum-0.3.5/src/level07070100000058000081A40000000000000000000000015FD8FA6800008372000000000000000000000000000000000000002100000000freenukum-0.3.5/src/level/mod.rspub mod raw;
pub mod solids;
pub mod tiles;

use crate::{
    actor::{
        ActorAdder, ActorMessageQueue, ActorQueue, ActorType, ActorsList,
        LevelActorAdder,
    },
    hero::HeroData,
    infobox::InfoMessageQueue,
    rendering::Renderer,
    shot::{Shot, ShotList},
    Result, ANIMATION_START, HALFTILE_WIDTH, LEVEL_HEIGHT, LEVEL_WIDTH,
    SOLID_BLACK, SOLID_CONVEYORBELT_LEFTEND, TILE_HEIGHT, TILE_WIDTH,
};
use log::warn;
use raw::LevelRaw;
use sdl2::{
    pixels::Color,
    rect::{Point, Rect},
    surface::Surface,
};
use solids::LevelSolids;
use std::convert::TryFrom;
use std::io::Read;
use tiles::LevelTiles;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlayState {
    Playing,
    LevelFinished,
    KilledPlayingAnimation(usize),
    RestartLevel,
    GoToMainScreen,
}

impl PlayState {
    pub fn keep_acting(&self) -> bool {
        matches!(self, PlayState::Playing | PlayState::KilledPlayingAnimation(_))
    }

    pub fn hero_can_act(&self) -> bool {
        matches!(self, PlayState::Playing)
    }
}

#[derive(Debug)]
pub struct LevelData {
    pub tiles: LevelTiles,
    pub solids: LevelSolids,
    pub play_state: PlayState,
    pub actors: ActorsList,
    pub animated_frames_since_last_act: usize,
    pub shots: ShotList,
}

impl LevelData {
    pub fn load<R: Read>(
        reader: &mut R,
        hero: &mut HeroData,
        raw: &mut Option<&mut LevelRaw>,
    ) -> Result<Self> {
        let mut tiles = LevelTiles::new();
        let mut solids = LevelSolids::new();
        let mut actor_queue = ActorQueue::new();

        for i in 0..LEVEL_HEIGHT * LEVEL_WIDTH {
            let x = i % LEVEL_WIDTH;
            let y = i / LEVEL_WIDTH;

            let mut tile_buf = [0u8; 2];
            reader.read_exact(&mut tile_buf)?;

            let tx = (x * TILE_WIDTH) as i32;
            let ty = (y * TILE_HEIGHT) as i32;

            let mut aa = |actor_type, x, y| {
                actor_queue.add_actor(actor_type, Point::new(x, y));
            };

            let tile = u16::from_le_bytes(tile_buf);
            if let Some(raw) = raw {
                raw.set(x, y, tile);
            }
            if tile >= 4 && tile <= 0x2fe0 {
                tiles.set(x, y, tile / 0x20);
                solids.set(x, y, tile >= 0x1800);
            }
            match tile {
                0x0000 => {}
                0x0080 =>
                /* written text on black screen */
                {
                    aa(ActorType::TextOnScreenBackground, tx, ty);
                }
                0x0100 =>
                /* blue high voltage flash */
                {
                    aa(ActorType::HighVoltageFlashBackground, tx, ty)
                }
                0x0180 =>
                /* red flash light */
                {
                    aa(ActorType::RedFlashlightBackground, tx, ty)
                }
                0x0200 =>
                /* blue high voltage flash */
                {
                    aa(ActorType::BlueFlashlightBackground, tx, ty)
                }
                0x0280 =>
                /* key panel on the wall */
                {
                    aa(ActorType::KeypanelBackground, tx, ty)
                }
                0x0300 =>
                /* red rotation light */
                {
                    aa(ActorType::RedRotationLightBackground, tx, ty)
                }
                0x0380 =>
                /* flashing up arrow */
                {
                    aa(ActorType::UpArrowBackground, tx, ty)
                }
                0x0400 =>
                /* background blinking blue box */
                {
                    aa(ActorType::BlueLightBackground1, tx, ty)
                }
                0x0420 =>
                /* background blinking blue box */
                {
                    aa(ActorType::BlueLightBackground2, tx, ty)
                }
                0x0440 =>
                /* background blinking blue box */
                {
                    aa(ActorType::BlueLightBackground3, tx, ty)
                }
                0x0460 =>
                /* background blinking blue box */
                {
                    aa(ActorType::BlueLightBackground4, tx, ty)
                }
                0x0500 =>
                /* background green poison liquid */
                {
                    aa(ActorType::GreenPoisonBackground, tx, ty)
                }
                0x0580 =>
                /* background lava */
                {
                    aa(ActorType::LavaBackground, tx, ty)
                }
                0x1800 =>
                /* solid wall which can be shot */
                {
                    tiles.set(x, y, 0x17E0 / 0x20);
                    aa(ActorType::ShootableWall, tx, ty);
                }
                0x1C00 =>
                /* center conveyor */
                {
                    tiles.set(x, y, SOLID_BLACK as u16);
                    solids.set(x, y, true);
                }
                0x3000 =>
                /* grey box, empty */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyEmpty, tx, ty);
                }
                0x3001 =>
                /* lift */
                {
                    solids.set(x, y, true);
                    aa(ActorType::Lift, tx, ty);
                }
                0x3002 =>
                /* left end of left-moving conveyor */
                {
                    tiles.set(x, y, SOLID_CONVEYORBELT_LEFTEND as u16);
                    solids.set(x, y, true);
                }
                0x3003 =>
                /* right end of left-moving conveyor */
                {
                    tiles.set(x, y, SOLID_BLACK as u16);
                    solids.set(x, y, true);
                    aa(ActorType::ConveyorLeftMovingRightEnd, tx, ty);
                }
                0x3004 =>
                /* left end of right-moving conveyor */
                {
                    tiles.set(x, y, SOLID_CONVEYORBELT_LEFTEND as u16);
                    solids.set(x, y, true);
                }
                0x3005 =>
                /* right end of right-moving conveyor */
                {
                    tiles.set(x, y, SOLID_BLACK as u16);
                    solids.set(x, y, true);
                    aa(ActorType::ConveyorRightMovingRightEnd, tx, ty);
                }
                0x3006 =>
                /* grey box with boots inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyBoots, tx, ty);
                }
                0x3007 =>
                /* rocket which gets started if shot
                 * and leaves a blue box with a balloon */
                {
                    aa(ActorType::Rocket, tx, ty);
                }
                0x3008 =>
                /* grey box with clamps inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyClamps, tx, ty);
                }
                0x3009 =>
                /* fire burning to the right */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::FireRight, tx, ty);
                }
                0x300A =>
                /* fire burning to the left */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::FireLeft, tx, ty);
                }
                0x300b =>
                /* flying techbot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::FlyingBot, tx, ty);
                }
                0x300c =>
                /* footbot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::FootBot, tx, ty);
                }
                0x300d =>
                /* tankbot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::TankBot, tx, ty);
                }
                0x300e =>
                /* fire wheel bot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::FireWheelBot, tx, ty);
                }
                0x300F =>
                /* grey box with gun inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyGun, tx, ty);
                }
                0x3010 =>
                /* robot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Robot, tx, ty);
                }
                0x3011 =>
                /* exit door */
                {
                    aa(ActorType::ExitDoor, tx, ty - TILE_HEIGHT as i32);
                }
                0x3012 =>
                /* grey box with bomb inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyBomb, tx, ty);
                }
                0x3013 =>
                /* bot consisting of several white-blue balls */
                {
                    aa(ActorType::SnakeBot, tx, ty);
                }
                0x3014 =>
                /* water mirroring everything that is above */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::Water, tx, ty);
                }
                0x3015 =>
                /* red box with soda inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxRedSoda, tx, ty);
                }
                0x3016 =>
                /* crab bot crawling along wall left of him */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::WallCrawlerBotLeft, tx, ty);
                }
                0x3017 =>
                /* crab bot crawling along wall right of him */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::WallCrawlerBotRight, tx, ty);
                }
                0x3018 =>
                /* red box with chicken inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxRedChicken, tx, ty);
                }
                0x3019 =>
                /* floor that breaks on second jump onto it */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::UnstableFloor, tx, ty);
                }
                0x301a =>
                /* horizontal electric arc which gets deactivated when mill is shot */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::ElectricArc, tx, ty);
                }
                0x301b =>
                /* fan wheel mounted on right wall blowing to the left */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::FanLeft, tx, ty);
                }
                0x301c =>
                /* fan wheel mounted on left wall blowing to the right*/
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::FanRight, tx, ty);
                }
                0x301d =>
                /* blue box with football insdie */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueFootball, tx, ty);
                }
                0x301e =>
                /* blue box with joystick inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueJoystick, tx, ty);
                }
                0x301f =>
                /* blue box with disk inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueDisk, tx, ty);
                }
                0x3020 =>
                /* grey box with glove inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyGlove, tx, ty);
                }
                0x3021 =>
                /* laser beam which is deactivated by access card */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::AccessCardDoor, tx, ty);
                }
                0x3022 =>
                /* helicopter */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::HelicopterBot, tx, ty);
                }
                0x3023 =>
                /* blue box with balloon inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueBalloon, tx, ty);
                }
                0x3024 =>
                /* camera */
                {
                    aa(ActorType::Camera, tx, ty);
                }
                0x3025 =>
                /* broken wall background */
                {
                    /* take the part from one above */
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::BrokenWallBackground, tx, ty);
                }
                0x3026 =>
                /* left end of background stone wall */
                { /* TODO */ }
                0x3027 =>
                /* right end of background stone wall */
                { /* TODO */ }
                0x3028 =>
                /* window inside background stone wall */
                {
                    aa(ActorType::StoneWindowBackground, tx, ty);
                }
                0x3029 =>
                /* grey box with full life */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyFullLife, tx, ty);
                }
                0x302a =>
                /* "ACME" brick that comes falling down */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::Acme, tx, ty);
                }
                0x302b =>
                /* rotating mill that can kill duke on touch */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::Mill, tx, ty);
                }
                0x302c =>
                /* single spike standing out of the floor */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::Spike, tx, ty);
                }
                0x302d =>
                /* blue box with flag inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueFlag, tx, ty);
                }
                0x302e =>
                /* blue box with radio inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxBlueRadio, tx, ty);
                }
                0x302f =>
                /* teleporter station */
                {
                    aa(ActorType::Teleporter1, tx, ty);
                }
                0x3030 =>
                /* teleporter station */
                {
                    aa(ActorType::Teleporter2, tx, ty);
                }
                0x3031 =>
                /* jumping mines */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::RedBallJumping, tx, ty);
                }
                0x3032 =>
                /* we found our hero! */
                {
                    hero.enter_level(tx, ty - TILE_HEIGHT as i32);
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                }
                0x3033 =>
                /* grey box with the access card inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyAccessCard, tx, ty);
                }
                0x3034 =>
                /* slot for access card */
                {
                    aa(ActorType::AccessCardSlot, tx, ty);
                }
                0x3035 =>
                /* slot for glove */
                {
                    aa(ActorType::GloveSlot, tx, ty);
                }
                0x3036 =>
                /* floor which expands to right by access of glove slot */
                {
                    solids.set(x, y, true);
                    aa(ActorType::ExpandingFloor, tx, ty);
                }
                0x3037 =>
                /* grey box with a D inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyLetterD, tx, ty);
                }
                0x3038 =>
                /* grey box with a U inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyLetterU, tx, ty);
                }
                0x3039 =>
                /* grey box with a K inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyLetterK, tx, ty);
                }
                0x303a =>
                /* grey box with a E inside */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::BoxGreyLetterE, tx, ty);
                }
                0x303b =>
                /* bunny bot */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::RabbitoidBot, tx, ty);
                }
                0x303c =>
                /* fire gnome */
                {
                    aa(ActorType::FlameGnomeBot, tx, ty);
                }
                0x303d =>
                /* fence with backdrop 1 behind it */
                {
                    aa(ActorType::FenceBackground, tx, ty);
                }
                0x303e =>
                /* window - left part */
                {
                    tiles.set(x, y, 0);
                    aa(ActorType::WindowLeftBackground, tx, ty);
                }
                0x303f =>
                /* window - right part */
                {
                    tiles.set(x, y, 0);
                    aa(ActorType::WindowRightBackground, tx, ty);
                }
                0x3040 =>
                /* the notebook */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Notebook, tx, ty);
                }
                0x3041 =>
                /* the surveillance screen */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::SurveillanceScreen, tx, ty);
                }
                0x3043 =>
                /* dr proton -the final opponent */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::DrProton, tx, ty);
                }
                0x3044 =>
                /* red key */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::KeyRed, tx, ty);
                }
                0x3045 =>
                /* green key */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::KeyGreen, tx, ty);
                }
                0x3046 =>
                /* blue key */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::KeyBlue, tx, ty);
                }
                0x3047 =>
                /* pink key */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::KeyPink, tx, ty);
                }
                0x3048 =>
                /* red keyhole */
                {
                    aa(ActorType::KeyholeRed, tx, ty);
                }
                0x3049 =>
                /* green keyhole */
                {
                    aa(ActorType::KeyholeGreen, tx, ty);
                }
                0x304a =>
                /* blue keyhole */
                {
                    aa(ActorType::KeyholeBlue, tx, ty);
                }
                0x304b =>
                /* pink keyhole */
                {
                    aa(ActorType::KeyholePink, tx, ty);
                }
                0x304c =>
                /* red door */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::DoorRed, tx, ty);
                }
                0x304d =>
                /* green door */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::DoorGreen, tx, ty);
                }
                0x304e =>
                /* blue door */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::DoorBlue, tx, ty);
                }
                0x304f =>
                /* pink door */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    solids.set(x, y, true);
                    aa(ActorType::DoorPink, tx, ty);
                }
                0x3050 =>
                /* football on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Football, tx, ty);
                }
                0x3051 =>
                /* single chicken on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::ChickenSingle, tx, ty);
                }
                0x3052 =>
                /* soda on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Soda, tx, ty);
                }
                0x3053 =>
                /* a disk on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Disk, tx, ty);
                }
                0x3054 =>
                /* a joystick on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Joystick, tx, ty);
                }
                0x3055 =>
                /* a flag on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Flag, tx, ty);
                }
                0x3056 =>
                /* a radio on its own */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::Radio, tx, ty);
                }
                0x3057 =>
                /* the red mine lying on the ground */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::RedBallLying, tx, ty);
                }
                0x3058 =>
                /* spikes showing up */
                {
                    if y > 0 {
                        tiles.copy_from_to(x, y - 1, x, y);
                    }
                    aa(ActorType::SpikesUp, tx, ty);
                }
                0x3059 =>
                /* spikes showing down */
                {
                    if x > 0 {
                        tiles.copy_from_to(x - 1, y, x, y);
                    }
                    aa(ActorType::SpikesDown, tx, ty);
                }
                t if t >= 4 && t <= 0x2fe0 => {}
                t if (t as usize / 0x20 >= ANIMATION_START) => {
                    warn!(
                        "Unknown tile 0x{:04x} at x: {}, y: {}\n",
                        t, x, y
                    );
                    tiles.set(x, y, 2);
                }
                t => {
                    unreachable!("Unknown tile code: 0x{:04x}", t);
                }
            }
        }

        let mut actors = ActorsList::new();
        let mut actor_adder = LevelActorAdder {
            solids: &mut solids,
            tiles: &mut tiles,
            actors: &mut actors,
        };
        actor_queue.process(&mut actor_adder);

        Ok(LevelData {
            tiles,
            solids,
            play_state: PlayState::Playing,
            actors,
            animated_frames_since_last_act: 0,
            shots: Vec::new(),
        })
    }

    pub fn hero_interact_start(
        &mut self,
        hero: &mut HeroData,
        info_message_queue: &mut InfoMessageQueue,
        actor_message_queue: &mut ActorMessageQueue,
    ) {
        self.actors.start_interaction(
            &mut self.play_state,
            hero,
            info_message_queue,
            actor_message_queue,
        );
    }

    pub fn hero_interact_end(&mut self, hero: &mut HeroData) {
        self.actors.end_interaction(&mut self.play_state, hero);
    }

    pub fn animated_frames_since_last_act_increase(&mut self) -> usize {
        self.animated_frames_since_last_act += 1;
        self.animated_frames_since_last_act %= 1;
        self.animated_frames_since_last_act
    }

    pub fn render(
        &mut self,
        renderer: &mut dyn Renderer,
        hero: &mut HeroData,
        draw_collision_bounds: bool,
        srcrect: Rect,
        backdrop1: Option<&Surface>,
        _backdrop2: Option<&Surface>,
    ) -> Result<()> {
        if let Some(backdrop) = backdrop1 {
            renderer.place_surface(backdrop, srcrect)?;
        } else {
            renderer.fill_rect(srcrect, Color::RGB(0, 0, 0))?;
        }

        let start_x =
            u32::try_from(srcrect.left()).unwrap_or_default() / TILE_WIDTH;
        let end_x = (u32::try_from(srcrect.right()).unwrap_or_default()
            / TILE_WIDTH)
            + 1;
        let start_y =
            u32::try_from(srcrect.top()).unwrap_or_default() / TILE_HEIGHT;
        let end_y = (u32::try_from(srcrect.bottom()).unwrap_or_default()
            / TILE_HEIGHT)
            + 1;

        for y in start_y..std::cmp::min(end_y, LEVEL_HEIGHT) {
            for x in start_x..std::cmp::min(end_x, LEVEL_WIDTH) {
                let tilenr = self.tiles.get(x, y);
                if tilenr > 1 && tilenr < (48 * 8) {
                    let point = Point::new(
                        (TILE_WIDTH * x) as i32,
                        (TILE_HEIGHT * y) as i32,
                    );
                    renderer.place_tile(tilenr as usize, point)?;
                }
            }
        }

        self.actors.update_visibility(srcrect);

        self.actors
            .render_background_actors(renderer, draw_collision_bounds)?;

        hero.render(renderer, &self.solids, draw_collision_bounds)?;

        self.actors
            .render_foreground_actors(renderer, draw_collision_bounds)?;

        for shot in self.shots.iter() {
            shot.render(renderer, draw_collision_bounds)?;
        }
        Ok(())
    }

    pub fn act(
        &mut self,
        hero_data: &mut HeroData,
        actor_queue: &mut ActorQueue,
        actor_message_queue: &mut ActorMessageQueue,
    ) -> Result<()> {
        let animated_frames =
            self.animated_frames_since_last_act_increase();

        for shot in self.shots.iter_mut() {
            shot.act(
                hero_data,
                &mut self.actors,
                &mut self.solids,
                &mut self.tiles,
                actor_queue,
                actor_message_queue,
            );
        }
        self.shots.retain(|s| s.is_alive);

        for message in actor_message_queue.messages.drain(..) {
            self.actors.send_message(
                message.receivers,
                message.message,
                hero_data,
                &mut self.solids,
            );
        }

        self.actors.act(
            &mut self.solids,
            &mut self.tiles,
            hero_data,
            actor_queue,
            &mut self.play_state,
        );

        if self.play_state.hero_can_act() && animated_frames == 0 {
            hero_data.act(&self.solids, actor_queue)?;
        }
        hero_data.next_frame();
        hero_data.update_animation();

        if hero_data.health.life().is_none()
            && self.play_state == PlayState::Playing
        {
            self.play_state = PlayState::KilledPlayingAnimation(80);
        }

        Ok(())
    }

    pub fn fire_shot(
        &mut self,
        hero: &mut HeroData,
        actor_adder: &mut dyn ActorAdder,
        actor_message_queue: &mut ActorMessageQueue,
    ) {
        if self.shots.len() < hero.firepower.num_shots() as usize
            && self.play_state.hero_can_act()
        {
            let heropos = hero.position.geometry;
            let mut shot = Shot::new(heropos.x, heropos.y, hero.direction);

            let distance =
                HALFTILE_WIDTH as i32 * shot.direction.as_factor_i32();

            // we only push half of the distance, but do it twice, so that
            // also the intermediate position gets covered, not just the
            // end position.
            shot.push(
                hero,
                &mut self.actors,
                &mut self.solids,
                &mut self.tiles,
                distance,
                actor_adder,
                actor_message_queue,
            );

            self.shots.push(shot);
        }
    }
}
07070100000059000081A40000000000000000000000015FD8FA68000002D8000000000000000000000000000000000000002100000000freenukum-0.3.5/src/level/raw.rsuse crate::{LEVEL_HEIGHT, LEVEL_WIDTH};

#[derive(Debug)]
pub struct LevelRaw {
    raw: [[u16; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
}

impl Default for LevelRaw {
    fn default() -> Self {
        Self::new()
    }
}

impl LevelRaw {
    pub fn new() -> Self {
        LevelRaw {
            raw: [[0u16; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
        }
    }

    pub fn set(&mut self, x: u32, y: u32, tile: u16) {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.raw[y as usize][x as usize] = tile;
    }

    pub fn get(&self, x: u32, y: u32) -> u16 {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.raw[y as usize][x as usize]
    }
}
0707010000005A000081A40000000000000000000000015FD8FA6800001480000000000000000000000000000000000000002400000000freenukum-0.3.5/src/level/solids.rsuse crate::{LEVEL_HEIGHT, LEVEL_WIDTH, TILE_HEIGHT, TILE_WIDTH};
use sdl2::rect::Rect;

#[derive(Debug)]
pub struct LevelSolids {
    solids: [[bool; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
}

impl Default for LevelSolids {
    fn default() -> Self {
        Self::new()
    }
}

impl LevelSolids {
    pub fn new() -> Self {
        LevelSolids {
            solids: [[false; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
        }
    }

    pub fn new_all_solid() -> Self {
        LevelSolids {
            solids: [[true; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
        }
    }

    pub fn dump(&self) {
        print!("    ");
        for i in 0..LEVEL_WIDTH {
            print!("{}", i % 10);
        }
        println!();

        print!("   ┏");
        for _ in 0..LEVEL_WIDTH {
            print!("━");
        }
        println!("┓");

        for (i, row) in self.solids.iter().enumerate() {
            print!("{:>3}┃", i);
            for col in row {
                if *col {
                    print!("█");
                } else {
                    print!("░");
                }
            }
            println!("┃");
        }

        print!("   ┗");
        for _ in 0..LEVEL_WIDTH {
            print!("━");
        }
        println!("┛");

        print!("    ");
        for i in 0..LEVEL_WIDTH {
            print!("{}", i % 10);
        }
        println!();

        print!("    ");
        for i in 0..LEVEL_WIDTH {
            if i % 10 == 0 {
                print!("^{:<9}", i / 10);
            }
        }
        println!();
    }

    pub fn set(&mut self, x: u32, y: u32, value: bool) {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.solids[y as usize][x as usize] = value;
    }

    pub fn get(&self, x: u32, y: u32) -> bool {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.solids[y as usize][x as usize]
    }

    pub fn collides(&self, rect: Rect) -> bool {
        let mut solidrect = Rect::new(0, 0, TILE_WIDTH, TILE_HEIGHT);
        let left_edge = rect.left() as u32 / TILE_WIDTH;
        let right_edge = rect.right() as u32 / TILE_WIDTH + 1;
        let top_edge = rect.top() as u32 / TILE_HEIGHT;
        let bottom_edge = rect.bottom() as u32 / TILE_HEIGHT + 1;

        for i in left_edge..right_edge {
            for j in top_edge..bottom_edge {
                if self.get(i, j) {
                    solidrect.x = (i * TILE_WIDTH) as i32;
                    solidrect.y = (j * TILE_HEIGHT) as i32;
                    if rect.has_intersection(solidrect) {
                        return true;
                    }
                }
            }
        }
        false
    }

    pub fn push_rect_standing_on_ground(
        &self,
        rect: &mut Rect,
        offset: i32,
        gravity: u8,
    ) -> bool {
        if self.collides(*rect) {
            // locked in, can't move at all.
            return false;
        }
        // fall down as far as possible
        self.rect_fall_down(rect, gravity);

        // check if we stand on solid ground before movement
        let stood_solid = self.rect_stands_on_ground_completely(*rect);

        rect.offset(offset, 0);

        if self.collides(*rect) {
            rect.offset(-offset, 0);
            false
        } else if stood_solid
            && self.rect_stands_on_ground_completely(*rect)
        {
            // stood on solid ground before, still does.
            true
        } else if stood_solid {
            // stood on solid ground before, doesn't anymore.
            rect.offset(-offset, 0);
            false
        } else {
            // walk on partial solid ground as long as possible.
            true
        }
    }

    fn rect_fall_down(&self, rect: &mut Rect, distance: u8) -> u8 {
        if self.collides(*rect) {
            // can't fall down because collides with solid ground.
            return 0;
        }
        if self.rect_stands_on_ground_partially(*rect) {
            // partially stands on solid ground, can't fall down.
            return 0;
        }
        for i in 0..distance {
            // check how far we can fall down.
            rect.y += 1;
            if self.rect_stands_on_ground_partially(*rect) {
                return i;
            }
        }
        distance
    }

    fn rect_stands_on_ground_partially(&self, rect: Rect) -> bool {
        if (rect.bottom() as u32 % TILE_HEIGHT) > 0 {
            return false;
        }
        let j = rect.bottom() as u32 / TILE_HEIGHT;
        for i in (rect.left() as u32 / TILE_WIDTH)
            ..(rect.right() as u32 + 1) / TILE_WIDTH + 1
        {
            if self.get(i, j) {
                return true;
            }
        }
        false
    }

    fn rect_stands_on_ground_completely(&self, rect: Rect) -> bool {
        if (rect.bottom() % TILE_HEIGHT as i32) > 0 {
            return false;
        }
        let j = rect.bottom() as u32 / TILE_HEIGHT;
        for i in (rect.left() as u32 / TILE_WIDTH)
            ..(rect.right() as u32 + 1) / TILE_WIDTH + 1
        {
            if !self.get(i, j) {
                return false;
            }
        }
        true
    }
}
0707010000005B000081A40000000000000000000000015FD8FA68000003AD000000000000000000000000000000000000002300000000freenukum-0.3.5/src/level/tiles.rsuse crate::{LEVEL_HEIGHT, LEVEL_WIDTH};

#[derive(Debug)]
pub struct LevelTiles {
    tiles: [[u16; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
}

impl Default for LevelTiles {
    fn default() -> Self {
        Self::new()
    }
}

impl LevelTiles {
    pub fn new() -> Self {
        LevelTiles {
            tiles: [[0u16; LEVEL_WIDTH as usize]; LEVEL_HEIGHT as usize],
        }
    }

    pub fn set(&mut self, x: u32, y: u32, value: u16) {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.tiles[y as usize][x as usize] = value;
    }

    pub fn get(&self, x: u32, y: u32) -> u16 {
        assert!(x < LEVEL_WIDTH);
        assert!(y < LEVEL_HEIGHT);

        self.tiles[y as usize][x as usize]
    }

    pub fn copy_from_to(
        &mut self,
        x_from: u32,
        y_from: u32,
        x_to: u32,
        y_to: u32,
    ) {
        self.set(x_to, y_to, self.get(x_from, y_from));
    }
}
0707010000005C000081A40000000000000000000000015FD8FA6800002929000000000000000000000000000000000000001B00000000freenukum-0.3.5/src/lib.rs#[macro_use]
extern crate serde_derive;

pub mod actor;
pub mod backdrop;
pub mod borders;
pub mod data;
pub mod episodes;
pub mod event;
pub mod game;
pub mod geometry;
pub mod graphics;
pub mod hero;
pub mod infobox;
pub mod inputbox;
pub mod inputfield;
pub mod level;
pub mod mainmenu;
pub mod menu;
pub mod messagebox;
pub mod picture;
pub mod rendering;
pub mod settings;
pub mod shot;
pub mod text;
pub mod tile;
pub mod tilecache;
pub mod tileprovider;

use anyhow::Result;
use tileprovider::TileProvider;

pub const GAME_INTERVAL: u32 = 80;

pub const HALFTILE_WIDTH: u32 = 8;
pub const HALFTILE_HEIGHT: u32 = 8;
pub const TILE_WIDTH: u32 = HALFTILE_WIDTH * 2;
pub const TILE_HEIGHT: u32 = HALFTILE_HEIGHT * 2;
pub const MAX_TILES_PER_FILE: usize = 50;
pub const HEALTH_COUNT: usize = 8;
pub const INVENTORY_WIDTH: usize = HEALTH_COUNT / 2;
pub const FONT_WIDTH: u32 = 8;
pub const FONT_HEIGHT: u32 = 8;
pub const WINDOW_WIDTH: u32 = 320;
pub const WINDOW_HEIGHT: u32 = 200;
pub const PICTURE_WIDTH: u32 = 40;
pub const PICTURE_HEIGHT: u32 = 200;
pub const BACKDROP_WIDTH: u32 = 13;
pub const BACKDROP_HEIGHT: u32 = 10;
pub const MAX_LIFE: usize = 8;
pub const MAX_FIREPOWER: usize = 4;
pub const SCORE_DIGITS: usize = 8;
pub const LEVELWINDOW_WIDTH: u32 = 13;
pub const LEVELWINDOW_HEIGHT: u32 = 10;

/// The height of the level in full tiles
pub const LEVEL_HEIGHT: u32 = 90;
/// The width of the level in full tiles
pub const LEVEL_WIDTH: u32 = 128;

const BACKGROUND_START: usize = 0;
const BACKGROUND_LIGHT_GREY: usize = BACKGROUND_START + 70;

const FONT_START: usize = 19 * 48 + 3 * 50;
const FONT_ASCII_UPPERCASE: usize = FONT_START + 10;
const FONT_ASCII_LOWERCASE: usize = FONT_START + 69;
const FONT_QUESTIONMARK: usize = FONT_START + 67;
const BORDER_START: usize = 19 * 48 + 5 * 50;
const BORDER_BLUE_MIDDLE: usize = BORDER_START + 17;
const BORDER_BLUE_TOPLEFT: usize = BORDER_START + 18;
const BORDER_BLUE_TOPRIGHT: usize = BORDER_START + 19;
const BORDER_BLUE_BOTTOMLEFT: usize = BORDER_START + 20;
const BORDER_BLUE_BOTTOMRIGHT: usize = BORDER_START + 21;
const BORDER_BLUE_LEFT: usize = BORDER_START + 22;
const BORDER_BLUE_RIGHT: usize = BORDER_START + 23;
const BORDER_BLUE_TOP: usize = BORDER_START + 24;
const BORDER_BLUE_BOTTOM: usize = BORDER_START + 25;
const BORDER_GREY_START: usize = BORDER_START;

const NUMBER_START: usize = BORDER_START + 48;

const NUMBER_100: usize = NUMBER_START;
const NUMBER_200: usize = NUMBER_START + 2;
const NUMBER_500: usize = NUMBER_START + 4;
const NUMBER_1000: usize = NUMBER_START + 6;
const NUMBER_2000: usize = NUMBER_START + 8;
const NUMBER_5000: usize = NUMBER_START + 10;
const NUMBER_10000: usize = NUMBER_START + 12;
const NUMBER_BONUS_1_LEFT: usize = NUMBER_START + 14;
const NUMBER_BONUS_1_RIGHT: usize = NUMBER_START + 16;
const NUMBER_BONUS_2_LEFT: usize = NUMBER_START + 18;
const NUMBER_BONUS_2_RIGHT: usize = NUMBER_START + 20;
const NUMBER_BONUS_3_LEFT: usize = NUMBER_START + 22;
const NUMBER_BONUS_3_RIGHT: usize = NUMBER_START + 24;
const NUMBER_BONUS_4_LEFT: usize = NUMBER_START + 26;
const NUMBER_BONUS_4_RIGHT: usize = NUMBER_START + 28;
const NUMBER_BONUS_5_LEFT: usize = NUMBER_START + 30;
const NUMBER_BONUS_5_RIGHT: usize = NUMBER_START + 32;
const NUMBER_BONUS_6_LEFT: usize = NUMBER_START + 34;
const NUMBER_BONUS_6_RIGHT: usize = NUMBER_START + 36;
const NUMBER_BONUS_7_LEFT: usize = NUMBER_START + 38;
const NUMBER_BONUS_7_RIGHT: usize = NUMBER_START + 40;

const SOLID_START: usize = 4 * 48;
const SOLID_SHOOTABLE_WALL_BRICKS: usize = SOLID_START;
const SOLID_ELEVATOR: usize = SOLID_START + 23;
const SOLID_BLACK: usize = SOLID_START + 65;
const SOLID_EXPANDINGFLOOR: usize = SOLID_START + 191;
const SOLID_CONVEYORBELT: usize = SOLID_START + 0x1C;
const SOLID_CONVEYORBELT_LEFTEND: usize = SOLID_CONVEYORBELT;
const SOLID_CONVEYORBELT_CENTER: usize = SOLID_CONVEYORBELT + 4;
const SOLID_CONVEYORBELT_RIGHTEND: usize = SOLID_CONVEYORBELT + 6;

const SOLID_END: usize = SOLID_START + 4 * 48;
const ANIMATION_START: usize = SOLID_END;

const _ANIMATION_FOOTBOT: usize = ANIMATION_START + 10;
const ANIMATION_CARBOT: usize = ANIMATION_START + 34;
const ANIMATION_EXPLOSION: usize = ANIMATION_START + 42;
const ANIMATION_FIREWHEEL_OFF: usize = ANIMATION_START + 48;
const ANIMATION_FIREWHEEL_ON: usize = ANIMATION_START + 64;
const ANIMATION_ROBOT: usize = ANIMATION_START + 80;
const ANIMATION_BOMBFIRE: usize = ANIMATION_START + 90;
const ANIMATION_EXITDOOR: usize = ANIMATION_START + 96;
const ANIMATION_BOMB: usize = ANIMATION_START + 112;
const ANIMATION_SODA: usize = ANIMATION_START + 128;
const ANIMATION_SODAFLY: usize = ANIMATION_START + 132;
const ANIMATION_WALLCRAWLERBOT_LEFT: usize = ANIMATION_START + 136;
const ANIMATION_WALLCRAWLERBOT_RIGHT: usize = ANIMATION_START + 140;
const ANIMATION_FAN: usize = ANIMATION_START + 156;
const ANIMATION_CAMERA_LEFT: usize = ANIMATION_START + 200;
const ANIMATION_CAMERA_CENTER: usize = ANIMATION_START + 201;
const ANIMATION_CAMERA_RIGHT: usize = ANIMATION_START + 202;
const ANIMATION_BROKENWALLBG: usize = ANIMATION_START + 203;
const ANIMATION_STONEWINDOWBG: usize = ANIMATION_START + 206;
const ANIMATION_TELEPORTER1: usize = ANIMATION_START + 212;
const ANIMATION_MINE: usize = ANIMATION_START + 223;
const ANIMATION_WINDOWBG: usize = ANIMATION_START + 253;
const ANIMATION_BADGUYSCREEN: usize = ANIMATION_START + 260;

const OBJECT_START: usize = ANIMATION_START + 6 * 48;
const OBJECT_BOX_GREY: usize = OBJECT_START;
const OBJECT_SPARK_PINK: usize = OBJECT_START + 1;
const OBJECT_SPARK_BLUE: usize = OBJECT_START + 2;
const OBJECT_SPARK_WHITE: usize = OBJECT_START + 3;
const OBJECT_SPARK_GREEN: usize = OBJECT_START + 4;
const OBJECT_ELEVATOR_TOP: usize = OBJECT_START + 5;
const OBJECT_SHOT: usize = OBJECT_START + 6;
const OBJECT_BOOT: usize = OBJECT_START + 10;
const OBJECT_ROCKET: usize = OBJECT_START + 11;
const OBJECT_CLAMP: usize = OBJECT_START + 18;
const OBJECT_DUSTCLOUD: usize = OBJECT_START + 19;
const OBJECT_FIRERIGHT: usize = OBJECT_START + 24;
const OBJECT_FIRELEFT: usize = OBJECT_START + 29;
const OBJECT_STEAM: usize = OBJECT_START + 34;
const OBJECT_HOSTILESHOT: usize = OBJECT_START + 39;
const OBJECT_GUN: usize = OBJECT_START + 43;
const OBJECT_CHICKEN_SINGLE: usize = OBJECT_START + 44;
const OBJECT_CHICKEN_DOUBLE: usize = OBJECT_START + 45;
const OBJECT_ELECTRIC_ARC: usize = OBJECT_START + 50;
const OBJECT_ELECTRIC_ARC_HURTING: usize = OBJECT_START + 54;
const OBJECT_FOOTBALL: usize = OBJECT_START + 58;
const OBJECT_JOYSTICK: usize = OBJECT_START + 59;
const OBJECT_DISK: usize = OBJECT_START + 60;
const OBJECT_HEALTH: usize = OBJECT_START + 61;
const OBJECT_NONHEALTH: usize = OBJECT_START + 62;
const OBJECT_GLOVE: usize = OBJECT_START + 63;
const OBJECT_LASERBEAM: usize = OBJECT_START + 65;
const OBJECT_ACCESS_CARD: usize = OBJECT_START + 64;
const OBJECT_BALLOON: usize = OBJECT_START + 69;
const OBJECT_NUCLEARMOLECULE: usize = OBJECT_START + 74;
const OBJECT_FALLINGBLOCK: usize = OBJECT_START + 83;
const OBJECT_POINT: usize = OBJECT_START + 85;
const OBJECT_ROTATINGCYLINDER: usize = OBJECT_START + 90;
const OBJECT_SPIKE: usize = OBJECT_START + 95;
const OBJECT_FLAG: usize = OBJECT_START + 97;
const OBJECT_BOX_BLUE: usize = OBJECT_START + 100;
const OBJECT_BOX_RED: usize = OBJECT_START + 101;
const OBJECT_RADIO: usize = OBJECT_START + 102;
const OBJECT_ACCESS_CARD_SLOT: usize = OBJECT_START + 105;
const OBJECT_GLOVE_SLOT: usize = OBJECT_START + 114;
const OBJECT_LETTER_D: usize = OBJECT_START + 118;
const OBJECT_LETTER_U: usize = OBJECT_START + 119;
const OBJECT_LETTER_K: usize = OBJECT_START + 120;
const OBJECT_LETTER_E: usize = OBJECT_START + 121;
const OBJECT_NOTEBOOK: usize = OBJECT_START + 123;
const OBJECT_KEY_RED: usize = OBJECT_START + 124;
const OBJECT_KEY_GREEN: usize = OBJECT_START + 125;
const OBJECT_KEY_BLUE: usize = OBJECT_START + 126;
const OBJECT_KEY_PINK: usize = OBJECT_START + 127;
const OBJECT_DOOR: usize = OBJECT_START + 128;
const OBJECT_KEYHOLE_BLACK: usize = OBJECT_START + 136;
const OBJECT_KEYHOLE_RED: usize = OBJECT_START + 137;
const OBJECT_KEYHOLE_GREEN: usize = OBJECT_START + 138;
const OBJECT_KEYHOLE_BLUE: usize = OBJECT_START + 139;
const OBJECT_KEYHOLE_PINK: usize = OBJECT_START + 140;
const OBJECT_SPIKES_UP: usize = OBJECT_START + 148;
const OBJECT_SPIKES_DOWN: usize = OBJECT_START + 149;

const HERO_START: usize = OBJECT_START + 150;

const HERO_NUM_WALKING: usize = 4;
const HERO_WALKING_LEFT: usize = HERO_START;
const HERO_WALKING_RIGHT: usize = HERO_START + 0x10;
const HERO_NUM_JUMPING: usize = 1;
const HERO_JUMPING_LEFT: usize = HERO_START + 0x20;
const HERO_JUMPING_RIGHT: usize = HERO_START + 0x24;
const HERO_NUM_FALLING: usize = 1;
const HERO_FALLING_LEFT: usize = HERO_START + 0x28;
const HERO_FALLING_RIGHT: usize = HERO_START + 0x2C;
const HERO_NUM_STANDING: usize = 1;
const HERO_STANDING_LEFT: usize = HERO_START + 0x30;
const HERO_STANDING_RIGHT: usize = HERO_START + 0x34;
const HERO_JUMPING_LEFT_SOMERSAULT: usize = HERO_START + 0x38;
const HERO_JUMPING_RIGHT_SOMERSAULT: usize = HERO_START + 0x54;
const HERO_SKELETON_LEFT: usize = HERO_START + 0xB0;
const HERO_SKELETON_RIGHT: usize = HERO_START + 0xB4;

fn directories() -> directories_next::ProjectDirs {
    directories_next::ProjectDirs::from("", "", "freenukum").unwrap()
}

fn config_dir() -> std::path::PathBuf {
    directories().config_dir().to_path_buf()
}

pub fn data_dir() -> std::path::PathBuf {
    directories().data_dir().to_path_buf()
}

fn collision_bounds_color() -> sdl2::pixels::Color {
    sdl2::pixels::Color::RGB(182, 6, 0)
}

#[derive(Hash, Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub enum HorizontalDirection {
    Left,
    Right,
}

impl HorizontalDirection {
    pub fn opposite(self) -> Self {
        match self {
            HorizontalDirection::Left => HorizontalDirection::Right,
            HorizontalDirection::Right => HorizontalDirection::Left,
        }
    }

    pub fn reverse(&mut self) {
        *self = self.opposite();
    }

    pub fn as_factor_i32(&self) -> i32 {
        match self {
            HorizontalDirection::Left => -1,
            HorizontalDirection::Right => 1,
        }
    }

    pub fn map<T>(&self, left: T, right: T) -> T {
        match self {
            HorizontalDirection::Left => left,
            HorizontalDirection::Right => right,
        }
    }
}

#[derive(Debug, Eq, PartialEq)]
pub enum VerticalDirection {
    Up,
    Down,
}

#[derive(Debug, Eq, PartialEq)]
pub enum UserEvent {
    Timer,
    Redraw,
}
0707010000005D000081A40000000000000000000000015FD8FA6800000DD4000000000000000000000000000000000000002000000000freenukum-0.3.5/src/mainmenu.rsuse super::menu::{Menu, MenuEntry};
use crate::TileProvider;
use anyhow::Result;
use sdl2::{
    event::EventSender, render::WindowCanvas, EventPump, TimerSubsystem,
};
use std::convert::Into;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MainMenuEntry {
    Start,
    Restore,
    Instructions,
    OrderingInfo,
    FullScreenToggle,
    EpisodeChange,
    HighScores,
    Previews,
    ViewUserDemo,
    TitleScreen,
    Credits,
    Quit,
    Invalid,
}

impl From<char> for MainMenuEntry {
    fn from(c: char) -> MainMenuEntry {
        use MainMenuEntry as M;
        match c {
            'S' | 's' => M::Start,
            'R' | 'r' => M::Restore,
            'I' | 'i' => M::Instructions,
            'O' | 'o' => M::OrderingInfo,
            'F' | 'f' => M::FullScreenToggle,
            'E' | 'e' => M::EpisodeChange,
            'H' | 'h' => M::HighScores,
            'P' | 'p' => M::Previews,
            'V' | 'v' => M::ViewUserDemo,
            'T' | 't' => M::TitleScreen,
            'C' | 'c' => M::Credits,
            'Q' | 'q' => M::Quit,
            _ => M::Invalid,
        }
    }
}

impl Into<char> for MainMenuEntry {
    fn into(self) -> char {
        use MainMenuEntry as M;
        match self {
            M::Start => 's',
            M::Restore => 'r',
            M::Instructions => 'i',
            M::OrderingInfo => 'o',
            M::FullScreenToggle => 'f',
            M::EpisodeChange => 'e',
            M::HighScores => 'h',
            M::Previews => 'p',
            M::ViewUserDemo => 'v',
            M::TitleScreen => 't',
            M::Credits => 'c',
            M::Quit => 'q',
            M::Invalid => '\0',
        }
    }
}

impl Into<MenuEntry> for MainMenuEntry {
    fn into(self) -> MenuEntry {
        use MainMenuEntry as M;
        let shortcut: char = self.into();
        let name = match self {
            M::Start => "S)tart a new game",
            M::Restore => "R)estore an old game",
            M::Instructions => "I)nstructions",
            M::OrderingInfo => "O)rdering information",
            M::FullScreenToggle => "F)ullscreen toggle",
            M::EpisodeChange => "E)pisode change",
            M::HighScores => "H)igh score",
            M::Previews => "P)reviews / Main Demo!",
            M::ViewUserDemo => "V)iew user demo",
            M::TitleScreen => "T)itle screen",
            M::Credits => "C)redits",
            M::Quit => "Q)uit to OS",
            M::Invalid => "",
        }
        .to_string();
        MenuEntry { shortcut, name }
    }
}

pub fn mainmenu(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    event_pump: &mut EventPump,
    event_sender: &EventSender,
    timer_subsystem: &TimerSubsystem,
) -> Result<MainMenuEntry> {
    use MainMenuEntry as M;
    let msg = r"
  FREENUKUM MAIN MENU 
  -------------------";

    let mut menu = Menu::new(msg.to_string());
    menu.append(M::Start.into());
    menu.append(M::Restore.into());
    menu.append(M::Instructions.into());
    menu.append(M::OrderingInfo.into());
    menu.append(M::FullScreenToggle.into());
    menu.append(M::EpisodeChange.into());
    menu.append(M::HighScores.into());
    menu.append(M::Previews.into());
    menu.append(M::ViewUserDemo.into());
    menu.append(M::TitleScreen.into());
    menu.append(M::Credits.into());
    menu.append(M::Quit.into());

    Ok(MainMenuEntry::from(menu.get_choice(
        canvas,
        tileprovider,
        event_pump,
        event_sender,
        timer_subsystem,
    )?))
}
0707010000005E000081A40000000000000000000000015FD8FA6800001995000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/menu.rsuse super::messagebox;
use crate::event::{MenuEvent, OnOffTracking, WaitEvent};
use crate::rendering::{CanvasRenderer, Renderer};
use crate::{
    TileProvider, UserEvent, FONT_HEIGHT, FONT_WIDTH, GAME_INTERVAL,
    OBJECT_POINT,
};
use anyhow::{anyhow, Result};
use sdl2::{
    event::EventSender,
    rect::{Point, Rect},
    render::WindowCanvas,
    EventPump, TimerSubsystem,
};
use std::collections::BTreeSet;

pub struct MenuEntry {
    pub shortcut: char,
    pub name: String,
}

pub struct Menu {
    header: String,
    entries: Vec<MenuEntry>,
    current: usize,
    width: usize,
}

impl Menu {
    pub fn new(header: String) -> Self {
        Menu {
            header,
            entries: Vec::new(),
            current: 0usize,
            width: 0usize,
        }
    }

    pub fn append(&mut self, entry: MenuEntry) {
        self.width = std::cmp::max(self.width, entry.name.len());
        self.entries.push(entry);
    }

    pub fn get_choice(
        &mut self,
        canvas: &mut WindowCanvas,
        tileprovider: &dyn TileProvider,
        event_pump: &mut EventPump,
        event_sender: &EventSender,
        timer_subsystem: &TimerSubsystem,
    ) -> Result<char> {
        let texture_creator = canvas.texture_creator();
        let (headercols, headerrows) =
            messagebox::get_information(&self.header);

        let textcols = std::cmp::max(headercols, self.width);

        let contents = format!(
            "{}\n{}\n  {}",
            &self.header,
            " ".repeat(textcols + 2),
            self.entries
                .iter()
                .map(|entry| entry.name.to_string())
                .collect::<Vec<_>>()
                .join("\n  "),
        );

        let messagebox = messagebox::messagebox(
            &contents,
            tileprovider,
            &texture_creator,
        )?;

        let surface = canvas
            .window()
            .surface(event_pump)
            .map_err(|s| anyhow!(s))?;
        let destrect = Rect::from_center(
            Point::new(
                surface.width() as i32 / 2,
                surface.height() as i32 / 2,
            ),
            messagebox.width(),
            messagebox.height(),
        );

        let timer = timer_subsystem.add_timer(
            GAME_INTERVAL,
            Box::new(move || {
                event_sender.push_custom_event(UserEvent::Timer).unwrap();
                GAME_INTERVAL
            }),
        );

        let mut changed = true;
        let mut animationframe = 0;

        let mut next_enabled = BTreeSet::new();
        let mut previous_enabled = BTreeSet::new();

        loop {
            if changed {
                canvas
                    .copy(
                        &messagebox.as_texture(&texture_creator)?,
                        None,
                        destrect,
                    )
                    .map_err(|s| anyhow!(s))?;

                let mut renderer = CanvasRenderer {
                    canvas,
                    texture_creator: &texture_creator,
                    tileprovider,
                };

                let point_pos = Point::new(
                    destrect.left() + (FONT_WIDTH as i32 / 2) * 3,
                    destrect.top()
                        + FONT_HEIGHT as i32
                            * (self.current as i32
                                + headerrows as i32
                                + 2),
                );

                renderer.place_tile(
                    OBJECT_POINT + animationframe,
                    point_pos,
                )?;
                canvas.present();
                changed = false;
            }

            let choice: Option<char> = match MenuEvent::wait(event_pump)? {
                MenuEvent::ChooseCurrentEntry | MenuEvent::ClickMouse => {
                    Some(self.entries[self.current].shortcut)
                }
                MenuEvent::Abort => Some('\0'),
                MenuEvent::NextEntry { context, enabled } => {
                    next_enabled.set_enabled(
                        context,
                        enabled,
                        &mut || {
                            self.current += 1;
                            self.current %= self.entries.len();
                        },
                        &mut || {},
                    );
                    None
                }
                MenuEvent::PreviousEntry { context, enabled } => {
                    previous_enabled.set_enabled(
                        context,
                        enabled,
                        &mut || {
                            if self.current == 0 {
                                self.current = self.entries.len();
                            }
                            self.current -= 1;
                        },
                        &mut || {},
                    );
                    None
                }
                MenuEvent::ChooseShortcutEntry(key) => {
                    let mut choice = None;
                    for entry in self.entries.iter() {
                        if key == entry.shortcut {
                            choice = Some(entry.shortcut);
                        }
                    }
                    choice
                }
                MenuEvent::MoveMouse { x, y } => {
                    let x = x - destrect.x() - FONT_WIDTH as i32 * 3;
                    let y = y
                        - destrect.y()
                        - FONT_HEIGHT as i32 * (headerrows as i32 + 2);

                    if x > 0
                        && x < (FONT_WIDTH * messagebox.width()) as i32
                    {
                        let menuitem = y / FONT_HEIGHT as i32;
                        if menuitem >= 0
                            && menuitem < self.entries.len() as i32
                        {
                            self.current = menuitem as usize;
                        }
                    }

                    None
                }
                MenuEvent::RefreshScreen => {
                    canvas.present();
                    None
                }
                MenuEvent::TimerTriggered => {
                    animationframe += 1;
                    animationframe %= 4;
                    changed = true;
                    None
                }
            };
            if let Some(choice) = choice {
                drop(timer);
                return Ok(choice);
            }
        }
    }
}
0707010000005F000081A40000000000000000000000015FD8FA6800000A8B000000000000000000000000000000000000002200000000freenukum-0.3.5/src/messagebox.rsuse super::text;
use crate::rendering::{CanvasRenderer, MovePositionRenderer, Renderer};
use crate::{
    Result, TileProvider, BORDER_BLUE_BOTTOM, BORDER_BLUE_BOTTOMLEFT,
    BORDER_BLUE_BOTTOMRIGHT, BORDER_BLUE_LEFT, BORDER_BLUE_MIDDLE,
    BORDER_BLUE_RIGHT, BORDER_BLUE_TOP, BORDER_BLUE_TOPLEFT,
    BORDER_BLUE_TOPRIGHT, FONT_HEIGHT, FONT_WIDTH,
};
use anyhow::anyhow;
use sdl2::{
    rect::Point,
    render::{Canvas, TextureCreator},
    surface::Surface,
};

pub fn get_information(text: &str) -> (usize, usize) {
    let mut columns = 0;
    let mut rows = 0;

    for line in text.lines() {
        columns = std::cmp::max(columns, line.len());
        rows += 1;
    }
    (columns, rows)
}

pub fn messagebox<'t, T>(
    text: &str,
    tileprovider: &dyn TileProvider,
    texture_creator: &TextureCreator<T>,
) -> Result<Surface<'t>> {
    let (columns, rows) = get_information(text);

    let surface = Surface::new(
        FONT_WIDTH * (columns as u32 + 2),
        FONT_HEIGHT * (rows as u32 + 2),
        texture_creator.default_pixel_format(),
    )
    .map_err(|s| anyhow!(s))?;

    let mut canvas =
        Canvas::from_surface(surface).map_err(|s| anyhow!(s))?;

    {
        let texture_creator = canvas.texture_creator();
        let mut renderer = CanvasRenderer {
            canvas: &mut canvas,
            texture_creator: &texture_creator,
            tileprovider,
        };

        for row in 0..=rows {
            for col in 0..=columns {
                let tilenr = match (row, col) {
                    (0, 0) => BORDER_BLUE_TOPLEFT,
                    (0, c) if c == columns => BORDER_BLUE_TOPRIGHT,
                    (r, 0) if r == rows => BORDER_BLUE_BOTTOMLEFT,
                    (r, c) if r == rows && c == columns => {
                        BORDER_BLUE_BOTTOMRIGHT
                    }
                    (0, _) => BORDER_BLUE_TOP,
                    (_, 0) => BORDER_BLUE_LEFT,
                    (r, _) if r == rows => BORDER_BLUE_BOTTOM,
                    (_, c) if c == columns => BORDER_BLUE_RIGHT,
                    _ => BORDER_BLUE_MIDDLE,
                };

                let point = Point::new(
                    col as i32 * FONT_WIDTH as i32,
                    row as i32 * FONT_HEIGHT as i32,
                );

                renderer.place_tile(tilenr, point)?;
            }
        }

        let mut move_renderer = MovePositionRenderer {
            offset_x: FONT_WIDTH as i32,
            offset_y: FONT_HEIGHT as i32,
            upstream: &mut renderer,
        };
        text::render(&mut move_renderer, text)?;
    }
    canvas.present();

    let surface = canvas.into_surface();

    Ok(surface)
}
07070100000060000081A40000000000000000000000015FD8FA6800000FDD000000000000000000000000000000000000001F00000000freenukum-0.3.5/src/picture.rsuse super::messagebox::messagebox;
use crate::event::{ConfirmEvent, WaitEvent};
use crate::{
    Result, TileProvider, PICTURE_HEIGHT, PICTURE_WIDTH, WINDOW_HEIGHT,
    WINDOW_WIDTH,
};
use anyhow::anyhow;
use sdl2::{
    pixels::{Color, PixelFormatEnum},
    rect::Rect,
    render::{Canvas, WindowCanvas},
    surface::Surface,
    EventPump,
};
use std::fs::File;
use std::io::Read;

pub fn load<'t>(input: &mut File) -> Result<Surface<'t>> {
    let surface =
        Surface::new(WINDOW_WIDTH, WINDOW_HEIGHT, PixelFormatEnum::RGB888)
            .map_err(|s| anyhow!(s))?;

    const NUM_LOADS: usize = (PICTURE_WIDTH * PICTURE_HEIGHT) as usize;
    let mut buffer = [0u8; NUM_LOADS];

    let mut data =
        [0u8; PICTURE_WIDTH as usize * PICTURE_HEIGHT as usize * 4 * 8];
    let mut index;

    // read blue
    index = 2;
    input.read_exact(&mut buffer)?;
    for item in buffer.iter().take(NUM_LOADS) {
        for j in 0..8 {
            let blue_pixel: u8 = (item >> (7 - j)) & 1;
            data[index] += blue_pixel * 0x54 * 2;
            index += 4;
        }
    }

    // read green
    index = 1;
    input.read_exact(&mut buffer)?;
    for item in buffer.iter().take(NUM_LOADS) {
        for j in 0..8 {
            let green_pixel: u8 = (item >> (7 - j)) & 1;
            data[index] += green_pixel * 0x54 * 2;
            index += 4;
        }
    }

    // read red
    index = 0;
    input.read_exact(&mut buffer)?;
    for item in buffer.iter().take(NUM_LOADS) {
        for j in 0..8 {
            let red_pixel: u8 = (item >> (7 - j)) & 1;
            data[index] += red_pixel * 0x54 * 2;
            index += 4;
        }
    }

    // read brighten, and set pixels opaque
    index = 0;
    input.read_exact(&mut buffer)?;
    for item in buffer.iter().take(NUM_LOADS) {
        for j in 0..8 {
            let bright_pixel: u8 = (item >> (7 - j)) & 1;

            // brighten red
            data[index] += bright_pixel * 0x54;
            index += 1;

            // brighten blue
            data[index] += bright_pixel * 0x54;
            index += 1;

            // brighten green
            data[index] += bright_pixel * 0x54;
            index += 1;

            // set opaque
            data[index] = 0xff;
            index += 1;
        }
    }

    use crate::graphics::SurfaceExt;
    let mut canvas =
        Canvas::from_surface(surface).map_err(|s| anyhow!(s))?;
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.set_data(&data, PICTURE_WIDTH * 8, PICTURE_HEIGHT)?;
    let surface = canvas.into_surface();

    Ok(surface)
}

pub fn show_splash(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    file: &mut File,
    event_pump: &mut EventPump,
) -> Result<()> {
    show_splash_with_message(
        canvas,
        tileprovider,
        file,
        event_pump,
        None,
        0,
        0,
    )
}

pub fn show_splash_with_message(
    canvas: &mut WindowCanvas,
    tileprovider: &dyn TileProvider,
    file: &mut File,
    event_pump: &mut EventPump,
    message: Option<&str>,
    x: i32,
    y: i32,
) -> Result<()> {
    let picture = load(file)?;

    let texture_creator = canvas.texture_creator();

    canvas
        .copy(&picture.as_texture(&texture_creator)?, None, None)
        .map_err(|s| anyhow!(s))?;

    if let Some(message) = message {
        let messagebox =
            messagebox(message, tileprovider, &texture_creator)?;
        let destrect =
            Rect::new(x, y, messagebox.width(), messagebox.height());

        canvas
            .copy(
                &messagebox.as_texture(&texture_creator)?,
                None,
                destrect,
            )
            .map_err(|s| anyhow!(s))?;
    }
    canvas.present();

    loop {
        match ConfirmEvent::wait(event_pump)? {
            ConfirmEvent::Confirmed | ConfirmEvent::Aborted => {
                return Ok(())
            }
            ConfirmEvent::RefreshScreen => {
                canvas.present();
            }
        }
    }
}
07070100000061000081A40000000000000000000000015FD8FA6800000CDF000000000000000000000000000000000000002100000000freenukum-0.3.5/src/rendering.rsuse crate::Result;
use crate::TileProvider;
use anyhow::anyhow;
use sdl2::{
    pixels::Color,
    rect::{Point, Rect},
    render::{Canvas, RenderTarget, TextureCreator},
    surface::Surface,
};

pub type TileIndex = usize;

pub trait Renderer {
    fn place_surface(
        &mut self,
        surface: &Surface,
        rect: Rect,
    ) -> Result<()>;

    fn place_tile(
        &mut self,
        tile: TileIndex,
        destination: Point,
    ) -> Result<()>;
    fn fill_rect(&mut self, rect: Rect, color: Color) -> Result<()>;
    fn fill(&mut self, color: Color) -> Result<()>;
    fn draw_rect(&mut self, rect: Rect, color: Color) -> Result<()>;
}

pub struct MovePositionRenderer<'a> {
    pub offset_x: i32,
    pub offset_y: i32,
    pub upstream: &'a mut dyn Renderer,
}

impl<'a> Renderer for MovePositionRenderer<'a> {
    fn place_surface(
        &mut self,
        surface: &Surface,
        mut rect: Rect,
    ) -> Result<()> {
        rect.offset(self.offset_x, self.offset_y);
        self.upstream.place_surface(surface, rect)
    }

    fn place_tile(
        &mut self,
        tile: TileIndex,
        destination: Point,
    ) -> Result<()> {
        self.upstream.place_tile(
            tile,
            destination.offset(self.offset_x, self.offset_y),
        )
    }

    fn fill_rect(&mut self, mut rect: Rect, color: Color) -> Result<()> {
        rect.offset(self.offset_x, self.offset_y);
        self.upstream.fill_rect(rect, color)
    }

    fn fill(&mut self, color: Color) -> Result<()> {
        self.upstream.fill(color)
    }

    fn draw_rect(&mut self, mut rect: Rect, color: Color) -> Result<()> {
        rect.offset(self.offset_x, self.offset_y);
        self.upstream.draw_rect(rect, color)
    }
}

pub struct CanvasRenderer<'a, RT: RenderTarget, T> {
    pub canvas: &'a mut Canvas<RT>,
    pub texture_creator: &'a TextureCreator<T>,
    pub tileprovider: &'a dyn TileProvider,
}

impl<'a, RT: RenderTarget, T> Renderer for CanvasRenderer<'a, RT, T> {
    fn place_surface(
        &mut self,
        surface: &Surface,
        rect: Rect,
    ) -> Result<()> {
        self.canvas
            .copy(&surface.as_texture(&self.texture_creator)?, None, rect)
            .map_err(|s| anyhow!(s))?;
        Ok(())
    }

    fn place_tile(
        &mut self,
        tile: TileIndex,
        destination: Point,
    ) -> Result<()> {
        let tile = self.tileprovider.get_tile(tile).unwrap();
        let rect = Rect::new(
            destination.x,
            destination.y,
            tile.width(),
            tile.height(),
        );
        self.canvas
            .copy(&tile.as_texture(&self.texture_creator)?, None, rect)
            .map_err(|s| anyhow!(s))?;
        Ok(())
    }

    fn fill_rect(&mut self, rect: Rect, color: Color) -> Result<()> {
        self.canvas.set_draw_color(color);
        self.canvas.fill_rect(rect).map_err(|s| anyhow!(s))?;
        Ok(())
    }

    fn fill(&mut self, color: Color) -> Result<()> {
        self.canvas.set_draw_color(color);
        self.canvas.clear();
        Ok(())
    }

    fn draw_rect(&mut self, rect: Rect, color: Color) -> Result<()> {
        self.canvas.set_draw_color(color);
        self.canvas.draw_rect(rect).map_err(|s| anyhow!(s))?;
        Ok(())
    }
}
07070100000062000081A40000000000000000000000015FD8FA6800000890000000000000000000000000000000000000002000000000freenukum-0.3.5/src/settings.rsuse std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug)]
pub struct Settings {
    pixelsize: u8,
    pub fullscreen: bool,
    pub draw_collision_bounds: bool,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            pixelsize: 1,
            fullscreen: false,
            draw_collision_bounds: false,
        }
    }
}

impl Settings {
    fn savepath() -> PathBuf {
        crate::config_dir().join("settings.toml")
    }

    pub fn load_or_create() -> Settings {
        let path = Self::savepath();
        match std::fs::read_to_string(&path) {
            Ok(s) => match toml::de::from_str(&s) {
                Ok(settings) => settings,
                Err(e) => {
                    eprintln!(
                        "Error reading from settings file {:?}: {:?}",
                        path.to_string_lossy(),
                        e
                    );
                    eprintln!("Using default settings");
                    Settings::default()
                }
            },
            Err(e) => {
                eprintln!(
                    "Error opening settings file {:?}: {:?}",
                    path.to_string_lossy(),
                    e
                );
                eprintln!("Using default settings");
                Settings::default()
            }
        }
    }

    pub fn save(&self) {
        let config_dir = crate::config_dir();
        match std::fs::create_dir_all(&config_dir) {
            Ok(()) => {}
            Err(e) => {
                eprintln!(
                    "Error creating config directory {:?}: {:?}",
                    config_dir.to_string_lossy(),
                    e
                );
                return;
            }
        }

        let savepath = Self::savepath();
        match std::fs::write(
            &savepath,
            toml::ser::to_string(self).unwrap().as_bytes(),
        ) {
            Ok(()) => {}
            Err(e) => {
                eprintln!(
                    "Error saving config file {:?}: {:?}",
                    savepath.to_string_lossy(),
                    e
                );
            }
        }
    }
}
07070100000063000081A40000000000000000000000015FD8FA68000012AF000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/shot.rsuse crate::{
    actor::{ActorAdder, ActorMessageQueue, ActorType, ActorsList},
    hero::HeroData,
    level::{solids::LevelSolids, tiles::LevelTiles},
    rendering::Renderer,
    HorizontalDirection, Result, HALFTILE_WIDTH, LEVELWINDOW_WIDTH,
    OBJECT_SHOT, TILE_HEIGHT, TILE_WIDTH,
};
use sdl2::rect::Rect;

pub type ShotList = Vec<Shot>;

#[derive(Debug)]
pub struct Shot {
    position: Rect,
    pub is_alive: bool,
    pub direction: HorizontalDirection,
    counter: usize,
    countdown: usize,
}

impl Shot {
    pub fn new(x: i32, y: i32, direction: HorizontalDirection) -> Self {
        let w = 4;
        let h = TILE_HEIGHT - 4;
        Shot {
            position: Rect::new(
                x + HALFTILE_WIDTH as i32 - w as i32 / 2
                    + direction.as_factor_i32() * w as i32,
                y + TILE_HEIGHT as i32 - h as i32,
                w,
                h,
            ),
            is_alive: true,
            direction,
            counter: 0,
            countdown: 2,
        }
    }

    /// Returns whether the shot is still alive after acting.
    pub fn act(
        &mut self,
        hero_data: &mut HeroData,
        actors: &mut ActorsList,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        actor_adder: &mut dyn ActorAdder,
        actor_message_queue: &mut ActorMessageQueue,
    ) -> bool {
        self.counter += 1;
        self.counter %= 4;

        if self.countdown == 1 {
            self.is_alive = false;
            self.countdown -= 1;
        }

        let x_start = hero_data.position.geometry.x()
            - TILE_WIDTH as i32 * LEVELWINDOW_WIDTH as i32 / 2;
        let x_end = hero_data.position.geometry.x()
            + hero_data.position.geometry.w as i32
            + TILE_WIDTH as i32 * LEVELWINDOW_WIDTH as i32 / 2;

        if self.countdown >= 2 {
            let distance =
                HALFTILE_WIDTH as i32 * self.direction.as_factor_i32();

            // we only push half of the distance, but do it twice, so that
            // also the intermediate position gets covered, not just the
            // end position.
            self.push(
                hero_data,
                actors,
                solids,
                tiles,
                distance,
                actor_adder,
                actor_message_queue,
            );
            self.push(
                hero_data,
                actors,
                solids,
                tiles,
                distance,
                actor_adder,
                actor_message_queue,
            );

            let x = self.position.x;

            if x < x_start || x > x_end {
                self.countdown = 1;
            }
        }
        self.is_alive
    }

    pub fn render(
        &self,
        renderer: &mut dyn Renderer,
        draw_collision_bounds: bool,
    ) -> Result<()> {
        if self.is_alive {
            let mut destrect = self.position;
            destrect.set_x(
                destrect.x() + destrect.width() as i32 / 2
                    - HALFTILE_WIDTH as i32,
            );
            destrect.set_width(TILE_WIDTH);

            renderer.place_tile(
                OBJECT_SHOT + self.counter,
                destrect.top_left(),
            )?;
            if draw_collision_bounds {
                let color = crate::collision_bounds_color();
                renderer.draw_rect(self.position, color)?;
            }
        }
        Ok(())
    }

    pub fn push(
        &mut self,
        hero_data: &mut HeroData,
        actors: &mut ActorsList,
        solids: &mut LevelSolids,
        tiles: &mut LevelTiles,
        offset: i32,
        actor_adder: &mut dyn ActorAdder,
        actor_message_queue: &mut ActorMessageQueue,
    ) {
        if self.countdown >= 2 {
            self.position.x += offset;
            if self.countdown == 2 {
                if actors.process_shot(
                    self.position,
                    solids,
                    tiles,
                    actor_adder,
                    hero_data,
                    actor_message_queue,
                ) {
                    self.countdown = 1;
                }
            } else {
                self.countdown -= 1;
            }
        }
        if self.countdown >= 2 && solids.collides(self.position) {
            self.countdown = 1;
            actor_adder.add_actor(
                ActorType::Explosion,
                self.position.top_left().offset(
                    self.position.width() as i32 / 2
                        - HALFTILE_WIDTH as i32,
                    0,
                ),
            );
        }
    }

    pub fn set_is_alive(&mut self, is_alive: bool) {
        self.is_alive = is_alive;
    }
}
07070100000064000081A40000000000000000000000015FD8FA680000048E000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/text.rsuse crate::rendering::{MovePositionRenderer, Renderer};
use crate::{
    Result, FONT_ASCII_LOWERCASE, FONT_ASCII_UPPERCASE, FONT_HEIGHT,
    FONT_QUESTIONMARK, FONT_WIDTH,
};
use sdl2::rect::Point;

fn render_letter(renderer: &mut dyn Renderer, letter: char) -> Result<()> {
    let tilenr = match letter {
        c if c >= ' ' && c <= 'Z' => {
            c as usize - ' ' as usize + FONT_ASCII_UPPERCASE
        }
        c if c >= 'a' && c <= 'z' => {
            c as usize - 'a' as usize + FONT_ASCII_LOWERCASE
        }
        _ => FONT_QUESTIONMARK,
    } as usize;
    renderer.place_tile(tilenr, Point::new(0, 0))
}

pub fn render(renderer: &mut dyn Renderer, text: &str) -> Result<()> {
    let mut offset_x = 0;
    let mut offset_y = 0;

    for c in text.chars() {
        if c == '\n' {
            offset_x = 0;
            offset_y += FONT_HEIGHT as i32;
        } else {
            let mut renderer = MovePositionRenderer {
                offset_x,
                offset_y,
                upstream: renderer,
            };
            render_letter(&mut renderer, c)?;
            offset_x += FONT_WIDTH as i32;
        }
    }
    Ok(())
}
07070100000065000081A40000000000000000000000015FD8FA68000009D9000000000000000000000000000000000000001C00000000freenukum-0.3.5/src/tile.rsuse crate::Result;
use anyhow::anyhow;
use sdl2::{
    pixels::{Color, PixelFormatEnum},
    render::Canvas,
    surface::Surface,
};
use std::io::Read;

#[derive(Clone, Copy)]
pub struct TileHeader {
    pub tiles: u8,
    pub width: u8,
    pub height: u8,
}

impl TileHeader {
    pub fn load_from<R: Read>(r: &mut R) -> Result<Self> {
        let mut buf = [0u8; 3];
        r.read_exact(&mut buf)?;
        Ok(TileHeader {
            tiles: buf[0],
            width: buf[1],
            height: buf[2],
        })
    }
}

pub fn load<'t, R: Read>(
    r: &mut R,
    header: TileHeader,
    has_transparency: bool,
) -> Result<Surface<'t>> {
    let width: u32 = header.width as u32 * 8;
    let height: u32 = header.height as u32;

    let surface = Surface::new(width, height, PixelFormatEnum::RGBA8888)
        .map_err(|s| anyhow!(s))?;
    let mut data: Vec<u8> =
        Vec::with_capacity(width as usize * height as usize * 4);

    let mut readbuf = [0u8; 5];

    let num_loads = width as usize * height as usize / 8;

    for _ in 0..num_loads {
        r.read_exact(&mut readbuf)?;

        let opaque_row = readbuf[0];
        let blue_row = readbuf[1];
        let green_row = readbuf[2];
        let red_row = readbuf[3];
        let bright_row = readbuf[4];

        for i in 0..8 {
            let bright_pixel = (bright_row >> (7 - i)) & 1;
            let red_pixel = (red_row >> (7 - i)) & 1;
            let green_pixel = (green_row >> (7 - i)) & 1;
            let blue_pixel = (blue_row >> (7 - i)) & 1;
            let opaque_pixel = if has_transparency {
                (opaque_row >> (7 - i)) & 1
            } else {
                1
            };
            let ugly_yellow = if red_pixel == 1
                && green_pixel == 1
                && blue_pixel == 0
                && bright_pixel == 0
            {
                1
            } else {
                0
            };

            data.push(0x54 * (red_pixel * 2 + bright_pixel));
            data.push(
                0x54 * (green_pixel * 2 + bright_pixel - ugly_yellow),
            );
            data.push(0x54 * (blue_pixel * 2 + bright_pixel));
            data.push(opaque_pixel * 0xff);
        }
    }

    use crate::graphics::SurfaceExt;
    let mut canvas =
        Canvas::from_surface(surface).map_err(|s| anyhow!(s))?;
    canvas.set_draw_color(Color::RGBA(0, 0, 0, 0));
    canvas.clear();
    canvas.set_data(&data, width, height)?;
    let surface = canvas.into_surface();

    Ok(surface)
}
07070100000066000081A40000000000000000000000015FD8FA6800000B58000000000000000000000000000000000000002100000000freenukum-0.3.5/src/tilecache.rsuse super::tile::{self, TileHeader};
use crate::Result;
use crate::TileProvider;
use sdl2::surface::Surface;
use std::fs::File;
use std::io::Read;
use std::path::Path;

pub struct TileCache<'t> {
    tiles: Vec<Surface<'t>>,
}

pub struct FileProperties {
    pub transparent: bool,
    pub name: &'static str,
    pub num_tiles: usize,
}

impl FileProperties {
    fn build(
        transparent: bool,
        name: &'static str,
        num_tiles: usize,
    ) -> FileProperties {
        FileProperties {
            transparent,
            name,
            num_tiles,
        }
    }

    pub fn get_all() -> Vec<Self> {
        let p = Self::build;
        vec![
            p(true, "back0.dn1", 48),
            p(false, "back1.dn1", 48),
            p(false, "back2.dn1", 48),
            p(false, "back3.dn1", 48),
            p(true, "solid0.dn1", 48),
            p(false, "solid1.dn1", 48),
            p(false, "solid2.dn1", 48),
            p(false, "solid3.dn1", 48),
            p(true, "anim0.dn1", 48),
            p(true, "anim1.dn1", 48),
            p(true, "anim2.dn1", 48),
            p(true, "anim3.dn1", 48),
            p(true, "anim4.dn1", 48),
            p(true, "anim5.dn1", 48),
            p(true, "object0.dn1", 50),
            p(true, "object1.dn1", 50),
            p(true, "object2.dn1", 50),
            p(true, "man0.dn1", 48),
            p(true, "man1.dn1", 48),
            p(true, "man2.dn1", 48),
            p(true, "man3.dn1", 48),
            p(true, "man4.dn1", 48),
            p(true, "font1.dn1", 50),
            p(true, "font2.dn1", 50),
            p(true, "border.dn1", 48),
            p(true, "numbers.dn1", 44),
        ]
    }
}

impl<'t> TileProvider for TileCache<'t> {
    fn get_tile(&self, index: usize) -> Option<&Surface> {
        self.tiles.get(index)
    }
}

impl<'t> TileCache<'t> {
    pub fn load_from_path(path: &Path) -> Result<Self> {
        let mut tiles = Vec::new();

        for FileProperties {
            transparent,
            name,
            num_tiles,
        } in FileProperties::get_all().into_iter()
        {
            let path = path.join(name);
            let mut file = File::open(path)?;
            let header = TileHeader::load_from(&mut file)?;
            let num_tiles =
                std::cmp::min(num_tiles, header.tiles as usize);
            tiles.append(&mut Self::load_file(
                &mut file,
                header,
                num_tiles,
                transparent,
            )?);
        }

        Ok(TileCache { tiles })
    }

    fn load_file<R: Read>(
        r: &mut R,
        header: TileHeader,
        num_tiles: usize,
        has_transparency: bool,
    ) -> Result<Vec<Surface<'t>>> {
        let mut tiles = Vec::new();
        for _ in 0..num_tiles {
            tiles.push(tile::load(r, header, has_transparency)?);
        }
        Ok(tiles)
    }
}
07070100000067000081A40000000000000000000000015FD8FA6800000093000000000000000000000000000000000000002400000000freenukum-0.3.5/src/tileprovider.rsuse sdl2::surface::Surface;

pub type TileIndex = usize;

pub trait TileProvider {
    fn get_tile(&self, index: TileIndex) -> Option<&Surface>;
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!940 blocks
openSUSE Build Service is sponsored by