diff --git a/contrib/games/hydracastlelabyrinth/LICENSE b/contrib/games/hydracastlelabyrinth/LICENSE
new file mode 100644
index 0000000000..23cb790338
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+ 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
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/contrib/games/hydracastlelabyrinth/Makefile b/contrib/games/hydracastlelabyrinth/Makefile
new file mode 100644
index 0000000000..8d61d39af2
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/Makefile
@@ -0,0 +1,94 @@
+CC = kos32-gcc
+LD = kos32-ld
+OBJCOPY = kos32-objcopy
+KPACK = kpack
+STRIP = kos32-strip
+
+HCL = hcl
+
+SDK_DIR = $(abspath ../../sdk)
+
+CFLAGS = -c -O2 -std=c11 -mpreferred-stack-boundary=2 -fno-ident -fomit-frame-pointer -fno-stack-check \
+ -fno-stack-protector -mno-stack-arg-probe -fno-exceptions -fno-asynchronous-unwind-tables \
+ -ffast-math -mno-ms-bitfields -march=pentium-mmx \
+ -U__WIN32__ -U_Win32 -U_WIN32 -U__MINGW32__ -UWIN32 -D_KOLIBRI \
+ -D_GNU_SOURCE=1 -Wno-missing-field-initializers -D_SDL -DUSE_SDL=1 -DOGG_MUSIC
+
+LDFLAGS = -static -S -nostdlib -T $(SDK_DIR)/sources/newlib/app.lds -Map=output.map --image-base 0 --subsystem native
+
+INCLUDES = -I$(SDK_DIR)/sources/newlib/libc/include -I$(SDK_DIR)/sources/SDL-1.2.2_newlib/include -I$(SDK_DIR)/sources/SDL_mixer-1.2.12 -Isrc
+LIBPATH = -L$(SDK_DIR)/lib
+
+GAME_OBJS = \
+ src/collision.o \
+ src/effect.o \
+ src/enemy.o \
+ src/game.o \
+ src/hero.o \
+ src/ini.o \
+ src/inventory.o \
+ src/main.o \
+ src/object.o \
+ src/options.o \
+ src/PHL.o \
+ src/platform.o \
+ src/qda.o \
+ src/stagedata.o \
+ src/text.o \
+ src/titlescreen.o \
+ src/weapon.o \
+ src/enemies/batboss.o \
+ src/enemies/bat.o \
+ src/enemies/bee.o \
+ src/enemies/boar.o \
+ src/enemies/boomknight.o \
+ src/enemies/crab.o \
+ src/enemies/devil.o \
+ src/enemies/dodo.o \
+ src/enemies/dog.o \
+ src/enemies/firewheel.o \
+ src/enemies/fish.o \
+ src/enemies/garm.o \
+ src/enemies/gas.o \
+ src/enemies/ghoul.o \
+ src/enemies/golem.o \
+ src/enemies/gyra.o \
+ src/enemies/heads.o \
+ src/enemies/hydra.o \
+ src/enemies/jellyfish.o \
+ src/enemies/knight.o \
+ src/enemies/lolidra.o \
+ src/enemies/pendulum.o \
+ src/enemies/podoboo.o \
+ src/enemies/poisonknight.o \
+ src/enemies/pumpkin.o \
+ src/enemies/seal.o \
+ src/enemies/skeleton.o \
+ src/enemies/skull.o \
+ src/enemies/slime.o \
+ src/enemies/slug.o \
+ src/enemies/thwomp.o \
+ src/enemies/waterjumper.o \
+ src/enemies/wizard.o
+
+SDL_OBJS = src/sdl/audio.o \
+ src/sdl/input.o \
+ src/sdl/graphics.o \
+ src/sdl/system.o \
+ src/sdl/joystick_stub.o
+
+MISC_OBJS = src/misc.o
+
+LIBS = -lSDL_mixer -lvorbis -logg -lSDLn -lsound -lgcc -lc.dll
+
+$(HCL): $(GAME_OBJS) $(SDL_OBJS) $(MISC_OBJS)
+ $(LD) $(LDFLAGS) $(LIBPATH) $(GAME_OBJS) $(SDL_OBJS) $(MISC_OBJS) -o $(HCL) $(LIBS)
+ $(STRIP) -S $(HCL)
+ $(OBJCOPY) $(HCL) -O binary
+ $(KPACK) --nologo $(HCL)
+
+%.o : %.c
+ $(CC) $(CFLAGS) $(INCLUDES) -o $@ $<
+
+clean:
+ rm $(GAME_OBJS) $(SDL_OBJS)
diff --git a/contrib/games/hydracastlelabyrinth/README.md b/contrib/games/hydracastlelabyrinth/README.md
new file mode 100644
index 0000000000..2c47f519be
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/README.md
@@ -0,0 +1,71 @@
+# Hydra Castle Labyrinth
+
+![HCL build status](https://api.travis-ci.org/ptitSeb/hydracastlelabyrinth.png "HCL build status")
+
+This version of Hydra Castle Labyrinth is based on the 3DS port (see below for original notice)
+
+This version use SDL (either 1.2 or 2.0) and build on Linux, OpenPandora, PocketCHIP and ODROID.
+
+You'll need SDL and SDL_mixer to build, either version 1.2 or 2.0.
+The SDL2 version use SDL Renderer and so is hardware accelerated.
+The SDL1.2 version use plain bitmap (no OpenGL or GLES needed).
+
+On Debian and friend, to prepare and build, you can do (if you never build anything before, start with `sudo apt install build-essential git`):
+For SDL1.2
+```
+sudo apt install libsdl-dev libsdl-mixer1.2-dev cmake
+cd ~
+git clone https://github.com/ptitSeb/hydracastlelabyrinth.git
+cd hydracastlelabyrinth
+cmake .
+make
+```
+For SDL2.0
+```
+sudo apt install libsdl2-2.0-dev libsdl2-mixer2.0-dev cmake
+cd ~
+git clone https://github.com/ptitSeb/hydracastlelabyrinth.git
+cd hydracastlelabyrinth
+cmake . -DUSE_SDL2=ON
+make
+```
+
+To hear music, you can optionnaly use timidity, but it will play OGG track by default.
+```
+sudo apt install timidity
+```
+And launch the game with
+```
+./hcl
+```
+To start windowed 640x480 game. You can have fullscreen with `./hcl -f` or `./hcl -d` to have fullscreen at current desktop resolution.
+
+![sreenshot on Pandora](screenshot.png "screenshot on Pandora")
+
+# Web Version
+
+You can play an Emscripten version directly on your browser here: https://ptitseb.github.io/hydracastlelabyrinth/
+
+# Original Notice
+
+
+
+**This work has been done by an anon from /hbg/ on 4chan.org/vg/ and not me!**
+
+
+
+
+I've asked the original author about what license to use and he allowed me to use the GPLv2.
+Therefore consider each file to be licensed under the GPLv2, even if there is no disclaimer inside of each file.
+You will have gotten a copy of the license as part of the git and if not, get a copy from `https://www.gnu.org/licenses/gpl-2.0.html`.
+
+Original author notes:
+Source code for the fan-made port of Hydra Castle Labyrinth for 3DS
+
+Anything related to the PSP and Wii are unfinished.
+
+(Yes, it does look like a 3rd grader programmed this.)
+
+
+The game's originally done by E.Hashimoto (a.k.a. Buster).
+You can download some of his works under this [link](http://hp.vector.co.jp/authors/VA025956/).
diff --git a/contrib/games/hydracastlelabyrinth/compile_flags.txt b/contrib/games/hydracastlelabyrinth/compile_flags.txt
new file mode 100644
index 0000000000..67ce30805a
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/compile_flags.txt
@@ -0,0 +1,2 @@
+-D_SDL
+-D_KOLIBRI
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/icon.png b/contrib/games/hydracastlelabyrinth/icon.png
new file mode 100644
index 0000000000..1d6cf0177d
Binary files /dev/null and b/contrib/games/hydracastlelabyrinth/icon.png differ
diff --git a/contrib/games/hydracastlelabyrinth/src/PHL.c b/contrib/games/hydracastlelabyrinth/src/PHL.c
new file mode 100644
index 0000000000..53ea73e9d1
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/PHL.c
@@ -0,0 +1,85 @@
+#include "PHL.h"
+#include
+#include
+#include "qda.h"
+#include "game.h"
+
+int WHITE, RED, YELLOW;
+
+void PHL_Init()
+{
+ PHL_GraphicsInit();
+ PHL_AudioInit(); // DBG
+
+ #ifdef _3DS
+ Result rc = romfsInit();
+ /*if (rc) {
+ printf("romfsInit: %08lX\n", rc);
+ //while(1){}
+ }
+ else
+ {
+ printf("\nromfs Init Successful!\n");
+ }*/
+ #endif
+
+ WHITE = 0;
+ RED = 1;
+ YELLOW = 2;
+}
+
+void PHL_Deinit()
+{
+ PHL_AudioClose();
+ PHL_GraphicsExit();
+
+ #ifdef _3DS
+ romfsExit();
+ #endif
+}
+
+//Extracts bmps from the bmp.qda archive file
+PHL_Surface PHL_LoadQDA(char* fname)
+{
+ PHL_Surface surf;
+
+ int numofsheets = 29;
+
+ for (int i = 0; i < numofsheets; i++)
+ {
+ if (strcmp(fname, (char*)headers[i].fileName) == 0) { //Match found
+ //printf("\nMatch Found: %s", fname);
+ surf = PHL_LoadBMP(i);
+ i = numofsheets; //End search
+ }
+ }
+
+ return surf;
+}
+
+void PHL_DrawTextBold(char* txt, int dx, int dy, int col)
+{
+ int i, cx, cy;
+
+ for (i = 0; i < strlen(txt); i++)
+ {
+ cx = (txt[i] - 32) * 16;
+ cy = 32 * col;
+
+ while (cx >= 512) {
+ cx -= 512;
+ cy += 16;
+ }
+
+ PHL_DrawSurfacePart(dx + (16 * i), dy, cx, cy, 16, 16, images[imgBoldFont]);
+ }
+}
+
+void PHL_DrawTextBoldCentered(char* txt, int dx, int dy, int col)
+{
+ if (dy < 640 && dy > -16) {
+ int stringW = strlen(txt) * 16;
+
+ PHL_DrawTextBold(txt, dx - (stringW / 2), dy, col);
+ }
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/PHL.h b/contrib/games/hydracastlelabyrinth/src/PHL.h
new file mode 100644
index 0000000000..32466552a2
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/PHL.h
@@ -0,0 +1,56 @@
+/*
+PHL stands for Portable Homebrew Library
+*/
+#ifndef PHL_H
+#define PHL_H
+
+#ifdef _3DS
+ #include "3ds/system.h"
+ #include "3ds/graphics.h"
+ #include "3ds/input.h"
+ #include "3ds/audio.h"
+#endif
+
+#ifdef _WII
+ #include "wii/system.h"
+ #include "wii/graphics.h"
+ #include "wii/input.h"
+ #include "wii/audio.h"
+#endif
+
+#ifdef _PSP
+ #include "psp/system.h"
+ #include "psp/graphics.h"
+ #include "psp/input.h"
+ #include "psp/audio.h"
+#endif
+
+#ifdef _SDL
+ #ifdef _SDL2
+ #include "sdl2/system.h"
+ #include "sdl2/graphics.h"
+ #include "sdl2/input.h"
+ #include "sdl2/audio.h"
+ #else
+ #include "sdl/system.h"
+ #include "sdl/graphics.h"
+ #include "sdl/input.h"
+ #include "sdl/audio.h"
+ #endif
+#endif
+
+
+typedef struct {
+ int x, y, w, h;
+} PHL_Rect;
+
+void PHL_Init();
+void PHL_Deinit();
+
+extern int WHITE, RED, YELLOW;
+
+PHL_Surface PHL_LoadQDA(char* fname);
+void PHL_DrawTextBold(char* txt, int dx, int dy, int col);
+void PHL_DrawTextBoldCentered(char* txt, int dx, int dy, int col);
+
+#endif
diff --git a/contrib/games/hydracastlelabyrinth/src/amigaos.h b/contrib/games/hydracastlelabyrinth/src/amigaos.h
new file mode 100644
index 0000000000..8bb60178e2
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/amigaos.h
@@ -0,0 +1,15 @@
+#include
+#include
+
+inline void littleBigEndian (void *x, int sz) {
+ unsigned char *toConvert = (unsigned char *)(x);
+ unsigned char tmp;
+ for (size_t i = 0; i < sz/2; ++i) {
+ tmp = toConvert[i];
+ toConvert[i] = toConvert[sz - i - 1];
+ toConvert[sz - i - 1] = tmp;
+ }
+}
+
+inline void BE16(uint16_t* w) {littleBigEndian(w, 2);}
+inline void BE32(uint32_t* i) {littleBigEndian(i, 4);}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/collision.c b/contrib/games/hydracastlelabyrinth/src/collision.c
new file mode 100644
index 0000000000..34d4ddb21d
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/collision.c
@@ -0,0 +1,336 @@
+#include "collision.h"
+#include "math.h"
+#include "game.h"
+#include "PHL.h"
+#include "object.h"
+
+int checkMix(Mask r, Mask c);
+int checkRect(Mask r1, Mask r2);
+int checkCircle(Mask c1, Mask c2);
+
+int checkCollision(Mask m1, Mask m2)
+{
+ if (m1.unused != 1 && m2.unused != 1) {
+ if (m1.circle == 0 && m2.circle == 0) {
+ return checkRect(m1, m2);
+ }else if (m1.circle == 1 && m2.circle == 1) {
+ return checkCircle(m1, m2);
+ }else if (m1.circle == 1 && m2.circle == 0) {
+ return checkMix(m2, m1);
+ }else if (m1.circle == 0 && m2.circle == 1) {
+ return checkMix(m1, m2);
+ }
+ }
+
+ return 0;
+}
+
+int checkCollisionXY(Mask m, int x, int y)
+{
+ int result = 0;
+
+ if (m.unused != 1) {
+ if (m.circle == 1) {
+ if (sqrt( pow(x - m.x, 2) + pow(y - m.y, 2) ) <= m.w) {
+ result = 1;
+ }
+ }else{
+ if (x < m.x || x > m.x + m.w || y < m.y || y > m.y + m.h) {
+ }else{
+ result = 1;
+ }
+ }
+ }
+
+ return result;
+}
+
+//Returns 1 or 0 depending on if there is a collision with a type of tile
+int checkTileCollision(int type, Mask m)
+{
+ int result = 0;
+
+ if (m.x < 0) {
+ m.x = 0;
+ }else if (m.x + m.w > 640) {
+ m.x = 640 - m.w;
+ }
+
+ if (m.y < 0) {
+ m.y = 0;
+ }else if (m.y + m.h > 480) {
+ m.y = 480 - m.h;
+ }
+
+ int i;
+ for (i = 0; i < 4; i++) {
+ int tileX = (int)m.x / 40;
+ int tileY = (int)m.y / 40;
+
+ if (i == 1) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ }else if (i == 2) {
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }else if (i == 3) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }
+
+ if (collisionTiles[tileX][tileY] == type) {
+ result = 1;
+ i = 4;
+ }
+ }
+
+ return result;
+}
+
+//Returns a tile's demension. Overkill for a lot of situations.
+PHL_Rect getTileCollision(int type, Mask m)
+{
+ PHL_Rect result;
+ result.x = -1;
+ result.y = -1;
+ result.w = 40;
+ result.h = 40;
+
+ //updateMask();
+
+ if (m.x < 0) {
+ m.x = 0;
+ }else if (m.x + m.w > 640) {
+ m.x = 640 - m.w;
+ }
+
+ if (m.y < 0) {
+ m.y = 0;
+ }else if (m.y + m.h > 480) {
+ m.y = 480 - m.h;
+ }
+
+ //PHL_DrawRect(mask.x, mask.y, mask.w, mask.h, PHL_NewRGB(0x00, 0x00, 0xFF));
+
+ int i;
+ for (i = 0; i < 4; i++) {
+ int tileX = (int)m.x / 40;
+ int tileY = (int)m.y / 40;
+
+ if (i == 1) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ }else if (i == 2) {
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }else if (i == 3) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }
+
+ if (collisionTiles[tileX][tileY] == type) {
+ result.x = tileX * 40;
+ result.y = tileY * 40;
+ i = 4;
+ //PHL_DrawRect(result.x, result.y, 40, 40, PHL_NewRGB(0xFF, 0x00, 0x00));
+ }
+ //PHL_DrawRect(tileX * 40, tileY * 40, 40, 40, PHL_NewRGB(0x00, 0xFF, 0x00));
+ }
+
+ //updateMask();
+ return result;
+}
+
+int checkTileCollisionXY(int type, int x, int y)
+{
+ int result = 0;
+
+ if (x < 0) {
+ x = 0;
+ }else if (x > 640) {
+ x = 640;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }else if (y > 480) {
+ y = 480;
+ }
+
+ int tileX = (int)x / 40;
+ int tileY = (int)y / 40;
+
+ if (collisionTiles[tileX][tileY] == type) {
+ result = 1;
+ }
+
+ return result;
+}
+
+PHL_Rect getTileCollisionXY(int type, int x, int y)
+{
+ PHL_Rect result;
+ result.x = -1;
+ result.y = -1;
+ result.w = 40;
+ result.h = 40;
+
+ if (x < 0) {
+ x = 0;
+ }else if (x > 640) {
+ x = 640;
+ }
+
+ if (y < 0) {
+ y = 0;
+ }else if (y > 480) {
+ y = 480;
+ }
+
+ int tileX = (int)x / 40;
+ int tileY = (int)y / 40;
+
+ if (collisionTiles[tileX][tileY] == type) {
+ result.x = tileX * 40;
+ result.y = tileY * 40;
+ }
+
+ return result;
+}
+
+void PHL_DrawMask(Mask m)
+{
+ if (m.circle == 0) {
+ PHL_DrawRect(m.x, m.y, m.w, m.h, PHL_NewRGB(255, 255, 255));
+ }else if (m.circle == 1) {
+ PHL_DrawRect(m.x - m.w, m.y - m.w, m.w * 2, m.w * 2, PHL_NewRGB(255, 255, 255));
+ }
+}
+
+int checkMix(Mask r, Mask c)
+{
+ int insidex = 0, insidey = 0;
+
+ if (c.x >= r.x && c.x <= r.x + r.w) {
+ insidex = 1;
+ }
+ if (c.y >= r.y && c.y <= r.y + r.h) {
+ insidey = 1;
+ }
+
+ //Check if circle center is inside rectangle
+ if (insidex == 1 && insidey == 1) {
+ }
+ else if (insidex == 1) {
+ if ((c.y < r.y && r.y - c.y <= c.w) ||
+ (c.y > (r.y + r.h) && c.y - (r.y + r.h) <= c.w)) {
+ }else{
+ return 0;
+ }
+ }else if (insidey == 1) {
+ if ((c.x < r.x && r.x - c.x <= c.w) ||
+ (c.x > (r.x + r.w) && c.x - (r.x + r.w) <= c.w)) {
+ }else{
+ return 0;
+ }
+ }else{
+ //Check points
+ if (sqrt( pow(r.x - c.x, 2) + pow(r.y - c.y, 2) ) <= c.w) {
+ }else if (sqrt( pow(r.x + r.w - c.x, 2) + pow(r.y - c.y, 2) ) <= c.w) {
+ }else if (sqrt( pow(r.x - c.x, 2) + pow(r.y + r.h - c.y, 2) ) <= c.w) {
+ }else if (sqrt( pow(r.x + r.w - c.x, 2) + pow( r.y + r.h - c.y, 2) ) <= c.w) {
+ }else{
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int checkRect(Mask r1, Mask r2)
+{
+ if (r1.x > r2.x + r2.w ||
+ r1.x + r1.w < r2.x ||
+ r1.y > r2.y + r2.h ||
+ r1.y + r1.h < r2.y)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+int checkCircle(Mask c1, Mask c2)
+{
+ int maxdis = c1.w + c2.w;
+ int dis = sqrt(pow(c2.x - c1.x, 2) + pow(c2.y - c1.y, 2));
+
+ if (dis <= maxdis) {
+ return 1;
+ }
+
+ return 0;
+}
+
+//Heavier tile collision that omits destroyable blocks
+PHL_Rect getTileCollisionWeapon(int type, Mask m)
+{
+ PHL_Rect result;
+ result.x = -1;
+ result.y = -1;
+ result.w = 40;
+ result.h = 40;
+
+ //updateMask();
+
+ if (m.x < 0) {
+ m.x = 0;
+ }else if (m.x + m.w > 640) {
+ m.x = 640 - m.w;
+ }
+
+ if (m.y < 0) {
+ m.y = 0;
+ }else if (m.y + m.h > 480) {
+ m.y = 480 - m.h;
+ }
+
+ //PHL_DrawRect(mask.x, mask.y, mask.w, mask.h, PHL_NewRGB(0x00, 0x00, 0xFF));
+
+ int i;
+ for (i = 0; i < 4; i++) {
+ int tileX = (int)m.x / 40;
+ int tileY = (int)m.y / 40;
+
+ if (i == 1) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ }else if (i == 2) {
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }else if (i == 3) {
+ tileX = (int)((m.x + m.w - 1) / 40);
+ tileY = (int)((m.y + m.h - 1) / 40);
+ }
+
+ if (collisionTiles[tileX][tileY] == type) {
+ result.x = tileX * 40;
+ result.y = tileY * 40;
+
+ //Check if destroyable block
+ int a;
+ for (a = 0; a < MAX_OBJECTS; a++) {
+ if (objects[a] != NULL) {
+ if (objects[a]->type == 3) {
+ Destroyable* d = objects[a]->data;
+ if (result.x == d->x && result.y == d->y) {
+ result.x = -1;
+ result.y = -1;
+ }
+ }
+ }
+ }
+
+ if (result.x != -1) {
+ i = 4;
+ }
+ }
+ }
+
+ //updateMask();
+ return result;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/collision.h b/contrib/games/hydracastlelabyrinth/src/collision.h
new file mode 100644
index 0000000000..d13fa17cdf
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/collision.h
@@ -0,0 +1,27 @@
+#ifndef COLLISION_H
+#define COLLISION_H
+
+#include "PHL.h"
+
+typedef struct {
+ int circle; //1 if circle, 0 is rectangle
+ int x, y;
+ int w, h; //width is the radius if it's a circle
+ int unused;
+} Mask;
+
+void PHL_DrawMask(Mask m);
+
+int checkCollision(Mask m1, Mask m2);
+
+int checkTileCollision(int type, Mask m);
+PHL_Rect getTileCollision(int type, Mask m);
+
+int checkCollisionXY(Mask m, int x, int y);
+
+int checkTileCollisionXY(int type, int x, int y);
+PHL_Rect getTileCollisionXY(int type, int x, int y);
+
+PHL_Rect getTileCollisionWeapon(int type, Mask m);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/effect.c b/contrib/games/hydracastlelabyrinth/src/effect.c
new file mode 100644
index 0000000000..e375a7c534
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/effect.c
@@ -0,0 +1,384 @@
+#include "effect.h"
+#include "game.h"
+#include "PHL.h"
+#include "hero.h"
+#include "collision.h"
+#include
+#include
+
+void createEffect(int type, int x, int y)
+{
+ createEffectExtra(type, x, y, 0, 0, 0);
+}
+
+void createEffectExtra(int t, int x, int y, double hsp, double vsp, int val)
+{
+ int i;
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] == NULL) {
+ Effect* e = malloc(sizeof *e);
+
+ e->id = i;
+ e->type = t;
+
+ e->x = x;
+ e->y = y;
+
+ e->vsp = vsp;
+ e->hsp = hsp;
+ e->grav = 0;
+
+ e->imageIndex = 0;
+ e->imageSpeed = 0;
+
+ e->cropx = 0;
+ e->cropy = 0;
+
+ e->width = 40;
+ e->height = 40;
+
+ e->image = imgMisc20;
+ e->timer = 60;
+
+
+ e->visible = 1;
+ e->val1 = 0;
+
+ e->loop = 0;
+ e->frames = 0;
+
+ e->depth = 1;
+
+ //Sword collision
+ if (e->type == 1) {
+ e->cropx = 440;
+ e->cropy = 40;
+ e->imageSpeed = 0.25;
+ e->timer = 19;
+ }
+
+ //Enemy poof
+ else if (e->type == 2) {
+ PHL_PlaySound(sounds[sndBom01], CHN_EFFECTS);
+ e->width = 64;
+ e->height = 64;
+ e->imageSpeed = 0.33;
+ e->timer = 30;
+ e->image = imgMisc32;
+ }
+
+ //Dust after landing from a fall - left/right
+ else if (e->type == 3) {
+ e->cropx = 320;
+ e->cropy = 80;
+ e->hsp = -1;
+ if (hsp > 0) {
+ e->hsp = 1;
+ e->cropx = 0;
+ }
+ e->imageSpeed = 0.33;
+ e->timer = 8 * (1 / e->imageSpeed);
+ }
+
+ //Block destroy/debris
+ else if (e->type == 4) {
+ e->grav = 0.2;
+ e->loop = 1;
+ e->frames = 4;
+ e->timer = 60;
+
+ //Set flash offset
+ if ((e->hsp > 0 && val == 0) || (e->hsp > 0 && val == 1)) {
+ e->timer -= 1;
+ }
+
+ e->imageSpeed = 0.34;
+
+ int size = (rand() % 2) + 1;
+
+ e->cropx = 0;
+ if (e->hsp < 0) {
+ e->cropx = 160;
+ }
+ if (size == 1) { //Big
+ e->cropy = 40;
+ }else{ //Small
+ e->cropy = 440;
+ }
+
+ }
+
+ //Chest sparkle
+ else if (e->type == 5) {
+ /*e->x -= 20;
+ e->y -= 20;
+ e->x += -20 + (rand() % 40) + 1;
+ e->y += -20 + (rand() % 40) + 1;
+ */
+ e->x -= 20;
+ e->y -= 20;
+ e->cropx = 440;
+ e->cropy = 120;
+ e->imageSpeed = 0.3;
+ e->timer = 16;
+ e->depth = 0;
+ }
+
+ //Charge orbs
+ else if (e->type == 6) {
+ e->x -= 20;
+ e->y -= 20;
+ e->cropx = 0;
+ e->cropy = 200;
+
+ e->val1 = (rand() % 360) + 1;
+ e->imageSpeed = 0.3;
+ e->timer = 20;
+ e->depth = 0;
+ }
+
+ //Poison bubble
+ else if (e->type == 7) {
+ PHL_PlaySound(sounds[sndPi02], CHN_EFFECTS);
+ e->x -= 30;
+ e->x += (rand() % 20) + 1;
+ e->cropx = 280;
+ e->cropy = 120;
+ e->timer = 35;
+
+ e->vsp = -2;
+ e->imageIndex = 0;
+ e->imageSpeed = 0.16;
+ e->depth = 0;
+ }
+
+ //Stone break free
+ else if (e->type == 8) {
+ e->image = imgMisc32;
+ e->cropy = 64;
+ e->width = 64;
+ e->height = 64;
+
+ e->imageSpeed = 0.32;
+ e->timer = 18;
+ }
+
+ //Tiny stone debris
+ else if (e->type == 9) {
+ e->x -= 20;
+ e->y -= 20;
+ e->image = imgMisc20;
+ e->cropy = 40;
+ e->cropx = 320 + ((rand() % 3) * 40);
+ e->imageSpeed = 0;
+
+ e->vsp = -2 - (0.25 * (rand() % 8));
+ e->hsp = -1 + (0.25 * (rand() % 8));
+ e->grav = 0.1;
+
+ e->timer = 60;
+ e->depth = 0;
+ }
+
+ //Lava top animation
+ else if (e->type == 10) {
+ e->cropy = 40;
+ e->cropx = 80;
+
+ e->imageSpeed = 0.125;
+ e->depth = -1;
+ e->loop = 1;
+ e->frames = 3;
+ e->timer = 100;
+
+ e->image = imgTiles;
+ }
+
+ //Water top animation
+ else if (e->type == 11) {
+ e->cropy = 40;
+ e->cropx = 240;
+
+ e->imageSpeed = 0.125;
+ e->depth = -1;
+ e->loop = 1;
+ e->frames = 4;
+ e->timer = 100;
+
+ e->image = imgTiles;
+ }
+
+ //Hero Air Bubble
+ else if (e->type == 12) {
+ e->x -= 20;
+ e->val1 = e->x; //Start x
+ e->y -= 20;
+ e->cropx = 440;
+ e->loop = 1;
+ e->frames = 2;
+ e->imageSpeed = 0.2;
+ e->timer = 120;
+ e->vsp = -0.5;
+ e->depth = 0;
+ }
+
+ //Water splash
+ else if (e->type == 13) {
+ e->cropx = 200;
+ e->imageSpeed = 0.1;
+ e->timer = 55;
+ e->grav = 0.1;
+ }
+
+ //Lava splash
+ else if (e->type == 14) {
+ e->cropx = 400;
+ e->cropy = 200;
+ e->imageSpeed = 0.1;
+ e->timer = 55;
+ e->grav = 0.1;
+ }
+
+ effects[i] = e;
+ i = MAX_EFFECTS;
+ }
+ }
+}
+
+void effectStep(Effect* e)
+{
+ e->x += e->hsp;
+ e->y += e->vsp;
+ e->vsp += e->grav;
+ e->imageIndex += e->imageSpeed;
+
+ if (e->loop == 1) {
+ if (e->imageIndex >= e->frames) {
+ e->imageIndex -= e->frames;
+ }
+ }
+
+ if (e->type == 12) { //Hero Air Bubble
+ e->x = e->val1 + 5 * sin((e->timer * 5) * 3.14159 / 180);
+ if (checkTileCollisionXY(4, e->x + 20, e->y + 20) == 0) {
+ e->timer = 0;
+ }
+ }
+
+ if (e->type == 10 || e->type == 11) { //Lava top
+ e->timer = 100;
+ }
+
+ if (e->type == 4 || e->type == 9 || e->type == 12) { //Stone Rubble
+ if (e->timer <= 30 && e->timer % 2 != 0) {
+ e->visible = 0;
+ }else{
+ e->visible = 1;
+ }
+ }
+ else if (e->type == 6) { //Charge orb
+ if (e->timer % 2 == 0) {
+ e->visible = 1;
+ e->x = herox + ((e->timer * 3) * sin(e->val1 * 3.14159 / 180)) - 20;
+ e->y = heroy + ((e->timer * 3) * cos(e->val1 * 3.14159 / 180));
+ }else{
+ e->visible = 0;
+ }
+ }
+
+ e->timer -= 1;
+ if (e->timer <= 0) {
+ effectDestroy(e->id);
+ }
+}
+
+void effectDraw(Effect* e)
+{
+ //if (e->type != 4 || (e->timer > 30 || e->timer % 2 == 0)) {
+ if (e->visible == 1) {
+ if (e->type == 7) { //Poison Bubble
+ int animation[6] = {0, 1, 2, 1, 0, 3};
+ PHL_DrawSurfacePart(e->x, e->y, e->cropx + (e->width * (animation[(int)e->imageIndex])), e->cropy, e->width, e->height, images[e->image]);
+ }else{
+ PHL_DrawSurfacePart(e->x, e->y, e->cropx + (e->width * ((int)e->imageIndex)), e->cropy, e->width, e->height, images[e->image]);
+ }
+ }
+}
+
+void effectDestroy(int id)
+{
+ if (effects[id] != NULL) {
+ free(effects[id]);
+ }
+ effects[id] = NULL;
+}
+
+void createRockSmash(int x, int y)
+{
+ x -= 20;
+
+ int randvsp = (rand() % 3) + 1;
+ createEffectExtra(4, x, y, -1.5, -2 - randvsp, 0);
+
+ randvsp = (rand() % 3) + 1;
+ createEffectExtra(4, x, y, -1, -5 - randvsp, 1);
+
+ randvsp = (rand() % 3) + 1;
+ createEffectExtra(4, x, y, 1.5, -2 - randvsp, 0);
+
+ randvsp = (rand() % 3) + 1;
+ createEffectExtra(4, x, y, 1, -5 - randvsp, 1);
+ PHL_PlaySound(sounds[sndBom02], 2);
+}
+
+void createSplash(int x, int y)
+{
+ double chsp = 0,
+ cvsp = 0;
+
+ x -= 20;
+
+ chsp = -0.25 - ((rand() % 9) * 0.25);
+ cvsp = -1.5 - ((rand() % 9) * 0.25);
+ createEffectExtra(13, x, y, chsp, cvsp, 0);
+
+ chsp = 0.25 + ((rand() % 9) * 0.25);
+ cvsp = -1.5 - ((rand() % 9) * 0.25);
+ createEffectExtra(13, x, y, chsp, cvsp, 0);
+
+ chsp = -0.25 - ((rand() % 9) * 0.25);
+ cvsp = -0.5 - ((rand() % 4) * 0.25);
+ createEffectExtra(13, x, y, chsp, cvsp, 0);
+
+ chsp = 0.25 + ((rand() % 9) * 0.25);
+ cvsp = -0.5 - ((rand() % 4) * 0.25);
+ createEffectExtra(13, x, y, chsp, cvsp, 0);
+
+ PHL_PlaySound(sounds[sndWater01], CHN_EFFECTS);
+}
+
+void createLavaSplash(int x, int y)
+{
+ double chsp = 0,
+ cvsp = 0;
+
+ x -= 20;
+
+ chsp = -0.25 - ((rand() % 9) * 0.25);
+ cvsp = -1.5 - ((rand() % 9) * 0.25);
+ createEffectExtra(14, x, y, chsp, cvsp, 0);
+
+ chsp = 0.25 + ((rand() % 9) * 0.25);
+ cvsp = -1.5 - ((rand() % 9) * 0.25);
+ createEffectExtra(14, x, y, chsp, cvsp, 0);
+
+ chsp = -0.25 - ((rand() % 9) * 0.25);
+ cvsp = -0.5 - ((rand() % 4) * 0.25);
+ createEffectExtra(14, x, y, chsp, cvsp, 0);
+
+ chsp = 0.25 + ((rand() % 9) * 0.25);
+ cvsp = -0.5 - ((rand() % 4) * 0.25);
+ createEffectExtra(14, x, y, chsp, cvsp, 0);
+
+ PHL_PlaySound(sounds[sndShot07], CHN_EFFECTS);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/effect.h b/contrib/games/hydracastlelabyrinth/src/effect.h
new file mode 100644
index 0000000000..afbc630cd1
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/effect.h
@@ -0,0 +1,31 @@
+#ifndef EFFECTS_H
+#define EFFECTS_H
+
+typedef struct {
+ int id, type;
+ double x, y,
+ vsp, hsp, grav,
+ imageIndex, imageSpeed;
+
+ int cropx, cropy;
+ int width, height;
+ int image, timer;
+
+ int visible;
+ int val1;
+ int loop, frames;
+ int depth;
+} Effect;
+
+void createEffect(int type, int x, int y);
+void createEffectExtra(int t, int x, int y, double hsp, double vsp, int val);
+
+void effectStep(Effect* e);
+void effectDraw(Effect* e);
+void effectDestroy(int id);
+
+void createRockSmash(int x, int y);
+void createSplash(int x, int y);
+void createLavaSplash(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/bat.c b/contrib/games/hydracastlelabyrinth/src/enemies/bat.c
new file mode 100644
index 0000000000..90ff12f978
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/bat.c
@@ -0,0 +1,172 @@
+#include "bat.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+#include
+
+void batStep(Bat* b);
+void batDraw(Bat* b);
+
+void createBat(int x, int y, int type)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* result = /*(Enemy*)*/malloc(sizeof *result);
+ Bat* b = /*(Bat*)*/malloc(sizeof *b);
+ b->id = i;
+
+ b->x = b->xstart = x;
+ b->y = b->ystart = y;
+ b->type = type;
+
+ b->imageIndex = 5;
+ b->counter = 0;
+ b->timer = 0;
+ b->state = 0;
+ b->dir = 1;
+
+ result->data = b;
+ result->enemyStep = batStep;
+ result->enemyDraw = batDraw;
+ result->type = 1;
+
+ enemies[i] = result;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void batStep(Bat* b)
+{
+ //Wait
+ if (b->state == 0)
+ {
+ //Animate
+ {
+ b->imageIndex = 5;
+ }
+
+ //wait for hero to get near
+ {
+ if (b->timer <= 0) {
+ Mask area;
+ area.circle = 0;
+ area.unused = 0;
+ area.x = b->xstart - 60;
+ area.y = b->ystart;
+ area.w = 160; area.h = 100;
+
+ if (checkCollisionXY(area, herox, heroy + 20)) {
+ PHL_PlaySound(sounds[sndPi07], CHN_ENEMIES);
+ b->state = 1;
+ b->timer = 270;
+ if (b->type == 1) {
+ b->counter = 1;
+ if (herox < b->x + 20) {
+ b->dir = -1;
+ }else{
+ b->dir = 1;
+ }
+ }
+ }
+ }else{
+ b->timer -= 1;
+ }
+ }
+ }
+ //Fly
+ else if (b->state == 1)
+ {
+ //Animate
+ {
+ b->imageIndex += 0.25;
+ if (b->imageIndex >= 5) {
+ b->imageIndex -= 5;
+ }
+ }
+
+ //Rotation angle
+ {
+ b->timer += 4;
+ if (b->timer >= 360) {
+ b->timer -= 360;
+ }
+ }
+
+ //Movement
+ {
+ b->y = b->ystart + 30 + (30 * sin(b->timer * 3.14159 / 180));
+ //Red bat
+ if (b->type == 1) {
+ b->x += 2 * b->dir;
+ }
+ }
+
+ //Return to perch
+ {
+ if (b->timer == 270) {
+ if (b->type == 1 && b->counter > 0) {
+ b->dir *= -1;
+ b->timer = 270;
+ b->counter -= 1;
+ }else{
+ b->state = 0;
+ b->timer = 70;
+ }
+ }
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 32;
+ mask.h = 28;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ mask.y = b->y;
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, heroMask)) {
+ heroHit(10, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ //Death
+ createEffect(2, b->x - 12, b->y - 6);
+ spawnCollectable(b->x + 20, b->y);
+ enemyDestroy(b->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+}
+
+void batDraw(Bat* b)
+{
+ int cropX = 0,
+ cropY = 120;
+
+ if (b->type == 1) {
+ cropX = 400;
+ cropY = 280;
+ }
+
+ cropX += (int)b->imageIndex * 40;
+
+ PHL_DrawSurfacePart(b->x, b->y - 4, cropX, cropY, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/bat.h b/contrib/games/hydracastlelabyrinth/src/enemies/bat.h
new file mode 100644
index 0000000000..bd42156a90
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/bat.h
@@ -0,0 +1,16 @@
+#ifndef BAT_H
+#define BAT_H
+
+typedef struct {
+ int id;
+ double x, y;
+ int xstart, ystart;
+ int type; //0 = gray | 1 = red
+ int dir;
+ double imageIndex;
+ int counter, timer, state;
+} Bat;
+
+void createBat(int x, int y, int type);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/batboss.c b/contrib/games/hydracastlelabyrinth/src/enemies/batboss.c
new file mode 100644
index 0000000000..8ea408d47e
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/batboss.c
@@ -0,0 +1,319 @@
+#include "batboss.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include "heads.h"
+#include
+#include
+
+int boss2flag = 5;
+
+//void updateBatMask(Batboss* b);
+void batbossStep(Batboss* b);
+void batbossDraw(Batboss* b);
+
+void createBatboss(int x, int y)
+{
+ if (flags[boss2flag] == 0) { //have not beaten boss 2
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss03.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ setBossRoom();
+
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Batboss* b = /*(Batboss*)*/malloc(sizeof *b);
+ b->id = i;
+
+ b->x = x;
+ b->y = y;
+
+ b->vsp = 0;
+ b->hsp = 0;
+ b->grav = 0.1;
+
+ b->imageIndex = 0;
+
+ b->ypos = y;
+ b->rot = 0;
+
+ b->hp = 35;
+
+ b->invincible = 0;
+ b->state = 0;
+ b->timer = 0;
+ b->mode = 0; //0 for flame, 1 for tornado stomp
+
+ /*
+ b->mask.unused = b->mask.circle = 0;
+ b->mask.w = 100;
+ b->mask.h = 68;
+ updateBatMask(b);
+ */
+ //Setup phase
+ b->state = 0;
+ b->hsp = 2;
+ b->ypos = b->y - 24;
+ b->timer = 60;
+
+ e->data = b;
+ e->enemyStep = batbossStep;
+ e->enemyDraw = batbossDraw;
+ e->type = 41;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void batbossStep(Batboss* b)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ //Wing flap
+ if (b->state == 0 || b->state == 1 || b->state == 2 || b->state == 5 || b->state == 6) {
+ b->imageIndex += 0.1;
+ if (b->imageIndex >= 2) {
+ b->imageIndex -= 2;
+ }
+ }
+ //Twister
+ if (b->state == 3 || b->state == 4) {
+ b->imageIndex += 0.2;
+ if (b->imageIndex >= 5) {
+ b->imageIndex -= 3;
+ }
+ }
+ }
+
+ //Counters
+ {
+ if (b->timer > 0) {
+ b->timer -= 1;
+ }
+
+ if (b->invincible > 0) {
+ b->invincible -= 1;
+ }
+ }
+
+ //Large vertical movement
+ {
+ if (b->state == 0 || b->state == 1) {
+ b->rot += 2;
+ if (b->rot >= 360) { b->rot -= 360; }
+
+ b->y = b->ypos - (40 * sin(b->rot * 3.14159 / 180));
+ }
+ }
+
+ //Small vertical movement
+ {
+ if (b->state == 2) {
+ b->rot += 2;
+ if (b->rot >= 360) { b->rot -= 360; }
+
+ b->y = b->ypos - (20 * sin(b->rot * 3.14159 / 180));
+ }
+ }
+
+ //Horizontal movement
+ if (b->state == 0) {
+ b->x += b->hsp;
+
+ if (b->x >= 520 || b->x <= 120) { //Hit walls
+ b->hsp *= -1;
+ }
+
+ if (b->timer <= 0) {
+ b->state = 1;
+ }
+ }
+ //Slow to halt
+ else if (b->state == 1) {
+ b->x += b->hsp;
+
+ if (b->x >= 520 || b->x <= 120) { //Hit walls
+ b->hsp *= -1;
+ }
+
+ double rate = 0.03;
+ if (b->hsp > 0) {
+ b->hsp -= rate;
+ if (b->hsp <= 0) { b->hsp = 0; }
+ }
+ else if (b->hsp < 0) {
+ b->hsp += rate;
+ if (b->hsp >= 0) { b->hsp = 0; }
+ }
+
+ if (b->hsp == 0 && b->rot <= 2) {
+ b->state = 2;
+ b->timer = 60;
+ }
+ }
+ else if (b->state == 2) {
+ if (b->timer == 1) {
+ //Shoot flame
+ int fx = b->x;
+ int fy = b->y + 24;
+ int fangle = (atan2(heroy - fy, fx - (herox - 20)) * 180 / 3.14159) + 270;
+ createFireball(fx, fy, fangle, b->id);
+ createFireball(fx, fy, fangle - 15, b->id);
+ createFireball(fx, fy, fangle + 15, b->id);
+ PHL_PlaySound(sounds[sndShot03], CHN_ENEMIES);
+ }
+
+ if (b->timer <= 0 && b->rot <= 2) {
+ if (b->mode == 0) {
+ b->state = 0;
+ b->timer = 60;
+ b->hsp = 2;
+ b->mode = 1;
+ }
+ else{
+ b->mode = 0;
+ b->state = 3;
+ b->imageIndex = 2;
+ b->vsp = -4;
+ PHL_PlaySound(sounds[sndShot06], CHN_ENEMIES);
+ }
+
+ if (herox < b->x) {
+ b->hsp *= -1;
+ }
+ }
+ }
+ //Stomp
+ else if (b->state == 3) {
+ b->y += b->vsp;
+ b->vsp += b->grav;
+ if (b->vsp >= 6) { b->vsp = 6; }
+
+ //Hit floor
+ if (b->y >= 480 - 176) {
+ b->y = 480 - 176;
+ b->state = 4;
+ b->timer = 120;
+ quakeTimer = 30;
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+ b->hsp = 1;
+ if (b->x > herox) {
+ b->hsp *= -1;
+ }
+ }
+ }
+ //Chase
+ else if (b->state == 4) {
+ b->x += b->hsp;
+
+ if (b->timer <= 0 || b->x >= 520 || b->x <= 120) {
+ b->state = 5;
+ b->timer = 80 + (rand() % 61);
+ }
+ }
+ //Rise
+ else if (b->state == 5) {
+ b->y -= 1;
+
+ if (b->timer <= 0) {
+ b->state = 0;
+ b->ypos = b->y;
+ b->rot = 0;
+ b->timer = 60;
+
+ b->hsp = 2;
+ if (b->x > herox) {
+ b->hsp *= -1;
+ }
+ }
+ }
+ //Death
+ else if (b->state == 6) {
+ b->y += 0.2;
+
+ if (b->timer % 12 == 0) {
+ createEffect(2, b->x - 64 + (rand() % 100), b->y + (rand() % 80));
+ }
+
+ if (b->timer <= 0) {
+ dead = 1;
+ }
+ }
+
+ if (b->state != 6) {
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ if (b->state == 3 || b->state == 4) {
+ mask.w = 64;
+ mask.h = 96;
+ mask.y = b->y;
+ }else{
+ mask.w = 100;
+ mask.h = 68;
+ mask.y = b->y + 18;
+ }
+ mask.x = b->x - (mask.w / 2);
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(30, b->x);
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ //Hit
+ b->invincible = 15;
+ b->hp -= 1;
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ if (b->hp <= 0) {
+ b->state = 6;
+ b->timer = 180;
+ b->invincible = 200;
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(b->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss2flag] = 1;
+ PHL_StopMusic();
+ }
+ }
+
+}
+
+void batbossDraw(Batboss* b)
+{
+ if (b->invincible % 2 == 0) {
+ PHL_DrawSurfacePart(b->x - 64, b->y, (int)b->imageIndex * 128, 0, 128, 96, images[imgBoss]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/batboss.h b/contrib/games/hydracastlelabyrinth/src/enemies/batboss.h
new file mode 100644
index 0000000000..efac963432
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/batboss.h
@@ -0,0 +1,18 @@
+#ifndef BATBOSS_H
+#define BATBOSS_H
+
+typedef struct {
+ int id;
+ double x, y;
+ double hsp, vsp, grav;
+ double imageIndex;
+ double ypos;
+ double rot;
+ int hp;
+ int state, timer, mode;
+ int invincible;
+} Batboss;
+
+void createBatboss(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/bee.c b/contrib/games/hydracastlelabyrinth/src/enemies/bee.c
new file mode 100644
index 0000000000..1a6206f277
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/bee.c
@@ -0,0 +1,214 @@
+#include "bee.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+void beeStep(Bee* b);
+void beeDraw(Bee* b);
+
+void createBee(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Bee* b = /*(Bee*)*/malloc(sizeof *b);
+ b->id = i;
+
+ b->x = x;
+ b->y = y;
+ b->xstart = b->x;
+ b->ystart = b->y;
+
+ b->hsp = 0;
+ b->vsp = 0;
+
+ b->timer = 0;
+ b->imageIndex = 0;
+ b->dir = 1;
+ b->state = 0;
+
+ b->hoverdir = 180;
+
+ if (dir == 1) {
+ b->hoverdir = 0;
+ b->dir = -1;
+ }
+
+ e->data = b;
+ e->enemyStep = beeStep;
+ e->enemyDraw = beeDraw;
+ e->type = 24;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void beeStep(Bee* b)
+{
+ //Animate
+ {
+ b->imageIndex += 0.33;
+ if (b->imageIndex >= 3) {
+ b->imageIndex -= 3;
+ }
+ }
+
+ //Mindless hovering
+ if (b->state == 0)
+ {
+ b->hoverdir += 2.6;
+ if (b->hoverdir >= 360) {
+ b->hoverdir -= 360;
+ }
+
+ b->dir = 1;
+ if (b->hoverdir <= 180) {
+ b->dir = -1;
+ }
+
+ b->x = b->xstart + (20 * cos(b->hoverdir * 3.14159 /180));
+
+ //If player is within range
+ Mask area;
+ area.unused = area.circle = 0;
+ area.x = b->x - 80;
+ area.y = b->y;
+ area.w = 200;
+ area.h = 100;
+
+ if (checkCollision(area, getHeroMask())) {
+ b->state = 1;
+
+ b->dir = 1;
+ if (b->x + 20 > herox) {
+ b->dir = -1;
+ }
+
+ b->hsp = -5.5 * b->dir;
+
+ PHL_PlaySound(sounds[sndBee01], CHN_ENEMIES);
+ }
+ }
+ //Fly backwards
+ else if (b->state == 1)
+ {
+ b->hsp += 0.25 * b->dir;
+
+ if ((b->dir == 1 && b->hsp >= 0) || (b->dir == -1 && b->hsp <= 0)) {
+ b->hsp = 0;
+ b->state = 2;
+ b->vsp = 3.75;
+ }
+ }
+ //Fly downwards
+ else if (b->state == 2)
+ {
+ b->vsp -= 0.1;
+ if (b->vsp <= 0) {
+ b->state = 3;
+ b->vsp = 0;
+
+ b->dir = 1;
+ if (b->x + 20 > herox) {
+ b->dir = -1;
+ }
+ b->hsp = 3 * b->dir;
+ }
+ }
+ //Fly diaganal
+ else if (b->state == 3)
+ {
+ b->vsp -= 0.1;
+
+ if (b->vsp < -3) {
+ b->vsp = -3;
+ }
+
+ if (b->y <= b->ystart) {
+ b->state = 4;
+
+ b->vsp = 0;
+ b->y = b->ystart;
+
+ if (b->x < b->xstart) {
+ b->dir = 1;
+ }else{
+ b->dir = -1;
+ }
+ b->hsp = b->dir;
+ }
+ }
+ //Fly back to start
+ else if (b->state == 4)
+ {
+ if ((b->dir == 1 && b->x >= b->xstart) || (b->dir == -1 && b->x <= b->xstart)) {
+ b->state = 0;
+ b->hsp = 0;
+
+ b->hoverdir = 0;
+ }
+ }
+
+ //Movement
+ {
+ b->x += b->hsp;
+ b->y += b->vsp;
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 24;
+ mask.h = 32;
+ mask.y = b->y + 6;
+ mask.x = b->x + 14;
+ if (b->dir == -1) {
+ mask.x = b->x + 2;
+ }
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon Collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ createEffect(2, b->x - 12, b->y - 6);
+ spawnCollectable(b->x + 20, b->y);
+ enemyDestroy(b->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+}
+
+void beeDraw(Bee* b)
+{
+ int cropx = 280;
+
+ if (b->dir == -1) {
+ cropx += 120;
+ }
+
+ cropx += (int)b->imageIndex * 40;
+
+ PHL_DrawSurfacePart(b->x, b->y, cropx, 480, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/bee.h b/contrib/games/hydracastlelabyrinth/src/enemies/bee.h
new file mode 100644
index 0000000000..1230f7185e
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/bee.h
@@ -0,0 +1,16 @@
+#ifndef BEE_H
+#define BEE_H
+
+typedef struct {
+ int id;
+ double x, y;
+ int xstart, ystart;
+ double hsp, vsp;
+ double imageIndex;
+ int dir, state, timer;
+ double hoverdir;
+} Bee;
+
+void createBee(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/boar.c b/contrib/games/hydracastlelabyrinth/src/enemies/boar.c
new file mode 100644
index 0000000000..d9582f519b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/boar.c
@@ -0,0 +1,221 @@
+#include "boar.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void boarStep(Boar* b);
+void boarDraw(Boar* b);
+
+void createBoar(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Boar* b = /*(Boar*)*/malloc(sizeof *b);
+
+ b->id = i;
+
+ b->hp = 3;
+
+ b->x = x;
+ b->y = y;
+
+ b->hsp = 0;
+
+ b->imageIndex = 0;
+ b->dir = 1;
+
+ b->blink = 0;
+
+ b->state = 0;
+ b->timer = 0;
+
+ e->data = b;
+ e->enemyStep = boarStep;
+ e->enemyDraw = boarDraw;
+ e->type = 26;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void boarStep(Boar* b)
+{
+ //Setup mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = 32;
+ mask.h = 28;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ mask.y = b->y + 40 - mask.h;
+ }
+
+ //Blink animation
+ {
+ if (b->blink > 0) {
+ b->blink -= 1;
+ }
+ }
+
+ //Patterns
+ {
+ //Dance
+ if (b->state == 0)
+ {
+ //Animate
+ b->imageIndex += 0.15;
+ if (b->imageIndex >= 8) {
+ b->imageIndex -= 8;
+ }
+
+ //if player gets near
+ Mask area;
+ area.unused = area.circle = 0;
+ area.x = b->x - 80;
+ area.y = b->y - 40;
+ area.w = 200;
+ area.h = 80;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ b->state = 1;
+ b->timer = -1;
+ b->imageIndex = 0;
+ b->dir = 1;
+ if (herox < b->x + 20) {
+ b->dir = -1;
+ }
+ }
+ }
+ //Rev up
+ else if (b->state == 1)
+ {
+ b->timer += 1;
+
+ //Play sound
+ if (b->timer % 10 == 0) {
+ PHL_PlaySound(sounds[sndShot01], CHN_ENEMIES);
+ }
+
+ //Create effect
+ if (b->timer % 16 == 0) {
+ if (b->dir == 1) {
+ createEffectExtra(3, b->x + 20 - 30, b->y + 8, -1, 0, 0);
+ }
+ if (b->dir == -1) {
+ createEffectExtra(3, b->x + 20 - 10, b->y + 8, 1, 0, 0);
+ }
+ }
+
+ if (b->timer >= 60) {
+ b->state = 2;
+ b->hsp = 3;
+ }
+ }
+ //Running
+ else if (b->state == 2)
+ {
+ b->x += b->hsp * b->dir;
+ mask.x = b->x + ((40 - mask.w) / 2);
+
+ //Collide with wall
+ if (checkTileCollision(1, mask) == 1) {
+ b->x -= b->hsp * b->dir;
+ b->dir *= -1;
+ }
+
+ //On edge
+ {
+ mask.x = b->x + ((40 - mask.w) / 2);
+
+ mask.x += mask.w * b->dir;
+ mask.y += 1;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x == -1) {
+ b->dir *= -1;
+ }
+
+ mask.y -= 1;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ }
+
+ b->hsp -= 0.05;
+ if (b->hsp <= 0) {
+ b->state = 0;
+ }
+ }
+
+ //Running animation
+ if (b->state == 1 || b->state == 2) {
+ //Animate
+ b->imageIndex += 0.2;
+ if (b->imageIndex >= 2) {
+ b->imageIndex -= 2;
+ }
+ }
+ }
+
+ //Collide with hero
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(30, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ b->hp -= 1;
+ b->blink = 15;
+
+ //Death
+ if (b->hp <= 0) {
+ createEffect(2, b->x - 12, b->y - 12);
+ spawnCollectable(b->x + 20, b->y);
+ enemyDestroy(b->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+}
+
+void boarDraw(Boar* b)
+{
+ if (b->blink % 2 == 0)
+ {
+ int cropx = 0, cropy = 360;
+ int drawx = b->x, drawy = b->y;
+
+ //Dance
+ if (b->state == 0) {
+ int animation[8] = {0, 1, 2, 1, 0, 3, 4, 3};
+ cropx = 160 + (animation[(int)b->imageIndex] * 40);
+ }
+ //Charge
+ else{
+ cropx = (int)b->imageIndex * 40;
+
+ if (b->dir == -1) {
+ cropx += 80;
+ }
+ }
+
+ PHL_DrawSurfacePart(drawx, drawy, cropx, cropy, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/boar.h b/contrib/games/hydracastlelabyrinth/src/enemies/boar.h
new file mode 100644
index 0000000000..8f4a61cc15
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/boar.h
@@ -0,0 +1,18 @@
+#ifndef BOAR_H
+#define BOAR_H
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double hsp;
+ double imageIndex;
+ int blink;
+ int dir;
+ int state;
+ int timer;
+} Boar;
+
+void createBoar(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.c b/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.c
new file mode 100644
index 0000000000..3fc3e83dfb
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.c
@@ -0,0 +1,285 @@
+#include "boomknight.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void boomknightStep(Boomknight* b);
+void boomknightDraw(Boomknight* b);
+
+void boomStep(Boom* b);
+void boomDraw(Boom* b);
+
+void createBoomknight(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Boomknight* b = malloc(sizeof *b);
+ b->id = i;
+
+ b->hp = 2;
+ b->blink = 0;
+
+ b->x = x;
+ b->y = y;
+
+ b->dir = 1;
+ if (herox < b->x + 20) {
+ b->dir = -1;
+ }
+
+ b->imageIndex = 0;
+
+ b->state = 0;
+ b->timer = 0;
+
+ e->data = b;
+ e->enemyStep = boomknightStep;
+ e->enemyDraw = boomknightDraw;
+ e->type = 31;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void boomknightStep(Boomknight* b)
+{
+ //Animate
+ {
+ b->imageIndex += 0.1;
+ if (b->imageIndex >= 2) {
+ b->imageIndex -= 2;
+ }
+
+ if (b->blink > 0) {
+ b->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 30;
+ mask.h = 32;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ mask.y = b->y + (40 - mask.h);
+ }
+
+ //Walk
+ if (b->state == 0) {
+ //Movement
+ {
+ double hsp = 0.5;
+ b->x += hsp * b->dir;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ }
+
+ //Hit wall
+ {
+ if (checkTileCollision(1, mask) == 1) {
+ b->dir *= -1;
+ }
+ }
+
+ //On edge
+ {
+ mask.x += mask.w * b->dir;
+ mask.y += 20;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x == -1) {
+ b->dir *= -1;
+ }
+ }
+
+ //Player is close
+ {
+ if (b->timer <= 0) {
+ Mask area;
+ {
+ area.circle = area.unused = 0;
+ area.w = 120;
+ area.h = 40;
+ area.x = b->x + 20;
+ if (b->dir == -1) {
+ area.x -= area.w;
+ }
+ area.y = b->y;
+ }
+ if (checkCollision(area, getHeroMask()) == 1) {
+ b->state = 1;
+ b->timer = 0;
+ }
+
+ }else{
+ b->timer -= 1;
+ }
+ }
+
+ }
+
+ //Throw
+ else if (b->state == 1) {
+ //Animate
+ {
+ b->imageIndex = 0;
+ if (b->timer >= 15) {
+ b->imageIndex = 2;
+ }
+ }
+
+ b->timer += 1;
+ if (b->timer == 15) {
+ createBoom(b->x, b->y, b->dir);
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ }
+
+ if (b->timer >= 110) {
+ b->state = 0;
+ b->imageIndex = 0;
+ b->timer = 120;
+ }
+ }
+
+ //Update Mask
+ mask.x = b->x + ((40 - mask.w) / 2);
+ mask.y = b->y + (40 - mask.h);
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon Collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ b->hp -= 1;
+ b->blink = 15;
+
+ //Death
+ if (b->hp <= 0) {
+ createEffect(2, b->x - 12, b->y - 6);
+ spawnCollectable(b->x + 20, b->y);
+ enemyDestroy(b->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void boomknightDraw(Boomknight* b)
+{
+ if (b->blink % 2 == 0) {
+ int cropX = 400 + ((int)b->imageIndex * 40);
+
+ if (b->dir == -1) {
+ cropX += 120;
+ }
+
+ PHL_DrawSurfacePart(b->x, b->y, cropX, 400, 40, 40, images[imgEnemies]);
+ }
+}
+
+
+//Enemy boomerang
+void createBoom(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Boom* b = malloc(sizeof *b);
+ b->id = i;
+
+ b->dir = dir;
+ b->x = x;
+ b->y = y;
+
+ b->hsp = 6 * b->dir;
+ b->imageIndex = 0;
+
+ b->timer = 90;
+
+ e->data = b;
+ e->enemyStep = boomStep;
+ e->enemyDraw = boomDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void boomStep(Boom* b)
+{
+ //Animate
+ {
+ b->imageIndex += 0.33;
+ if (b->imageIndex >= 8) {
+ b->imageIndex -= 8;
+ }
+ }
+
+ //Movement
+ {
+ b->x += b->hsp;
+
+ double fric = 0.125;
+ b->hsp -= fric * b->dir;
+ }
+
+ //Hero collision
+ {
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 24;
+ mask.h = 24;
+ mask.x = b->x + ((40 - mask.w) / 2);
+ mask.y = b->y + ((40 - mask.h) / 2);
+ }
+
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(10, mask.x + (mask.w / 2));
+ }
+ }
+
+ b->timer -= 1;
+ if (b->timer <= 0) {
+ createEffectExtra(5, b->x + 20, b->y + 20, 0, 0, 0);
+ enemyDestroy(b->id);
+ }
+}
+
+void boomDraw(Boom* b)
+{
+ int cropX = (int)b->imageIndex * 40;
+
+ if (b->dir == -1) {
+ cropX += 320;
+ }
+
+ PHL_DrawSurfacePart(b->x, b->y, cropX, 360, 40, 40, images[imgMisc20]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.h b/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.h
new file mode 100644
index 0000000000..b93d31f658
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/boomknight.h
@@ -0,0 +1,27 @@
+#ifndef BOOMKNIGHT_H
+#define BOOMKNIGHT_H
+
+typedef struct {
+ int id;
+ int hp;
+ int blink;
+ double x, y;
+ int dir;
+ double imageIndex;
+ int state, timer;
+} Boomknight;
+
+void createBoomknight(int x, int y);
+
+typedef struct {
+ int id;
+ int dir;
+ double x, y;
+ double hsp;
+ double imageIndex;
+ int timer;
+} Boom;
+
+void createBoom(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/crab.c b/contrib/games/hydracastlelabyrinth/src/enemies/crab.c
new file mode 100644
index 0000000000..b12860d8fa
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/crab.c
@@ -0,0 +1,509 @@
+#include "crab.h"
+#include "../PHL.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+int boss3flag = 13;
+
+void crabStep(Crab* c);
+void crabDraw(Crab* c);
+void updateCrabMask(Crab* c);
+void crabDestroy(Crab* c);
+
+void electricityStep(Electricity* e);
+void electricityDraw(Electricity* e);
+
+void createCrab(int x, int y)
+{
+ if (flags[boss3flag] == 0) { //have not beaten boss 3
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss06.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ //Boss start
+ setBossRoom();
+
+ Enemy* e = /*(Enemy*)*/malloc(sizeof(Enemy));
+ Crab* c = /*(Crab*)*/malloc(sizeof(Crab));
+ c->id = i;
+
+ //c->hp = 1;
+ c->hp = 35;
+ c->invincible = 0;
+
+ c->x = x;
+ c->y = y;
+
+ c->vsp = 0;
+ c->hsp = 0;
+
+ c->imageIndex = 0;
+
+ c->state = 0;
+ c->timer = 0;
+ c->counter = 0;
+
+ c->mask.unused = 0;
+ c->mask.circle = 1;
+ c->mask.w = 33;
+ c->mask.h = 33;
+ updateCrabMask(c);
+
+ //Setup phase
+ c->timer = 60;
+
+ e->data = c;
+ e->enemyStep = crabStep;
+ e->enemyDraw = crabDraw;
+ e->type = 42;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void crabStep(Crab* c)
+{
+ char dead = 0;
+ double grav = 0.15;
+
+ if (c->invincible > 0) {
+ c->invincible -= 1;
+ }
+
+ //Wait
+ if (c->state == 0)
+ {
+ c->imageIndex = 0;
+
+ if (c->timer <= 0) {
+ c->timer = 0;
+ if (c->counter == 2 || c->counter == 5) { //Goto roll
+ c->state = 3;
+ if (c->counter == 5) {
+ c->counter = 0;
+ }else{
+ c->counter = 3;
+ }
+ }else if (c->counter == 3) {
+ c->state = 2;
+ }else{
+ c->state = 1; //Goto shoot
+ }
+ }else{
+ c->timer -= 1;
+ }
+ }
+ //Shoot Electric orbs
+ else if (c->state == 1)
+ {
+ //Create orbs
+ if (c->timer == 0) {
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+
+ double angle = (atan2(heroy + 20 - (c->y + 60), c->x - (herox - 20)) * 180 / 3.14159) + 270;
+ createElectricity(c->x, c->y + 60, angle - 45, c->id);
+ createElectricity(c->x, c->y + 60, angle - 22.5, c->id);
+ createElectricity(c->x, c->y + 60, angle, c->id);
+ createElectricity(c->x, c->y + 60, angle + 22.5, c->id);
+ createElectricity(c->x, c->y + 60, angle + 45, c->id);
+ }
+
+ if (c->timer >= 20) {
+ c->state = 2;
+ c->timer = 0;
+ }else{
+ c->timer += 1;
+ }
+ }
+ //Leap
+ else if (c->state == 2)
+ {
+ c->imageIndex = 1;
+
+ //Hopping down or hopping up
+ int hopup = 1;
+ if (c->counter > 2) {
+ hopup = 0;
+ }
+
+ //Jump
+ if (c->timer == 0) {
+ PHL_PlaySound(sounds[sndJump02], CHN_ENEMIES);
+
+ c->vsp = -6.5;
+ if (hopup == 0) {
+ c->vsp = -1.5;
+ }
+ c->timer = 1;
+ }
+
+ //Vertical velocity
+ c->y += c->vsp;
+ c->vsp += grav;
+
+ if (c->vsp >= 6) {
+ c->vsp = 6;
+ }
+
+ //Check if onground
+ if ((hopup == 1 && c->vsp > 0) || (hopup == 0 && c->vsp >= 6)) {
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = 40;
+ area.h = 10;
+ area.x = c->x - (area.w / 2);
+ area.y = c->y + (80 - area.h);
+
+ PHL_Rect collide = getTileCollision(1, area);
+ if (collide.x != -1) {
+ c->y = collide.y - 80;
+ c->state = 0;
+ c->counter += 1;
+ c->timer = 25;
+ if (c->counter == 2 || c->counter == 5) {
+ c->timer = 3;
+ }
+ }
+ }
+ }
+ //Roll hop
+ else if (c->state == 3)
+ {
+ //Animate
+ if (c->hsp > 0) {
+ c->imageIndex += 0.25;
+ }
+ if (c->hsp < 0) {
+ c->imageIndex -= 0.25;
+ }
+ if (c->imageIndex < 2) { c->imageIndex += 4; }
+ if (c->imageIndex >= 6) { c->imageIndex -= 4; }
+
+
+ if (c->timer == 0) {
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+
+ c->timer = 1;
+ c->vsp = -1.5;
+ c->imageIndex = 2;
+ if (c->x > 320) {
+ c->hsp = -8;
+ }else{
+ c->hsp = 8;
+ }
+ }
+
+ //Movement
+ c->y += c->vsp;
+ c->vsp += grav;
+
+ //Check if onground
+ if (c->vsp > 0) {
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = 40;
+ area.h = 10;
+ area.x = c->x - (area.w / 2);
+ area.y = c->y + (80 - area.h);
+
+ PHL_Rect collide = getTileCollision(1, area);
+ if (collide.x != -1) {
+ c->y = collide.y - 80;
+ c->state = 4;
+ }
+ }
+ }
+ //Roll
+ if (c->state == 4)
+ {
+ //Animate
+ if (c->hsp > 0) {
+ c->imageIndex += 0.25;
+ }
+ if (c->hsp < 0) {
+ c->imageIndex -= 0.25;
+ }
+ if (c->imageIndex < 2) { c->imageIndex += 4; }
+ if (c->imageIndex >= 6) { c->imageIndex -= 4; }
+
+ //Movement
+ c->x += c->hsp;
+
+ //Collide with wall
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = area.h = c->mask.w * 2;
+ area.x = c->x - c->mask.w;
+ area.y = c->y + (40 - c->mask.h);
+
+ if (checkTileCollision(1, area) == 1) {
+ c->state = 5;
+ c->timer = 0;
+ }
+ }
+ //Bounce off wall
+ if (c->state == 5)
+ {
+ if (c->timer == 0) {
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+
+ c->timer = 1;
+ c->vsp = -2;
+ c->hsp = 2;
+ if (c->x > 320) {
+ c->hsp *= -1;
+ }
+ }
+
+ c->imageIndex = 1;
+
+ c->x += c->hsp;
+
+ c->y += c->vsp;
+ c->vsp += grav;
+
+ //Check if onground
+ if (c->vsp > 0) {
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = 40;
+ area.h = 10;
+ area.x = c->x - (area.w / 2);
+ area.y = c->y + (80 - area.h);
+
+ PHL_Rect collide = getTileCollision(1, area);
+ if (collide.x != -1) {
+ c->y = collide.y - 80;
+ c->state = 0;
+ c->timer = 65;
+ }
+ }
+ }
+ //Death
+ else if (c->state == 6)
+ {
+ c->imageIndex = 1;
+
+ c->y += 0.2;
+
+ c->timer -= 1;
+
+ if (c->timer % 12 == 0) {
+ createEffect(2, c->x - 64 + (rand() % 100), c->y + (rand() % 80));
+ }
+
+ if (c->timer <= 0) {
+ crabDestroy(c);
+ dead = 1;
+ }
+ }
+
+ if (dead == 0) {
+ if (c->state != 6) {
+ //Update Mask
+ c->mask.x = c->x;
+ c->mask.y = c->y + 40;
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(c->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ c->invincible = 15;
+ c->hp -= 1;
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+ //Hit Player
+ if (checkCollision(c->mask, getHeroMask())) {
+ heroHit(30, c->x);
+ }
+
+ //Die
+ if (c->hp <= 0) {
+ c->state = 6;
+ c->timer = 180;
+ c->invincible = 200;
+ }
+ }
+ }
+}
+
+void crabDraw(Crab* c)
+{
+ if (c->invincible % 2 == 0) {
+ PHL_DrawSurfacePart(c->x - 40, c->y, (int)c->imageIndex * 80, 0, 80, 80, images[imgBoss]);
+ }
+}
+
+void updateCrabMask(Crab* c)
+{
+ c->mask.x = c->x;
+ c->mask.y = c->y + 40;
+}
+
+void crabDestroy(Crab* c)
+{
+ enemyDestroy(c->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss3flag] = 1;
+ PHL_StopMusic();
+}
+
+
+void createElectricity(int x, int y, double angle, int minid)
+{
+ int i;
+ for (i = minid; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Electricity* el = /*(Electricity*)*/malloc(sizeof *el);
+ el->id = i;
+
+ el->x = x;
+ el->y = y;
+
+ //Fix angle
+ if (angle < 0) {
+ angle += 360;
+ }
+ if (angle >= 360) {
+ angle -= 360;
+ }
+
+ el->angle = angle;
+ el->imageIndex = 0;
+
+ el->mask.unused = 0;
+ el->mask.circle = 1;
+ el->mask.w = 16;
+ el->mask.h = 16;
+ el->mask.x = x;
+ el->mask.y = y;
+
+ e->data = el;
+ e->enemyStep = electricityStep;
+ e->enemyDraw = electricityDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ /*
+ int thisid = -1;
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ if (i <= minid) {
+ thisid = i;
+ i = MAX_ENEMIES;
+ }else{
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+
+ if (thisid == -1) {
+ for (i = minid + 1; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ enemies[i] = enemies[minid];
+ Crab* c = enemies[i]->data;
+ c->id = i;
+ thisid = minid;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+
+ if (thisid != -1) {
+ Enemy* e = (Enemy*)malloc(sizeof(Enemy));
+ Electricity* el = (Electricity*)malloc(sizeof(Electricity));
+ el->id = thisid;
+
+ el->x = x;
+ el->y = y;
+
+ //Fix angle
+ if (angle < 0) {
+ angle += 360;
+ }
+ if (angle >= 360) {
+ angle -= 360;
+ }
+
+ el->angle = angle;
+ el->imageIndex = 0;
+
+ el->mask.unused = 0;
+ el->mask.circle = 1;
+ el->mask.w = 16;
+ el->mask.h = 16;
+ el->mask.x = x;
+ el->mask.y = y;
+
+ e->data = el;
+ e->enemyStep = electricityStep;
+ e->enemyDraw = electricityDraw;
+ e->type = -1;
+
+ enemies[thisid] = e;
+ }
+ */
+}
+
+void electricityStep(Electricity* e)
+{
+ double spd = 3;
+ e->x += spd * sin(e->angle * 3.14159 / 180);
+ e->y += spd * cos(e->angle * 3.14159 / 180);
+
+ //Update Mask
+ e->mask.x = e->x;
+ e->mask.y = e->y;
+
+ //Collide with Shield
+ if (checkCollision(shieldMask, e->mask) == 1) {
+ createEffect(1, e->x - 20, e->y - 20);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ enemyDestroy(e->id);
+ }else{
+ //Collide with Hero
+ if (checkCollision(getHeroMask(), e->mask) == 1) {
+ if (heroHit(25, e->x) == 1) {
+ heroStun();
+ }
+ }
+ }
+
+ //Animate
+ e->imageIndex += 0.25;
+ if (e->imageIndex >= 3) {
+ e->imageIndex -= 3;
+ }
+
+ //Outside of screen
+ if (e->x < -20 || e->x > 660 || e->y < -20 || e->y > 500) {
+ enemyDestroy(e->id);
+ }
+}
+
+void electricityDraw(Electricity* e)
+{
+ PHL_DrawSurfacePart(e->x - 20, e->y - 20, 40 + ((int)e->imageIndex * 40), 0, 40, 40, images[imgMisc20]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/crab.h b/contrib/games/hydracastlelabyrinth/src/enemies/crab.h
new file mode 100644
index 0000000000..15e2b593ea
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/crab.h
@@ -0,0 +1,30 @@
+#ifndef CRAB_H
+#define CRAB_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ int hp, invincible;
+ double x, y;
+ double hsp, vsp;
+ double imageIndex;
+ int state, timer, counter;
+
+ Mask mask;
+} Crab;
+
+void createCrab(int x, int y);
+
+typedef struct {
+ int id;
+ double x, y;
+ double angle;
+ double imageIndex;
+
+ Mask mask;
+} Electricity;
+
+void createElectricity(int x, int y, double angle, int minid);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/devil.c b/contrib/games/hydracastlelabyrinth/src/enemies/devil.c
new file mode 100644
index 0000000000..ae22e98b22
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/devil.c
@@ -0,0 +1,458 @@
+#include "devil.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+#include
+
+int boss6flag = 31;
+
+void devilStep(Devil* d);
+void devilDraw(Devil* d);
+
+void orbStep(Orb* o);
+void orbDraw(Orb* o);
+
+void createDevil(int x, int y)
+{
+ if (flags[boss6flag] == 0) { //have not beaten boss 6
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss04.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ setBossRoom();
+
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Devil* d = /*(Devil*)*/malloc(sizeof *d);
+ d->id = i;
+
+ d->x = x;
+ d->y = y;
+
+ d->ystart = d->y;
+ d->newystart = d->ystart;
+ d->hsp = -2.5;
+
+ d->hp = 100;
+ //d->hp = 1;
+
+ d->state = 0;
+ d->timer = 0;
+
+ d->blink = 0;
+ d->boblen = 32;
+ d->bobspd = 3;
+
+ d->tailangle = 90;
+
+ d->rotcounter = 0;
+ d->bobcounter = 0;
+
+ d->bobspd = 3;
+ d->rotspd = 1;
+
+ d->imageIndex = 0;
+
+ e->data = d;
+ e->enemyStep = devilStep;
+ e->enemyDraw = devilDraw;
+ e->type = 45;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void devilStep(Devil* d)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ d->imageIndex += 0.1;
+ if (d->imageIndex >= 2) {
+ d->imageIndex -= 2;
+ }
+
+ if (d->blink > 0) {
+ d->blink -= 1;
+ }
+ }
+
+ //Bob
+ {
+ if (d->state != 4) {
+ d->bobcounter += d->bobspd;
+ if (d->bobcounter >= 360) {
+ d->bobcounter -= 360;
+ }
+
+ d->y = d->ystart + (d->boblen * cos(d->bobcounter * 3.14159 / 180));
+ }
+ }
+
+ //Swing tail
+ {
+ d->rotcounter += d->rotspd;
+ if (d->rotcounter >= 360) {
+ d->rotcounter -= 360;
+ }
+
+ d->tailangle = 90 + (55 * cos(d->rotcounter * 3.14159 / 180));
+ }
+
+ //Patterns
+ {
+ //movement
+ if (d->state == 0 || d->state == 2)
+ {
+ d->rotspd = 1;
+ d->boblen = 32;
+ d->bobspd = 3;
+
+ //Re-align ystart
+ if (d->ystart > d->newystart) {
+ d->ystart -= 1;
+ }
+ if (d->ystart < d->newystart) {
+ d->ystart += 1;
+ }
+
+ d->x += d->hsp;
+
+ //Slow Down
+ double rate = 0.016;
+ if (d->hsp < 0) {
+ d->hsp += rate;
+ if (d->hsp >= 0) {
+ d->hsp = 0;
+ }
+ }
+
+ if (d->hsp > 0) {
+ d->hsp -= rate;
+ if (d->hsp <= 0) {
+ d->hsp = 0;
+ }
+ }
+
+ if (d->hsp == 0) {
+ d->timer = 0;
+ if (d->state == 0) {
+ d->state = 1;
+ }
+
+ if (d->state == 2) {
+ if ((d->rotcounter >= 90 && d->rotcounter <= 90 + d->rotspd) || (d->rotcounter >= 270 && d->rotcounter <= 270 + d->rotspd)) {
+ d->state = 3;
+ }
+ }
+ }
+ }
+ //mid room pause
+ else if (d->state == 1)
+ {
+ d->timer += 1;
+ if (d->timer >= 60) {
+ if (d->state == 1) {
+ d->hsp = 2.5;
+ if (herox < d->x) {
+ d->hsp *= -1;
+ }
+ }
+ d->state = 2;
+ }
+ }
+ //Shoot
+ else if (d->state == 3)
+ {
+ d->rotspd = 3;
+ d->boblen = 10;
+ d->bobspd = 10;
+
+ d->timer += 1;
+
+ //Shoot orbs
+ if (d->timer == 120 || d->timer == 240 || d->timer == 360) {
+ int aim = (atan2((heroy + 20) - d->y, d->x - herox) * 180 / 3.14159) + 270;
+
+ int spawnY = d->y + 20;
+ createOrb(d->x, spawnY, aim + 22);
+ createOrb(d->x, spawnY, aim + 11);
+ createOrb(d->x, spawnY, aim);
+ createOrb(d->x, spawnY, aim - 11);
+ createOrb(d->x, spawnY, aim - 22);
+
+ PHL_PlaySound(sounds[sndShot03], CHN_ENEMIES);
+ }
+
+ if (d->timer == 360) {
+ d->state = 0;
+ d->hsp = 2.5;
+
+ if (d->x > 320) {
+ d->hsp *= -1;
+ }
+
+ int chaseY = heroy - d->ystart;
+ if (chaseY > 52) { chaseY = 52; }
+ if (chaseY < -52) { chaseY = -52; }
+
+ d->newystart = d->ystart + chaseY;
+ }
+ }
+
+ //Death
+ if (d->state == 4) {
+ d->rotspd = 3;
+ d->y += 0.2;
+ d->timer -= 1;
+
+ if (d->timer % 12 == 0) {
+ createEffect(2, d->x - 64 + (rand() % 100), d->y - 64 + (rand() % 80));
+ }
+
+ if (d->timer <= 0) {
+ dead = 1;
+ }
+ }
+ }
+
+ //Collisions
+ if (d->state != 4) {
+ //Setup masks
+ Mask masks[6];
+
+ //Head mask
+ masks[0].unused = masks[0].circle = 0;
+ masks[0].w = 100;
+ masks[0].h = 104;
+ masks[0].x = d->x - (masks[0].w / 2);
+ masks[0].y = d->y - (masks[0].h / 2);
+
+ //Link masks
+ for (int i = 1; i < 5; i++) {
+ int taildis[4] = {54, 80, 108, 134};
+ int taillag[4] = {10, 15, 10, 5};
+
+ double newtailangle = 90 + (55 * cos((d->rotcounter - taillag[i-1]) * 3.14159 / 180));
+
+ masks[i].unused = 0;
+ masks[i].circle = 1;
+ masks[i].w = 16;
+ masks[i].h = 16;
+ masks[i].x = d->x + (taildis[i-1] * cos(newtailangle * 3.14159 / 180));
+ masks[i].y = d->y + (taildis[i-1] * sin(newtailangle * 3.14159 / 180));
+ }
+
+ //Barb mask
+ masks[5].unused = masks[5].circle = 0;
+ masks[5].w = 40;
+ masks[5].h = 40;
+ masks[5].x = (d->x + (160 * cos(d->tailangle * 3.14159 / 180))) - (masks[5].w / 2);
+ masks[5].y = (d->y + (160 * sin(d->tailangle * 3.14159 / 180))) - (masks[5].h / 2);
+
+ //Collisions
+ int hitHead = 0;
+ for (int a = 0; a < 6; a++)
+ {
+ if (a == 0 || a == 5) {
+ //Hit Player
+ if (checkCollision(masks[a], getHeroMask())) {
+
+ int damage = 25;
+ if (a == 0) {
+ damage = 50;
+ }
+
+ if (heroHit(damage, masks[a].x + (masks[a].w / 2)) == 1) {
+ if (a == 5) { //Barb
+ heroStun();
+ }
+ }
+ }
+ }
+
+ //Weapon collision
+ if (hitHead == 0) {
+ for (int i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(masks[a], weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ //Head
+ if (a == 0) {
+ d->hp -= 1;
+ d->blink = 15;
+ hitHead = 1;
+
+ if (d->hp <= 0) {
+ d->state = 4;
+ d->timer = 180;
+ d->blink = 200;
+ }
+ }else{
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Destroy
+ if (dead == 1) {
+ enemyDestroy(d->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss6flag] = 1;
+ PHL_StopMusic();
+ }
+}
+
+void devilDraw(Devil* d)
+{
+ if (d->blink % 2 == 0)
+ {
+ int dx, dy;
+
+ //Draw tail
+ int taildis[4] = {54, 80, 108, 134};
+ int taillag[4] = {10, 15, 10, 5};
+ for (int i = 0; i < 4; i++) {
+ double newtailangle = 90 + (55 * cos((d->rotcounter - taillag[i]) * 3.14159 / 180));
+
+ dx = d->x + (taildis[i] * cos(newtailangle * 3.14159 / 180)) - 32;
+ dy = d->y + (taildis[i] * sin(newtailangle * 3.14159 / 180)) - 32;
+ PHL_DrawSurfacePart(dx, dy, 0, 128, 64, 64, images[imgBoss]);
+ }
+
+ //Draw Head
+ dx = d->x - 64;
+ dy = d->y - 64;
+ PHL_DrawSurfacePart(dx, dy, (int)d->imageIndex * 128, 0, 128, 128, images[imgBoss]);
+
+ //Draw Tail Tip
+ dx = d->x + (160 * cos(d->tailangle * 3.14159 / 180)) - 32;
+ dy = d->y + (160 * sin(d->tailangle * 3.14159 / 180)) - 32;
+ PHL_DrawSurfacePart(dx, dy, 64, 128, 64, 64, images[imgBoss]);
+
+ }
+}
+
+
+//Stone Orbs
+void createOrb(int x, int y, double dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = /*(Enemy*)*/malloc(sizeof *e);
+ Orb* o = /*(Orb*)*/malloc(sizeof *o);
+ o->id = i;
+
+ o->x = x;
+ o->y = y;
+
+ o->dir = dir;
+
+ o->imageIndex = 0;
+
+ e->data = o;
+ e->enemyStep = orbStep;
+ e->enemyDraw = orbDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void orbStep(Orb* o)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ o->imageIndex += 0.33;
+ if (o->imageIndex >= 4) {
+ o->imageIndex -= 4;
+ }
+ }
+
+ //Movement
+ {
+ int spd = 4;
+ o->x += spd * sin(o->dir * 3.14159 / 180);
+ o->y += spd * cos(o->dir * 3.14159 / 180);
+ }
+
+ //Collision
+ {
+ Mask mask;
+ mask.unused = 0;
+ mask.circle = 1;
+ mask.w = 6;
+ mask.x = o->x;
+ mask.y = o->y;
+
+ //Collide with shield
+ /*if (checkCollision(mask, shieldMask)) {
+ createEffect(1, o->x - 20, o->y - 20);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ dead = 1;
+ }else{*/
+ //Hit player
+ if (checkCollision(getHeroMask(), mask)) {
+ heroStone();
+ heroHit(25, mask.x);
+ }
+ //}
+
+ //Collide with weapon
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ createEffect(2, o->x - 32, o->y - 32);
+ dead = 1;
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+ //Destroy if outside of room
+ {
+ if (o->x < -20 || o->x > 660 || o->y < -20 || o->y > 500) {
+ dead = 1;
+ }
+ }
+
+ //Finally erase object
+ {
+ if (dead == 1) {
+ enemyDestroy(o->id);
+ }
+ }
+}
+
+void orbDraw(Orb* o)
+{
+ int animation[4] = {0, 1, 0, 2};
+ PHL_DrawSurfacePart(o->x - 20, o->y - 20, 440 + (animation[(int)o->imageIndex] * 40), 480, 40, 40, images[imgMisc20]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/devil.h b/contrib/games/hydracastlelabyrinth/src/enemies/devil.h
new file mode 100644
index 0000000000..3b6d67d667
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/devil.h
@@ -0,0 +1,28 @@
+#ifndef DEVIL_H
+#define DEVIL_H
+
+typedef struct {
+ int id;
+ double x, y;
+ double ystart, newystart;
+ double hsp;
+ int hp;
+ int state, timer;
+ int blink;
+ int boblen, bobspd;
+ double tailangle, bobcounter, rotcounter, rotspd;
+ double imageIndex;
+} Devil;
+
+void createDevil(int x, int y);
+
+typedef struct {
+ int id;
+ double x, y;
+ double dir;
+ double imageIndex;
+} Orb;
+
+void createOrb(int x, int y, double dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/dodo.c b/contrib/games/hydracastlelabyrinth/src/enemies/dodo.c
new file mode 100644
index 0000000000..0fc26a4357
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/dodo.c
@@ -0,0 +1,587 @@
+#include "dodo.h"
+#include "../game.h"
+#include "../hero.h"
+#include "../PHL.h"
+#include "../enemy.h"
+#include
+
+void dodoStep(Dodo* d);
+void dodoDraw(Dodo* d);
+
+Mask updateDodoMask(Dodo* d, Mask mask);
+int dodoWallCollision(Dodo* d, Mask mask);
+
+int boss1flag = 1;
+
+void createDodo(int x, int y, int flag)
+{
+ char miniboss = 0;
+
+ if (flag != 0) {
+ miniboss = 1;
+ }else{
+ flag = boss1flag;
+ }
+
+ if (flags[flag] == 0) {
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss01.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+
+ if (miniboss == 0) {
+ setBossRoom();
+ }
+
+ Enemy* e = malloc(sizeof *e);
+ Dodo* d = malloc(sizeof *d);
+ d->id = i;
+
+ d->x = x;
+ d->y = y;
+
+ d->vsp = -6;
+ d->hsp = 0;
+ d->grav = 0.2;
+
+ d->onground = 0;
+ d->dir = -1;
+ if (herox > d->x) {
+ d->dir = 1;
+ }
+
+ d->imageIndex = 0;
+
+ d->timer = 0;
+ d->state = 2;
+
+ d->hp = 45;
+ d->blink = 0;
+
+ d->tojump = 1;
+ d->jumptoggle = 0;
+
+ d->flag = flag;
+
+ e->data = d;
+ e->enemyStep = dodoStep;
+ e->enemyDraw = dodoDraw;
+ e->type = 40;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void dodoStep(Dodo* d)
+{
+ char dead = 0;
+
+ //Constants
+ double fric = 0.06;
+
+ //Animation vars
+ double imgspd = 0;
+ int frames = 0;
+
+ //timers
+ {
+ if (d->timer > 0) {
+ d->timer -= 1;
+ }
+
+ if (d->blink > 0) {
+ d->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 56;
+ mask.h = 56;
+ mask = updateDodoMask(d, mask);
+ }
+
+ //Idle
+ if (d->state == 0)
+ {
+ d->hsp = 0;
+ d->vsp = 0;
+
+ //Animate
+ imgspd = 0.1;
+ frames = 4;
+
+ //End state
+ if (d->timer <= 0) {
+ //Go to chase
+ if (d->tojump == 0) {
+ d->state = 1;
+ d->timer = 260;
+ d->tojump = 1;
+ }
+ //Go to windup
+ else {
+ d->state = 3;
+ d->timer = 30;
+ }
+ }
+
+ //Fall
+ {
+ if (d->onground == 0) {
+ d->state = 6;
+ d->imageIndex = 1;
+ }
+ }
+ }
+
+ //Chase
+ else if (d->state == 1)
+ {
+ //Animate
+ imgspd = 0.2;
+ frames = 4;
+
+ //Chase
+ if ( (d->dir == -1 && herox < d->x) || (d->dir == 1 && herox > d->x) ) {
+ d->hsp += (fric / 2) * d->dir;
+
+ //limit speed
+ if (d->hsp > 3) {
+ d->hsp = 3;
+ }
+ if (d->hsp < -3) {
+ d->hsp = -3;
+ }
+ }
+
+ //Turn around
+ else{
+ d->hsp -= fric * d->dir;
+
+ //Done slowing down
+ if ( (d->dir == 1 && d->hsp <= 0) || (d->dir == -1 && d->hsp >= 0) ) {
+ d->hsp = 0;
+ d->state = 4;
+ d->imageIndex = 0;
+ }
+ }
+
+ //Stop running
+ {
+ if (d->timer <= 0) {
+ if (d->hsp >= 1 || d->hsp <= -1) {
+ d->state = 5;
+ }
+ }
+ }
+
+ //Fall
+ {
+ if (d->onground == 0) {
+ d->state = 6;
+ d->imageIndex = 1;
+ }
+ }
+
+ }
+
+ //Turn around
+ else if (d->state == 4)
+ {
+ //Animate
+ imgspd = 0;
+ d->imageIndex += 0.2;
+
+ //Done turning around
+ if (d->imageIndex >= 3) {
+ d->dir *= -1;
+ d->state = 1;
+ d->imageIndex = 0;
+ }
+
+ //Fall
+ {
+ if (d->onground == 0) {
+ d->state = 6;
+ d->imageIndex = 1;
+ }
+ }
+ }
+
+ //Jump
+ if (d->state == 2)
+ {
+ //Set image
+ imgspd = 0;
+ {
+ //Fall
+ d->imageIndex = 0;
+
+ //Jump
+ if (d->vsp < 0) {
+ d->imageIndex = 1;
+ }
+ }
+
+ //Face hsp
+ {
+ if (d->hsp > 0) {
+ d->dir = 1;
+ }
+ if (d->hsp < 0) {
+ d->dir = -1;
+ }
+ }
+
+ //Land
+ {
+ if (d->onground == 1) {
+ d->state = 5;
+ d->tojump = 0;
+
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+ quakeTimer = 30;
+ createEffectExtra(3, d->x - 30, d->y + 50, -1, 0, 0);
+ createEffectExtra(3, d->x - 10, d->y + 50, 1, 0, 0);
+ }
+ }
+
+ }
+
+ //Windup
+ if (d->state == 3)
+ {
+ //Set image
+ imgspd = 0;
+ d->imageIndex = 0;
+
+ //Jump
+ if (d->timer <= 0) {
+ d->state = 2;
+ PHL_PlaySound(sounds[sndJump01], CHN_ENEMIES);
+ if (d->jumptoggle == 0) {
+ d->jumptoggle = 1;
+ d->vsp = -3;
+ d->hsp = 2 * d->dir;
+ }else{
+ d->jumptoggle = 0;
+ d->hsp = 1.5 * d->dir;
+ d->vsp = -6;
+ }
+ }
+ }
+
+ //Slide to a stop
+ else if (d->state == 5)
+ {
+ //Friction
+ {
+ if (d->hsp > 0) {
+ d->hsp -= fric;
+ if (d->hsp <= 0) {
+ d->hsp = 0;
+ }
+ }
+ else if (d->hsp < 0) {
+ d->hsp += fric;
+ if (d->hsp >= 0) {
+ d->hsp = 0;
+ }
+ }
+ }
+
+ //Go to idle
+ {
+ if (d->hsp == 0) {
+ d->state = 0;
+ d->timer = 140;
+ }
+ }
+
+ }
+
+ //Fall
+ if (d->state == 6)
+ {
+ //Set image
+ imgspd = 0;
+ d->imageIndex = 0;
+
+ //Face hsp
+ {
+ if (d->hsp > 0) {
+ d->dir = 1;
+ }
+ if (d->hsp < 0) {
+ d->dir = -1;
+ }
+ }
+
+ //Land
+ {
+ if (d->onground == 1) {
+ d->state = 5;
+ d->tojump = 1;
+ }
+ }
+
+ }
+
+ //Death
+ if (d->state == 7)
+ {
+ imgspd = 0.2;
+ frames = 4;
+
+ //Movement
+ d->y += 0.2;
+
+ if (d->blink % 12 == 0) {
+ createEffect(2, d->x - 72 + (rand() % 80), d->y - 12 + (rand() % 76));
+ }
+
+ if (d->blink <= 0) {
+ dead = 1;
+ }
+ }
+
+ else{
+ //Horizontal movement
+ {
+ if (d->hsp != 0) {
+ d->x += d->hsp;
+
+ //Wall collision
+ if (d->state != 6) {
+ if (dodoWallCollision(d, mask) == 1) {
+ d->hsp *= -1;
+ if (d->state == 1) {
+ d->state = 5;
+ }
+ }
+ }
+
+ }
+ }
+
+ //Vertical movement
+ {
+ if (d->vsp != 0) {
+ d->y += d->vsp;
+
+ mask = updateDodoMask(d, mask);
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ if (d->vsp < 0) {
+ d->y = collide.y + 40 - (96 - 14 - mask.h);
+ }
+ else if (d->vsp > 0) {
+ d->y = collide.y - 96 + 14;
+ }
+ }
+ }
+ }
+
+ //Check if onground
+ mask = updateDodoMask(d, mask);
+ mask.y += 1;
+ if (!checkTileCollision(1, mask)) {
+ d->onground = 0;
+ }else{
+ d->onground = 1;
+ }
+ mask.y -= 1;
+
+ //Gravity
+ {
+ if (d->onground == 0) {
+ d->vsp += d->grav;
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ //Hit
+ d->blink = 15;
+ d->hp -= 1;
+
+ //Death
+ if (d->hp <= 0) {
+ d->state = 7;
+ d->blink = 180;
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(30, d->x);
+ }
+ }
+ }
+
+ //Animate
+ {
+ if (imgspd != 0) {
+ d->imageIndex += imgspd;
+
+ if (d->imageIndex >= frames) {
+ d->imageIndex -= frames;
+ }
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ //Is the level 1 boss
+ if (d->flag == boss1flag) {
+ bossDefeatedFlag = 1;
+ PHL_StopMusic();
+ }
+
+ roomSecret = 1;
+ flags[d->flag] = 1;
+ enemyDestroy(d->id);
+ }
+ }
+
+}
+
+void dodoDraw(Dodo* d)
+{
+ //PHL_DrawMask(d->mask);
+ if (d->blink % 2 == 0) {
+ int cropX = 0,
+ cropY = 0;
+
+ int dirW = 0;
+
+ //Idle
+ if (d->state == 0) {
+ dirW = 0;
+ int frame = 0;
+
+ if (d->dir == 1) {
+ int animation[4] = {0, 6, 7, 6};
+ frame = animation[(int)d->imageIndex];
+ }else{
+ int animation[4] = {3, 8, 9, 8};
+ frame = animation[(int)d->imageIndex];
+ }
+
+ cropX = frame * 96;
+
+ while (cropX >= 576) {
+ cropX -= 576;
+ cropY += 96;
+ }
+ }
+
+ //Chase
+ else if (d->state == 1 || d->state == 7) {
+ dirW = 288;
+ int animation[4] = {0, 1, 0, 2};
+
+ cropX = animation[(int)d->imageIndex] * 96;
+ }
+
+ //Jump
+ else if (d->state == 2) {
+ dirW = 192;
+
+ cropY = 192;
+ cropX = (int)d->imageIndex * 96;
+ }
+
+ //Turn around
+ else if (d->state == 4) {
+ dirW = 0;
+ cropY = 288;
+
+ if (d->dir == 1) {
+ int animation[3] = {0, 1, 2};
+ cropX = animation[(int)d->imageIndex] * 96;
+ }else{
+ int animation[3] = {2, 1, 0};
+ cropX = animation[(int)d->imageIndex] * 96;
+ }
+ }
+
+ //Duck
+ else if (d->state == 3 || d->state == 5 || d->state == 6) {
+ dirW = 192;
+
+ cropX = 0;
+ cropY = 192;
+ }
+
+ //Direction offset
+ {
+ if (dirW != 0 && d->dir == -1) {
+ cropX += dirW;
+ }
+ }
+
+ PHL_DrawSurfacePart(d->x - 48, d->y, cropX, cropY, 96, 96, images[imgBoss]);
+ }
+}
+
+Mask updateDodoMask(Dodo* d, Mask mask)
+{
+ mask.x = d->x - (mask.w / 2);
+ mask.y = d->y + (96 - 14 - mask.h);
+
+ return mask;
+}
+
+int dodoWallCollision(Dodo* d, Mask mask)
+{
+ int result = 0;
+
+ mask = updateDodoMask(d, mask);
+
+ //Stay inside of room
+ if (d->x < 24) {
+ result = 1;
+ d->x = 24;
+ }
+ else if (d->x > 616) {
+ result = 1;
+ d->x = 616;
+ }
+ else{
+ //Tile collision
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ result = 1;
+ if (d->hsp > 0) {
+ d->x = collide.x - (mask.w / 2);
+ }else{
+ d->x = collide.x + 40 + (mask.w / 2);
+ }
+ }
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/dodo.h b/contrib/games/hydracastlelabyrinth/src/enemies/dodo.h
new file mode 100644
index 0000000000..8797116266
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/dodo.h
@@ -0,0 +1,22 @@
+#ifndef DODO_H
+#define DODO_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double vsp, hsp, grav;
+ int dir, onground;
+ double imageIndex;
+ int state, timer, hp;
+ int blink;
+ int tojump, jumptoggle;
+ int flag;
+
+ //Mask mask;
+} Dodo;
+
+void createDodo(int x, int y, int flag);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/dog.c b/contrib/games/hydracastlelabyrinth/src/enemies/dog.c
new file mode 100644
index 0000000000..6d0783a3ea
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/dog.c
@@ -0,0 +1,294 @@
+#include "dog.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void dogStep(Dog* d);
+void dogDraw(Dog* d);
+
+int hitWall(Dog* d, Mask mask);
+
+void createDog(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Dog* d = malloc(sizeof *d);
+ d->id = i;
+ d->hp = 3;
+ d->blink = 0;
+
+ d->x = x;
+ d->y = y;
+
+ d->hsp = 0;
+ d->vsp = 0;
+
+ d->imageIndex = 0;
+
+ d->dir = 1;
+ if (herox < d->x) {
+ d->dir = -1;
+ }
+
+ d->state = 0;
+ d->timer = 0;
+ d->counter = 0;
+
+ e->data = d;
+ e->enemyStep = dogStep;
+ e->enemyDraw = dogDraw;
+ e->type = 30;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void dogStep(Dog* d)
+{
+ double grav = 0.175;
+
+ char onground = 0;
+ char wallhit = 0;
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 32;
+ mask.h = 32;
+ mask.x = d->x + ((40 - mask.w) / 2);
+ mask.y = d->y + (40 - mask.h);
+ }
+
+ //Blink animation
+ {
+ if (d->blink > 0) {
+ d->blink -= 1;
+ }
+ }
+
+ //Horizontal movement
+ {
+ d->x += d->hsp;
+ mask.x = d->x + ((40 - mask.w) / 2);
+
+ //Wall collision
+ if (hitWall(d, mask) == 1) {
+ wallhit = 1;
+ mask.x = d->x + ((40 - mask.w) / 2);
+ }
+ }
+
+ //Vertical Movement
+ {
+ d->vsp += grav;
+ d->y += d->vsp;
+ mask.y = d->y + (40 - mask.h);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x != -1) {
+ //Floor
+ if (d->vsp >= 0) {
+ onground = 1;
+ d->vsp = 0;
+ d->y = collide.y - 40;
+ }
+ //Ceiling
+ if (d->vsp < 0) {
+ d->y = collide.y + 40 - (40 - mask.h);
+ }
+ mask.y = d->y + (40 - mask.h);
+ }
+ }
+
+ //Wait
+ if (d->state == 0)
+ {
+ double fric = 0.1;
+
+ //Animate
+ {
+ d->imageIndex += 0.1;
+ if (d->imageIndex >= 2) {
+ d->imageIndex -= 2;
+ }
+ }
+
+ //Collide with wall
+ {
+ if (wallhit == 1 && onground == 1) {
+ d->hsp *= -1;
+ }
+ }
+
+ //Slide to hault
+ if (d->hsp > 0) {
+ d->dir = 1;
+
+ d->hsp -= fric;
+ if (d->hsp <= 0) {
+ d->hsp = 0;
+ }
+ }
+ if (d->hsp < 0) {
+ d->dir = -1;
+
+ d->hsp += fric;
+ if (d->hsp >= 0) {
+ d->hsp = 0;
+ }
+ }
+
+ //Player is close
+ {
+ if (d->hsp == 0) {
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = 220;
+ area.h = 60;
+ area.x = d->x - 90;
+ area.y = d->y - 20;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ d->state = 1;
+ d->counter = 0;
+ d->vsp = 1;
+ }
+ }
+ }
+
+ }
+
+ //Hopping
+ else if (d->state == 1)
+ {
+ int spd = 2;
+
+ d->hsp = spd * d->dir;
+
+ //Land on floor
+ {
+ if (onground == 1) {
+
+ //Landed
+ d->counter += 1;
+ d->vsp = -1.5;
+ if (d->counter == 3) {
+ d->vsp = -4;
+ }
+ if (d->counter == 4) {
+ d->state = 0;
+ d->counter = 0;
+ d->vsp = 0;
+ d->hsp = spd * d->dir;
+ }else{
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ d->dir = 1;
+ if (herox < d->x + 20) {
+ d->dir = -1;
+ }
+ }
+ }
+ }
+
+ //Animate
+ {
+ d->imageIndex = 1;
+ if (d->vsp < 0) {
+ d->imageIndex = 2;
+ }
+ }
+
+ }
+
+ //Update mask to be safe
+ mask.x = d->x + ((40 - mask.w) / 2);
+ mask.y = d->y + (40 - mask.h);
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ if (heroHit(10, mask.x + (mask.w / 2)) == 1) {
+ heroStun();
+ }
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ //Hit
+ d->blink = 15;
+ d->hp -= 1;
+
+ //Death
+ if (d->hp <= 0) {
+ createEffect(2, d->x - 12, d->y - 6);
+ spawnCollectable(d->x + 20, d->y);
+ enemyDestroy(d->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void dogDraw(Dog* d)
+{
+ if (d->blink % 2 == 0) {
+ int cropX = 240 + ((int)d->imageIndex * 40);
+
+ if (d->dir == -1) {
+ cropX += 120;
+ }
+
+ PHL_DrawSurfacePart(d->x, d->y, cropX, 40, 40, 40, images[imgEnemies]);
+ }
+}
+
+int hitWall(Dog* d, Mask mask)
+{
+ PHL_Rect collide = getTileCollision(1, mask);
+
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x != -1) {
+ int dir = 1;
+ if (d->hsp < 0) {
+ dir = -1;
+ }
+ d->x = collide.x + 20 - ((20 + (mask.w / 2)) * dir) - 20;
+
+ return 1;
+ }else{
+ if (d->x < -20) {
+ d->x = -20;
+ return 1;
+ }
+
+ if (d->x > 620) {
+ d->x = 620;
+ return 1;
+ }
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/dog.h b/contrib/games/hydracastlelabyrinth/src/enemies/dog.h
new file mode 100644
index 0000000000..ee93ce72d7
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/dog.h
@@ -0,0 +1,17 @@
+#ifndef DOG_H
+#define DOG_H
+
+typedef struct {
+ int id;
+ int hp;
+ int blink;
+ double x, y;
+ double vsp, hsp;
+ double imageIndex;
+ int dir;
+ int state, timer, counter;
+} Dog;
+
+void createDog(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.c b/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.c
new file mode 100644
index 0000000000..7368761512
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.c
@@ -0,0 +1,272 @@
+#include "firewheel.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+
+void firewheelRotate(Firewheel* f, int clockwise);
+
+void firewheelStep(Firewheel* f);
+void firewheelDraw(Firewheel* f);
+
+void createFirewheel(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Firewheel* f = malloc(sizeof *f);
+
+ f->id = i;
+
+ f->x = x;
+ f->y = y;
+
+ f->imageIndex = 0;
+
+ f->hp = 2;
+ f->blink = 0;
+
+ f->hsp = 1;
+ f->vsp = 0;
+
+ f->wallx = 0;
+ f->wally = 1;
+
+ f->timer = 0;
+ if (x % 40 != 0) {
+ f->timer = 20;
+ }
+
+ //Start on ceiling
+ {
+ Mask mask;
+ mask.circle = mask.unused = 0;
+ mask.w = 40;
+ mask.h = 40;
+ mask.x = f->x;
+ mask.y = f->y + 10;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x == -1) {
+ f->wally = -1;
+ f->hsp *= -1;
+ }
+ }
+
+ f->dir = 1;
+ if (dir == 1) {
+ f->hsp *= -1;
+ f->dir = -1;
+ }
+
+ e->data = f;
+ e->enemyStep = firewheelStep;
+ e->enemyDraw = firewheelDraw;
+ e->type = 27;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void firewheelStep(Firewheel* f)
+{
+ //Animate
+ {
+ f->imageIndex += 0.33;
+ if (f->imageIndex >= 4) {
+ f->imageIndex -= 4;
+ }
+
+ if (f->blink > 0) {
+ f->blink -= 1;
+ }
+ }
+
+ //Movement
+ int spd = 2;
+ f->x += spd * f->hsp;
+ f->y += spd * f->vsp;
+
+ //Setup mask
+ Mask mask;
+ mask.circle = mask.unused = 0;
+ mask.w = 40;
+ mask.h = 40;
+ mask.x = f->x;
+ mask.y = f->y;
+
+ //Check if ready to change angle
+ if ( (f->hsp != 0 && (int)f->x % 20 == 0) || (f->vsp != 0 && (int)f->y % 20 == 0) )
+ {
+ int doCheck = 1;
+ while (doCheck == 1) {
+ doCheck = 0;
+
+ //Check on edge
+ mask.x += (f->wallx * 10);
+ mask.y += (f->wally * 10);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ //Outside of room
+ if (f->y <= -40) {
+ collide.x = 1;
+ }
+
+ //On edge
+ if (collide.x == -1) {
+ int tempHsp = f->hsp;
+ int tempVsp = f->vsp;
+ f->hsp = f->wallx;
+ f->vsp = f->wally;
+
+ f->wallx = -tempHsp;
+ f->wally = -tempVsp;
+ doCheck = 1;
+ }
+ //Hit wall
+ else {
+ mask.x = f->x;
+ mask.y = f->y;
+ mask.x += f->hsp * 10;
+ mask.y += f->vsp * 10;
+
+ collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ //Outside of room
+ if (collide.x == -1) {
+ if (f->y <= -40 && f->vsp != 1) {
+ collide.x = 1;
+ }
+ }
+
+ //Did collide with wall
+ if (collide.x != -1) {
+ int tempWallx = f->wallx;
+ int tempWally = f->wally;
+ f->wallx = f->hsp;
+ f->wally = f->vsp;
+
+ f->hsp = -tempWallx;
+ f->vsp = -tempWally;
+
+ doCheck = 1;
+ }
+
+ }
+ }
+
+ /*
+ mask.x += f->hsp * 10;
+ mask.y += f->vsp * 10;
+
+ //Collide with wall
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ //Outside of room
+ if (collide.x == -1) {
+ if (mask.y <= 0 && f->vsp < 0) {
+ collide.x = f->x;
+ collide.y = -40;
+ collide.w = 40;
+ collide.h = 40;
+ }
+ }
+
+ //Did collide with wall
+ if (collide.x != -1) {
+ int tempWallx = f->wallx;
+ int tempWally = f->wally;
+ f->wallx = f->hsp;
+ f->wally = f->vsp;
+
+ f->hsp = -tempWallx;
+ f->vsp = -tempWally;
+ }
+ //Edge rotate
+ else{
+ mask.x = f->x;
+ mask.y = f->y;
+ mask.x += (f->wallx * 10);
+ mask.y += (f->wally * 10);
+
+ collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x == -1) {
+ int tempHsp = f->hsp;
+ int tempVsp = f->vsp;
+ f->hsp = f->wallx;
+ f->vsp = f->wally;
+
+ f->wallx = -tempHsp;
+ f->wally = -tempVsp;
+ }
+ }
+*/
+ }
+
+ //Update Mask
+ mask.w = 30;
+ mask.h = 30;
+ mask.x = f->x + 5;
+ mask.y = f->y + 5;
+
+ //Collide with hero
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(20, mask.x + (mask.w / 2));
+ }
+
+ //Weapon collision
+ for (int i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ f->hp -= 1;
+ f->blink = 15;
+
+ //Death
+ if (f->hp <= 0) {
+ createEffect(2, f->x - 12, f->y - 12);
+ spawnCollectable(f->x + 20, f->y);
+ enemyDestroy(f->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+}
+
+void firewheelDraw(Firewheel* f)
+{
+ if (f->blink % 2 == 0) {
+ int cy = 80;
+ if (f->dir == -1) {
+ cy += 40;
+ }
+
+ PHL_DrawSurfacePart(f->x, f->y, 480 + ((int)f->imageIndex * 40), cy, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.h b/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.h
new file mode 100644
index 0000000000..7a5c6e4c4f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/firewheel.h
@@ -0,0 +1,18 @@
+#ifndef FIREWHEEL_H
+#define FIREWHEEL_H
+
+typedef struct {
+ int id;
+ double x, y;
+ double imageIndex;
+ int hp;
+ int blink;
+ int dir;
+ int hsp, vsp;
+ int wallx, wally;
+ int timer;
+} Firewheel;
+
+void createFirewheel(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/fish.c b/contrib/games/hydracastlelabyrinth/src/enemies/fish.c
new file mode 100644
index 0000000000..54a99710f2
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/fish.c
@@ -0,0 +1,139 @@
+#include "fish.h"
+#include "../game.h"
+#include "../enemy.h"
+#include "../PHL.h"
+#include "../collision.h"
+#include "../hero.h"
+#include
+
+void createFish(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Fish* f = malloc(sizeof *f);
+ f->id = i;
+
+ f->x = f->xstart = x;
+ f->y = y;
+
+ f->imageIndex = 0;
+
+ f->spd = 1;
+
+ f->turning = 0;
+ f->dir = 1;
+ if (dir == 1) {
+ f->dir = -1;
+ f->spd = -1;
+ }
+
+ f->mask.circle = f->mask.unused = 0;
+ f->mask.x = x + 3;
+ f->mask.y = y + 6;
+ f->mask.w = 34;
+ f->mask.h = 32;
+
+ e->data = f;
+ e->enemyStep = fishStep;
+ e->enemyDraw = fishDraw;
+ e->type = 13;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void fishStep(Fish* f)
+{
+ double fric = 0.02;
+
+ f->x += f->spd;
+ f->mask.x = f->x + 3;
+
+ if (f->turning == 0) {
+ f->imageIndex += 0.1;
+ if (f->imageIndex >= 2) {
+ f->imageIndex -= 2;
+ }
+ }else{
+ f->imageIndex += 0.25;
+ if (f->imageIndex >= 3) {
+ f->turning = 0;
+ }
+ }
+
+ if (f->dir == 1) {
+ if (f->x > f->xstart + 25) {
+ f->spd -= fric;
+
+ if (f->spd < 0) {
+ f->dir = -1;
+ f->turning = 1;
+ f->imageIndex = 0;
+ }
+ }else{
+ f->spd += fric;
+ if (f->spd > 1) {
+ f->spd = 1;
+ }
+ }
+ }else if (f->dir == -1) {
+ if (f->x < f->xstart - 25) {
+ f->spd += fric;
+
+ if (f->spd > 0) {
+ f->dir = 1;
+ f->turning = 1;
+ f->imageIndex = 0;
+ }
+ }else{
+ f->spd -= fric;
+ if (f->spd < -1) {
+ f->spd = -1;
+ }
+ }
+ }
+
+ if (checkCollision(f->mask, getHeroMask())) {
+ heroHit(15, f->x + 20);
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(f->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ createEffect(2, f->x - 12, f->y - 12);
+ spawnCollectable(f->x + 20, f->y);
+ enemyDestroy(f->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+}
+
+void fishDraw(Fish* f)
+{
+ int thisImage = 0;
+ if (f->turning == 1) {
+ if (f->dir == -1) {
+ int animation[3] = {4, 6, 5};
+ thisImage = animation[(int)f->imageIndex];
+ }else{
+ int animation[3] = {5, 6, 4};
+ thisImage = animation[(int)f->imageIndex];
+ }
+ }else{
+ thisImage = f->imageIndex;
+ if (f->spd < 0) {
+ thisImage += 2;
+ }
+ }
+
+ PHL_DrawSurfacePart(f->x, f->y, 360 + (thisImage * 40), 360, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/fish.h b/contrib/games/hydracastlelabyrinth/src/enemies/fish.h
new file mode 100644
index 0000000000..9648bcd7a8
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/fish.h
@@ -0,0 +1,24 @@
+#ifndef FISH_H
+#define FISH_H
+
+#include "../enemy.h"
+#include "../collision.h"
+
+typedef struct {
+ int id;
+
+ double x, y;
+ int xstart;
+ double imageIndex;
+ double spd;
+ int dir, turning;
+
+ Mask mask;
+} Fish;
+
+void createFish(int x, int y, int dir);
+
+void fishStep(Fish* f);
+void fishDraw(Fish* f);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/garm.c b/contrib/games/hydracastlelabyrinth/src/enemies/garm.c
new file mode 100644
index 0000000000..b9c59ecb51
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/garm.c
@@ -0,0 +1,578 @@
+#include "garm.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+
+int boss7flag = 47;
+
+void garmStep(Garm* g);
+void garmDraw(Garm* g);
+
+void garmrockStep(Garmrock* g);
+void garmrockDraw(Garmrock* g);
+
+void createGarm(int x, int y)
+{
+ if (flags[boss7flag] == 0) { //have not beaten boss 7
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss07.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ setBossRoom();
+
+ Enemy* e = malloc(sizeof *e);
+ Garm* g = malloc(sizeof *g);
+
+ g->id = i;
+
+ g->hp = 105;
+ //g->hp = 1;
+
+ g->x = x;
+ g->y = y;
+
+ g->hsp = 0;
+ g->vsp = 0;
+
+ g->dir = -1;
+
+ g->imageIndex = 0;
+
+ g->state = 0;
+ g->timer = 0;
+
+ g->blink = 0;
+
+ g->substate = 0;
+ g->wallcounter = 0;
+ g->targx = 0;
+
+ e->data = g;
+ e->enemyStep = garmStep;
+ e->enemyDraw = garmDraw;
+ e->type = 46;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void garmStep(Garm* g)
+{
+ char dead = 0;
+
+ //Blink animation
+ {
+ if (g->blink > 0) {
+ g->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 88;
+ mask.h = 104;
+ mask.x = g->x - (mask.w / 2);
+ mask.y = g->y + (120 - mask.h);
+ }
+
+ //Stand still
+ if (g->state == 0)
+ {
+ //Animate
+ {
+ g->imageIndex += 0.0625;
+ if (g->imageIndex >= 2) {
+ g->imageIndex -= 2;
+ }
+ }
+
+ //End state
+ {
+ g->timer += 1;
+ if (g->timer >= 60) {
+ g->state = 1;
+ //g->vsp = -4.5;
+ g->counter = 0;
+ g->timer = 0;
+ //PHL_PlaySound(sounds[sndPi09], CHN_ENEMIES);
+ }
+ }
+ }
+
+ //Bounce
+ else if (g->state == 1)
+ {
+ //Animate
+ {
+ g->imageIndex += 0.33;
+ if (g->imageIndex >= 3) {
+ g->imageIndex -= 3;
+ }
+ }
+
+ if (g->timer > 0) {
+ g->vsp = 0;
+ g->imageIndex = 0;
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ //End state
+ if (g->counter >= 3) {
+ g->state = 2;
+ g->counter = 0;
+ g->imageIndex = 0;
+ g->vsp = -6;
+ g->hsp = 8;
+ if (g->x > herox) {
+ g->hsp *= -1;
+ }
+
+ if (g->substate == 0) {
+ g->wallcounter = 1;
+ g->substate = 1;
+ }else{
+ g->wallcounter = 2;
+ g->substate = 0;
+ }
+ }else{
+ g->vsp = -5;
+ }
+ }
+ }
+
+ else if (g->timer == 0) {
+ double grav = 0.25;
+
+ //Movement
+ if (g->timer == 0) {
+ g->y += g->vsp;
+ g->vsp += grav;
+ mask.y = g->y + (120 - mask.h);
+ }
+
+ //Land on ground
+ if (g->vsp >= 0 && g->timer == 0) {
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ g->y = collide.y - 120;
+ mask.y = g->y + (120 - mask.h);
+ g->vsp = 0;
+ g->timer = 3;
+ g->counter += 1;
+ PHL_PlaySound(sounds[sndPi09], CHN_ENEMIES);
+ }
+ }
+ }
+ }
+
+ //Leap towards wall
+ else if (g->state == 2)
+ {
+ double grav = 0.25;
+
+ //Set image
+ {
+ if (g->hsp > 0) {
+ g->imageIndex = 0;
+ }
+
+ if (g->hsp < 0) {
+ g->imageIndex = 1;
+ }
+ }
+
+ //Movement
+ {
+ g->y += g->vsp;
+ g->vsp += grav;
+ mask.y = g->y + (120 - mask.h);
+
+ g->x += g->hsp;
+ mask.x = g->x - (mask.w / 2);
+ }
+
+ if (g->wallcounter > 0)
+ {
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ g->wallcounter -= 1;
+ if (g->hsp < 0) {
+ g->x = collide.x + 40 + (mask.w / 2);
+ }
+ if (g->hsp > 0) {
+ g->x = collide.x - (mask.w / 2);
+ }
+ g->state = 3;
+ g->timer = 0;
+ }
+ }
+
+ //Ground pound
+ else {
+ char action = 0;
+
+ if ( (g->hsp > 0 && g->x > g->targx) || (g->hsp < 0 && g->x < g->targx) ) {
+ action = 1;
+ }
+ //Wall collision backup
+ else{
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ if (g->hsp < 0) {
+ g->x = collide.x + 40 + (mask.w / 2);
+ }
+ if (g->hsp > 0) {
+ g->x = collide.x - (mask.w / 2);
+ }
+ action = 1;
+ }
+ }
+
+ if (action == 1) {
+ g->state = 4;
+ g->vsp = -4;
+ PHL_PlaySound(sounds[sndWolf01], CHN_ENEMIES);
+ }
+ }
+ }
+
+ //Grab wall
+ else if (g->state == 3)
+ {
+ g->timer += 1;
+ if (g->timer > 5) {
+ g->state = 2;
+ g->vsp = -6;
+ g->hsp *= -1;
+ PHL_PlaySound(sounds[sndPi09], CHN_ENEMIES);
+
+ g->targx = herox;
+
+ if (g->wallcounter <= 0) {
+ //Get distance from player
+ int dis = g->x - g->targx;
+ {
+ if (dis < 0) {
+ dis *= -1;
+ }
+ }
+
+ if (dis < 200 || g->substate == 1) {
+ g->hsp /= 2;
+ }
+ }
+ }
+ }
+
+ //Ground pound
+ else if (g->state == 4)
+ {
+ double grav = 0.2;
+
+ //Animate
+ {
+ g->imageIndex += 0.33;
+ if (g->imageIndex >= 3) {
+ g->imageIndex -= 3;
+ }
+ }
+
+ g->y += g->vsp;
+ g->vsp += grav;
+ mask.y = g->y + (120 - mask.h);
+
+ //Collide with floor
+ {
+ PHL_Rect collide = getTileCollision(1, mask);
+
+ if (collide.x != -1) {
+ g->y = collide.y - 120;
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+ quakeTimer = 30;
+ g->state = 0;
+ g->timer = -20;
+ //Create rocks
+ createGarmrock(g->x + 64, g->y + 100, 2, -4);
+ createGarmrock(g->x + 34, g->y + 100, 1, -5);
+ createGarmrock(g->x - 34, g->y + 100, -1, -5);
+ createGarmrock(g->x - 64, g->y + 100, -2, -4);
+
+ createEffectExtra(3, g->x - 50, g->y + 90, -1, 0, 0);
+ createEffectExtra(3, g->x + 10, g->y + 90, 1, 0, 0);
+ }
+ }
+ }
+
+ //Dead
+ if (g->state == 5) {
+ //Animate
+ {
+ g->imageIndex += 0.33;
+ if (g->imageIndex >= 3) {
+ g->imageIndex -= 3;
+ }
+ }
+
+ g->y += 0.2;
+
+ if (g->blink % 12 == 0) {
+ createEffect(2, g->x - 64 + (rand() % 100), g->y + 60 - 64 + (rand() % 80));
+ }
+
+ if (g->blink <= 0) {
+ dead = 1;
+ }
+ }
+
+ else{
+ if (dead == 0) {
+ //Update Mask
+ {
+ mask.x = g->x - (mask.w / 2);
+ mask.y = g->y + (120 - mask.h);
+ }
+
+ //Hero collision
+ {
+ if (checkCollision(getHeroMask(), mask) == 1) {
+ heroHit(40, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ g->hp -= 1;
+ g->blink = 15;
+ //Dead
+ if (g->hp <= 0) {
+ g->state = 5;
+ g->blink = 200;
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ if (dead == 1) {
+ //Destroy
+ {
+ enemyDestroy(g->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss7flag] = 1;
+ PHL_StopMusic();
+ }
+ }
+
+}
+
+void garmDraw(Garm* g)
+{
+ if (g->blink % 2 == 0) {
+ int cropX = 0,
+ cropY = 0;
+
+ //Jump Spinning
+ if ((g->state == 1 && g->timer == 0) || g->state == 4 || g->state == 5) {
+ cropY = 128;
+ cropX = 256;
+ }
+
+ //Jump
+ if (g->state == 2) {
+ cropY = 128;
+ }
+
+ //Wall grab
+ if (g->state == 3) {
+ cropX = 384;
+ }
+
+ cropX += (int)g->imageIndex * 128;
+
+ PHL_DrawSurfacePart(g->x - 64, g->y - 8, cropX, cropY, 128, 128, images[imgBoss]);
+ }
+}
+
+//Rocks
+void createGarmrock(int x, int y, double hsp, double vsp)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Garmrock* g = malloc(sizeof *g);
+
+ g->id = i;
+ g->hp = 3;
+
+ g->x = x;
+ g->y = y;
+
+ g->hsp = hsp;
+ g->vsp = vsp;
+
+ g->imageIndex = 0;
+
+ g->counter = 0;
+ g->inwall = 0;
+ g->blink = 0;
+
+ e->data = g;
+ e->enemyStep = garmrockStep;
+ e->enemyDraw = garmrockDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void garmrockStep(Garmrock* g)
+{
+ char dead = 0;
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 44;
+ mask.h = 44;
+ mask.x = g->x - (mask.w / 2);
+ mask.y = g->y - (mask.h / 2);
+ }
+
+ //Animate
+ {
+ g->imageIndex += 0.2;
+ if (g->imageIndex >= 4) {
+ g->imageIndex -= 4;
+ }
+
+ if (g->blink > 0) {
+ g->blink -= 1;
+ }
+ }
+
+ //Horizontal movement
+ {
+ g->x += g->hsp;
+ mask.x = g->x - (mask.w / 2);
+
+ g->inwall = 0;
+ if (checkTileCollision(1, mask) == 1) {
+ g->inwall = 1;
+ }
+ }
+
+ //Vertical movement
+ {
+ double grav = 0.1;
+
+ g->y += g->vsp;
+ g->vsp += grav;
+ mask.y = g->y - (mask.h / 2);
+
+ if (g->inwall == 0 && g->counter == 0) {
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ g->counter = 1;
+ g->y = collide.y - (mask.h / 2);
+ g->vsp = -2;
+ PHL_PlaySound(sounds[sndHit06], CHN_ENEMIES);
+ }
+ }
+ }
+
+ //Update mask
+ {
+ mask.x = g->x - (mask.w / 2);
+ mask.y = g->y - (mask.h / 2);
+ }
+
+ //Hero collision
+ {
+ if (checkCollision(getHeroMask(), mask) == 1) {
+ heroHit(30, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ g->hp -= 1;
+ g->blink = 15;
+
+ if (g->hp <= 0) {
+ dead = 1;
+ createRockSmash(g->x, g->y + 20);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ //Destroy when out of room
+ {
+ if (mask.y > 480) {
+ dead = 1;
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(g->id);
+ }
+ }
+}
+
+void garmrockDraw(Garmrock* g)
+{
+ if (g->blink % 2 == 0) {
+ int cropX = 256,
+ cropY = 192;
+
+ if (g->hsp < 0) {
+ cropX = 512;
+ }
+
+ cropX += (int)g->imageIndex * 64;
+
+ while (cropX >= 640) {
+ cropX -= 640;
+ cropY += 64;
+ }
+
+ PHL_DrawSurfacePart(g->x - 32, g->y - 32, cropX, cropY, 64, 64, images[imgMisc32]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/garm.h b/contrib/games/hydracastlelabyrinth/src/enemies/garm.h
new file mode 100644
index 0000000000..e44a998a86
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/garm.h
@@ -0,0 +1,31 @@
+#ifndef GARM_H
+#define GARM_H
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double hsp, vsp;
+ int dir;
+ double imageIndex;
+ int state, timer, blink, counter;
+ int wallcounter, substate;
+ int targx;
+} Garm;
+
+void createGarm(int x, int y);
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double vsp, hsp;
+ double imageIndex;
+ int counter;
+ int blink;
+ int inwall;
+} Garmrock;
+
+void createGarmrock(int x, int y, double hsp, double vsp);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/gas.c b/contrib/games/hydracastlelabyrinth/src/enemies/gas.c
new file mode 100644
index 0000000000..aee6dbca69
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/gas.c
@@ -0,0 +1,116 @@
+#include "gas.h"
+#include "../PHL.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void gasStep(Gas* g);
+void gasDraw(Gas* g);
+
+void createGas(int x, int y, int temp)
+{
+ if (temp == 0 || hasKey[7] == 0) {
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Gas* g = malloc(sizeof *g);
+ g->id = i;
+
+ g->x = x;
+ g->y = y;
+
+ g->state = 0;
+ g->timer = 0;
+ g->imageIndex = 0;
+
+ /*
+ g->mask.unused = g->mask.circle = 0;
+ g->mask.w = g->mask.h = 24;
+ g->mask.x = x + 20 - (g->mask.w / 2);
+ g->mask.y = y + 40 - g->mask.h;
+ */
+
+ e->data = g;
+ e->enemyStep = gasStep;
+ e->enemyDraw = gasDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void gasStep(Gas* g)
+{
+ if (g->state != 0) {
+ g->imageIndex += 0.2;
+ }
+
+ if (g->state == 0) { //Wait
+ Mask tempMask;
+ tempMask.circle = tempMask.unused = 0;
+ tempMask.x = g->x - 100;
+ tempMask.y = g->y - 20;
+ tempMask.w = 240;
+ tempMask.h = 60;
+
+ if (checkCollisionXY(tempMask, herox, heroy + 20)) {
+ g->state = 1;
+ g->imageIndex = 3;
+ g->timer = 32;
+ PHL_PlaySound(sounds[sndGas01], CHN_ENEMIES);
+ }
+ }
+ else if (g->state == 1 || g->state == 3) { //Small puff
+ if (g->imageIndex >= 5) {
+ g->imageIndex -= 2;
+ }
+
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ if (g->state == 3) {
+ g->state = 0;
+ }else{
+ g->state = 2;
+ g->imageIndex = 0;
+ g->timer = 175;
+ }
+ }
+ }
+ else if (g->state == 2) { //Big puff
+ if (g->imageIndex >= 3) {
+ g->imageIndex -= 3;
+ }
+
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ g->state = 3;
+ g->timer = 120;
+ g->imageIndex = 3;
+ }
+
+ if (hasItem[7] != 1) { //Does not have gas mask
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = mask.h = 24;
+ mask.x = g->x + 20 - (mask.w / 2);
+ mask.y = g->y + 40 - mask.h;
+
+ if (checkCollision(getHeroMask(), mask)) {
+ if (heroHit(15, g->x + 20)) {
+ heroPoison();
+ }
+ }
+ }
+ }
+}
+
+void gasDraw(Gas* g)
+{
+ if (g->state != 0) {
+ PHL_DrawSurfacePart(g->x, g->y, (int)g->imageIndex * 40, 400, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/gas.h b/contrib/games/hydracastlelabyrinth/src/enemies/gas.h
new file mode 100644
index 0000000000..7bd1ebf59a
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/gas.h
@@ -0,0 +1,18 @@
+#ifndef GAS_H
+#define GAS_H
+
+//#include "../enemy.h"
+//#include "../collision.h"
+
+typedef struct {
+ int id;
+ int x, y;
+ int state, timer;
+ double imageIndex;
+
+ //Mask mask;
+} Gas;
+
+void createGas(int x, int y, int temp);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.c b/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.c
new file mode 100644
index 0000000000..fca1fbd657
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.c
@@ -0,0 +1,218 @@
+#include "ghoul.h"
+#include "../game.h"
+#include "../enemy.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+
+void ghoulStep(Ghoul* g);
+void ghoulDraw(Ghoul* g);
+
+void createGhoul(int x, int y, int type)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Ghoul* g = malloc(sizeof *g);
+ g->id = i;
+ g->hp = 2;
+
+ g->x = x;
+ g->y = y;
+
+ g->vsp = 0;
+ g->grav = 0.1;
+
+ g->dir = 0;
+ g->type = type;
+ g->onground = 0;
+
+ g->timer = 0;
+ g->state = 0;
+ g->invincible = 0;
+
+ g->imageIndex = 0;
+
+ g->mask.circle = 0;
+ g->mask.unused = 1;
+ g->mask.w = 24;
+ g->mask.h = 32;
+ g->mask.x = g->x + ((40 - g->mask.w) / 2);
+ g->mask.y = g->y + (40 - g->mask.h);
+
+ e->data = g;
+ e->enemyStep = ghoulStep;
+ e->enemyDraw = ghoulDraw;
+ e->type = 18;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+
+}
+
+void ghoulStep(Ghoul* g)
+{
+ if (g->invincible > 0) {
+ g->invincible -= 1;
+ }
+
+ if (g->state == 0) { //Wait
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = 280;
+ area.h = 80;
+ area.x = g->x - 120;
+ area.y = g->y - 20;
+
+ if (checkCollisionXY(area, herox, heroy + 20) == 1) {
+ g->state = 1;
+ g->mask.unused = 0;
+ g->imageIndex = 0;
+
+ g->dir = 1;
+ if (herox < g->x + 20) {
+ g->dir = -1;
+ }
+ }
+ }
+ else if (g->state == 1) { //Pop-up
+ g->imageIndex += 0.16;
+
+ if (g->imageIndex >= 4) {
+ g->state = 2;
+ g->vsp = -1;
+ g->imageIndex = 0;
+ PHL_PlaySound(sounds[sndPi05],CHN_ENEMIES);
+ }
+ }
+ else if (g->state == 2) { //Walking
+ g->mask.unused = 0;
+ if (g->onground == 0) {
+ //Vertical movement
+ g->y += g->vsp;
+ g->vsp += g->grav;
+
+ g->mask.y = g->y + (40 - g->mask.h);
+
+ PHL_Rect collide = getTileCollision(1, g->mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, g->mask);
+ }
+ if (collide.x != -1) {
+ g->onground = 1;
+ g->vsp = 0;
+ g->y = collide.y - 40;
+ g->mask.y = g->y + (40 - g->mask.h);
+ }
+ }
+
+ g->imageIndex += 0.1;
+ if (g->imageIndex >= 2) {
+ g->imageIndex -= 2;
+ }
+
+ double hsp = 1;
+
+ if ((int)g->imageIndex == 0) {
+ hsp = 0.5;
+ }
+
+ //Purple
+ if (g->type == 1) {
+ hsp *= 2;
+ }
+
+ g->x += hsp * g->dir;
+ g->mask.x = g->x + ((40 - g->mask.w) / 2);
+
+ if (g->onground == 1) {
+ if ((g->x < -20 || g->x > 660) || checkTileCollision(1, g->mask) == 1) {
+ g->dir *= -1;
+
+ PHL_Rect collide = getTileCollision(1, g->mask);
+ if (collide.x != -1) {
+ g->x = collide.x + (40 * g->dir);
+ }
+ }
+ else {
+ //check on ledge
+ g->mask.w = 5;
+ if (g->dir == 1) {
+ g->mask.x = g->x + 30;
+ }
+ if (g->dir == -1) {
+ g->mask.x = g->x + 5;
+ }
+ g->mask.y += 20;
+
+ if (checkTileCollision(1, g->mask) == 0 && checkTileCollision(3, g->mask) == 0) {
+ g->dir *= -1;
+ }
+ g->mask.w = 24;
+ g->mask.x = g->x + ((40 - g->mask.w) / 2);
+ g->mask.y = g->y + (40 - g->mask.h);
+ }
+ }
+ }
+
+ g->mask.x = g->x + ((40 - g->mask.w) / 2);
+ g->mask.y = g->y + (40 - g->mask.h);
+
+ //Hit Player
+ {
+ if (checkCollision(g->mask, getHeroMask())) {
+ if (heroHit(10, g->x + 20) == 1 && g->type == 1) {
+ heroPoison();
+ }
+ }
+ }
+
+ //Weapon Collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(g->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ g->hp -= 1;
+ g->invincible = 15;
+ //Death
+ if (g->hp <= 0) {
+ createEffect(2, g->x - 12, g->y - 6);
+ spawnCollectable(g->x + 20, g->y);
+ enemyDestroy(g->id);
+ }
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void ghoulDraw(Ghoul* g)
+{
+ if (g->state != 0 && g->invincible % 2 == 0) {
+ int cx = (int)g->imageIndex * 40,
+ cy = 160;
+
+ if (g->state == 1) {
+ cx += 160;
+ }else{
+ if (g->dir == -1) {
+ cx += 80;
+ }
+ }
+
+ //Purple palette
+ cy += 160 * g->type;
+
+ PHL_DrawSurfacePart(g->x, g->y, cx, cy, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.h b/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.h
new file mode 100644
index 0000000000..fd6db3ac43
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/ghoul.h
@@ -0,0 +1,22 @@
+#ifndef GHOUL_H
+#define GHOUL_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double vsp, grav;
+ int type;
+ int onground;
+ int dir;
+ int state, timer, invincible;
+ double imageIndex;
+
+ Mask mask;
+} Ghoul;
+
+void createGhoul(int x, int y, int type);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/golem.c b/contrib/games/hydracastlelabyrinth/src/enemies/golem.c
new file mode 100644
index 0000000000..a372368586
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/golem.c
@@ -0,0 +1,199 @@
+#include "golem.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include "../game.h"
+#include
+
+void golemStep(Golem* g);
+void golemDraw(Golem* g);
+
+void createGolem(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Golem* g = malloc(sizeof *g);
+ g->id = i;
+
+ g->x = x;
+ g->y = y;
+
+ g->hp = 4;
+
+ g->dir = 1;
+ if (dir == 1) {
+ g->dir = -1;
+ }
+
+ g->imageIndex = 0;
+ g->state = 0;
+ g->blink = 0;
+
+ e->data = g;
+ e->enemyStep = golemStep;
+ e->enemyDraw = golemDraw;
+ e->type = 28;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void golemStep(Golem* g)
+{
+ double imageSpeed = 0.2;
+
+ //Timers
+ {
+ if (g->blink > 0) {
+ g->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = 36;
+ mask.h = 36;
+ mask.x = g->x + ((40 - mask.w) / 2);
+ mask.y = g->y + (40 - mask.h);
+ }
+
+ //Rolling
+ if (g->state == 0)
+ {
+ //Animate
+ {
+ g->imageIndex += imageSpeed * g->dir;
+
+ if (g->imageIndex >= 8) {
+ g->imageIndex -= 8;
+ }
+
+ if (g->imageIndex < 0) {
+ g->imageIndex += 8;
+ }
+ }
+
+ //Movement
+ double hsp = 1;
+ {
+ g->x += hsp * g->dir;
+ mask.x = g->x + ((40 - mask.w) / 2);
+ }
+
+ char nextState = 0;
+
+ //Check on ledge
+ {
+ mask.x += 30 * g->dir;
+ mask.y += 10;
+
+ if (checkTileCollision(1, mask) == 0 && checkTileCollision(3, mask) == 0) {
+ nextState = 1;
+ }
+
+ mask.x = g->x + ((40 - mask.w) / 2);
+ mask.y = g->y + (40 - mask.h);
+ }
+
+ //Collide with wall
+ {
+ mask.x += hsp * g->dir;
+
+ if (checkTileCollision(1, mask) == 1) {
+ nextState = 1;
+ }
+
+ mask.x = g->x + ((40 - mask.w) / 2);
+ }
+
+ if (nextState == 1) {
+ PHL_PlaySound(sounds[sndPi10], CHN_ENEMIES);
+ g->state = 1;
+ g->imageIndex = 0;
+ }
+ }
+
+ //Forming
+ else if (g->state == 1)
+ {
+ //Animate
+ {
+ g->imageIndex += imageSpeed;
+
+ if (g->imageIndex >= 12) {
+ g->imageIndex = 0;
+ g->state = 0;
+ g->dir *= -1;
+ }
+ }
+
+ }
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ //Tink
+ if (g->state == 0) {
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }else{
+ g->hp -= 1;
+ g->blink = 15;
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ //Death
+ {
+ if (g->hp <= 0) {
+ createRockSmash(mask.x + (mask.w / 2), mask.y + (mask.h / 2));
+ spawnCollectable(g->x + 20, g->y);
+ enemyDestroy(g->id);
+ }
+ }
+}
+
+void golemDraw(Golem* g)
+{
+ if (g->blink % 2 == 0) {
+ int cropX = 320,
+ cropY = 160;
+
+ int drawY = g->y;
+
+ if (g->state == 0) {
+ cropX += (int)g->imageIndex * 40;
+ drawY += 2;
+ }else{
+ cropY = 280;
+ cropX = 240;
+
+ int animation[12] = {0, 1, 2, 3, 3, 3, 3, 3, 3, 2, 1, 0};
+ cropX += animation[(int)g->imageIndex] * 40;
+ }
+
+ PHL_DrawSurfacePart(g->x, drawY, cropX, cropY, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/golem.h b/contrib/games/hydracastlelabyrinth/src/enemies/golem.h
new file mode 100644
index 0000000000..2653605ba5
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/golem.h
@@ -0,0 +1,16 @@
+#ifndef GOLEM_H
+#define GOLEM_H
+
+typedef struct {
+ int id;
+ double x, y;
+ double imageIndex;
+ int hp;
+ int dir;
+ int state;
+ int blink;
+} Golem;
+
+void createGolem(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/gyra.c b/contrib/games/hydracastlelabyrinth/src/enemies/gyra.c
new file mode 100644
index 0000000000..a09ea9dac8
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/gyra.c
@@ -0,0 +1,332 @@
+#include "gyra.h"
+#include "../game.h"
+#include "../enemy.h"
+#include "../hero.h"
+#include
+#include
+
+void gyraStep(Gyra* g);
+void gyraDraw(Gyra* g);
+void gyraDestroy(Gyra* g);
+
+int boss4flag = 21;
+
+void createGyra(int x, int y)
+{
+ if (flags[boss4flag] == 0) { //have not yet beaten boss 4
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss02.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ //Boss start
+ setBossRoom();
+
+ Enemy* e = malloc(sizeof *e);
+ Gyra* g = malloc(sizeof *g);
+
+ g->id = i;
+ g->hp = 50;
+ //g->hp = 1;
+
+ g->x = x;
+ g->y = y;
+
+ g->targx = g->x;
+ g->targy = g->y;
+
+ g->state = 0;
+ g->timer = 260;
+ g->counter = 0;
+
+ g->invincible = 0;
+ g->dir = 0;
+ g->imageIndex = 0;
+
+ //Setup
+ g->targx = g->x - 32;
+ g->targy = g->y + 64;
+ g->dir = 160;
+
+ g->x = g->targx + (80 * sin(g->dir * 3.14159 / 180));
+ g->y = g->targy + (80 * cos(g->dir * 3.14159 / 180));
+
+ int a;
+ for (a = 0; a < 144; a++) {
+ g->xrecord[a] = g->x;
+ g->yrecord[a] = g->y;
+ }
+
+ e->data = g;
+ e->enemyStep = gyraStep;
+ e->enemyDraw = gyraDraw;
+ e->type = 43;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+
+ }
+ }
+ }
+}
+
+void gyraStep(Gyra* g)
+{
+ //Animate
+ g->imageIndex += 0.1;
+ if (g->imageIndex >= 2) {
+ g->imageIndex -= 2;
+ }
+
+ int pattern[6] = {0, 1, 2, 1, 0, 2};
+
+ //Move in a circle
+ if (g->state == 0)
+ {
+ int len = 80;
+
+ //Setup
+ if (g->timer == 0) {
+ g->targx = g->x + (len * sin((g->dir + 90) * 3.14159 / 180));
+ g->targy = g->y + (len * cos((g->dir + 90) * 3.14159 / 180));
+ g->dir -= 90;
+ g->timer = 250;
+ }
+
+ g->dir += 1.5;
+ if (g->dir >= 360) {
+ g->dir -= 360;
+ }
+
+ g->x = g->targx + (len * sin(g->dir * 3.14159 / 180));
+ g->y = g->targy + (len * cos(g->dir * 3.14159 / 180));
+
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ g->counter += 1;
+ g->state = pattern[g->counter];
+ /*
+ if (g->state != 1 && (g->x < 40 || g->x > 600 || g->y < 40 || g->y > 440)) {
+ g->state = 1;
+ }*/
+
+ g->timer = 0;
+ }
+ }
+ //Attack
+ else if (g->state == 1)
+ {
+ //Setup
+ if (g->timer == 0) {
+ g->targx = herox;
+ g->targy = heroy + 20;
+ g->dir += 90;
+ g->timer = 320;
+ }
+
+ double spd = 2;
+ double diralt = 1.2;
+
+ double targdir = (atan2(g->targy - g->y, g->x - g->targx) * 180 / 3.14159) + 270;
+
+ targdir = g->dir - targdir;
+ while (targdir >= 360) { targdir -= 360; }
+ while (targdir < 0) { targdir += 360; }
+
+ if (targdir > 180) {
+ g->dir += diralt;
+ }
+ if (targdir < 180) {
+ g->dir -= diralt;
+ }
+
+ //Movement
+ g->x += spd * sin(g->dir * 3.14159 / 180);
+ g->y += spd * cos(g->dir * 3.14159 / 180);
+
+ //Get (close) to targ coords
+ g->timer -= 1;
+ if (g->timer <= 0 || sqrt( pow(g->x - g->targx, 2) + pow(g->y - g->targy, 2) ) <= spd * 2) {
+ g->counter += 1;
+ if (g->counter >= 5) {
+ g->counter = 0;
+ }
+ g->state = pattern[g->counter];
+ g->timer = 0;
+ }
+ }
+ //Oval movement
+ else if (g->state == 2)
+ {
+ int wlen = 120,
+ hlen = 80;
+
+ //Setup
+ if (g->timer == 0) {
+ g->targx = g->x + (wlen * sin((g->dir - 90) * 3.14159 / 180));
+ g->targy = g->y + (hlen * cos((g->dir - 90) * 3.14159 / 180));
+ g->dir += 90;
+ g->timer = 200;
+ }
+
+ g->dir -= 1.5;
+ if (g->dir < 0) {
+ g->dir += 360;
+ }
+
+ g->x = g->targx + (wlen * sin(g->dir * 3.14159 / 180));
+ g->y = g->targy + (hlen * cos(g->dir * 3.14159 / 180));
+
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ g->counter += 1;
+ if (g->counter >= 5) {
+ g->counter = 0;
+ }
+ g->state = pattern[g->counter];
+ /*
+ if (g->state != 1 && (g->x < 40 || g->x > 600 || g->y < 40 || g->y > 440)) {
+ g->state = 1;
+ g->timer = 0;
+ }*/
+ }
+ }
+
+ //Death
+ if (g->state == 3)
+ {
+ g->timer -= 1;
+ if (g->timer <= 0) {
+ g->timer = 12;
+
+ int cx = g->xrecord[128 - (g->counter * 16)],
+ cy = g->yrecord[128 - (g->counter * 16)];
+
+ createEffect(2, cx - 32, cy - 32);
+
+ g->counter += 1;
+ if (g->counter == 9) {
+ gyraDestroy(g);
+ }
+ }
+ }else{
+ //Update tail record
+ int i;
+ for (i = 142; i >= 0; i--) {
+ g->xrecord[i + 1] = g->xrecord[i];
+ g->yrecord[i + 1] = g->yrecord[i];
+ }
+ g->xrecord[0] = g->x;
+ g->yrecord[0] = g->y;
+
+ //for (i = 8; i >= 0; i--) {
+ for (i = 0; i <= 8; i++) {
+ int cx = g->x, cy = g->y;
+
+ if (i != 0) {
+ cx = g->xrecord[i * 16];
+ cy = g->yrecord[i * 16];
+ }
+
+ Mask mask;
+ mask.unused = 0;
+ mask.circle = 1;
+ mask.x = cx;
+ mask.y = cy;
+ mask.w = mask.h = 28;
+
+ int a;
+ for (a = 0; a < MAX_WEAPONS; a++) {
+ if (weapons[a] != NULL) {
+ if (weapons[a]->cooldown == 0) {
+ if (checkCollision(mask, weapons[a]->weaponMask)) {
+ g->invincible = -15;
+ weaponHit(weapons[a]);
+
+ if (i == 8) {
+ g->hp -= 1;
+ g->invincible = 15;
+ }else{
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+
+ a = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+ //Hit player
+ if (checkCollision(getHeroMask(), mask)) {
+ if (heroHit(30, mask.x) && i == 0) {
+ heroPoison();
+ }
+ }
+ }
+
+ //Death
+ if (g->hp <= 0) {
+ g->state = 3;
+ g->timer = 0;
+ g->counter = 0;
+ g->invincible = 200;
+ }
+ }
+
+ if (g->invincible > 0) {
+ g->invincible -= 1;
+ }
+ if (g->invincible < 0) {
+ g->invincible += 1;
+ }
+
+}
+
+void gyraDraw(Gyra* g)
+{
+ if (g->invincible <= 0 || g->invincible % 2 == 0) {
+ //Draw Tail Tip
+ if (g->state != 3 || g->counter <= 0) {
+ PHL_DrawSurfacePart(g->xrecord[126] - 40, g->yrecord[126] - 40, 320 + ((int)g->imageIndex * 80), 0, 80, 80, images[imgBoss]);
+ }
+
+ //Draw Tail
+ int i;
+ for (i = 7; i > 0; i--) {
+ if (g->state != 3 || g->counter <= (7 - i) + 1) {
+ PHL_DrawSurfacePart(g->xrecord[i * 16] - 40, g->yrecord[i * 16] - 40, 160 + ((int)g->imageIndex * 80), 0, 80, 80, images[imgBoss]);
+ }
+ }
+
+ //Draw Head
+ PHL_DrawSurfacePart(g->x - 40, g->y - 40, (int)g->imageIndex * 80, 0, 80, 80, images[imgBoss]);
+ }
+
+ //PHL_DrawRect(g->targx, g->targy, 10, 10, PHL_NewRGB(255, 255, 255));
+ //heroAmmo = g->state;
+
+ /*
+ int i;
+ for (i = 8; i >= 0; i--) {
+ int cx = g->x, cy = g->y;
+
+ if (i != 0) {
+ cx = g->xrecord[i * 16];
+ cy = g->yrecord[i * 16];
+ }
+
+ PHL_DrawRect(cx, cy, 10, 10, PHL_NewRGB(255, 255, 255));
+ }
+ */
+}
+
+void gyraDestroy(Gyra* g)
+{
+ enemyDestroy(g->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss4flag] = 1;
+ PHL_StopMusic();
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/gyra.h b/contrib/games/hydracastlelabyrinth/src/enemies/gyra.h
new file mode 100644
index 0000000000..badd7e9d9c
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/gyra.h
@@ -0,0 +1,19 @@
+#ifndef GYRA_H
+#define GYRA_H
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double xrecord[144];
+ double yrecord[144];
+ int state, timer, counter;
+ int targx, targy;
+ int invincible;
+ double dir;
+ double imageIndex;
+} Gyra;
+
+void createGyra(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/heads.c b/contrib/games/hydracastlelabyrinth/src/enemies/heads.c
new file mode 100644
index 0000000000..802d2dcf6b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/heads.c
@@ -0,0 +1,838 @@
+#include "heads.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+#include
+
+void headStep(Head* h);
+void headDraw(Head* h);
+
+void bulletStep(Bullet* b);
+void bulletDraw(Bullet* b);
+
+void fireballStep(Fireball* f);
+void fireballDraw(Fireball* f);
+
+void laserStep(Laser* l);
+void laserDraw(Laser* l);
+
+void flameStep(Flame* f);
+void flameDraw(Flame* f);
+
+void rockStep(Rock* r);
+void rockDraw(Rock* r);
+
+void airStep(Air* a);
+void airDraw(Air* a);
+
+void createHead(int type, int x, int y, int dir, int offset, int cooloff)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Head* h = malloc(sizeof *h);
+
+ h->id = i;
+ h->type = type;
+
+ h->x = x;
+ h->y = y;
+
+ h->state = 0;
+
+ h->hp = 5;
+ h->invincible = 0;
+ h->counter = 0;
+
+ h->dir = 1;
+ if (dir == 1) {
+ h->dir = -1;
+ }
+
+ h->timer = 30 * offset;
+ h->cooloff = 60;
+ if (cooloff != 0) {
+ h->cooloff = 30 * cooloff;
+ }
+
+ e->type = -1;
+ if (h->type == 0) {
+ e->type = 4;
+ h->cooloff = 120;
+ }
+ else if (h->type == 1) {
+ e->type = 6;
+ }
+ else if (h->type == 2) {
+ e->type = 5;
+ }
+ else if (h->type == 3) {
+ e->type = 7;
+ h->cooloff = 120;
+ }
+ else if (h->type == 4) {
+ e->type = 10;
+ h->dir = 0;
+ }
+ else if (h->type == 5) {
+ e->type = 25;
+ h->dir = 0;
+ }
+ e->data = h;
+ e->enemyStep = headStep;
+ e->enemyDraw = headDraw;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void headStep(Head* h)
+{
+ int RHYNO = 0,
+ MEDUSA = 1,
+ DRAGON = 2,
+ DEMON = 3,
+ FIRE = 4,
+ JAR = 5;
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.x = h->x;
+ mask.y = h->y + 1;
+ mask.w = 40;
+ mask.h = 39;
+ }
+
+ //Timers
+ {
+ if (h->invincible > 0) {
+ h->invincible -= 1;
+ }
+
+ if (h->timer > 0) {
+ h->timer -= 1;
+ }
+ }
+
+ //Wait
+ if (h->state == 0)
+ {
+ char endstate = 0;
+
+ if (h->timer <= 0) {
+ //Proximity
+ if (h->type == RHYNO || h->type == DEMON) {
+ Mask area;
+ area.circle = area.unused = 0;
+ area.h = 80;
+ area.w = 400;
+ area.y = h->y - 20;
+ area.x = h->x;
+ if (h->dir == -1) {
+ area.x -= area.w - 40;
+ }
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ endstate = 1;
+ }
+ }else{
+ endstate = 1;
+ }
+ }
+
+ //Move onto next state
+ if (endstate == 1) {
+ h->state = 1;
+ h->timer = 30;
+ }
+ }
+
+ //Blink
+ else if (h->state == 1)
+ {
+ //Shoot projectile
+ if (h->timer <= 0) {
+ //Play Sound
+ {
+ int soundtoplay[6] = {sndShot03, sndShot04, sndFire01, sndHit06, sndShot03, sndShot06};
+ PHL_PlaySound(sounds[soundtoplay[h->type]], CHN_ENEMIES);
+ }
+
+ //Set vars
+ {
+ h->state = 0;
+ h->timer = h->cooloff;
+ }
+
+ //Create projectile
+ {
+ //Rhyno head
+ if (h->type == RHYNO) {
+ createBullet(mask.x + (mask.w / 2), h->y + 24, h->dir, h->id);
+ }
+ //Medusa head
+ if (h->type == MEDUSA) {
+ createLaser(h->x, h->y, h->dir);
+ }
+ //Dragon head
+ if (h->type == DRAGON) {
+ createFlame(h->x + 20 + (20 * h->dir), h->y - 10, h->dir);
+ }
+ //Demon head
+ if (h->type == DEMON) {
+ createRock(h->x + (20 * h->dir), h->y, h->dir);
+ }
+ //Fireball Statue
+ if (h->type == FIRE) {
+ createFireball(h->x + 20, h->y + 20, (atan2(heroy - h->y, h->x - (herox - 20)) * 180 / 3.14159) + 270, h->id);
+ }
+ //Air Jar
+ if (h->type == JAR) {
+ h->state = 3;
+ h->timer = 12;
+ h->counter = 0;
+ }
+ }
+
+ }
+ }
+
+ //Air Jar
+ else if (h->state == 3)
+ {
+ if (h->timer <= 0) {
+ h->counter += 1;
+ h->timer = 12;
+ createAir(h->x, h->y - 20);
+ }
+
+ if (h->counter >= 6) {
+ h->counter = 0;
+ h->state = 0;
+ h->timer = h->cooloff;
+ }
+ }
+
+ //Hit player
+ if (h->type != JAR) {
+ if (checkCollision(getHeroMask(), mask)) {
+ heroHit(10, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ h->hp -= 1;
+ h->invincible = 15;
+ weaponHit(weapons[i]);
+ //Death
+ if (h->hp <= 0) {
+ createRockSmash(h->x + 20, h->y + 20);
+ spawnCollectable(h->x + 20, h->y);
+ enemyDestroy(h->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+}
+
+void headDraw(Head* h)
+{
+ if (h->invincible % 2 == 0)
+ {
+ int sheetX[6] = {0, 320, 160, 240, 560, 400};
+ int sheetY[6] = {80, 80, 80, 120, 0, 120};
+
+ int cropX = sheetX[h->type];
+
+ int addx[6] = {6, 2, 0, 0, 0, 0};
+ int frames = 2;
+
+ //Change dir
+ if (h->dir == 0) {
+ frames = 1;
+ }else{
+ frames = 2;
+ if (h->dir == -1) {
+ cropX += 40;
+ }
+ }
+
+ //White flash
+ if (h->state == 1 && h->timer % 6 < 3) {
+ cropX += 40 * frames;
+ }
+
+ PHL_DrawSurfacePart(h->x - (addx[h->type] * h->dir), h->y, cropX, sheetY[h->type], 40, 40, images[imgEnemies]);
+ }
+}
+
+//Bullets
+void createBullet(int x, int y, int dir, int minid)
+{
+ int i;
+ for (i = minid; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Bullet* b = malloc(sizeof *b);
+ b->id = i;
+
+ b->x = x;
+ b->y = y;
+
+ b->hsp = dir * 4;
+
+ b->imageIndex = 0;
+
+ e->data = b;
+ e->enemyStep = bulletStep;
+ e->enemyDraw = bulletDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void bulletStep(Bullet* b)
+{
+ char dead = 0;
+
+ //Movement
+ {
+ b->x += b->hsp;
+ }
+
+ //Create Mask
+ Mask mask;
+ {
+ mask.unused = 0;
+ mask.circle = 1;
+ mask.w = mask.h = 10;
+ mask.x = b->x;
+ mask.y = b->y;
+ }
+
+ //Animation
+ {
+ if (b->hsp > 0) {
+ b->imageIndex += 0.33;
+ }else{
+ b->imageIndex -= 0.33;
+ }
+
+ if (b->imageIndex < 0) {
+ b->imageIndex += 4;
+ }
+ if (b->imageIndex >= 4) {
+ b->imageIndex -= 4;
+ }
+ }
+
+ //Collide with wall
+ {
+ if (checkTileCollision(1, mask) == 1) {
+ createEffect(1, b->x - 20, b->y - 20);
+ dead = 1;
+ }
+ }
+
+ //Collide with hero
+ {
+ //Shield collision
+ if (checkCollision(mask, shieldMask) == 1) {
+ dead = 1;
+ createEffect(1, b->x - 20, b->y - 20);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ }
+ //Collide with hero
+ else{
+ if (checkCollision(getHeroMask(), mask)) {
+ heroHit(10, mask.x);
+ }
+ }
+ }
+
+ //Destroy if outside of view
+ {
+ if (b->x > 660 || b->x < -20 || b->y < -20 || b->y > 520) {
+ dead = 1;
+ }
+ }
+
+ //Destroy
+ {
+ if (dead == 1) {
+ enemyDestroy(b->id);
+ }
+ }
+}
+
+void bulletDraw(Bullet* b)
+{
+ PHL_DrawSurfacePart(b->x - 20, b->y - 20, 160 + (40 * (int)b->imageIndex), 480, 40, 40, images[imgMisc20]);
+}
+
+//Fireball
+void createFireball(int x, int y, int angle, int minid)
+{
+ //General idea: try to place fireball over spawner
+ int i;
+ for (i = minid; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Fireball* f = malloc(sizeof *f);
+ f->id = i;
+
+ f->x = x;
+ f->y = y;
+
+ f->spd = 3;
+
+ f->imageIndex = 0;
+ f->angle = angle;
+
+ f->mask.circle = 1;
+ f->mask.unused = 0;
+ f->mask.x = x;
+ f->mask.y = y;
+ f->mask.w = f->mask.h = 14;
+
+ e->data = f;
+ e->enemyStep = fireballStep;
+ e->enemyDraw = fireballDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void fireballStep(Fireball* f)
+{
+ f->x += (f->spd) * sin(f->angle * 3.14159 / 180);
+ f->y += (f->spd) * cos(f->angle * 3.14159 / 180);
+
+ f->mask.x = f->x;
+ f->mask.y = f->y;
+
+ f->imageIndex += 0.5;
+ if (f->imageIndex >= 8) {
+ f->imageIndex -= 8;
+ }
+
+ //Collide with shield
+ if (checkCollision(f->mask, shieldMask)) {
+ createEffect(1, f->x - 20, f->y - 20);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ enemyDestroy(f->id);
+ }else{
+ //Hit player
+ if (checkCollision(getHeroMask(), f->mask)) {
+ heroHit(10, f->mask.x);
+ }
+ //Destroy if outside of view
+ if (f->x > 660 || f->x < -20 || f->y < -20 || f->y > 520) {
+ enemyDestroy(f->id);
+ }
+ }
+}
+
+void fireballDraw(Fireball* f)
+{
+ PHL_DrawSurfacePart(f->x - 20, f->y - 20, 320 + (40 * (int)f->imageIndex), 440, 40, 40, images[imgMisc20]);
+}
+
+//Laser
+void createLaser(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Laser* l = malloc(sizeof *l);
+ l->id = i;
+
+ l->x = x;
+ l->y = y;
+
+ l->dir = dir;
+ l->imageIndex = 0;
+
+ l->mask.circle = l->mask.unused = 0;
+ l->mask.x = x;
+ l->mask.y = y + 17;
+ l->mask.w = 40;
+ l->mask.h = 6;
+
+ e->data = l;
+ e->enemyStep = laserStep;
+ e->enemyDraw = laserDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void laserStep(Laser* l)
+{
+ char dead = 0;
+
+ l->x += l->dir * 10;
+ l->mask.x = l->x;
+
+ l->imageIndex += 0.34;
+ if (l->imageIndex >= 2) {
+ l->imageIndex -= 2;
+ }
+
+ if (checkCollision(shieldMask, l->mask)) { //Hit shield
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ createEffect(1, l->x + (20 * l->dir), l->y);
+ enemyDestroy(l->id);
+ dead = 1;
+ }else if (checkCollision(getHeroMask(), l->mask)) {
+ heroStone();
+ heroHit(15, l->x + 20);
+ }
+
+ if (dead == 0) {
+ if (checkTileCollision(1, l->mask)) {
+ createEffect(1, l->x + (20 * l->dir), l->y);
+ enemyDestroy(l->id);
+ dead = 1;
+ }
+
+ if (dead == 0) {
+ if (l->mask.x > 640 || l->mask.x + l->mask.w <= 0) {
+ enemyDestroy(l->id);
+ }
+ }
+ }
+}
+
+void laserDraw(Laser* l)
+{
+ int dx = 0,
+ dy = 480;
+ if (l->dir == -1) {
+ dx += 80;
+ }
+
+ PHL_DrawSurfacePart(l->x, l->y, dx + (((int)l->imageIndex) * 40), dy, 40, 40, images[imgMisc20]);
+}
+
+//Dragon Flame
+void createFlame(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Flame* f = malloc(sizeof *f);
+ f->id = i;
+
+ f->x = x;
+ f->y = y;
+
+ f->dir = dir;
+ f->timer = 60;
+
+ f->imageIndex = 0;
+
+ e->data = f;
+ e->enemyStep = flameStep;
+ e->enemyDraw = flameDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void flameStep(Flame* f)
+{
+ f->imageIndex += 0.25;
+
+ if (f->timer > 0) {
+ if (f->imageIndex >= 3) {
+ f->imageIndex -= 3;
+ }
+ }
+
+ f->timer -= 1;
+
+ if (f->timer == 0) {
+ f->imageIndex = 3;
+ }
+
+ //Hero Collision
+ {
+ Mask mask;
+ mask.circle = mask.unused = 0;
+ mask.x = f->x;
+ mask.y = f->y + 16;
+ mask.w = 120;
+ mask.h = 18;
+ if (f->dir == -1) {
+ mask.x -= 120;
+ }
+
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ int centerX = mask.x + 60 - (60 * f->dir);
+
+ //Hero is on ladder
+ if (getHeroState() == 3) {
+ centerX = herox;
+ }
+
+ heroHit(30, centerX);
+ }
+ }
+
+ if (f->timer < 0 && f->imageIndex >= 6) {
+ enemyDestroy(f->id);
+ }
+}
+
+void flameDraw(Flame* f)
+{
+ int drawX = f->x,
+ drawY = f->y;
+
+ int cropX = 0,
+ cropY = 0;
+
+ if (f->dir == -1) {
+ cropX += 720;
+ drawX -= 120;
+ }
+
+ cropX += 120 * (int)f->imageIndex;
+
+ while (cropX >= 600) {
+ cropX -= 600;
+ cropY += 40;
+ }
+
+ PHL_DrawSurfacePart(drawX, drawY, cropX, cropY, 120, 40, images[imgMisc6020]);
+}
+
+//Demon Rock
+void createRock(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Rock* r = malloc(sizeof *r);
+ r->id = i;
+
+ r->x = x;
+ r->y = y;
+
+ r->vsp = -3;
+ r->dir = dir;
+
+ r->imageIndex = 0;
+
+ e->data = r;
+ e->enemyStep = rockStep;
+ e->enemyDraw = rockDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void rockStep(Rock* r)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ r->imageIndex += 0.25 * r->dir;
+ if (r->imageIndex >= 8) {
+ r->imageIndex -= 8;
+ }
+ if (r->imageIndex < 0) {
+ r->imageIndex += 8;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.x = r->x + 2;
+ mask.y = r->y + 2;
+ mask.w = 36;
+ mask.h = 36;
+ }
+
+ int hsp = 3;
+ double grav = 0.12;
+
+ //Movement
+ {
+ r->y += r->vsp;
+ r->vsp += grav;
+
+ //Collide with floor
+ {
+ mask.y = r->y + 2;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x != -1) {
+ PHL_PlaySound(sounds[sndHit06], CHN_ENEMIES);
+ r->y = collide.y - mask.h - 2;
+ r->vsp = -3;
+ mask.y = r->y + 2;
+ }
+ }
+
+ r->x += hsp * r->dir;
+
+ //Collide with wall
+ {
+ mask.x = r->x + 2;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+
+ if (collide.x != -1) {
+ dead = 1;
+ }
+ }
+ }
+
+ //Collision
+ {
+ //Hero collision
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(20, mask.x + (mask.w / 2));
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ //Destroy
+ if (dead == 1) {
+ createRockSmash(r->x + 20, r->y);
+ enemyDestroy(r->id);
+ }
+}
+
+void rockDraw(Rock* r)
+{
+ PHL_DrawSurfacePart(r->x, r->y, 320 + ((int)r->imageIndex * 40), 160, 40, 40, images[imgEnemies]);
+}
+
+//Air Stream
+void createAir(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Air* a = malloc(sizeof *a);
+ a->id = i;
+
+ a->x = x;
+ a->y = y;
+
+ a->imageIndex = 0;
+
+ e->data = a;
+ e->enemyStep = airStep;
+ e->enemyDraw = airDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+//Air Puff
+void airStep(Air* a)
+{
+ Mask mask;
+ mask.circle = mask.unused = 0;
+ mask.w = 36;
+ mask.h = 30;
+ mask.x = a->x + ((40 - mask.w) / 2);
+
+ //Animate
+ a->imageIndex += 0.5;
+ if (a->imageIndex >= 2) {
+ a->imageIndex -= 2;
+ }
+
+ //Movement
+ a->y -= 6;
+ mask.y = a->y + (40 - mask.h);
+
+ //Collide with player
+ if (getHeroState() != 2) {
+ if (checkCollision(mask, getHeroMask())) {
+ if (hasItem[27] == 0) {
+ heroHit(10, mask.x + (mask.w / 2));
+ }else{
+ //Floating stuff
+ if (getHeroVsp() > -5) {
+ setHeroVsp(-5);
+ setHeroOnground(0);
+ }
+ }
+ }
+ }
+
+ //destroy if outside of room
+ if (mask.y + mask.h < 0) {
+ enemyDestroy(a->id);
+ }
+}
+
+void airDraw(Air* a)
+{
+ PHL_DrawSurfacePart(a->x, a->y, (int)a->imageIndex * 40, 560, 40, 40, images[imgMisc20]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/heads.h b/contrib/games/hydracastlelabyrinth/src/enemies/heads.h
new file mode 100644
index 0000000000..75fcab00f3
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/heads.h
@@ -0,0 +1,89 @@
+#ifndef HEADS_H
+#define HEADS_H
+
+#include "../collision.h"
+
+//Goblin/medusa/dragon head statues
+typedef struct {
+ int id, type; //0 = Rhyno head | 1 = Goblin | 2 = Dragon | 3 = Demon | 4 = Fireball | 5 = Air Jar
+ int state, timer;
+ double x, y;
+ int dir;
+ int hp, invincible;
+ int cooloff;
+ int counter;
+
+ //Mask mask;
+} Head;
+
+void createHead(int type, int x, int y, int dir, int offset, int cooloff);
+
+//Bullet from Rhyno statues
+typedef struct {
+ int id;
+ double x, y;
+ int hsp;
+ double imageIndex;
+
+ //Mask mask;
+} Bullet;
+
+void createBullet(int x, int y, int dir, int minid); //Minid is the spawner's id
+
+//Fireball
+typedef struct {
+ int id;
+ double x, y;
+ int angle;
+ int spd;
+ double imageIndex;
+
+ Mask mask;
+} Fireball;
+
+void createFireball(int x, int y, int angle, int minid);
+
+//Medusa lazer
+typedef struct {
+ int id;
+ double x, y;
+ int dir;
+ double imageIndex;
+
+ Mask mask;
+} Laser;
+
+void createLaser(int x, int y, int dir);
+
+//Dragon flame
+typedef struct {
+ int id;
+ int x, y;
+ int dir;
+ int timer;
+ double imageIndex;
+} Flame;
+
+void createFlame(int x, int y, int dir);
+
+//Demon Boulder
+typedef struct {
+ int id;
+ double x, y;
+ double vsp;
+ int dir;
+ double imageIndex;
+} Rock;
+
+void createRock(int x, int y, int dir);
+
+//Air
+typedef struct {
+ int id;
+ double x, y;
+ double imageIndex;
+} Air;
+
+void createAir(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/hydra.c b/contrib/games/hydracastlelabyrinth/src/enemies/hydra.c
new file mode 100644
index 0000000000..d770c9db61
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/hydra.c
@@ -0,0 +1,1127 @@
+#include "hydra.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+const double PI = 3.14159;
+
+double headRot = 0;
+
+void hydraStep(Hydra* h);
+void hydraDraw(Hydra* h);
+
+void hydraDestroy(Hydra* h);
+
+void hydraheadStep(Hydrahead* h);
+void hydraheadDraw(Hydrahead* h);
+
+void hydragoopStep(Hydragoop* h);
+void hydragoopDraw(Hydragoop* h);
+
+void hydrarockStep(Hydrarock* h);
+void hydrarockDraw(Hydrarock* h);
+
+void hydrashockStep(Hydrashock* h);
+void hydrashockDraw(Hydrashock* h);
+
+double getHydraX(Hydrahead* h);
+double getHydraY(Hydrahead* h);
+
+Mask getHydraMask(Hydra* h);
+int checkWeaponCollision(Mask m);
+
+double lengthdir_x(double ang, double len);
+double lengthdir_y(double ang, double len);
+
+void setHeadState(int headid, int state);
+
+
+//#hydra
+void createHydra(int x)
+{
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("lboss01.bmp");
+
+ int i;
+ for (i = 4; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ setBossRoom();
+
+ Enemy* e = malloc(sizeof *e);
+ Hydra* h = malloc(sizeof *h);
+ h->id = i;
+
+ h->hp = 10;
+ //h->hp = 1;
+ h->blink = 0;
+
+ h->x = x;
+ h->y = -64;
+
+ h->hsp = 0;
+ h->vsp = 0;
+
+ h->imageIndex = 0;
+
+ h->state = 0;
+ h->timer = 0;
+
+ h->patternCounter = 0;
+
+ h->onground = 0;
+ h->noheads = 0;
+
+ e->data = h;
+ e->enemyStep = hydraStep;
+ e->enemyDraw = hydraDraw;
+ e->type = 47;
+
+ enemies[i] = e;
+
+ h->headid[0] = createHydrahead(-1, 0, i);
+ h->headid[1] = createHydrahead(1, 0, i);
+ h->headid[2] = createHydrahead(1, 1, i);
+ h->headid[3] = createHydrahead(-1, 1, i);
+
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void hydraStep(Hydra* h)
+{
+ double grav = 0.2;
+ double fric = 0.1;
+
+ //Death
+ if (h->state == 6) {
+ h->y += 0.2;
+
+ h->timer -= 1;
+ h->blink -= 1;
+
+ if (h->timer % 12 == 0) {
+ createEffect(2, h->x - 64 + (rand() % 128) - 32, h->y - 64 + (rand() % 128));
+ }
+
+ if (h->timer <= 0) {
+ hydraDestroy(h);
+ }
+ }
+ else{
+ //Setup Mask
+ Mask mask = getHydraMask(h);
+
+ //States with hydra heads
+ if (h->noheads == 0) {
+ //Fall in intro
+ if (h->state == 0) {
+ h->hsp = 0;
+ h->timer += 1;
+ if (h->timer >= 50) {
+ h->timer = 50;
+ h->imageIndex = 2;
+
+ if (h->onground == 1) {
+ h->state = 1;
+ h->timer = 0;
+ }
+ }else{
+ grav = 0;
+ }
+ }
+
+ //Wait/Pattern activate
+ else if (h->state == 1)
+ {
+ h->timer += 1;
+
+ //Stop speed animation
+ if (h->timer >= 120) {
+ int patternSize = 9;
+ int pattern[9] = {4, 0, 4, 1, 4, 2, 4, 3, 2};
+
+ //Head seizure
+ if (pattern[h->patternCounter] == 4) {
+ h->state = 4;
+ h->timer = 0;
+ }
+ //Small hop
+ if (pattern[h->patternCounter] == 0) {
+ h->state = 2;
+ h->timer = 0;
+ }
+
+ //Goop
+ if (pattern[h->patternCounter] == 1) {
+ h->timer = -120;
+ setHeadState(h->headid[0], 2);
+ setHeadState(h->headid[1], 2);
+ }
+
+ //Big Hop
+ if (pattern[h->patternCounter] == 2) {
+ h->state = 3;
+ h->timer = 0;
+ }
+
+ //Electricity
+ if (pattern[h->patternCounter] == 3) {
+ h->timer = -40;
+ setHeadState(h->headid[2], 3);
+ setHeadState(h->headid[3], 3);
+ }
+
+ h->patternCounter += 1;
+ if (h->patternCounter >= patternSize) {
+ h->patternCounter = 0;
+ }
+ }
+ }
+
+ //Head seizure state
+ else if (h->state == 4) {
+ //Speed up head animation
+ if (h->timer == 0) {
+ setHeadState(h->headid[0], 1);
+ setHeadState(h->headid[1], 1);
+ setHeadState(h->headid[2], 1);
+ setHeadState(h->headid[3], 1);
+ }
+
+ h->timer += 1;
+
+ //Stop speed animation
+ if (h->timer == 120) {
+ setHeadState(h->headid[0], 0);
+ setHeadState(h->headid[1], 0);
+ setHeadState(h->headid[2], 0);
+ setHeadState(h->headid[3], 0);
+
+ //Pattern
+ h->state = 1;
+ h->timer = 120;
+ }
+ }
+
+ //Switch to noheads mode
+ if (h->onground == 1 &&
+ enemies[h->headid[0]] == NULL &&
+ enemies[h->headid[1]] == NULL &&
+ enemies[h->headid[2]] == NULL &&
+ enemies[h->headid[3]] == NULL)
+ {
+ h->noheads = 1;
+ h->state = 1;
+ h->timer = -15;
+ h->patternCounter = 0;
+ }
+ }
+
+ //States without hydra heads
+ else{
+ //Wait/pattern activate
+ if (h->state == 1) {
+ h->timer += 1;
+
+ if (h->timer >= 0) {
+ int patternSize = 3;
+ int pattern[3] = {0, 0, 2};
+
+ //Small hop
+ if (pattern[h->patternCounter] == 0) {
+ h->state = 2;
+ h->timer = 0;
+ }
+
+ //Big Hop
+ if (pattern[h->patternCounter] == 2) {
+ h->state = 3;
+ h->timer = 0;
+ }
+
+ h->patternCounter += 1;
+ if (h->patternCounter >= patternSize) {
+ h->patternCounter = 0;
+ }
+ }
+ }
+ }
+
+ //States used by both modes
+ {
+ //Small hop
+ if (h->state == 2) {
+ //Setup
+ if (h->timer == 0) {
+ h->vsp = -2;
+ h->onground = 0;
+ h->hsp = 2.5;
+ if (herox < h->x) {
+ h->hsp *= -1;
+ }
+ }
+
+ h->timer += 1;
+
+ if (h->onground == 1) {
+ if (h->noheads == 0 || h->hsp == 0) {
+ h->state = 1;
+ h->timer = 0;
+ }
+ }
+ }
+
+ //Large Hop
+ else if (h->state == 3) {
+ h->hsp = 0;
+
+ //Setup
+ if (h->timer == 0) {
+ h->timer = 1;
+ if (h->noheads == 0) {
+ h->vsp = -8;
+ }else{
+ h->vsp = -5;
+ }
+ h->onground = 0;
+ }
+
+ if (h->onground == 1) {
+ h->timer += 1;
+
+ if (h->timer % 20 == 0) {
+ createHydrarock();
+ }
+
+ if (h->timer >= 220) {
+ h->state = 1;
+ h->timer = -15;
+ }
+ }
+ }
+ }
+
+ //Animate
+ {
+ if (h->onground == 1) {
+ h->imageIndex += 0.1;
+ if (h->imageIndex >= 2) {
+ h->imageIndex -= 2;
+ }
+ }else{
+ if (h->vsp < 0) {
+ h->imageIndex = 3;
+ }
+ else {
+ h->imageIndex = 2;
+ }
+ }
+
+ //Blink
+ if (h->blink > 0) {
+ h->blink -= 1;
+ }
+ }
+
+ //Movement
+ {
+ //Horizontal
+ if (h->hsp != 0) {
+ h->x += h->hsp;
+ mask = getHydraMask(h);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ int dir = 1;
+ if (h->hsp < 0) {
+ dir = -1;
+ }
+ h->x = collide.x + 20 - ((20 + (mask.w / 2)) * dir);
+
+ h->hsp *= -1;
+ }
+ }
+
+ //Friction
+ {
+ if (h->onground == 1) {
+ if (h->hsp > 0) {
+ h->hsp -= fric;
+ if (h->hsp < 0) {
+ h->hsp = 0;
+ }
+ }
+ if (h->hsp < 0) {
+ h->hsp += fric;
+ if (h->hsp > 0) {
+ h->hsp = 0;
+ }
+ }
+ }
+ }
+
+ //Vertical
+ {
+ int maxVsp = 9;
+
+ if (h->onground == 0) {
+ h->y += h->vsp;
+ h->vsp += grav;
+ mask = getHydraMask(h);
+
+ //Limit vsp
+ {
+ if (h->vsp > maxVsp) {
+ h->vsp = maxVsp;
+ }
+ }
+
+ //Collide with floor
+ {
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ h->y = collide.y - 64;
+ h->vsp = 0;
+ h->onground = 1;
+ PHL_PlaySound(sounds[sndHit04], CHN_ENEMIES);
+ quakeTimer = 30;
+ createEffectExtra(3, h->x - 30, h->y + 32, -1, 0, 0);
+ createEffectExtra(3, h->x - 10, h->y + 32, 1, 0, 0);
+ }
+ }
+ }
+ }
+
+ }
+
+ //Update mask
+ mask = getHydraMask(h);
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(25, h->x);
+ }
+ }
+
+ //Weapon Collision
+ {
+ int wid = checkWeaponCollision(mask);
+ if (wid != -1) {
+ //Pushed back
+ if (h->noheads == 0) {
+ h->hsp = weapons[wid]->dir;
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ }else{
+ h->hp -= 1;
+ h->blink = 15;
+ }
+ weaponHit(weapons[wid]);
+ //Die
+ if (h->hp <= 0) {
+ h->state = 6;
+ h->timer = 180;
+ h->blink = 200;
+ }
+
+ }
+ }
+ }
+
+}
+
+void hydraDraw(Hydra* h)
+{
+ if (h->blink % 2 == 0) {
+ int cropX = (int)h->imageIndex * 128;
+ int cropY = 128;
+
+ if (h->noheads == 1) {
+ cropY += 128;
+ }
+
+ PHL_DrawSurfacePart(h->x - 64, h->y - 64, cropX, cropY, 128, 128, images[imgBoss]);
+ }
+}
+
+void hydraDestroy(Hydra* h)
+{
+ enemyDestroy(h->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ PHL_StopMusic();
+}
+
+Mask getHydraMask(Hydra* h)
+{
+ Mask mask;
+
+ mask.unused = mask.circle = 0;
+ mask.w = 84;
+ mask.h = 84;
+ mask.x = h->x - (mask.w / 2);
+ mask.y = h->y - 64 + (128 - mask.h);
+
+ return mask;
+}
+
+//#heads
+int createHydrahead(int dir, int position, int bodyid)
+{
+ int result = -1;
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Hydrahead* h = malloc(sizeof *h);
+
+ h->id = i;
+ result = i;
+
+ h->hp = 25;
+ //h->hp = 1;
+ h->blink = 0;
+
+ h->dir = dir;
+ h->position = position;
+
+ h->imageIndex = 0;
+
+ h->neckRot = 0;
+ if (position != 0) {
+ h->neckRot -= 45;
+ }
+
+ h->state = 0;
+ h->timer = 0;
+ h->counter = 0;
+
+ h->bodyid = bodyid;
+
+ int a;
+ for (a = 0; a < 7; a++) {
+ h->bodyposX[a] = 0;
+ h->bodyposY[a] = 0;
+ }
+
+ e->data = h;
+ e->enemyStep = hydraheadStep;
+ e->enemyDraw = hydraheadDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+
+ return result;
+}
+
+void hydraheadStep(Hydrahead* h)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ h->imageIndex += 0.1;
+ if (h->imageIndex >= 2) {
+ h->imageIndex -= 2;
+ }
+
+ if (h->blink > 0) {
+ h->blink -= 1;
+ }
+
+ h->neckRot += 2;
+ if (h->neckRot >= 360) {
+ h->neckRot -= 360;
+ }
+ }
+
+ //States
+ {
+ //Death
+ if (h->state == 4) {
+ h->timer += 1;
+ if (h->timer % 6 == 0) {
+ createEffect(2, h->bodyposX[6 - h->counter] - 32, h->bodyposY[6 - h->counter] - 32);
+ h->counter += 1;
+ }
+
+ if (h->counter >= 7) {
+ dead = 1;
+ }
+ }
+ else{
+ if (h->state == 0) {
+ //Do nothing special
+ }
+
+ //Fast movements
+ else if (h->state == 1) {
+ h->neckRot += 2;
+ }
+
+ //Shoot goop
+ else if (h->state == 2) {
+ h->neckRot += 2;
+ h->timer += 1;
+
+ //Create Goop
+ if (h->timer % 15 == 0) {
+ int ghsp = -4 + (rand() % 9),
+ gvsp = -6;
+
+ createHydragoop(h->bodyposX[6], h->bodyposY[6], ghsp, gvsp);
+ }
+
+ if (h->timer >= 120) {
+ h->state = 0;
+ }
+ }
+
+ //Shoot electricity
+ else if (h->state == 3) {
+ if (h->timer == 0) {
+ h->timer = 1;
+ }
+
+ if (h->timer % 20 == 0) {
+ if (h->counter == 0) {
+ createHydrashock(h->bodyposX[6] + (70 * h->dir), h->bodyposY[6] + 20);
+ }
+ h->neckRot -= 2;
+ h->counter += 1;
+ if (h->counter >= 20) {
+ h->counter = 0;
+ h->timer += 1;
+ }
+ }else{
+ h->neckRot += 2;
+ h->timer += 1;
+ }
+
+ if (h->timer > 80) {
+ h->state = 0;
+ /*
+ Hydra* body = enemies[h->bodyid]->data;
+ body->state = 1;
+ body->timer = 239;
+ */
+ }
+
+ }
+
+ Mask mask;
+ mask.circle = mask.unused = 0;
+
+ //Collide with player
+ {
+ int i;
+ for (i = 0; i < 7; i+=2) {
+ //Setup mask
+ {
+ mask.w = 48;
+ mask.h = 48;
+
+ //Head
+ if (i == 6) {
+ mask.w = 60;
+ mask.h = 36;
+ }
+
+ mask.x = h->bodyposX[i] - (mask.w / 2);
+ mask.y = h->bodyposY[i] - (mask.h / 2);
+ }
+
+ //Collide
+ if (checkCollision(getHeroMask(), mask) == 1) {
+ heroHit(25, getHydraX(h));
+ }
+ }
+ }
+
+ //Weapon collision
+ {
+ //Mask should still be on the head
+ int wid = checkWeaponCollision(mask);
+ if (wid != -1) {
+ h->blink = 15;
+ h->hp -= 1;
+ weaponHit(weapons[wid]);
+
+ if (h->hp <= 0) {
+ h->state = 4;
+ h->timer = 0;
+ h->counter = 0;
+ }
+ }
+ }
+
+
+ }
+
+ }
+
+ //Destroy object
+ if (dead == 1) {
+ enemyDestroy(h->id);
+ }
+}
+
+void hydraheadDraw(Hydrahead* h)
+{
+ /*
+ char c[10];
+ sprintf(c, "%02d", h->timer);
+ PHL_DrawTextBold(c, h->bodyposX[6], 0, 0);
+ */
+
+ h->bodyposX[0] = getHydraX(h) + 20;
+ h->bodyposY[0] = getHydraY(h);
+
+ double drawX = getHydraX(h) + 20;
+ double drawY = getHydraY(h);
+
+ int dis = 24;
+ int angle = -25;
+
+ if (h->position == 1) {
+ angle = -60;
+
+ drawX -= 5;
+ drawY -= 20;
+ }
+
+ int i;
+ for (i = 0; i < 7; i++) {
+ double wavlen = sin((h->neckRot + (45 * i)) * PI / 180);
+
+ double incang = 45;
+
+ if (h->position != 0) {
+ incang = 45;
+ }
+
+ if (i == 6) {
+ //incang += 15;
+ incang = 50;
+
+ if (h->position == 1) {
+ incang = 80;
+ }
+ }
+
+ drawX += lengthdir_x(angle + (incang * wavlen), dis);
+ drawY += lengthdir_y(angle + (incang * wavlen), dis);
+
+ h->bodyposX[i] = drawX;
+ h->bodyposY[i] = drawY;
+
+ if (h->dir == -1) {
+ double difference = h->bodyposX[i] - getHydraX(h);
+ h->bodyposX[i] = getHydraX(h) - difference;
+ }
+
+ if (h->blink % 2 == 0) {
+ if (h->state != 4 || (6 - h->counter >= i)) {
+ if (i != 6) {
+ int cropX = 0;
+
+ if (h->dir == -1) {
+ cropX += 64;
+ }
+
+ PHL_DrawSurfacePart(h->bodyposX[i] - 32, h->bodyposY[i] - 32, cropX, 64, 64, 64, images[imgBoss]);
+ }else{
+ int cropX = 0;
+
+ if (h->dir == -1) {
+ cropX += 320;
+ }
+
+ cropX += (int)h->imageIndex * 80;
+
+ PHL_DrawSurfacePart(h->bodyposX[i] - 40, h->bodyposY[i] - 32, cropX, 0, 80, 64, images[imgBoss]);
+ }
+ }
+ }
+ }
+
+}
+
+int checkWeaponCollision(Mask m)
+{
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(weapons[i]->weaponMask, m) == 1) {
+ return i;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+double lengthdir_x(double ang, double len)
+{
+ return cos(ang * PI / 180) * len;
+}
+
+double lengthdir_y(double ang, double len)
+{
+ return sin(ang * PI / 180) * len;
+}
+
+double getHydraX(Hydrahead* h)
+{
+ if (enemies[h->bodyid] != NULL) {
+ Hydra* hbody = enemies[h->bodyid]->data;
+ return hbody->x;
+ }
+
+ return -1;
+}
+
+double getHydraY(Hydrahead* h)
+{
+ if (enemies[h->bodyid] != NULL) {
+ Hydra* hbody = enemies[h->bodyid]->data;
+ return hbody->y;
+ }
+
+ return -1;
+}
+
+void setHeadState(int headid, int state)
+{
+ if (enemies[headid] != NULL) {
+ Hydrahead* h = enemies[headid]->data;
+ if (h->state != 4) {
+ h->state = state;
+ h->timer = 0;
+ h->counter = 0;
+ }
+ }
+}
+
+//#goop
+void createHydragoop(int x, int y, int hsp, int vsp)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Hydragoop* h = malloc(sizeof *h);
+ h->id = i;
+
+ h->x = x;
+ h->y = y;
+
+ h->hsp = hsp;
+ h->vsp = vsp;
+
+ h->inwall = 0;
+ h->bounce = 0;
+
+ h->imageIndex = 0;
+
+ e->data = h;
+ e->enemyStep = hydragoopStep;
+ e->enemyDraw = hydragoopDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+
+ PHL_PlaySound(sounds[sndPi06], CHN_ENEMIES);
+ }
+ }
+}
+
+void hydragoopStep(Hydragoop* h)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ h->imageIndex += 0.16;
+ if (h->imageIndex >= 3) {
+ h->imageIndex -= 3;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 36;
+ mask.h = 36;
+ mask.x = h->x - mask.w / 2;
+ mask.y = h->y - mask.h / 2;
+ }
+
+ //Movement
+ {
+ double grav = 0.2;
+
+ h->x += h->hsp;
+ mask.x = h->x - mask.w / 2;
+
+ if (checkTileCollision(1, mask) == 1) {
+ h->inwall = 1;
+ }
+
+ h->y += h->vsp;
+ h->vsp += grav;
+ mask.y = h->y - mask.h / 2;
+
+ if (h->inwall == 0 && h->bounce == 0) {
+ if (checkTileCollision(1, mask) == 1) {
+ h->bounce = 1;
+ h->vsp = -2;
+ }
+ }
+ }
+
+ //Outside of room
+ {
+ if ( (h->y > 500 && h->vsp >= 0) ||
+ (h->x < -20 && h->hsp <= 0) ||
+ (h->x > 660 && h->hsp >= 0) )
+ {
+ dead = 1;
+ }
+ }
+
+ //Collide with hero
+ {
+ //Collide with shield
+ if (checkCollision(mask, shieldMask) == 1) {
+ createEffect(1, h->x - 20, h->y - 20);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ dead = 1;
+ }
+ else if (checkCollision(mask, getHeroMask()) == 1) {
+ if (heroHit(25, h->x) == 1) {
+ heroPoison();
+ }
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(h->id);
+ }
+ }
+
+}
+
+void hydragoopDraw(Hydragoop* h)
+{
+ int cropX = 320;
+
+ cropX += (int)h->imageIndex * 40;
+
+ PHL_DrawSurfacePart(h->x - 20, h->y - 20, cropX, 480, 40, 40, images[imgMisc20]);
+}
+
+//#rock
+void createHydrarock()
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Hydrarock* h = malloc(sizeof *h);
+ h->id = i;
+
+ h->x = 70 + (rand() % 26) * 20;
+ h->y = -24;
+
+ h->vsp = 0;
+
+ h->bounce = 0;
+
+ h->imageIndex = 0;
+
+ e->data = h;
+ e->enemyStep = hydrarockStep;
+ e->enemyDraw = hydrarockDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void hydrarockStep(Hydrarock* h)
+{
+ //Animate
+ {
+ h->imageIndex += 0.25;
+ if (h->imageIndex >= 8) {
+ h->imageIndex -= 8;
+ }
+ }
+
+ //Movement
+ {
+ double grav = 0.15;
+
+ h->y += h->vsp;
+ h->vsp += grav;
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 44;
+ mask.h = 44;
+ mask.x = h->x - mask.w / 2;
+ mask.y = h->y - mask.h / 2;
+ }
+
+ if (h->bounce == 0) {
+ if (checkTileCollision(1, mask) == 1) {
+ h->bounce = 1;
+ h->vsp = -2;
+ PHL_PlaySound(sounds[sndHit06], CHN_ENEMIES);
+ }
+ }
+
+ //Hero collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(30, h->x);
+ }
+ }
+
+ //Weapon Collision
+ {
+ int wid = checkWeaponCollision(mask);
+ if (wid != -1) {
+ weaponHit(weapons[wid]);
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+ }
+
+ if (h->y >= 520) {
+ enemyDestroy(h->id);
+ }
+}
+
+void hydrarockDraw(Hydrarock* h)
+{
+ int cropX = 128;
+
+ cropX += (int)h->imageIndex * 64;
+
+ PHL_DrawSurfacePart(h->x - 32, h->y - 32, cropX, 128, 64, 64, images[imgMisc32]);
+}
+
+//#electricity
+void createHydrashock(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Hydrashock* h = malloc(sizeof *h);
+ h->id = i;
+
+ h->timer = 0;
+
+ h->x = x;
+ h->y = y;
+
+ h->angle = 0;
+
+ h->imageIndex = 0;
+
+ e->data = h;
+ e->enemyStep = hydrashockStep;
+ e->enemyDraw = hydrashockDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+
+ PHL_PlaySound(sounds[sndShot03], CHN_ENEMIES);
+ }
+ }
+}
+
+void hydrashockStep(Hydrashock* h)
+{
+ //Animate
+ {
+ h->imageIndex += 0.5;
+ if (h->imageIndex >= 4) {
+ h->imageIndex -= 4;
+ }
+ }
+
+ h->timer += 1;
+
+ if (h->timer >= 20) {
+ if (h->timer == 20) {
+ //Set angle
+ h->angle = (atan2(h->x - (herox), heroy + 20 - h->y) * 180 / PI) + 90;
+ }
+
+ h->timer = 22;
+
+ //Movement
+ {
+ int spd = 5;
+ h->x += lengthdir_x(h->angle, spd);
+ h->y += lengthdir_y(h->angle, spd);
+ }
+ }
+
+ //Setup mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = 28;
+ mask.h = 28;
+ mask.x = h->x - mask.w / 2;
+ mask.y = h->y - mask.h / 2;
+ }
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ if (heroHit(25, h->x) == 1) {
+ heroStun();
+ }
+ }
+ }
+
+ //Destroy if outside of room
+ {
+ if (mask.x > 660 || mask.x + mask.w < -20 || mask.y > 500 || mask.y + mask.h < -20) {
+ enemyDestroy(h->id);
+ }
+ }
+}
+
+void hydrashockDraw(Hydrashock* h)
+{
+ if (h->timer % 2 == 0) {
+ int cropX = (int)h->imageIndex * 64;
+
+ PHL_DrawSurfacePart(h->x - 32, h->y - 32, cropX, 192, 64, 64, images[imgMisc32]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/hydra.h b/contrib/games/hydracastlelabyrinth/src/enemies/hydra.h
new file mode 100644
index 0000000000..40cc28f232
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/hydra.h
@@ -0,0 +1,65 @@
+#ifndef HYDRA_H
+#define HYDRA_H
+
+typedef struct {
+ int id;
+ int hp, blink;
+ double x, y;
+ double hsp, vsp;
+ double imageIndex;
+ int state, timer;
+ int patternCounter;
+ char onground;
+ char noheads;
+ int headid[4];
+} Hydra;
+
+void createHydra(int x);
+
+typedef struct {
+ int id;
+ int hp, blink;
+ int dir;
+ int position; //0 = lower 1 = higher
+ double imageIndex;
+ double neckRot;
+ int state, timer, counter;
+ int bodyid;
+ double bodyposX[7];
+ double bodyposY[7];
+} Hydrahead;
+
+int createHydrahead(int dir, int position, int bodyid);
+
+typedef struct {
+ int id;
+ double x, y;
+ double hsp, vsp;
+ char inwall;
+ char bounce;
+ double imageIndex;
+} Hydragoop;
+
+void createHydragoop(int x, int y, int hsp, int vsp);
+
+typedef struct {
+ int id;
+ double x, y;
+ double vsp;
+ char bounce;
+ double imageIndex;
+} Hydrarock;
+
+void createHydrarock();
+
+typedef struct {
+ int id;
+ int timer;
+ double x, y;
+ double angle;
+ double imageIndex;
+} Hydrashock;
+
+void createHydrashock(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.c b/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.c
new file mode 100644
index 0000000000..def3198966
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.c
@@ -0,0 +1,195 @@
+#include "jellyfish.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+void jellyfishStep(Jellyfish* j);
+void jellyfishDraw(Jellyfish* j);
+
+void createJellyfish(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Jellyfish* j = malloc(sizeof *j);
+ j->id = i;
+
+ j->x = x;
+ j->y = j->ystart = y;
+ j->ystart += 20;
+
+ j->spd = 0;
+ j->angle = 0;
+
+ j->state = 0;
+ j->imageIndex = 0;
+
+ e->data = j;
+ e->enemyStep = jellyfishStep;
+ e->enemyDraw = jellyfishDraw;
+ e->type = 20;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void jellyfishStep(Jellyfish* j)
+{
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = mask.h = 30;
+ mask.x = j->x + 20 - (mask.w / 2);
+ mask.y = j->y + 20 - (mask.h / 2);
+
+ //Idle float
+ if (j->state == 0)
+ {
+ //Animate
+ j->imageIndex += 0.06;
+ if (j->imageIndex >= 4) {
+ j->imageIndex -= 4;
+ }
+
+ //Movement
+ j->angle += 2.5;
+ if (j->angle >= 360) { j->angle -= 360; }
+ j->y = j->ystart + (20 * sin(j->angle * 3.14159 / 180));
+
+ //Update mask
+ mask.y = j->y + 20 - (mask.h / 2);
+
+ //if player is close enough
+ Mask area;
+ area.unused = area.circle = 0;
+ area.w = area.h = 160;
+ area.x = j->x - 60;
+ area.y = j->y - 60;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ j->state = 1;
+ j->spd = 0;
+ }
+ }
+ //Attack
+ if (j->state == 1)
+ {
+ //Setup
+ if (j->spd == 0) {
+ PHL_PlaySound(sounds[sndPi02], CHN_ENEMIES);
+ j->spd = 3;
+
+ //Move Right
+ if (herox > j->x + 20) {
+ //Move Up
+ if (heroy < j->y) {
+ j->angle = 135;
+ }
+ //Move Down
+ else {
+ j->angle = 45;
+ }
+ }
+ //Move Left
+ else{
+ //Move Up
+ if (heroy < j->y) {
+ j->angle = 225;
+ }
+ //Move Down
+ else {
+ j->angle = 315;
+ }
+ }
+ }
+
+ //Movement
+ j->x += (j->spd) * sin(j->angle * 3.14159 / 180);
+ j->y += (j->spd) * cos(j->angle * 3.14159 / 180);
+
+ //Slow down
+ j->spd -= 0.075;
+ if (j->spd <= 0) {
+ j->spd = 0;
+ j->state = 2;
+ }
+ }
+ //Stablize
+ if (j->state == 2)
+ {
+ //Setup
+ if (j->spd == 0) {
+ j->spd = 1;
+ j->ystart = j->y - 20;
+ j->angle = 80;
+ }
+
+ //Movement
+ j->angle += 2.5;
+ if (j->angle >= 360) { j->angle -= 360; }
+ j->y = j->ystart + (20 * sin(j->angle * 3.14159 / 180));
+
+
+ if (j->angle >= 180) {
+ j->state = 0;
+ j->ystart = j->y - 20;
+ j->angle = 100;
+ }
+ }
+
+ //Update Mask
+ mask.x = j->x + 20 - (mask.w / 2);
+ mask.y = j->y + 20 - (mask.h / 2);
+
+ //Collide with hero
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, j->x + 20);
+ }
+
+ //Sword collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ spawnCollectable(j->x + 20, j->y);
+ weaponHit(weapons[i]);
+
+ createEffect(2, j->x - 12, j->y - 12);
+ enemyDestroy(j->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+}
+
+void jellyfishDraw(Jellyfish* j)
+{
+ int frame = 0;
+
+ //if (j->state == 0) {
+ int animation[4] = { 0, 1, 0, 2};
+ frame = animation[(int)j->imageIndex];
+ //}
+
+ if (j->state == 1) {
+ if (j->angle == 135) {
+ frame = 3;
+ }
+ else if (j->angle == 225) {
+ frame = 4;
+ }
+ else if (j->angle == 315) {
+ frame = 5;
+ }
+ else {
+ frame = 6;
+ }
+ }
+
+ PHL_DrawSurfacePart(j->x, j->y, frame * 40, 520, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.h b/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.h
new file mode 100644
index 0000000000..c247fe3dcd
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/jellyfish.h
@@ -0,0 +1,16 @@
+#ifndef JELLYFISH_H
+#define JELLYFISH_H
+
+typedef struct {
+ int id;
+ double x, y;
+ int ystart;
+ double spd;
+ double angle;
+ int state;
+ double imageIndex;
+} Jellyfish;
+
+void createJellyfish(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/knight.c b/contrib/games/hydracastlelabyrinth/src/enemies/knight.c
new file mode 100644
index 0000000000..06df4b704d
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/knight.c
@@ -0,0 +1,232 @@
+#include "knight.h"
+#include "../enemy.h"
+#include "../hero.h"
+#include "../PHL.h"
+#include "../game.h"
+#include
+
+void knightDestroy(Knight* k);
+
+void createKnight(int x, int y, int type)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Knight* k = malloc(sizeof *k);
+
+ k->id = i;
+ k->type = type;
+
+ k->x = x;
+ k->y = y;
+
+ //They face the player when they are spawned
+ k->dir = -1;
+ if (herox > x + 20) {
+ k->dir = 1;
+ }
+
+ k->vsp = 0;
+ k->grav = 0.2;
+
+ k->state = 0;
+ k->timer = 60 + (((rand() % 5) + 1) * 60);
+ k->imageIndex = 0;
+
+ k->hp = 2;
+ //Shield Knight
+ if (k->type == 1) {
+ k->hp = 3;
+ }
+
+ k->invincible = 0;
+ k->shieldhit = 0;
+
+ k->mask.circle = 0;
+ k->mask.unused = 0;
+ k->mask.x = x + 4;
+ k->mask.y = y + 8;
+ k->mask.w = 32;
+ k->mask.h = 32;
+
+ e->data = k;
+ e->enemyStep = knightStep;
+ e->enemyDraw = knightDraw;
+ e->type = 3;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void knightStep(Knight* k)
+{
+ if (k->shieldhit > 0) {
+ k->shieldhit -= 1;
+ }
+
+ if (k->invincible > 0) {
+ k->invincible -= 1;
+ }
+
+ if (k->state == 0) { //Walk
+ k->imageIndex += 0.1;
+ if (k->imageIndex >= 2) {
+ k->imageIndex -= 2;
+ }
+
+ double spd = 1;
+ if (k->type == 1) {
+ spd = 0.5;
+ }
+ spd *= k->dir;
+
+ k->x += spd;
+
+ k->mask.x = k->x + 4;
+ k->mask.y = k->y + 8;
+
+ Mask emask;
+ emask.circle = emask.unused = 0;
+ emask.w = 16;
+ emask.h = 32;
+ emask.x = k->x + 12;
+ emask.y = k->y + 8;
+
+ //Turn when colliding with a wall
+ if (checkTileCollision(1, emask)) {
+ k->dir *= -1;
+ }else{
+ //Turn when on an edge
+ k->mask.x += k->mask.w * k->dir;
+ k->mask.y += 1;
+ PHL_Rect collide = getTileCollision(1, k->mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, k->mask);
+ }
+ if (collide.x == -1) {
+ k->dir *= -1;
+ }
+ }
+
+ if (k->x + 20 >= 640 || k->x + 20 <= 0) {
+ k->dir *= -1;
+ }
+
+ k->mask.x = k->x + 4;
+ k->mask.y = k->y + 8;
+
+ k->timer -= 1;
+ if (k->timer <= 0) {
+ k->state = 1;
+ k->timer = 120;
+ k->imageIndex = 0;
+ }
+ }
+ else if (k->state == 1) { //Wait
+ k->timer -= 1;
+ if (k->timer <= 0) {
+ k->state = 0;
+ k->dir = 1;
+ if (herox < k->x + 20) {
+ k->dir = -1;
+ }
+ k->timer = 60 + (((rand() % 5) + 1) * 60);
+ }
+ }
+
+ //Green Sword Knight
+ if (k->type == 0) {
+ //Hit player
+ Mask swordMask;
+ swordMask.unused = 0;
+ swordMask.circle = 0;
+ swordMask.x = k->x + (24 * k->dir);
+ swordMask.y = k->y + 20;
+ swordMask.w = 40;
+ swordMask.h = 10;
+
+ if (checkCollision(getHeroMask(), swordMask)) {
+ heroHit(30, k->x + 20);
+ }
+ }
+
+ if (checkCollision(getHeroMask(), k->mask)) {
+ heroHit(15, k->x + 20);
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(k->mask, weapons[i]->weaponMask)) {
+ char gotHit = 1;
+
+ int weapondir = weapons[i]->dir;
+ weaponHit(weapons[i]);
+
+ //Shield Collision
+ if (k->type == 1) {
+ if (weapondir == k->dir * -1) {
+ gotHit = 0;
+ k->shieldhit = 15;
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+ }
+
+ if (gotHit == 1) {
+ k->hp -= 1;
+ k->invincible = 15;
+
+ i = MAX_WEAPONS;
+ }
+
+ if (k->hp <= 0) {
+ knightDestroy(k);
+ }
+ }
+ }
+ }
+ }
+}
+
+void knightDraw(Knight* k)
+{
+ if (k->invincible % 2 == 0) {
+ int cx = 0, cy = 200;
+
+ //Green Knight's Sword
+ if (k->type == 0) {
+ int swordimg = 0;
+ if (k->dir == -1) {
+ swordimg = 1;
+ }
+ int posx = 24, posy = 8;
+ if ((int)k->imageIndex == 1) {
+ posx -= 2;
+ posy -= 2;
+ }
+ PHL_DrawSurfacePart(k->x + (posx * k->dir), k->y + posy, 160 + (swordimg * 40), 200, 40, 40, images[imgEnemies]);
+ }
+
+ //Shield Knight
+ if (k->type == 1) {
+ cx = 240;
+ }
+
+ if (k->dir == -1) {
+ cx += 80;
+ }
+ PHL_DrawSurfacePart(k->x, k->y, cx + ((int)k->imageIndex * 40), cy, 40, 40, images[imgEnemies]);
+ }
+}
+
+void knightDestroy(Knight* k)
+{
+ createEffect(2, k->x - 12, k->y - 6);
+ spawnCollectable(k->x + 20, k->y);
+ enemyDestroy(k->id);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/knight.h b/contrib/games/hydracastlelabyrinth/src/enemies/knight.h
new file mode 100644
index 0000000000..a709fc878b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/knight.h
@@ -0,0 +1,23 @@
+#ifndef KNIGHT_H
+#define KNIGHT_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id, type;
+ double x, y,
+ vsp, grav;
+ int dir, state, timer;
+ double imageIndex;
+ int hp, invincible;
+ int shieldhit;
+
+ Mask mask;
+} Knight;
+
+void createKnight(int x, int y, int type);
+
+void knightStep(Knight* k);
+void knightDraw(Knight* k);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.c b/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.c
new file mode 100644
index 0000000000..15879a4cd7
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.c
@@ -0,0 +1,367 @@
+#include "lolidra.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+int boss5flag = 38;
+
+void lolidraDestroy(Lolidra* l);
+int getNumOfMinions();
+
+void createLolidra(int x, int y)
+{
+ if (flags[boss5flag] == 0) { //have not beaten boss 5
+ PHL_FreeSurface(images[imgBoss]);
+ images[imgBoss] = PHL_LoadQDA("boss05.bmp");
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ //Boss start
+ setBossRoom();
+
+ Enemy* e = malloc(sizeof *e);
+ Lolidra* l = malloc(sizeof *l);
+ l->id = i;
+
+ l->x = x;
+ l->y = y;
+
+ l->positionY = l->y;
+
+ l->imageIndex = 0;
+ l->hoverRot = 0;
+
+ l->hp = 100;
+ //l->hp = 1;
+ l->state = 0;
+ l->invincible = 0;
+ l->visible = 1;
+
+ l->timer = 0;
+ l->counter = 0;
+
+ l->mask.unused = 0;
+ l->mask.circle = 1;
+ l->mask.w = 46;
+ l->mask.h = 0;
+ l->mask.x = l->x;
+ l->mask.y = l->y;
+
+ e->data = l;
+ e->enemyStep = lolidraStep;
+ e->enemyDraw = lolidraDraw;
+ e->type = 44;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+ }
+}
+
+void lolidraStep(Lolidra* l)
+{
+ char dead = 0;
+
+ l->imageIndex += 0.1;
+ if (l->imageIndex >= 3) {
+ l->imageIndex -= 3;
+ }
+
+ if (l->invincible > 0) {
+ l->invincible -= 1;
+ }
+
+ //Spawn minions
+ if (l->state == 0)
+ {
+ if (l->counter < 5) {
+ l->counter += 1;
+ }else{
+ if (getNumOfMinions() < 10) {
+ l->counter = 0;
+ PHL_PlaySound(sounds[sndPi02], CHN_ENEMIES);
+ createMinion(l->x, l->y - 10);
+ }
+ }
+
+ l->timer += 1;
+ if (l->timer >= 600){
+ l->counter = 0;
+ l->timer = 0;
+ l->state = 1;
+ l->invincible = 20;
+ }
+ }
+ //Disappear
+ else if (l->state == 1 || l->state == 3)
+ {
+ if (l->invincible <= 0) {
+ l->visible = 0;
+ }
+
+ if (l->timer == 0) {
+ PHL_PlaySound(sounds[sndPi10], CHN_ENEMIES);
+ }
+
+ l->timer += 1;
+ if (l->timer >= 330) {
+ PHL_PlaySound(sounds[sndPi03], CHN_ENEMIES);
+ l->timer = 0;
+ l->visible = 1;
+ l->invincible = 20;
+ l->x = herox;
+ l->positionY = heroy - 40;
+
+ if (l->state == 1) {
+ l->state = 2;
+ }
+ if (l->state == 3) {
+ l->state = 0;
+ }
+ }
+ }
+ //Pop-up
+ else if (l->state == 2)
+ {
+ l->timer += 1;
+ if (l->timer >= 180) {
+ l->timer = 0;
+ l->state = 3;
+ l->invincible = 20;
+ }
+ }
+ //Death
+ else if (l->state == 4)
+ {
+ l->y += 0.2;
+
+ l->timer -= 1;
+ l->invincible -= 1;
+
+ if (l->timer % 12 == 0) {
+ createEffect(2, l->x - 64 + (rand() % 128), l->y - 64 + (rand() % 128));
+ }
+
+ if (l->timer <= 0) {
+ lolidraDestroy(l);
+ dead = 1;
+ }
+ }
+
+ if (dead == 0)
+ {
+ if (l->state != 4) {
+ //Hover
+ l->hoverRot += 5;
+ if (l->hoverRot >= 360) {
+ l->hoverRot -= 360;
+ }
+ l->y = l->positionY + (5 * sin(l->hoverRot * 3.14159 / 180));
+
+ //Update Mask
+ l->mask.x = l->x;
+ l->mask.y = l->y;
+
+ //Collisions
+ if (l->visible == 1) {
+ //Collide with Hero
+ if (checkCollision(getHeroMask(), l->mask) == 1) {
+ heroHit(30, l->x);
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(l->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ l->invincible = 15;
+ l->hp -= 1;
+
+ //Die
+ if (l->hp <= 0) {
+ l->state = 4;
+ l->timer = 180;
+ l->invincible = 200;
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void lolidraDraw(Lolidra* l)
+{
+ if (l->visible == 1 && l->invincible % 2 == 0) {
+ PHL_DrawSurfacePart(l->x - 64, l->y - 74, ((int)l->imageIndex) * 128, 0, 128, 128, images[imgBoss]);
+ }
+}
+
+void lolidraDestroy(Lolidra* l)
+{
+ enemyDestroy(l->id);
+ bossDefeatedFlag = 1;
+ roomSecret = 1;
+
+ flags[boss5flag] = 1;
+ PHL_StopMusic();
+}
+
+//Minions
+void createMinion(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Minion* m = malloc(sizeof *m);
+ m->id = i;
+
+ m->state = 0;
+ m->timer = 0;
+
+ m->x = x;
+ m->y = y;
+
+ m->positionY = m->y;
+
+ m->dir = rand() % 360;
+ m->spd = 8;
+
+ m->imageIndex = 0;
+
+ m->mask.circle = 1;
+ m->mask.unused = 0;
+ m->mask.w = 10;
+ m->mask.x = 0;
+ m->mask.y = 0;
+
+ e->data = m;
+ e->enemyStep = minionStep;
+ e->enemyDraw = minionDraw;
+ e->type = 23;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void minionStep(Minion* m)
+{
+ char dead = 0;
+
+ m->imageIndex += 0.2;
+ if (m->imageIndex >= 2) {
+ m->imageIndex -= 2;
+ }
+
+ //Slow down
+ if (m->state == 0)
+ {
+ if (m->spd > 0) {
+ m->spd -= 0.3;
+ }
+
+ if (m->spd <= 0) {
+ m->positionY = m->y;
+ m->spd = 0;
+ m->dir = 0;
+ m->state = 1;
+ }
+ }
+ //Hover
+ else if (m->state == 1)
+ {
+ //Hover
+ m->dir += 5;
+ if (m->dir >= 360) {
+ m->dir -= 360;
+ }
+ m->y = m->positionY + (10 * sin(m->dir * 3.14159 / 180));
+
+ m->timer += 1;
+ if (m->timer >= 120) {
+ m->timer = 0;
+ m->state = 2;
+ m->spd = (rand() % 2) + 1;
+ m->dir = (atan2(heroy + 20 - m->y, m->x - herox) * 180 / 3.14159) + 270;
+ }
+ }
+ //Suicide
+ else if (m->state == 2)
+ {
+ m->timer += 1;
+ if (m->timer >= 120) {
+ createEffect(5, m->x, m->y);
+ enemyDestroy(m->id);
+ dead = 1;
+ }
+ }
+
+ if (dead == 0)
+ {
+ //Movement
+ if (m->spd != 0) {
+ m->x += m->spd * sin(m->dir * 3.14159 / 180);
+ m->y += m->spd * cos(m->dir * 3.14159 / 180);
+ }
+
+ //Update Mask
+ m->mask.x = m->x;
+ m->mask.y = m->y;
+
+ //Collide with Hero
+ if (checkCollision(getHeroMask(), m->mask) == 1) {
+ if (heroHit(10, m->x) == 1) {
+ heroPoison();
+ }
+ }
+
+ //Weapon collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(m->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ createEffect(2, m->x - 32, m->y - 32);
+ enemyDestroy(m->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+}
+
+void minionDraw(Minion* m)
+{
+ PHL_DrawSurfacePart(m->x - 32, m->y - 32, ((int)m->imageIndex) * 64, 128, 64, 64, images[imgBoss]);
+}
+
+int getNumOfMinions()
+{
+ int result = 0;
+
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] != NULL) {
+ if (enemies[i]->type == 23) {
+ result += 1;
+ }
+ }
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.h b/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.h
new file mode 100644
index 0000000000..f9a9b516e6
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/lolidra.h
@@ -0,0 +1,40 @@
+#ifndef LOLIDRA_H
+#define LOLIDRA_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double positionY;
+ double imageIndex, hoverRot;
+ int hp, state, invincible,
+ visible, timer, counter;
+
+ Mask mask;
+} Lolidra;
+
+void createLolidra(int x, int y);
+
+void lolidraStep(Lolidra* l);
+void lolidraDraw(Lolidra* l);
+
+//Minion
+typedef struct {
+ int id;
+ int state;
+ int timer;
+ double x, y;
+ double positionY;
+ double imageIndex;
+ double dir, spd;
+
+ Mask mask;
+} Minion;
+
+void createMinion(int x, int y);
+
+void minionStep(Minion* m);
+void minionDraw(Minion* m);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.c b/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.c
new file mode 100644
index 0000000000..4eda905cad
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.c
@@ -0,0 +1,78 @@
+#include "pendulum.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+void createPendulum(int x, int y, int side)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Pendulum* p = malloc(sizeof *p);
+ p->id = i;
+
+ p->x = x;
+ p->y = y;
+
+ p->angle = 0;
+ p->rotCounter = 180;
+ if (side == 1) {
+ p->rotCounter += 180;
+ }
+
+ p->mask.circle = 1;
+ p->mask.unused = 0;
+ p->mask.w = 24;
+ p->mask.x = 0;
+ p->mask.y = 0;
+
+ e->data = p;
+ e->enemyStep = pendulumStep;
+ e->enemyDraw = pendulumDraw;
+ e->type = 22;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void pendulumStep(Pendulum* p)
+{
+ p->rotCounter += 2;
+ if (p->rotCounter >= 360) {
+ p->rotCounter -= 360;
+ }
+
+ p->angle += (3.15 * cos(p->rotCounter * 3.14159 / 180));
+
+ //Update Mask
+ p->mask.x = p->x + (96 * cos((p->angle + 90) * 3.14159 / 180));
+ p->mask.y = p->y + (96 * sin((p->angle + 90) * 3.14159 / 180));
+
+ //Hit Player
+ if (checkCollision(p->mask, getHeroMask())) {
+ heroHit(15, p->mask.x);
+ }
+}
+
+void pendulumDraw(Pendulum* p)
+{
+ int drawX = p->x,
+ drawY = p->y;
+
+ int len[] = {0, 16, 32, 48, 66, 96};
+ int cropX[] = {64, 64, 64, 64, 0, 576};
+ int cropY[] = {128, 128, 128, 128, 128, 64};
+
+ int i;
+ for (i = 0; i < 6; i++) {
+ drawX = p->x + (len[i] * cos((p->angle + 90) * 3.14159 / 180));
+ drawY = p->y + (len[i] * sin((p->angle + 90) * 3.14159 / 180));
+
+ PHL_DrawSurfacePart(drawX- 32, drawY - 32, cropX[i], cropY[i], 64, 64, images[imgMisc32]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.h b/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.h
new file mode 100644
index 0000000000..faaba05f42
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/pendulum.h
@@ -0,0 +1,19 @@
+#ifndef PENDULUM_H
+#define PENDULUM_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double rotCounter, angle;
+
+ Mask mask;
+} Pendulum;
+
+void createPendulum(int x, int y, int side);
+
+void pendulumStep(Pendulum* p);
+void pendulumDraw(Pendulum* p);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.c b/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.c
new file mode 100644
index 0000000000..1dfc30b261
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.c
@@ -0,0 +1,176 @@
+#include "podoboo.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include "../game.h"
+#include "../effect.h"
+#include
+#include
+
+void podobooStep(Podoboo* p);
+void podobooDraw(Podoboo* p);
+
+void createPodoboo(int x, int y, int offset, int height)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Podoboo* p = malloc(sizeof *p);
+
+ p->id = i;
+
+ p->x = x;
+ p->y = p->ystart = y;
+
+ p->hp = 2;
+ p->blink = 0;
+
+ p->yoffset = p->rot = 0;
+
+ p->vsp = 0;
+ p->grav = 0.13;
+
+ p->jumpheight = -5;
+ /*
+ if (height == 1) {
+ p->jumpheight = -5.4;
+ }
+ */
+ if (height == 1) {
+ p->jumpheight = -7;
+ }
+
+ p->imageIndex = 0;
+
+ p->timer = 30 * offset;
+ p->state = 0;
+
+ e->data = p;
+ e->enemyStep = podobooStep;
+ e->enemyDraw = podobooDraw;
+ e->type = 15;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void podobooStep(Podoboo* p)
+{
+ //Blinking
+ {
+ if (p->blink > 0) {
+ p->blink -= 1;
+ }
+ }
+
+ p->timer -= 1;
+
+ //Patterns
+ {
+ //Float in lava
+ if (p->state == 0)
+ {
+ //Animate
+ p->imageIndex += 0.1;
+ if (p->imageIndex >= 2) {
+ p->imageIndex -= 2;
+ }
+
+ //Bob movement
+ p->rot += 5;
+ if (p->rot >= 360) {
+ p->rot -= 360;
+ }
+ p->y = p->ystart + (5 * sin(p->rot * 3.14159 / 180));
+
+ //Jump
+ if (p->timer <= 0) {
+ p->state = 1;
+ createLavaSplash(p->x + 20, p->y);
+
+ p->y = p->ystart;
+ p->vsp = p->jumpheight;
+ }
+ }
+ //In air
+ else if (p->state == 1)
+ {
+ //Animate
+ p->imageIndex += 0.25;
+ if (p->imageIndex >= 3) {
+ p->imageIndex -= 3;
+ }
+
+ //Movement
+ p->y += p->vsp;
+ p->vsp += p->grav;
+
+ //Land in lava again
+ if (p->vsp > 0 && p->y >= p->ystart) {
+ createLavaSplash(p->x + 20, p->y);
+ p->y = p->ystart;
+ p->state = 0;
+ p->vsp = 0;
+ p->timer = 60;
+ }
+ }
+
+ }
+
+ //Create Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = mask.h = 30;
+ mask.x = p->x + 5;
+ mask.y = p->y + 5;
+ }
+
+ //Collide with hero
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ p->hp -= 1;
+ p->blink = 15;
+
+ //Death
+ if (p->hp <= 0) {
+ createEffect(2, p->x - 12, p->y - 12);
+ spawnCollectable(p->x + 20, p->y);
+ enemyDestroy(p->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void podobooDraw(Podoboo* p)
+{
+ if (p->blink % 2 == 0) {
+ int thisImage = p->imageIndex;
+
+ if (p->state == 1) {
+ thisImage += 2;
+ }
+
+ PHL_DrawSurfacePart(p->x, p->y, 280 + (40 * thisImage), 520, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.h b/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.h
new file mode 100644
index 0000000000..eec8ba648d
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/podoboo.h
@@ -0,0 +1,20 @@
+#ifndef PODOBOO_H
+#define PODOBOO_H
+
+typedef struct {
+ int id;
+ double x, y;
+ int ystart;
+ int hp;
+ int blink;
+ int rot;
+ double yoffset;
+ double vsp, grav;
+ double jumpheight;
+ double imageIndex;
+ int timer, state;
+} Podoboo;
+
+void createPodoboo(int x, int y, int offset, int height);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.c b/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.c
new file mode 100644
index 0000000000..5997ce4f18
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.c
@@ -0,0 +1,318 @@
+#include "poisonknight.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void poisonknightStep(Poisonknight* p);
+void poisonknightDraw(Poisonknight* p);
+
+void goopStep(Goop* g);
+void goopDraw(Goop* g);
+
+void createPoisonknight(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Poisonknight* p = malloc(sizeof *p);
+ p->id = i;
+ p->hp = 2;
+
+ p->x = x;
+ p->y = y;
+
+ p->imageIndex = 0;
+
+ p->dir = 1;
+ if (herox < p->x) {
+ p->dir = -1;
+ }
+
+ p->blink = 0;
+ p->timer = 0;
+ p->state = 0;
+
+ e->data = p;
+ e->enemyStep = poisonknightStep;
+ e->enemyDraw = poisonknightDraw;
+ e->type = 29;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void poisonknightStep(Poisonknight* p)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ p->imageIndex += 0.1;
+ if (p->imageIndex >= 2) {
+ p->imageIndex -= 2;
+ }
+
+ if (p->blink > 0) {
+ p->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 38;
+ mask.h = 36;
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + (40 - mask.h);
+ }
+
+ //Walk
+ if (p->state == 0) {
+ double hsp = 1;
+
+ p->x += hsp * p->dir;
+ mask.x = p->x + ((40 - mask.w) / 2);
+
+ //Hit wall
+ if (checkTileCollision(1, mask) == 1) {
+ p->dir *= -1;
+ }
+
+ //On wall edge
+ else {
+ mask.x += mask.w * p->dir;
+ mask.y += 10;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x == -1) {
+ p->dir *= -1;
+ }
+ }
+
+ //Hero is close enough
+ {
+ if (p->timer <= 0) {
+ Mask area;
+ area.circle = area.unused = 0;
+ area.x = p->x - 110;
+ area.y = p->y;
+ area.w = 260;
+ area.h = 40;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ p->dir = 1;
+ if (herox < p->x + 20) {
+ p->dir = -1;
+ }
+ p->imageIndex = 1;
+ p->timer = 0;
+ p->state = 1;
+ }
+
+ }else{
+ p->timer -= 1;
+ }
+ }
+ }
+
+ //*beat*
+ else if (p->state == 1)
+ {
+ //Animate
+ p->imageIndex = 1;
+
+ p->timer += 1;
+ if (p->timer >= 15) {
+ p->state = 2;
+ p->timer = 0;
+ p->imageIndex = 2;
+ }
+ }
+
+ //Shoot goop
+ else if (p->state == 2)
+ {
+ //Shoot goop
+ if (p->timer == 0) {
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ createGoop(p->x + (20 * p->dir), p->y - 2, p->dir);
+ }
+
+ //Animate
+ p->imageIndex = 2;
+
+ p->timer += 1;
+ if (p->timer >= 25) {
+ p->state = 0;
+ p->timer = 240;
+ }
+ }
+
+ //Update Mask
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + (40 - mask.h);
+
+
+ //Collide with hero
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ p->hp -= 1;
+ p->blink = 15;
+
+ if (p->hp <= 0) {
+ dead = 1;
+ createEffect(2, p->x - 12, p->y - 6);
+ spawnCollectable(p->x + 20, p->y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(p->id);
+ }
+ }
+}
+
+void poisonknightDraw(Poisonknight* p)
+{
+ if (p->blink % 2 == 0) {
+ int cropX = (int)p->imageIndex * 40;
+
+ if (p->dir == -1) {
+ cropX += 120;
+ }
+
+ PHL_DrawSurfacePart(p->x, p->y, cropX, 280, 40, 40, images[imgEnemies]);
+ }
+}
+
+//Poison Goop
+void createGoop(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Goop* g = malloc(sizeof *g);
+ g->id = i;
+
+ g->x = x;
+ g->y = y;
+
+ g->dir = dir;
+
+ g->imageIndex = 0;
+
+ e->data = g;
+ e->enemyStep = goopStep;
+ e->enemyDraw = goopDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void goopStep(Goop* g)
+{
+ char dead = 0;
+
+ //Animate
+ {
+ g->imageIndex += 0.33;
+ if (g->imageIndex >= 3) {
+ g->imageIndex -= 3;
+ }
+ }
+
+ //Movement
+ {
+ int hsp = 4;
+ g->x += hsp * g->dir;
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 22;
+ mask.h = 22;
+ mask.x = g->x + ((40 - mask.w) / 2);
+ mask.y = g->y + ((40 - mask.h) / 2);
+ }
+
+ //Collide with hero
+ {
+ //Collide with shield
+ if (checkCollision(mask, shieldMask) == 1) {
+ dead = 1;
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ createEffect(1, g->x, g->y);
+ }
+ //Collide with hero
+ else if (checkCollision(mask, getHeroMask()) == 1) {
+ if (heroHit(10, mask.x + (mask.w / 2)) == 1) {
+ heroPoison();
+ }
+ }
+ }
+
+ //Collide with wall
+ {
+ if (checkTileCollision(1, mask) == 1) {
+ dead = 1;
+ createEffect(1, g->x, g->y);
+ }
+ }
+
+ //Destroy if out of room
+ {
+ if (g->x + 40 < 0 || g->x > 640) {
+ dead = 1;
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(g->id);
+ }
+ }
+}
+
+void goopDraw(Goop* g)
+{
+ int cropX = 400 + ((int)g->imageIndex * 40);
+
+ if (g->dir == -1) {
+ cropX += 120;
+ }
+
+ PHL_DrawSurfacePart(g->x, g->y, cropX, 520, 40, 40, images[imgMisc20]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.h b/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.h
new file mode 100644
index 0000000000..13350d0753
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/poisonknight.h
@@ -0,0 +1,26 @@
+#ifndef POISONKNIGHT_H
+#define POISONKNIGHT_H
+
+typedef struct {
+ int id;
+ int hp;
+ double x, y;
+ double imageIndex;
+ int dir;
+ int blink;
+ int timer;
+ int state;
+} Poisonknight;
+
+void createPoisonknight(int x, int y);
+
+typedef struct {
+ int id;
+ double x, y;
+ int dir;
+ double imageIndex;
+} Goop;
+
+void createGoop(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.c b/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.c
new file mode 100644
index 0000000000..8feec0fcca
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.c
@@ -0,0 +1,375 @@
+#include "pumpkin.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void pumpkinenemyStep(Pumpkinenemy* p);
+void pumpkinenemyDraw(Pumpkinenemy* p);
+
+void pumpkinheadStep(Pumpkinhead* p);
+void pumpkinheadDraw(Pumpkinhead* p);
+
+void createPumpkinenemy(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Pumpkinenemy* p = malloc(sizeof *p);
+
+ p->id = i;
+
+ p->hp = 3;
+ p->blink = 0;
+
+ p->x = x;
+ p->y = y;
+
+ p->dir = 1;
+ if (herox < p->x + 20) {
+ p->dir = -1;
+ }
+
+ p->imageIndex = 0;
+
+ p->state = 0;
+ p->timer = 0;
+
+ e->data = p;
+ e->enemyStep = pumpkinenemyStep;
+ e->enemyDraw = pumpkinenemyDraw;
+ e->type = 32;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void pumpkinenemyStep(Pumpkinenemy* p)
+{
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = 20;
+ mask.h = 38;
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + (40 - mask.h);
+ }
+
+ //Animate
+ {
+ p->imageIndex += 0.1;
+ if (p->imageIndex >= 2) {
+ p->imageIndex -= 2;
+ }
+
+ if (p->blink > 0) {
+ p->blink -= 1;
+ }
+ }
+
+ //Walking
+ if (p->state == 0)
+ {
+ double hsp = 0.5;
+ p->x += hsp * p->dir;
+ mask.x = p->x + ((40 - mask.w) / 2);
+
+ //Hit wall
+ {
+ if (checkTileCollision(1, mask) == 1 || mask.x > 640 || mask.x + mask.w < 0) {
+ p->dir *= -1;
+ }
+ }
+
+ //On edge
+ {
+ mask.x += mask.w * p->dir;
+ mask.y += 20;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x == -1) {
+ p->dir *= -1;
+ }
+ }
+
+ //Player is close
+ {
+ if (p->timer <= 0) {
+ Mask area;
+ {
+ area.circle = area.unused = 0;
+ area.w = 240;
+ area.h = 80;
+ area.x = p->x - 100;
+ area.y = p->y - 40;
+ }
+ if (checkCollision(area, getHeroMask()) == 1) {
+ p->state = 1;
+ p->timer = 0;
+ p->dir = 1;
+ if (herox < p->x + 20) {
+ p->dir = -1;
+ }
+ }
+
+ }else{
+ p->timer -= 1;
+ }
+ }
+
+ }
+
+ //Deheaded
+ else if (p->state == 1) {
+ //Animate
+ {
+ p->imageIndex = 0;
+ if (p->timer >= 15) {
+ p->imageIndex = 2;
+ }
+ }
+
+ p->timer += 1;
+ if (p->timer == 15) {
+ createPumpkinhead(p->x, p->y - 6, p->dir);
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ }
+
+ if (p->timer >= 40) {
+ p->state = 0;
+ p->imageIndex = 0;
+ p->timer = 300;
+ }
+ }
+
+ //Update Mask
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + (40 - mask.h);
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon Collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ p->hp -= 1;
+ p->blink = 15;
+
+ //Death
+ if (p->hp <= 0) {
+ createEffect(2, p->x - 12, p->y - 6);
+ spawnCollectable(p->x + 20, p->y);
+ enemyDestroy(p->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void pumpkinenemyDraw(Pumpkinenemy* p)
+{
+ if (p->blink % 2 == 0) {
+ int cropX = (int)p->imageIndex * 40;
+
+ if (p->dir == -1) {
+ cropX += 120;
+ }
+
+ PHL_DrawSurfacePart(p->x, p->y, cropX, 560, 40, 40, images[imgEnemies]);
+ }
+}
+
+
+//Pumpkin bomb head
+void createPumpkinhead(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Pumpkinhead* p = malloc(sizeof *p);
+ p->id = i;
+
+ p->dir = dir;
+
+ p->x = x;
+ p->y = y;
+
+ p->vsp = -2;
+
+ p->imageIndex = 0;
+
+ p->state = 0;
+ p->timer = 0;
+
+ e->data = p;
+ e->enemyStep = pumpkinheadStep;
+ e->enemyDraw = pumpkinheadDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void pumpkinheadStep(Pumpkinhead* p)
+{
+ char dead = 0;
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 20;
+ mask.h = 22;
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + ((40 - mask.h) / 2);
+ }
+
+ //Pumpkin head
+ if (p->state == 0)
+ {
+ char explode = 0;
+
+ //Animate
+ {
+ p->imageIndex += 0.1;
+ if (p->imageIndex >= 2) {
+ p->imageIndex -= 2;
+ }
+ }
+
+ //Movement
+ {
+ int hsp = 3;
+ p->x += hsp * p->dir;
+ mask.x = p->x + ((40 - mask.w) / 2);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ p->x = collide.x + 20 - ((20 + (mask.w / 2)) * p->dir) - 20;
+ mask.x = p->x + ((40 - mask.w) / 2);
+ p->dir *= -1;
+ }
+
+ double grav = 0.15;
+ p->y += p->vsp;
+ p->vsp += grav;
+ mask.y = p->y + ((40 - mask.h) / 2);
+
+ collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+
+ if (collide.x != -1) {
+ p->y = collide.y - 40;
+ explode = 1;
+ }
+ }
+
+ //Update Mask
+ mask.x = p->x + ((40 - mask.w) / 2);
+ mask.y = p->y + ((40 - mask.h) / 2);
+
+ //Explode
+ {
+ if (explode == 1) {
+ PHL_PlaySound(sounds[sndBom03], CHN_ENEMIES);
+ p->state = 1;
+ p->imageIndex = 0;
+ p->timer = 0;
+ }
+ }
+
+ //Outside of room
+ {
+ if (mask.y > 480 || mask.x > 640 || mask.x + mask.w < 0) {
+ dead = 1;
+ }
+ }
+
+ }
+
+ //Explosion
+ else if (p->state == 1)
+ {
+ //Update Mask
+ {
+ mask.w = 68;
+ mask.h = 66;
+ mask.x = p->x - 44 + 64 - (mask.w / 2);
+ mask.y = p->y - 44 + (84 - mask.h);
+ }
+
+ //Animate
+ {
+ p->imageIndex += 0.33;
+ if (p->imageIndex >= 12) {
+ dead = 1;
+ }
+ }
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(40, mask.x + (mask.w / 2));
+ }
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(p->id);
+ }
+ }
+}
+
+void pumpkinheadDraw(Pumpkinhead* p)
+{
+ if (p->state == 0) {
+ int cropX = (int)p->imageIndex * 40;
+
+ if (p->dir == -1) {
+ cropX += 80;
+ }
+
+ PHL_DrawSurfacePart(p->x, p->y, cropX, 240, 40, 40, images[imgEnemies]);
+ }
+
+ if (p->state == 1) {
+ int cropX = (int)p->imageIndex * 128;
+ int cropY = 0;
+
+ while (cropX >= 640) {
+ cropX -= 640;
+ cropY += 96;
+ }
+
+ PHL_DrawSurfacePart(p->x - 44, p->y - 44, cropX, cropY, 128, 96, images[imgExplosion]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.h b/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.h
new file mode 100644
index 0000000000..6a2b87c91b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/pumpkin.h
@@ -0,0 +1,27 @@
+#ifndef PUMPKIN_H
+#define PUMPKIN_H
+
+typedef struct {
+ int id;
+ int hp;
+ int blink;
+ double x, y;
+ int dir;
+ double imageIndex;
+ int state, timer;
+} Pumpkinenemy;
+
+void createPumpkinenemy(int x, int y);
+
+typedef struct {
+ int id;
+ int dir;
+ double x, y;
+ double vsp;
+ double imageIndex;
+ int state, timer;
+} Pumpkinhead;
+
+void createPumpkinhead(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/seal.c b/contrib/games/hydracastlelabyrinth/src/enemies/seal.c
new file mode 100644
index 0000000000..f0a3fa9c4f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/seal.c
@@ -0,0 +1,204 @@
+#include "seal.h"
+#include "../game.h"
+#include "../enemy.h"
+#include "../collision.h"
+#include "../hero.h"
+#include
+
+void sealStep(Seal* s);
+void sealDraw(Seal* s);
+
+void createSeal(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Seal* s = malloc(sizeof *s);
+ s->id = i;
+ s->hp = 2;
+
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+
+ s->dir = 1;
+ if (x + 20 > herox) {
+ s->dir = -1;
+ }
+
+ s->state = 0;
+ s->timer = 0;
+
+ s->invincible = 0;
+
+ e->data = s;
+ e->enemyStep = sealStep;
+ e->enemyDraw = sealDraw;
+ e->type = 19;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void sealStep(Seal* s)
+{
+ if (s->invincible > 0) {
+ s->invincible -= 1;
+ }
+
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = mask.h = 28;
+ mask.x = s->x + ((40 - mask.w) / 2);
+ mask.y = s->y + (40 - mask.h);
+
+ //Walk
+ if (s->state == 0)
+ {
+ //Animate
+ s->imageIndex += 0.1;
+ if (s->imageIndex >= 2) {
+ s->imageIndex -= 2;
+ }
+
+ //Check if hit a wall
+ if (checkTileCollision(1, mask) == 1) {
+ s->dir *= -1;
+ }else{
+ //Check if on edge
+ mask.x += mask.w * s->dir;
+ mask.y += mask.h;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x == -1) {
+ s->dir *= -1;
+ }
+
+ mask.x = s->x + ((40 - mask.w) / 2);
+ mask.y = s->y + (40 - mask.h);
+ }
+
+ //Movement
+ s->x += 0.5 * s->dir;
+
+ if (s->timer <= 0) {
+ //Check if player is close enough
+ Mask area;
+ area.unused = area.circle = 0;
+ area.x = s->x - 40;
+ area.y = s->y;
+ area.w = 120;
+ area.h = 120;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ s->state = 1;
+ s->timer = -1;
+ }
+ }else{
+ s->timer -= 1;
+ }
+ }
+
+ //Rear back
+ else if (s->state == 1)
+ {
+ //Setup
+ if (s->timer == -1) {
+ s->imageIndex = 4;
+ s->timer = 20;
+ }
+
+ s->timer -= 1;
+
+ if (s->timer <= 0) {
+ s->state = 2;
+ s->timer = -1;
+ s->imageIndex = 0;
+ }
+ }
+
+ //Tounge attack
+ else if (s->state == 2)
+ {
+ //Setup
+ if (s->timer == -1) {
+ s->timer = 0;
+ PHL_PlaySound(sounds[sndGet01], CHN_ENEMIES);
+ }
+
+ //Animate
+ int animation[41] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 3, 3,
+ 2, 2, 1, 1, 0, 0, 5, 5, 5, 5, 5 };
+
+ s->imageIndex = animation[(int)s->timer];
+
+ //Update mask height to fit tounge
+ int len[6] = { 18, 38, 58, 64, 66, 0};
+ mask.h += len[(int)s->imageIndex];
+
+ s->timer += 1;
+
+ if (s->timer >= 41) {
+ s->state = 0;
+ s->timer = 120;
+ s->imageIndex = 0;
+ }
+ }
+
+ //Hit Player
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(10, s->x + 20);
+ }
+
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ s->hp -= 1;
+ s->invincible = 15;
+ weaponHit(weapons[i]);
+ if (s->hp <= 0) {
+ enemyDestroy(s->id);
+ createEffect(2, s->x - 12, s->y - 6);
+ spawnCollectable(s->x + 20, s->y);
+ }
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+}
+
+void sealDraw(Seal* s)
+{
+ if (s->invincible % 2 == 0) {
+ int cx = 400 + ((int)s->imageIndex * 40);
+
+ if (s->state == 0) {
+ if (s->dir == -1) {
+ cx += 80;
+ }
+ }
+
+ if (s->state == 2) {
+ cx = 600;
+ }
+
+ PHL_DrawSurfacePart(s->x, s->y, cx, 200, 40, 40, images[imgEnemies]);
+
+ //Draw tounge
+ if (s->state == 2) {
+ PHL_DrawSurfacePart(s->x, s->y + 28, 200 + ((int)s->imageIndex * 40), 0, 40, 80, images[imgMisc2040]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/seal.h b/contrib/games/hydracastlelabyrinth/src/enemies/seal.h
new file mode 100644
index 0000000000..ea1dbe8e73
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/seal.h
@@ -0,0 +1,14 @@
+#ifndef SEAL_H
+#define SEAL_H
+
+typedef struct {
+ int id, hp;
+ double x, y;
+ double imageIndex;
+ int dir, state, timer;
+ int invincible;
+} Seal;
+
+void createSeal(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.c b/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.c
new file mode 100644
index 0000000000..5d88605fc0
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.c
@@ -0,0 +1,298 @@
+#include "skeleton.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+
+void skeletonStep(Skeleton* s);
+void skeletonDraw(Skeleton* s);
+
+void boneStep(Bone* b);
+void boneDraw(Bone* b);
+
+void createSkeleton(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Skeleton* s = malloc(sizeof *s);
+ s->id = i;
+
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+
+ s->dir = 1;
+ if (dir == 1) {
+ s->dir = -1;
+ }
+
+ s->hsp = 0.5 * s->dir;
+
+ s->hp = 2;
+
+ s->state = 0;
+ s->timer = 0;
+ s->invincible = 0;
+
+ s->mask.unused = s->mask.circle = 0;
+ s->mask.w = 24;
+ s->mask.h = 36;
+ s->mask.x = s->x + 8;
+ s->mask.y = s->y + 4;
+
+ e->data = s;
+ e->enemyStep = skeletonStep;
+ e->enemyDraw = skeletonDraw;
+ e->type = 17;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void skeletonStep(Skeleton* s)
+{
+ if (s->invincible > 0) {
+ s->invincible -= 1;
+ }
+
+ //Collide with wall
+ if (checkTileCollision(1, s->mask) == 1) {
+ s->hsp *= -1;
+ }else{
+ //Check if on ledge
+ int tempdir = 1;
+ if (s->hsp < 0) {
+ tempdir = -1;
+ }
+ s->mask.x += tempdir * s->mask.w;
+ s->mask.y += 10;
+ if (checkTileCollision(1, s->mask) == 0 && checkTileCollision(3, s->mask) == 0) {
+ s->hsp *= -1;
+ }
+ s->mask.y -= 10;
+ s->mask.x = s->x + 8;
+ }
+
+ s->x += s->hsp;
+
+ if (s->timer >= 0) {
+ s->timer -= 1;
+ }
+
+ //Walk around
+ if (s->state == 0)
+ {
+ s->imageIndex += 0.1;
+
+ if (s->hsp < 0) {
+ s->dir = -1;
+ s->hsp = -0.5;
+ }else if (s->hsp > 0) {
+ s->dir = 1;
+ s->hsp = 0.5;
+ }else{
+ s->hsp = 0.5 * s->dir;
+ }
+
+ if (s->timer <= 0) {
+ //If hero is too close
+ Mask area;
+ area.unused = area.circle = 0;
+ area.x = s->x - 80;
+ area.y = s->y - 20;
+ area.w = 200;
+ area.h = 80;
+
+ if (checkCollision(area, getHeroMask()) == 1) {
+ s->state = 1;
+ s->timer = 30;
+ s->hsp = 0;
+
+ s->dir = 1;
+ if (herox < s->mask.x + (s->mask.w / 2)) {
+ s->dir = -1;
+ }
+ }
+ }
+ }
+ //Alert
+ else if (s->state == 1)
+ {
+ s->hsp = 0;
+ s->imageIndex += 0.1;
+
+ if (s->timer <= 0) {
+ s->state = 2;
+ s->hsp = 2.5 * -s->dir;
+ PHL_PlaySound(sounds[sndPi05], CHN_ENEMIES);
+ }
+ }
+ //Slide backwards
+ else if (s->state == 2)
+ {
+ s->imageIndex = 0;
+ double fric = 0.075;
+ if (s->hsp > 0) {
+ s->hsp -= fric;
+ if (s->hsp <= 0) { s->hsp = 0; }
+ }
+ else if (s->hsp < 0) {
+ s->hsp += fric;
+ if (s->hsp >= 0) { s->hsp = 0; }
+ }
+
+ if (s->hsp == 0) {
+ s->state = 3;
+ s->timer = 30;
+ createBone(s->x, s->y, s->dir);
+ PHL_PlaySound(sounds[sndShot05], CHN_ENEMIES);
+ }
+ }
+ //Throw bone
+ else if (s->state == 3)
+ {
+ s->imageIndex += 0.1;
+ if (s->timer <= 0) {
+ s->timer = 0;
+ s->state = 0;
+ }
+ }
+
+ if (s->imageIndex >= 2) {
+ s->imageIndex -= 2;
+ }
+
+ //Update mask
+ s->mask.x = s->x + 8;
+
+ //Hit Player
+ if (checkCollision(s->mask, getHeroMask())) {
+ heroHit(10, s->x + 20);
+ }
+
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(s->mask, weapons[i]->weaponMask)) {
+ s->hp -= 1;
+ s->invincible = 15;
+ weaponHit(weapons[i]);
+ if (s->hp <= 0) {
+ createRockSmash(s->x + 20, s->y + 20);
+ spawnCollectable(s->x + 20, s->mask.y + s->mask.h - 40);
+ enemyDestroy(s->id);
+ }
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+}
+
+void skeletonDraw(Skeleton* s)
+{
+ if (s->invincible % 2 == 0) {
+ int dx = 160 + ((int)s->imageIndex * 40);
+
+ if (s->dir == -1) {
+ dx += 80;
+ }
+
+ PHL_DrawSurfacePart(s->x, s->y, dx, 240, 40, 40, images[imgEnemies]);
+ }
+}
+
+
+
+void createBone(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Bone* b = malloc(sizeof *b);
+ b->id = i;
+
+ b->x = x;
+ b->y = y;
+
+ b->hsp = dir * 0.75;
+ b->vsp = -4;
+ b->grav = 0.1;
+
+ b->imageIndex = 0;
+
+ b->mask.unused = 0;
+ b->mask.circle = 1;
+ b->mask.w = 12;
+ b->mask.h = 12;
+ b->mask.x = b->x + 20;
+ b->mask.y = b->y + 20;
+
+ e->data = b;
+ e->enemyStep = boneStep;
+ e->enemyDraw = boneDraw;
+ e->type = -1;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void boneStep(Bone* b)
+{
+ if (b->hsp < 0) {
+ b->imageIndex += 0.25;
+ if (b->imageIndex >= 4) {
+ b->imageIndex -= 4;
+ }
+ }
+ else{
+ b->imageIndex -= 0.25;
+ if (b->imageIndex < 0) {
+ b->imageIndex += 4;
+ }
+ }
+
+ b->x += b->hsp;
+
+ b->y += b->vsp;
+ b->vsp += b->grav;
+
+ //Update Mask
+ b->mask.x = b->x + 20;
+ b->mask.y = b->y + 20;
+
+ if (b->y > 480) {
+ enemyDestroy(b->id);
+ }
+
+ //Hit Player
+ if (checkCollision(b->mask, shieldMask)) {
+ enemyDestroy(b->id);
+ PHL_PlaySound(sounds[sndHit07], CHN_EFFECTS);
+ createEffect(1, b->x, b->y);
+ }else{
+ if (checkCollision(b->mask, getHeroMask())) {
+ heroHit(10, b->x + 20);
+ }
+ }
+}
+
+void boneDraw(Bone* b)
+{
+ int img = 320 + ((int)b->imageIndex * 40);
+ if (b->hsp > 0) {
+ img += 160;
+ }
+
+ PHL_DrawSurfacePart(b->x, b->y, img, 240, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.h b/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.h
new file mode 100644
index 0000000000..a7b0816cba
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/skeleton.h
@@ -0,0 +1,31 @@
+#ifndef SKELETON_H
+#define SKELETON_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double hsp;
+ double imageIndex;
+ int dir;
+ int hp;
+ int state, timer, invincible;
+
+ Mask mask;
+} Skeleton;
+
+void createSkeleton(int x, int y, int dir);
+
+typedef struct {
+ int id;
+ double x, y;
+ double hsp, vsp, grav;
+ double imageIndex;
+
+ Mask mask;
+} Bone;
+
+void createBone(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/skull.c b/contrib/games/hydracastlelabyrinth/src/enemies/skull.c
new file mode 100644
index 0000000000..7ee5836b3f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/skull.c
@@ -0,0 +1,174 @@
+#include "skull.h"
+#include "../enemy.h"
+#include "../PHL.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+#include
+
+void skullStep(Skull* s);
+void skullDraw(Skull* s);
+
+void createSkull(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Skull* s = malloc(sizeof *s);
+ s->id = i;
+
+ //X/Y in center of sprite
+ s->x = x + 20;
+ s->y = y + 20;
+ s->yoffset = 0;
+
+ s->rot = 0;
+ s->state = 0;
+ s->timer = 0;
+ s->imageIndex = 0;
+ s->dir = 0;
+
+ e->data = s;
+ e->enemyStep = skullStep;
+ e->enemyDraw = skullDraw;
+ e->type = 12;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void skullStep(Skull* s)
+{
+ double imageSpeed = 0;
+
+ //Wait
+ if (s->state == 0)
+ {
+ imageSpeed = 0.2;
+
+ if (s->timer > 0) {
+ s->timer -= 1;
+ }else{
+ Mask tempmask;
+
+ tempmask.unused = tempmask.circle = 0;
+ tempmask.x = s->x - 100;
+ tempmask.y = s->y - 100;
+ tempmask.w = tempmask.h = 200;
+
+ if (checkCollisionXY(tempmask, herox, heroy + 20)) {
+
+ //Calculate distance
+ //int dis = sqrt(pow(s->x - herox, 2) + pow(s->y - (heroy + 20), 2));
+ //if (dis <= 100) {
+ s->state = 1;
+ //s->dir = (rand() % 8) * 45;
+ s->dir = (rand() % 360) + 1;
+ PHL_PlaySound(sounds[sndPi08], CHN_ENEMIES);
+ s->timer = 130;
+ }
+ }
+ }
+
+ //Chase
+ else if (s->state == 1)
+ {
+ imageSpeed = 0.3;
+
+ int spd = 2;
+ s->x += (spd * cos(s->dir * 3.14159 / 180));
+ s->y += (spd * sin(s->dir * 3.14159 / 180));
+
+ double herodir = ((atan2((heroy + 20) - s->y, herox - s->x) * 180) / 3.14159);
+ if (herodir >= 360) {
+ herodir -= 360;
+ }
+ if (herodir < 0) {
+ herodir += 360;
+ }
+
+ double tempdir = s->dir - herodir;
+ if (tempdir < 0) {
+ tempdir += 360;
+ }
+
+ if (tempdir < 180) {
+ s->dir -= 2;
+ }else{
+ s->dir += 2;
+ }
+ if (s->dir >= 360) {
+ s->dir -= 360;
+ }
+ if (s->dir < 0) {
+ s->dir += 360;
+ }
+
+ s->timer -= 1;
+ if (s->timer <= 0) {
+ s->state = 0;
+ s->timer = 10;
+ }
+ }
+
+ //Animate
+ {
+ s->imageIndex += imageSpeed;
+ if (s->imageIndex >= 4) {
+ s->imageIndex -= 4;
+ }
+ }
+
+ //Hover offset
+ {
+ s->rot += 5;
+ if (s->rot >= 360) {
+ s->rot -= 360;
+ }
+ s->yoffset = (5 * sin(s->rot * 3.14159 / 180));
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = 0;
+ mask.circle = 1;
+ mask.x = s->x;
+ mask.y = s->y;
+ mask.w = mask.h = 10;
+ }
+
+ //Hero collision
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x);
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ createEffect(2, s->x - 32, s->y - 32);
+ spawnCollectable(s->x, s->y - 20);
+ enemyDestroy(s->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+}
+
+void skullDraw(Skull* s)
+{
+ PHL_DrawSurfacePart(s->x - 20, s->y + s->yoffset - 20, 480 + ((int)s->imageIndex * 40), 40, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/skull.h b/contrib/games/hydracastlelabyrinth/src/enemies/skull.h
new file mode 100644
index 0000000000..b62ea2d387
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/skull.h
@@ -0,0 +1,15 @@
+#ifndef SKULL_H
+#define SKULL_H
+
+typedef struct {
+ int id, state, timer;
+ double x, y;
+ double yoffset;
+ int rot;
+ double dir;
+ double imageIndex;
+} Skull;
+
+void createSkull(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/slime.c b/contrib/games/hydracastlelabyrinth/src/enemies/slime.c
new file mode 100644
index 0000000000..297b190fa6
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/slime.c
@@ -0,0 +1,228 @@
+#include "slime.h"
+#include
+#include "../PHL.h"
+#include "../game.h"
+#include "../collision.h"
+#include "../hero.h"
+#include "../enemy.h"
+#include "../weapon.h"
+
+void slimeStep(Slime* s);
+void slimeDraw(Slime* s);
+
+void createSlime(int x, int y, int type, int offset)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* result = malloc(sizeof *result);
+ Slime* s = malloc(sizeof *s);
+
+ s->id = i;
+
+ s->x = x + 20;
+ s->y = y;
+ s->type = type;
+ s->offset = offset;
+
+ s->hp = 1;
+ s->state = 0;
+ s->counter = 0;
+ s->timer = 0;
+ s->grav = 0.125;
+ s->vsp = 0;
+ s->hsp = 0;
+ s->imageIndex = 0;
+
+ result->data = s;
+ result->enemyStep = slimeStep;
+ result->enemyDraw = slimeDraw;
+ result->type = 0;
+
+ enemies[i] = result;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void slimeStep(Slime* s)
+{
+ char dead = 0;
+
+ //Stay within room
+ {
+ if (s->x > 640) {
+ s->x = 640;
+ }
+
+ if (s->x < 0) {
+ s->x = 0;
+ }
+ }
+
+ //Setup Rectangle Mask
+ Mask mask;
+ {
+ mask.unused = 0;
+ mask.circle = 0;
+ mask.w = 24;
+ mask.h = 24;
+ mask.x = s->x - (mask.w / 2);
+ mask.y = s->y + 28 - (mask.h / 2);
+ }
+
+ //Idle
+ if (s->state == 0)
+ {
+ s->imageIndex += 0.25;
+
+ if (s->imageIndex >= 6) {
+ s->imageIndex = 0;
+
+ if (s->offset <= 0) {
+ s->state = 1;
+
+ //Red/Yellow Slime
+ if (s->type == 1 || s->type == 2)
+ {
+ s->hsp = 1;
+ if (s->type == 2) {
+ s->hsp = 1.5;
+ }
+ if ((int)(rand() % 2) == 0) {
+ s->hsp *= -1;
+ }
+ }
+
+ if (s->counter < 2) {
+ s->vsp = -2;
+ s->counter += 1;
+ }else{
+ s->vsp = -4;
+ s->counter = 0;
+ }
+ }else{
+ s->offset -= 1;
+ }
+ }
+ }
+
+ //Jump
+ else if (s->state == 1)
+ {
+ //Red/Yellow Slime
+ if (s->type == 1 || s->type == 2)
+ {
+ s->x += s->hsp;
+ mask.x = s->x - (mask.w / 2);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ if (s->hsp > 0) {
+ s->x = collide.x - (mask.w / 2);
+ }else if (s->hsp < 0) {
+ s->x = collide.x + 40 + (mask.w / 2);
+ }
+ }
+
+ mask.x = s->x - (mask.w / 2);
+ }
+
+ s->y += s->vsp;
+ s->vsp += s->grav;
+
+ mask.y = s->y + 28 - (mask.h / 2);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x != -1) {
+ if (s->vsp >= 0) {
+ s->state = 0;
+ s->hsp = 0;
+ s->y = collide.y - 40;
+ }else{
+ s->y = collide.y + 40 - (40 - mask.h) + 1;
+ }
+ }
+ }
+
+ //Setup Collision Mask
+ {
+ mask.unused = 0;
+ mask.circle = 1;
+ mask.w = 12;
+ mask.x = s->x;
+ mask.y = s->y + 28;
+ }
+
+ //Fell in a pit
+ {
+ if (s->y > 480) {
+ dead = 1;
+ }
+ }
+
+ //Collide with hero
+ {
+ if (checkCollision(mask, heroMask)) {
+ int dmg[3] = {10, 20, 20};
+
+ if (heroHit(dmg[s->type], s->x) == 1 && s->type == 2) {
+ heroStun();
+ }
+ }
+ }
+
+ //Sword collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ spawnCollectable(s->x, s->y + 6);
+ createEffect(2, s->x - 32, s->y - 12);
+ dead = 1;
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ enemyDestroy(s->id);
+ }
+ }
+}
+
+void slimeDraw(Slime* s)
+{
+ int cropX = 0,
+ cropY = 0;
+
+ //Idle
+ if (s->state == 0) {
+ int image[6] = { 0, 1, 2, 3, 4, 6 };
+ cropX = image[(int)s->imageIndex] * 40;
+ }
+
+ //Jump
+ else if (s->state == 1) {
+ cropX = 200;
+ if (s->vsp >= 0) {
+ cropX += 40;
+ }
+ }
+
+ //Color offsets
+ int addX[3] = {0, 280, 0};
+ int addY[3] = {0, 0, 480};
+
+ PHL_DrawSurfacePart(s->x - 20, s->y + 12, cropX + addX[s->type], cropY + addY[s->type], 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/slime.h b/contrib/games/hydracastlelabyrinth/src/enemies/slime.h
new file mode 100644
index 0000000000..bef215bb04
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/slime.h
@@ -0,0 +1,17 @@
+#ifndef SLIME_H
+#define SLIME_H
+
+typedef struct {
+ int id;
+ double x, y;
+ int type; //0 = blue | 1 = red | 2 = yellow
+ int offset;
+ double vsp, hsp, grav;
+ double imageIndex;
+ int counter, timer, state;
+ int hp;
+} Slime;
+
+void createSlime(int x, int y, int type, int offset);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/slug.c b/contrib/games/hydracastlelabyrinth/src/enemies/slug.c
new file mode 100644
index 0000000000..c8fcc2c4a6
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/slug.c
@@ -0,0 +1,164 @@
+#include "slug.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../hero.h"
+#include
+
+void slugStep(Slug* s);
+void slugDraw(Slug* s);
+
+void createSlug(int x, int y, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Slug* s = malloc(sizeof *s);
+ s->id = i;
+
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+ s->vsp = 0;
+
+ s->dir = 1;
+ if (dir == 1) {
+ s->dir = -1;
+ }
+
+ e->data = s;
+ e->enemyStep = slugStep;
+ e->enemyDraw = slugDraw;
+ e->type = 2;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void slugStep(Slug* s)
+{
+ //Create Mask
+ Mask mask;
+ {
+ mask.circle = 0;
+ mask.unused = 0;
+ mask.w = 32;
+ mask.h = 24;
+ mask.x = s->x + ((40 - mask.w) / 2);
+ mask.y = s->y + (40 - mask.h);
+ }
+
+ //Animate
+ {
+ s->imageIndex += 0.1;
+ if (s->imageIndex >= 4) {
+ s->imageIndex -= 4;
+ }
+ }
+
+ //Check if on ground
+ int onground = 1;
+ {
+ mask.y += 1;
+ if (checkTileCollision(1, mask) == 0 && checkTileCollision(3, mask) == 0) {
+ onground = 0;
+ }
+ mask.y -= 1;
+ }
+
+ if (onground == 0) {
+ double grav = 0.2;
+
+ //Fall
+ {
+ s->y += s->vsp;
+ s->vsp += grav;
+ }
+
+ //Land on ground
+ {
+ mask.y = mask.y + (40 - mask.h);
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x != -1) {
+ s->y = collide.y - 40;
+ s->vsp = 0;
+ }
+ }
+ }else{
+ //Check if on ledge
+ {
+ mask.x += mask.w * s->dir;
+ mask.y += 1;
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x == -1) {
+ s->dir *= -1;
+ }
+ }
+ }
+
+ //Horizontal movement
+ double hsp = 0.5;
+ {
+ s->x += s->dir * hsp;
+ }
+
+ //Check if hit a wall
+ {
+ mask.x = s->x + ((40 - mask.w) / 2);
+ mask.y = s->y + (40 - mask.h);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ s->dir *= -1;
+ }
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask) == 1) {
+ weaponHit(weapons[i]);
+
+ createEffect(2, s->x - 12, s->y - 6);
+ spawnCollectable(s->x + 20, s->y);
+ enemyDestroy(s->id);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+
+}
+
+void slugDraw(Slug* s)
+{
+ int anim[4] = { 1, 0, 2, 0 };
+
+ int cropx = anim[(int)s->imageIndex] * 40;
+ if (s->dir == -1) {
+ cropx += 120;
+ }
+
+ PHL_DrawSurfacePart(s->x, s->y + 10, cropx, 40, 40, 40, images[imgEnemies]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/slug.h b/contrib/games/hydracastlelabyrinth/src/enemies/slug.h
new file mode 100644
index 0000000000..77c2e14f11
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/slug.h
@@ -0,0 +1,13 @@
+#ifndef SLUG_H
+#define SLUG_H
+
+typedef struct {
+ int id;
+ int dir;
+ double x, y, vsp;
+ double imageIndex;
+} Slug;
+
+void createSlug(int x, int y, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.c b/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.c
new file mode 100644
index 0000000000..9c58f6b349
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.c
@@ -0,0 +1,265 @@
+#include "thwomp.h"
+#include "../enemy.h"
+#include "../hero.h"
+#include "../game.h"
+#include "../PHL.h"
+#include "../effect.h"
+#include
+
+void thwompStep(Thwomp* t);
+void thwompDraw(Thwomp* t);
+
+void createThwomp(int x, int y, int type, int offset, int delay, int dir)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Thwomp* t = malloc(sizeof *t);
+
+ t->id = i;
+
+ t->x = x;
+ t->y = y;
+
+ t->vsp = 0;
+ t->grav = 0.3;
+
+ t->imageIndex = 0;
+
+ t->type = type;
+ t->state = 0;
+ t->timer = offset * 30;
+ t->delay = delay * 30;
+ //default delay is 60
+ if (delay == 0) {
+ t->delay = 60;
+ }
+
+ t->dir = dir;
+
+ t->hp = 3;
+ t->blink = 0;
+
+ e->data = t;
+ e->enemyStep = thwompStep;
+ e->enemyDraw = thwompDraw;
+ e->type = 16;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void thwompStep(Thwomp* t)
+{
+ //Animate
+ {
+ t->imageIndex += 0.1;
+ if (t->imageIndex >= 3) {
+ t->imageIndex -= 3;
+ }
+ }
+
+ //Counters
+ {
+ if (t->blink > 0) {
+ t->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = mask.h = 36;
+ mask.x = t->x + ((40 - mask.w) / 2);
+ mask.y = t->y + ((40 - mask.h) / 2);
+ }
+
+ //Wait
+ if (t->state == 0) {
+ t->vsp = 0;
+
+ //Waiter
+ if (t->type == 0) {
+ Mask area;
+ area.unused = area.circle = 0;
+ area.x = t->x - 40;
+ area.y = t->y;
+ area.w = 120;
+ area.h = 160;
+
+ if (checkCollisionXY(area, herox, heroy)) {
+ t->state = 1;
+ }
+ }
+
+ //Automatic
+ else{
+ t->timer -= 1;
+ if (t->timer <= 0) {
+ t->state = 1;
+ }
+ }
+ }
+
+ //Fall
+ else if (t->state == 1) {
+ //Down
+ if (t->dir == 0) {
+ t->y += t->vsp;
+ }
+ //Left
+ if (t->dir == 1) {
+ t->x -= t->vsp;
+ }
+ //Right
+ if (t->dir == 2) {
+ t->x += t->vsp;
+ }
+
+ t->vsp += t->grav;
+
+ if (t->vsp >= 7) {
+ t->vsp = 7;
+ }
+
+ //Update Mask
+ mask.x = t->x + ((40 - mask.w) / 2);
+ mask.y = t->y + ((40 - mask.h) / 2);
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ int effX = t->x,
+ effY = t->y;
+ //Down
+ if (t->dir == 0) {
+ t->y = collide.y - 40;
+ effY = t->y + 20;
+ }
+ //Left
+ if (t->dir == 1) {
+ t->x = collide.x + 40;
+ effX = t->x - 20;
+ }
+ //Right
+ if (t->dir == 2) {
+ t->x = collide.x - 40;
+ effX = t->x + 20;
+ }
+
+ t->state = 2;
+ t->timer = 60;
+ createEffect(1, effX, effY);
+ PHL_PlaySound(sounds[sndHit07], CHN_ENEMIES);
+ }
+ }
+ else if (t->state == 2) {
+ if (t->type == 1) { //Automatic
+ t->timer -= 1;
+ if (t->timer <= 0) {
+ t->state = 3;
+ }
+ }
+ }
+ else if (t->state == 3) { //rise up
+ //Down
+ if (t->dir == 0) {
+ t->y -= 2;
+ }
+ //Left
+ if (t->dir == 1) {
+ t->x += 2;
+ }
+ //Right
+ if (t->dir == 2) {
+ t->x -= 2;
+ }
+
+ //Update Mask
+ mask.x = t->x + ((40 - mask.w) / 2);
+ mask.y = t->y + ((40 - mask.h) / 2);
+
+ if (t->dir == 0) {
+ mask.y -= 4;
+ }
+ if (t->dir == 1) {
+ mask.x += 4;
+ }
+ if (t->dir == 2) {
+ mask.x -= 4;
+ }
+
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x != -1) {
+ //Down
+ if (t->dir == 0) {
+ t->y = collide.y + 40 + 2;
+ }
+ //Left
+ if (t->dir == 1) {
+ t->x = collide.x - 40 + 2;
+ }
+ //Right
+ if (t->dir == 2) {
+ t->x = collide.x + 40 + 2;
+ }
+
+ t->state = 0;
+ t->timer = t->delay;
+ }
+ }
+
+ //Update Mask
+ {
+ mask.x = t->x + ((40 - mask.w) / 2);
+ mask.y = t->y + ((40 - mask.h) / 2);
+ }
+
+ //Hit Player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ if (hasItem[16] == 1) { //Has blue paper
+ t->hp -= 1;
+ t->blink = 15;
+
+ if (t->hp <= 0) {
+ createRockSmash(t->x + 20, t->y + 20);
+ spawnCollectable(t->x + 20, t->y);
+ enemyDestroy(t->id);
+ }
+ }else{
+ //Tink
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void thwompDraw(Thwomp* t)
+{
+ if (t->blink % 2 == 0) {
+ PHL_DrawSurfacePart(t->x, t->y, 240 + ((int)t->imageIndex * 40), 400, 40, 40, images[imgMisc20]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.h b/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.h
new file mode 100644
index 0000000000..87edebc7b9
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/thwomp.h
@@ -0,0 +1,18 @@
+#ifndef THWOMP_H
+#define THWOMP_H
+
+//#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double vsp, grav;
+ double imageIndex;
+ int type, state, timer, dir;
+ int hp, blink;
+ int delay;
+} Thwomp;
+
+void createThwomp(int x, int y, int type, int offset, int delay, int dir);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.c b/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.c
new file mode 100644
index 0000000000..d01d206345
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.c
@@ -0,0 +1,356 @@
+#include "waterjumper.h"
+#include
+#include
+#include "../game.h"
+#include "../enemy.h"
+#include "../hero.h"
+#include "../collision.h"
+
+void waterJumperStep(WaterJumper* w);
+void waterJumperDraw(WaterJumper* w);
+
+void createWaterJumper(int x, int y, int type, int offset, int height)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ WaterJumper* w = malloc(sizeof *w);
+ w->id = i;
+ w->type = type;
+
+ w->x = x;
+ w->y = w->ystart = y;
+
+ w->hp = 1;
+
+ w->hsp = w->vsp = 0;
+ w->grav = 0.115;
+ w->yoffset = 0;
+
+ w->imageIndex = 0;
+ w->blink = 0;
+
+ w->timer = 60;
+ w->timer += 60 * offset;
+
+ w->rot = 0;
+ w->state = 0;
+
+ if (type == 1) {
+ w->hp = 2;
+ w->timer = 60 * offset;
+ }
+
+ //Specific offset timer
+ if (offset > 10) {
+ w->timer = offset;
+ }
+
+ w->height = height;
+ /*
+ w->mask.unused = w->mask.circle = 0;
+ w->mask.x = x + 8;
+ w->mask.y = y + 8;
+ w->mask.w = 24;
+ w->mask.h = 24;
+ */
+ e->data = w;
+ e->enemyStep = waterJumperStep;
+ e->enemyDraw = waterJumperDraw;
+ e->type = 14;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void waterJumperStep(WaterJumper* w)
+{
+ //Counters
+ {
+ if (w->blink > 0) {
+ w->blink -= 1;
+ }
+
+ if (w->timer > 0) {
+ w->timer -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.w = 24;
+ mask.h = 24;
+ mask.x = w->x + ((40 - mask.w) / 2);
+ mask.y = w->y + ((40 - mask.h) / 2);
+ }
+
+ //Float
+ if (w->state == 0)
+ {
+ //Animate
+ {
+ w->imageIndex += 0.1;
+ if (w->imageIndex >= 2) {
+ w->imageIndex -= 2;
+ }
+ }
+
+ //Movement
+ {
+ w->rot += 5;
+ if (w->rot >= 360) {
+ w->rot -= 360;
+ }
+
+ w->y = w->ystart + (5 * sin(w->rot * 3.14159 / 180));
+ }
+
+ //Hop out of water
+ {
+ if (w->timer <= 0) {
+ w->state = 1;
+ w->timer = -1;
+
+ createSplash(w->x + 20, w->y);
+ w->y = w->ystart;
+ }
+ }
+ }
+ //In air
+ else if (w->state == 1)
+ {
+ //Animate
+ {
+ w->imageIndex += 0.25;
+ if (w->imageIndex >= 3) {
+ w->imageIndex -= 3;
+ }
+ }
+
+ //Green type
+ if (w->type == 0)
+ {
+ //State Start
+ {
+ if (w->timer == -1) {
+ w->timer = 0;
+
+ w->vsp = -5.5;
+ w->hsp = -2;
+ if (w->x + 20 < herox) {
+ w->hsp *= -1;
+ }
+ }
+ }
+
+ //Horizontal Movement
+ w->x += w->hsp;
+
+ //Land in water
+ {
+ if (w->vsp > 0 && w->y >= w->ystart) {
+ createSplash(w->x + 20, w->y);
+ w->y = w->ystart;
+ w->state = 0;
+ w->hsp = w->vsp = 0;
+ w->timer = 120;
+ }
+ }
+ }
+
+ //Blue type
+ else
+ {
+ //State Start
+ {
+ if (w->timer == -1) {
+ w->timer = 0;
+
+ if (w->height == 2) {
+ w->vsp = -5.5;
+ }
+ else if (w->height == 3) {
+ w->vsp = -6;
+ }
+ else if (w->height == 4) {
+ w->vsp = -7;
+ }
+ else if (w->height == 5) {
+ w->vsp = -7.5;
+ }
+ }
+ }
+
+ //Land on expected ground
+ {
+ if (w->vsp > 0) {
+ if (w->y >= w->ystart - 22 - (w->height * 40)) {
+ w->y = w->ystart - 22 - (w->height * 40);
+ w->imageIndex = 5;
+ w->state = 2;
+ w->timer = 240;
+ w->hsp = 2;
+ if (herox < w->x + 20) {
+ w->hsp *= -1;
+ }
+
+ }
+ }
+ }
+
+ }
+
+ //Vertical Movement
+ w->y += w->vsp;
+ w->vsp += w->grav;
+
+ }
+
+ //Walk
+ else if (w->state == 2) {
+ //Animate
+ {
+ w->imageIndex += 0.16;
+ if (w->imageIndex >= 7) {
+ w->imageIndex -= 2;
+ }
+ }
+
+ //Movement
+ {
+ w->x += w->hsp;
+ mask.x = w->x + ((40 - mask.w) / 2);
+ }
+
+ //Hit wall
+ if (checkTileCollision(1, mask) == 1) {
+ w->hsp *= -1;
+ }
+
+ //Turn on edge
+ else{
+ mask.x += (mask.w / 2) * w->hsp;
+ mask.y += mask.h;
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x == -1) {
+ w->hsp *= -1;
+ }
+ }
+
+ //End walk
+ {
+ if (w->timer <= 0) {
+ w->state = 3;
+ w->timer = -1;
+ }
+ }
+ }
+
+ //Jump Down
+ else if (w->state == 3)
+ {
+ //Setup
+ {
+ if (w->timer == -1) {
+ w->timer = 0;
+ PHL_PlaySound(sounds[sndPi02], CHN_ENEMIES);
+ w->vsp = -4;
+ w->imageIndex = 2;
+ }
+ }
+
+ //Animate
+ {
+ w->imageIndex += 0.25;
+ if (w->imageIndex >= 5) {
+ w->imageIndex -= 3;
+ }
+ }
+
+ //Movement
+ {
+ w->y += w->vsp;
+ w->vsp += w->grav;
+
+ if (w->vsp > 6) {
+ w->vsp = 6;
+ }
+ }
+
+ //Land in Water
+ {
+ if (w->y >= w->ystart) {
+ w->state = 0;
+ createSplash(w->x + 20, w->y);
+ w->timer = 60;
+ }
+ }
+ }
+
+ //Update Mask
+ {
+ mask.x = w->x + ((40 - mask.w) / 2);
+ mask.y = w->y + ((40 - mask.h) / 2);
+ }
+
+ //Collide with hero
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ heroHit(15, mask.x + (mask.w / 2));
+ }
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ w->blink = 15;
+ w->hp -= 1;
+ if (w->hp <= 0) {
+ createEffect(2, w->x - 12, w->y - 12);
+ spawnCollectable(w->x + 20, w->y);
+ enemyDestroy(w->id);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void waterJumperDraw(WaterJumper* w)
+{
+ if (w->blink % 2 == 0) {
+ int cx = (int)w->imageIndex * 40;
+
+ if (w->state == 1) {
+ cx += 80;
+ }
+
+ if (w->type == 1) {
+ cx += 200;
+
+ if (w->state == 2 && w->hsp < 0) {
+ cx += 80;
+ }
+ }
+
+ PHL_DrawSurfacePart(w->x, w->y, cx, 440, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.h b/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.h
new file mode 100644
index 0000000000..77823d8e6d
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/waterjumper.h
@@ -0,0 +1,23 @@
+#ifndef WATERJUMPER_H
+#define WATERJUMPER_H
+
+#include "../enemy.h"
+
+typedef struct {
+ int id, type;
+
+ double x, y;
+ int hp;
+ int blink;
+ int ystart, rot;
+ double yoffset;
+ double hsp, vsp, grav;
+
+ double imageIndex;
+ int state, timer;
+ int height;
+} WaterJumper;
+
+void createWaterJumper(int x, int y, int type, int offset, int height);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/wizard.c b/contrib/games/hydracastlelabyrinth/src/enemies/wizard.c
new file mode 100644
index 0000000000..b496a4552f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/wizard.c
@@ -0,0 +1,133 @@
+#include "wizard.h"
+#include "../enemy.h"
+#include "../game.h"
+#include "../hero.h"
+#include
+
+void createWizard(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] == NULL) {
+ Enemy* e = malloc(sizeof *e);
+ Wizard* w = malloc(sizeof *w);
+ w->id = i;
+
+ w->x = x;
+ w->y = y;
+
+ w->imageIndex = 0;
+
+ w->state = 0;
+ w->timer = 50;
+ w->visible = 1;
+
+ w->mask.circle = w->mask.unused = 0;
+ w->mask.w = 24;
+ w->mask.h = 38;
+ w->mask.x = w->x + 8;
+ w->mask.y = w->y + 2;
+
+ e->data = w;
+ e->enemyStep = wizardStep;
+ e->enemyDraw = wizardDraw;
+ e->type = 21;
+
+ enemies[i] = e;
+ i = MAX_ENEMIES;
+ }
+ }
+}
+
+void wizardStep(Wizard* w)
+{
+ w->imageIndex += 0.3;
+ if (w->imageIndex >= 3) {
+ w->imageIndex -= 3;
+ }
+
+ //Stand still
+ if (w->state == 0) {
+ w->timer -= 1;
+
+ if (w->timer <= 0) {
+ PHL_PlaySound(sounds[sndPi10], CHN_ENEMIES);
+ w->state = 1;
+ w->timer = 15;
+ }
+ }
+ //Flash
+ else if (w->state == 1 || w->state == 3) {
+ if (w->visible == 0) {
+ w->visible = 1;
+ }else{
+ w->visible = 0;
+ }
+
+ w->timer -= 1;
+ if (w->timer <= 0) {
+ if (w->state == 1) {
+ w->state = 2;
+ w->timer = 60;
+ }
+ else if (w->state == 3) {
+ w->visible = 1;
+ w->state = 0;
+ w->timer = 50;
+ }
+ }
+ }
+ //Invisible
+ else if (w->state == 2) {
+ w->visible = 0;
+
+ w->timer -= 1;
+ if (w->timer <= 0) {
+ PHL_PlaySound(sounds[sndPi03], CHN_ENEMIES);
+ w->state = 3;
+ w->timer = 15;
+
+ //Horizontal Jump
+ int gridX = w->x / 40,
+ gridY = w->y / 40,
+ lastGridX = gridX;
+
+ do {
+ gridX = (rand() % 16) + 1;
+ } while (collisionTiles[gridX][gridY] != 0 ||
+ collisionTiles[gridX][gridY+1] != 1 ||
+ gridX == lastGridX);
+
+ w->x = gridX * 40;
+ w->mask.x = w->x + 8;
+ }
+ }
+
+ if (w->state == 0 || w->state == 3) {
+ //Hit Player
+ if (checkCollision(w->mask, getHeroMask())) {
+ heroHit(15, w->x + 20);
+ }
+
+ //Weapon Collision
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(w->mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+ createEffect(2, w->x - 12, w->y - 6);
+ spawnCollectable(w->x + 20, w->y);
+ enemyDestroy(w->id);
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+}
+
+void wizardDraw(Wizard* w)
+{
+ if (w->visible == 1) {
+ PHL_DrawSurfacePart(w->x, w->y, 520 + (((int)w->imageIndex) * 40), 480, 40, 40, images[imgEnemies]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemies/wizard.h b/contrib/games/hydracastlelabyrinth/src/enemies/wizard.h
new file mode 100644
index 0000000000..00fed6dddb
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemies/wizard.h
@@ -0,0 +1,20 @@
+#ifndef WIZARD_H
+#define WIZARD_H
+
+#include "../collision.h"
+
+typedef struct {
+ int id;
+ double x, y;
+ double imageIndex;
+ int state, timer, visible;
+
+ Mask mask;
+} Wizard;
+
+void createWizard(int x, int y);
+
+void wizardStep(Wizard* w);
+void wizardDraw(Wizard* w);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/enemy.c b/contrib/games/hydracastlelabyrinth/src/enemy.c
new file mode 100644
index 0000000000..3fc49a0155
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemy.c
@@ -0,0 +1,16 @@
+#include "enemy.h"
+#include "game.h"
+#include
+
+void enemyDestroy(int id)
+{
+ if (enemies[id] != NULL) {
+ if (enemies[id]->data != NULL) {
+ free(enemies[id]->data);
+ }
+ enemies[id]->data = NULL;
+
+ free(enemies[id]);
+ }
+ enemies[id] = NULL;
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/enemy.h b/contrib/games/hydracastlelabyrinth/src/enemy.h
new file mode 100644
index 0000000000..14ced4146b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/enemy.h
@@ -0,0 +1,13 @@
+#ifndef ENEMY_H
+#define ENEMY_H
+
+typedef struct {
+ void* data; //Specific enemy struct
+ void (*enemyStep)();
+ void (*enemyDraw)();
+ int type;
+} Enemy;
+
+void enemyDestroy(int id);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/game.c b/contrib/games/hydracastlelabyrinth/src/game.c
new file mode 100644
index 0000000000..acce3dae8f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/game.c
@@ -0,0 +1,1976 @@
+#include "game.h"
+#include "hero.h"
+#include "PHL.h"
+#include "qda.h"
+#include "ini.h"
+#include "titlescreen.h"
+#include "options.h"
+#include "inventory.h"
+#include "object.h"
+#include "effect.h"
+#include "text.h"
+#include "stagedata.h"
+#include
+#include
+#include
+#ifdef EMSCRIPTEN
+#include
+#endif
+
+int gameStep();
+void gameDraw(char doDrawHud);
+
+void freeArrays();
+void drawHud();
+int getTileType(int valx, int valy);
+
+void loadUncommonImages();
+
+char forceGameExit = 0;
+
+int drawhp;
+int NumOfSounds = 43;
+int NumOfImages = 14;
+
+char autoSave = 1;
+int levelStartFlag = 0;
+
+char tilesetStrings[9][12] = {"stage01.bmp",
+ "stage02.bmp",
+ "stage02.bmp",
+ "stage03.bmp",
+ "stage04.bmp",
+ "stage03.bmp",
+ "stage02.bmp",
+ "stage05.bmp",
+ "stage08.bmp" };
+
+char musicStrings[9][14] = { "midi/main01",
+ "midi/main02",
+ "midi/main02",
+ "midi/main05",
+ "midi/main03",
+ "midi/main05",
+ "midi/main02",
+ "midi/main04",
+ "midi/main06" };
+
+int collisionTiles[16][12];
+PHL_Surface images[15];
+PHL_Sound sounds[43];
+PHL_Music bgmMusic;
+PHL_Music bgmSecret;
+PHL_Music bgmGameover;
+Object* objects[MAX_OBJECTS];
+Effect* effects[MAX_EFFECTS];
+Weapon* weapons[MAX_WEAPONS];
+Enemy* enemies[MAX_ENEMIES];
+Platform* platforms[MAX_PLATFORMS];
+
+int secretTimer;
+unsigned long playTime;
+
+Door* lastDoor;
+int quakeTimer;
+int bellFlag;
+int bossFlag;
+int bossDefeatedFlag;
+int roomSecret;
+char roomDarkness;
+
+int itemGotX;
+int itemGotY;
+
+PHL_Background background, foreground;
+
+unsigned char hasWeapon[5];
+unsigned char hasItem[28];
+unsigned char hasKey[8];
+
+unsigned char flags[60];
+
+double cutInTimer = 240;
+int transitionTimer = 0;
+int level = 0;
+int screenX = 5,
+ screenY = 2;
+
+#ifdef _SDL
+char savename[4096];
+char savemap[4096];
+#endif
+
+#ifdef EMSCRIPTEN
+extern int fileSynched;
+int em_state = -2;
+void em_loop_fn(void* arg)
+{
+ if(!PHL_MainLoop()) {
+ emscripten_cancel_main_loop();
+ }
+
+ int result;
+ switch (em_state) {
+ case -2: if(fileSynched) em_state++;
+ break;
+ case -1: em_state++;
+ // need to delay loading of init to let synchof files happens
+ iniInit();
+ //Load Resources
+ loadText();
+ loadResources();
+ break;
+ case 0:
+ PHL_StopMusic();
+ titleScreenSetup();
+ ++em_state;
+ break;
+ case 1: result = titleEMStep();
+ if (result == 3) {
+ em_state = 100;
+ } else if(result!=-1) {
+ if(result==2)
+ em_state = 60;
+ else {
+ //Reset game state
+ gameSetup();
+
+ //Load Game
+ if (result == 1)
+ {
+ if (fileExists(savename) == 1) {
+ loadSave(savename);
+ }else if (fileExists(savemap) == 1) {
+ loadSave(savemap);
+ }
+ }
+ ++em_state;
+ }
+ }
+ break;
+ case 2:
+ //Update resources, depending on level
+ loadUncommonImages();
+ PHL_FreeSurface(images[imgTiles]);
+ images[imgTiles] = PHL_LoadQDA(tilesetStrings[level]);
+
+ PHL_FreeMusic(bgmMusic);
+ bgmMusic = PHL_LoadMusic(musicStrings[level], 1);
+
+ loadScreen();
+ em_state = 10;
+ break;
+ case 10: // main game loop
+ PHL_MainLoop();
+ PHL_ScanInput();
+ result = gameStep();
+ if(!result)
+ em_state = 20;
+ else {
+ if(result!=-1)
+ em_state = result;
+ else {
+ PHL_StartDrawing();
+ gameDraw(1);
+ PHL_EndDrawing();
+ }
+ }
+ break;
+ case 20: // game ended
+ roomDarkness = 0;
+ freeArrays();
+
+ //Erase temp save if it exists
+ if (fileExists(savename))
+ {
+ remove(savename);
+ #ifdef EMSCRIPTEN
+ EM_ASM(
+ FS.syncfs(false,function () {
+ Module.print("File sych'd")
+ });
+ );
+ #endif
+ }
+ em_state = 0;
+ break;
+ case 30: // option menu
+ optionsSetup(0);
+ ++em_state;
+ // fall thru
+ case 31:
+ result = optionsEMStep();
+
+ //Reset Game
+ if (result == 1)
+ em_state = 20;
+ else if (result == 3) {
+ em_state = 100;
+ } else if (result!=-1)
+ em_state = 10;
+ break;
+ case 40:
+ inventorySetup();
+ ++em_state;
+ case 41:
+ result = inventoryEMStep();
+ if(result==0)
+ em_state = 10;
+ break;
+ case 50:
+ result = getItemEMStep();
+ if(result==0)
+ em_state = 10;
+ break;
+ case 60: // option menu
+ optionsSetup(1);
+ ++em_state;
+ // fall thru
+ case 61:
+ result = optionsEMStep();
+
+ if (result!=-1)
+ em_state = 0;
+ break;
+ case 100:
+ /*
+ //Free Resources
+ textFree();
+ freeResources();
+
+ //Deinit services
+ PHL_Deinit();
+ // end
+ emscripten_cancel_main_loop();
+ */
+ em_state = 0; // no quitting, as it make no sense in a browser (just kill the tab)
+ break;
+ }
+
+}
+#endif
+
+void game()
+{
+#ifdef _SDL
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ const char* home = "PROGDIR:";
+ #elif defined(EMSCRIPTEN)
+ const char* home = "hcl_data/";
+ #elif defined(_KOLIBRI)
+ const char* home = KOS_TMP_DIR;
+ #else
+ const char* home = getenv("HOME");
+ #endif
+ if(home)
+ {
+ strcpy(savename, home);
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ strcat(savename, ".hydracastlelabyrinth/");
+ #elif !defined(EMSCRIPTEN)
+ strcat(savename, "/.hydracastlelabyrinth/");
+ #endif
+ strcpy(savemap, savename);
+ strcat(savename, "save.tmp");
+ strcat(savemap, "save.map");
+ } else {
+ strcpy(savename, "data/save.tmp");
+ strcpy(savemap, savemap);
+ }
+#endif
+ //Setup services
+ printf("DBG:0\n");
+ PHL_Init();
+ if(1!=initQDA())
+ {
+ printf("DBG:QDA FAILED\n");
+ }
+ printf("DBG: 1\n");
+ textInit();
+ printf("DBG: 2\n");
+ #ifdef EMSCRIPTEN
+ emscripten_set_main_loop_arg(em_loop_fn, NULL, -1, 1);
+ #else
+ iniInit();
+ printf("DBG: 3\n");
+ //Load Resources
+ loadText();
+ printf("DBG: 4\n");
+ loadResources();
+ printf("DBG: 5\n");
+
+
+ while (PHL_MainLoop())
+ {
+ //Titlescreen
+ int titleScreenResult = titleScreen();
+ printf("DBG: titleScreen()\n");
+ //Exit game
+ if (titleScreenResult == 3) {
+ PHL_GameQuit();
+ }
+
+ // Options
+ else if(titleScreenResult == 2) {
+ int optionsResult = options(1);
+
+ //Exit Game
+ if (optionsResult == 3) {
+ PHL_GameQuit();
+ }
+ }
+ //Game Start
+ else{
+ //Reset game state
+ gameSetup();
+
+ //Load Game
+ if (titleScreenResult == 1)
+ {
+ if (fileExists(savename) == 1) {
+ loadSave(savename);
+ }else if (fileExists(savemap) == 1) {
+ loadSave(savemap);
+ }
+ }
+
+ //Update resources, depending on level
+ loadUncommonImages();
+
+ /*printf("\nTiles are ");
+ if (images[imgTiles].pxdata == NULL) {
+ printf("not loaded.");
+ }else{
+ printf("loaded.");
+ }*/
+ PHL_FreeSurface(images[imgTiles]);
+ images[imgTiles] = PHL_LoadQDA(tilesetStrings[level]);
+
+ PHL_FreeMusic(bgmMusic);
+ bgmMusic = PHL_LoadMusic(musicStrings[level], 1);
+
+ loadScreen();
+
+ //In game main loop
+ char gameLoop = 1;
+
+ while (PHL_MainLoop() == 1 && gameLoop == 1) {
+ PHL_ScanInput();
+
+ int gameResult = gameStep();
+
+ if (gameResult != -1) {
+ gameLoop = 0;
+ }
+
+ if (gameLoop == 1) {
+ PHL_StartDrawing();
+ gameDraw(1);
+ PHL_EndDrawing();
+ }
+ }
+
+ //Game end (return to titlescreen)
+ roomDarkness = 0;
+ freeArrays();
+
+ //Erase temp save if it exists
+ if (fileExists(savename))
+ {
+ #ifdef _SDL
+ remove(savename);
+ #else
+ char fullPath[128];
+ strcpy(fullPath, "");
+ #ifdef _3DS
+ strcat(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #endif
+ strcat(fullPath, savename);
+ remove(fullPath);
+ #endif
+ #ifdef EMSCRIPTEN
+ EM_ASM(
+ FS.syncfs(false,function () {
+ Module.print("File sych'd")
+ });
+ );
+ #endif
+ }
+ }
+ }
+
+ //Free Resources
+ textFree();
+ freeResources();
+
+ //Deinit services
+ PHL_Deinit();
+ #endif
+}
+
+void loadImages()
+{
+ images[imgTiles] = PHL_LoadQDA(tilesetStrings[level]);
+ images[imgEnemies] = PHL_LoadQDA("ene01.bmp");
+ images[imgHud] = PHL_LoadQDA("status.bmp");
+ images[imgMisc20] = PHL_LoadQDA("chr20.BMP");
+ images[imgMisc32] = PHL_LoadQDA("chr32.BMP");
+ images[imgHero] = PHL_LoadQDA("mychr.bmp");
+ images[imgItems] = PHL_LoadQDA("items.bmp");
+ images[imgExplosion] = PHL_LoadQDA("chr64.BMP");
+ images[imgBoss] = PHL_LoadQDA("boss01.bmp");
+ //images[imgMisc2040] = PHL_LoadQDA("chr20x40.BMP");
+ images[imgFontKana] = PHL_LoadQDA("font8x8-kana.bmp");
+ images[imgBoldFont] = PHL_LoadQDA("font8x8-01.bmp");
+ //images[imgDark] = PHL_LoadQDA("dark.bmp");
+ //images[imgMisc6020] = PHL_LoadQDA("chr60x20.bmp");
+ //images[imgHud].colorKey = PHL_NewRGB(0, 0, 0);
+ //PHL_SetColorKey(images[imgHud], 0, 0, 0);
+ images[imgTitle01] = PHL_LoadQDA("title01.BMP");
+}
+
+void loadResources()
+{
+ //Loading Images
+ loadImages();
+ puts("DBG loadResources1");
+ //Load Sounds
+ sounds[sndBee01] = PHL_LoadSound("wav/bee01.wav");
+ sounds[sndBell01] = PHL_LoadSound("wav/bell01.wav");
+ sounds[sndBom01] = PHL_LoadSound("wav/bom01.wav");
+ sounds[sndBom02] = PHL_LoadSound("wav/bom02.wav");
+ sounds[sndBom03] = PHL_LoadSound("wav/bom03.wav");
+ sounds[sndDoor00] = PHL_LoadSound("wav/door00.wav");
+ sounds[sndFire01] = PHL_LoadSound("wav/fire01.wav");
+ sounds[sndGas01] = PHL_LoadSound("wav/gas01.wav");
+ sounds[sndGet01] = PHL_LoadSound("wav/get01.wav");
+ sounds[sndGet02] = PHL_LoadSound("wav/get02.wav");
+ sounds[sndHit01] = PHL_LoadSound("wav/hit01.wav");
+ sounds[sndHit02] = PHL_LoadSound("wav/hit02.wav");
+ sounds[sndHit03] = PHL_LoadSound("wav/hit03.wav");
+ sounds[sndHit04] = PHL_LoadSound("wav/hit04.wav");
+ sounds[sndHit05] = PHL_LoadSound("wav/hit05.wav");
+ sounds[sndHit06] = PHL_LoadSound("wav/hit06.wav");
+ sounds[sndHit07] = PHL_LoadSound("wav/hit07.wav");
+ sounds[sndJump01] = PHL_LoadSound("wav/jump01.wav");
+ sounds[sndJump02] = PHL_LoadSound("wav/jump02.wav");
+ sounds[sndNg] = PHL_LoadSound("wav/ng.wav");
+ sounds[sndOk] = PHL_LoadSound("wav/ok.wav");
+ sounds[sndPi01] = PHL_LoadSound("wav/pi01.wav");
+ sounds[sndPi02] = PHL_LoadSound("wav/pi02.wav");
+ sounds[sndPi03] = PHL_LoadSound("wav/pi03.wav");
+ sounds[sndPi04] = PHL_LoadSound("wav/pi04.wav");
+ sounds[sndPi05] = PHL_LoadSound("wav/pi05.wav");
+ sounds[sndPi06] = PHL_LoadSound("wav/pi06.wav");
+ sounds[sndPi07] = PHL_LoadSound("wav/pi07.wav");
+ sounds[sndPi08] = PHL_LoadSound("wav/pi08.wav");
+ sounds[sndPi09] = PHL_LoadSound("wav/pi09.wav");
+ sounds[sndPi10] = PHL_LoadSound("wav/pi10.wav");
+ sounds[sndPower01] = PHL_LoadSound("wav/power01.wav");
+ sounds[sndPower02] = PHL_LoadSound("wav/power02.wav");
+ sounds[sndShot01] = PHL_LoadSound("wav/shot01.wav");
+ sounds[sndShot02] = PHL_LoadSound("wav/shot02.wav");
+ sounds[sndShot03] = PHL_LoadSound("wav/shot03.wav");
+ sounds[sndShot04] = PHL_LoadSound("wav/shot04.wav");
+ sounds[sndShot05] = PHL_LoadSound("wav/shot05.wav");
+ sounds[sndShot06] = PHL_LoadSound("wav/shot06.wav");
+ sounds[sndShot07] = PHL_LoadSound("wav/shot07.wav");
+ sounds[sndStep01] = PHL_LoadSound("wav/step01.wav");
+ sounds[sndWater01] = PHL_LoadSound("wav/water01.wav");
+ sounds[sndWolf01] = PHL_LoadSound("wav/wolf01.wav");
+ puts("DBG loadResources2");
+ //Load Music
+ bgmSecret = PHL_LoadMusic("midi/nazo", 0);
+ puts("DBG loadResources3");
+ bgmGameover = PHL_LoadMusic("midi/gameover", 0);
+ puts("DBG loadResources4");
+}
+
+void freeImages()
+{
+ int i;
+
+ //Free graphics
+ for (i = 0; i < NumOfImages; i++) {
+ PHL_FreeSurface(images[i]);
+ }
+}
+
+void freeResources()
+{
+ //Free sounds
+ int i;
+ for (i = 0; i < NumOfSounds; i++) {
+ PHL_FreeSound(sounds[i]);
+ }
+
+ //Free Music
+ PHL_FreeMusic(bgmMusic);
+ PHL_FreeMusic(bgmGameover);
+ PHL_FreeMusic(bgmSecret);
+
+ freeImages();
+}
+
+void gameSetup()
+{
+ //Reset Flags
+ {
+ quakeTimer = 0;
+ secretTimer = 0;
+ roomDarkness = 0;
+
+ bellFlag = 0;
+ bossFlag = 0;
+ bossDefeatedFlag = 0;
+
+ int i;
+ for (i = 0; i < 60; i++) {
+ flags[i] = 0;
+ }
+ }
+
+ //Save Data
+ {
+ playTime = 0;
+
+ //Inventory
+ int i;
+ for (i = 0; i < 5; i++) {
+ hasWeapon[i] = 0;
+ }
+
+ for (i = 0; i < 28; i++) {
+ hasItem[i] = 0;
+ }
+
+ for (i = 0; i< 8; i++) {
+ hasKey[i] = 0;
+ }
+ }
+
+ //Room Data
+ {
+ roomSecret = 0;
+ level = 0;
+ screenX = 5;
+ screenY = 2;
+ }
+
+ //Hero Setup
+ {
+ heroSetup();
+ drawhp = herohp;
+ }
+
+ //Reset object arrays
+ freeArrays();
+
+ //Setup screen transition
+ cutInTimer = 240;
+}
+
+int gameStep()
+{
+ //Manage Timers
+ {
+ playTime += 1;
+
+ secretCountdown();
+
+ if (quakeTimer > 0) {
+ quakeTimer -= 1;
+ }
+
+ if (cutInTimer > 0) {
+ cutInTimer -= 5;
+
+ //Play music when the transition ends
+ if (cutInTimer <= 0 && bossDefeatedFlag == 0 && bossFlag == 0) {
+ PHL_PlayMusic(bgmMusic);
+ }
+ }
+ }
+
+ //Hero step
+ {
+ //End game if hero died
+ if (heroStep() == 1) {
+ return 0;
+ }
+ }
+
+ //Menu button presses
+ {
+ if (getHeroState() <= 5 && cutInTimer <= 0) {
+ if (btnSelect.pressed == 1)
+ {
+ #ifdef EMSCRIPTEN
+ optionsSetup(0);
+ return 31;
+ #else
+ int optionsResult = options(0);
+
+ //Reset Game
+ if (optionsResult == 1) {
+ return 0;
+ }
+ //Exit Game
+ if (optionsResult == 3) {
+ PHL_GameQuit();
+ return 1;
+ }
+ #endif
+ }else if (btnStart.pressed == 1) {
+ #ifdef EMSCRIPTEN
+ return 40;
+ #else
+ inventory();
+ #endif
+ }
+ }
+ }
+
+ //Objects steps
+ {
+ int i;
+ for (i = 0; i < MAX_PLATFORMS; i++) {
+ if (platforms[i] != NULL) {
+ platformStep(platforms[i]);
+ }
+ }
+
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] != NULL) {
+ objects[i]->objectStep(objects[i]->data);
+ }
+ }
+
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ weaponStep(weapons[i]);
+ }
+ }
+
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] != NULL) {
+ effectStep(effects[i]);
+ }
+ }
+
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] != NULL) {
+ enemies[i]->enemyStep(enemies[i]->data);
+ }
+ }
+ }
+
+ if (forceGameExit == 1) {
+ forceGameExit = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+void gameDraw(char doDrawHud)
+{
+ PHL_DrawBackground(background, foreground);
+
+ int i;
+ //Draw water/lava top effects
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] != NULL) {
+ if (effects[i]->depth == -1) {
+ effectDraw(effects[i]);
+ }
+ }
+ }
+
+ for (i = 0; i < MAX_PLATFORMS; i++) {
+ if (platforms[i] != NULL) {
+ platformDraw(platforms[i]);
+ }
+ }
+
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] != NULL) {
+ objects[i]->objectDraw(objects[i]->data);
+ }
+ }
+
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ weaponDraw(weapons[i]);
+ }
+ }
+
+ //Draw effects under
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] != NULL) {
+ if (effects[i]->depth == 0) {
+ effectDraw(effects[i]);
+ }
+ }
+ }
+
+ //Draw enemies backwards, so bullets and such are underneath their spawners
+ //for (i = MAX_ENEMIES - 1; i >= 0; i--) {
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (enemies[i] != NULL) {
+ enemies[i]->enemyDraw(enemies[i]->data);
+ }
+ }
+
+ //Not Death, draw death later
+ if (getHeroState() != 8) {
+ heroDraw();
+ }
+
+ //Draw effects over
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] != NULL) {
+ if (effects[i]->depth == 1) {
+ effectDraw(effects[i]);
+ }
+ }
+ }
+
+ //Draw Darkness
+ if (roomDarkness == 1) {
+ int cornerX = herox - 160,
+ cornerY = heroy + 20 - 160;
+
+ PHL_DrawSurfacePart(cornerX, cornerY, 320 * hasItem[18], 0, 320, 320, images[imgDark]);
+
+ //Top darkness rectangle
+ if (cornerY > 0) {
+ PHL_DrawRect(0, 0, 640, cornerY, PHL_NewRGB(10, 0, 0));
+ }
+ //Bottom darkness rectangle
+ if (cornerY + 320 < 480) {
+ PHL_DrawRect(0, cornerY + 320, 640, 480, PHL_NewRGB(10, 0, 0));
+ }
+
+ //Left rectangle
+ if (cornerX > 0) {
+ PHL_DrawRect(0, cornerY, cornerX, 320, PHL_NewRGB(10, 0, 0));
+ }
+ //Right rectangle
+ if (cornerX + 320 < 640) {
+ PHL_DrawRect(cornerX + 320, cornerY, 640 - cornerX + 320, 320, PHL_NewRGB(10, 0, 0));
+ }
+ }
+
+ //Draw death over darkness
+ if (getHeroState() == 8) {
+ heroDraw();
+ }
+
+ if (doDrawHud == 1) {
+ drawHud();
+ }
+
+ //cut-in transition
+ {
+ if (cutInTimer > 0) {
+ PHL_DrawRect(0, 0, 640, cutInTimer, PHL_NewRGB(0, 0, 0));
+ PHL_DrawRect(0, 240 + (240 - cutInTimer), 640, 480, PHL_NewRGB(0, 0, 0));
+ }
+ }
+
+}
+#ifdef EMSCRIPTEN
+static int em_itemNum;
+static char getItemTimer;
+void getItemSetup(int itemNum)
+{
+ setHeroState(6);
+ setHeroImageIndex(0);
+
+ char getItemTimer = 0;
+}
+int getItemEMStep()
+{
+ int itemNum = em_itemNum;
+ char loop = 1;
+ PHL_MainLoop();
+#else
+void getItem(int itemNum)
+{
+ setHeroState(6);
+ setHeroImageIndex(0);
+
+ char getItemTimer = 0;
+ char loop = 1;
+
+ while (PHL_MainLoop() && loop == 1)
+#endif
+ {
+ secretCountdown();
+ //Get Item Step
+ if (getItemTimer == 0) {
+ setHeroImageIndex(getHeroImageIndex() + 0.3);
+ if (getHeroImageIndex() > 3) {
+ getItemTimer = 1;
+ }
+ }else if (getItemTimer == 1) {
+ //Wait for input
+ PHL_ScanInput();
+
+ if (btnAccept.pressed == 1 || btnFaceDown.pressed == 1 || btnFaceRight.pressed == 1 ||
+ btnFaceUp.pressed == 1 || btnFaceLeft.pressed == 1 || btnStart.pressed == 1) {
+ getItemTimer = 2;
+ }
+ }else if (getItemTimer == 2) {
+ setHeroImageIndex(getHeroImageIndex() + 0.3);
+ if (getHeroImageIndex() >= 7) {
+ loop = 0;
+
+ setHeroState(0);
+ setHeroImageIndex(0);
+ }
+ }
+
+ //Get Item Draw
+ {
+ PHL_StartDrawing();
+
+ gameDraw(1);
+
+ if (getHeroImageIndex() >= 3) {
+ char tempDarkness = roomDarkness;
+ roomDarkness = 0;
+
+ PHL_DrawRect(140, 208, 360, 64, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(142, 210, 356, 60, PHL_NewRGB(0, 20, 0));
+
+ PHL_DrawRect(148, 216, 48, 48, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(152, 220, 40, 40, PHL_NewRGB(119, 166, 219));
+ //Image
+ PHL_DrawSurfacePart(152, 220, itemGotX, itemGotY, 40, 40, images[imgItems]);
+ //Text
+ {
+ int drawX = 196, drawY = 216;
+ int twoLayers = 0;
+ if (itemName[itemNum]->length + found->length + 2 > 17) {
+ twoLayers = 1;
+ drawY -= 8;
+ }
+ drawX = drawCharacter(17, 2, drawX, drawY);
+ drawX = drawText(itemName[itemNum], drawX, drawY);
+ drawX = drawCharacter(18, 2, drawX, drawY);
+ if (twoLayers == 1) {
+ drawX = 204;
+ drawY += 24;
+ }
+ drawText(found, drawX, drawY);
+ }
+
+ roomDarkness = tempDarkness;
+ }
+
+ PHL_EndDrawing();
+ }
+ }
+#ifdef EMSCRIPTEN
+ return loop;
+#endif
+}
+
+void saveScreen()
+{
+ PHL_PlaySound(sounds[sndPower02], CHN_SOUND);
+ herohp = maxhp;
+ setHeroHsp(0);
+
+ int saveTimer = 60;
+ char loop = 1;
+
+ while (PHL_MainLoop() && loop == 1)
+ {
+ PHL_StartDrawing();
+
+ gameDraw(1);
+
+ PHL_DrawRect(140, 208, 360, 64, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(142, 210, 356, 60, PHL_NewRGB(0, 0, 255));
+ drawTextCentered(saving, 320, 216);
+
+ saveTimer -= 1;
+ if (saveTimer <= 0) {
+ loop = 0;
+ }
+
+ PHL_EndDrawing();
+ }
+
+ if (writeSave(savemap) == 1)
+ {
+ if (fileExists(savename))
+ {
+ char fullPath[128];
+ strcpy(fullPath, "");
+ #ifdef _3DS
+ strcat(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #endif
+ strcat(fullPath, savename);
+ remove(fullPath);
+ #ifdef EMSCRIPTEN
+ EM_ASM(
+ FS.syncfs(false,function () {
+ Module.print("File sych'd")
+ });
+ );
+ #endif
+ }
+ }
+}
+
+//Result screen and credits
+void gameEnding()
+{
+ int timer = 0;
+ char exitLoop = 0;
+
+ //Result screen
+ {
+ PHL_StopMusic();
+ PHL_FreeMusic(bgmMusic);
+ bgmMusic = PHL_LoadMusic("midi/allclear", 0);
+ PHL_PlayMusic(bgmMusic);
+
+ //Calculate completion percentage
+ char treasureString[11];
+ {
+ int itemCount = 0;
+ int ALLITEMS = 41;
+
+ int i;
+ for (i = 0; i < 5; i++) {
+ itemCount += hasWeapon[i];
+ }
+
+ for (i = 0; i < 28; i++) {
+ itemCount += hasItem[i];
+ }
+
+ for (i = 0; i < 8; i++) {
+ itemCount += hasKey[i];
+ }
+
+ sprintf(treasureString, "%d%%", itemCount * 100 / ALLITEMS);
+ }
+
+ //Calculate time
+ char timeString[12];
+ {
+ int hours = playTime / 216000;
+ int minutes = (playTime % 216000) / 3600;
+ int seconds = ((playTime % 216000) % 3600) / 60;
+
+ sprintf(timeString, "%02d:%02d:%02d", hours, minutes, seconds);
+ }
+
+ int transTimer = 0;
+
+ while (PHL_MainLoop() && exitLoop == 0)
+ {
+ timer += 1;
+ if (timer >= 500) {
+ transTimer += 8;
+ }
+
+ if (transTimer >= 360) {
+ exitLoop = 1;
+ }
+
+ //Animate Effects
+ int i;
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ if (effects[i] != NULL) {
+ effectStep(effects[i]);
+ }
+ }
+
+ PHL_StartDrawing();
+
+ gameDraw(0);
+
+ PHL_DrawTextBoldCentered("--- ALL CLEAR! ---", 320, 64, YELLOW);
+
+ PHL_DrawTextBoldCentered("TIME", 320, 128, YELLOW);
+ PHL_DrawTextBoldCentered(timeString, 320, 144, WHITE);
+
+ PHL_DrawTextBoldCentered("TREASURE", 320, 192, YELLOW);
+ PHL_DrawTextBoldCentered(treasureString, 320, 208, WHITE);
+
+ //transition
+ if (transTimer > 0) {
+ PHL_DrawRect(0, 0, 640, transTimer, PHL_NewRGB(0, 0, 0));
+ PHL_DrawRect(0, 240 + (240 - transTimer), 640, 480, PHL_NewRGB(0, 0, 0));
+ }
+
+ PHL_EndDrawing();
+ }
+ }
+
+ //Credits
+ {
+ timer = 0;
+ exitLoop = 0;
+
+ PHL_StopMusic();
+ PHL_FreeMusic(bgmMusic);
+ bgmMusic = PHL_LoadMusic("midi/ending", 0);
+ PHL_PlayMusic(bgmMusic);
+
+ int timer = 0;
+ double viewY = 0;
+ int maxViewY = 2200;
+ double imageIndex = 0;
+
+ while (PHL_MainLoop() && exitLoop == 0)
+ {
+ timer += 1;
+ if (timer >= 2220) {
+ exitLoop = 1;
+ }
+
+ viewY += 1;
+ if (viewY >= maxViewY - 480) {
+ viewY = maxViewY - 480;
+ }
+
+ imageIndex += 0.1;
+ if (imageIndex >= 2) {
+ imageIndex -= 2;
+ }
+
+ PHL_StartDrawing();
+
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+
+ if (exitLoop == 0) {
+ PHL_DrawTextBoldCentered("- STAFF -", 320, 480 - viewY, YELLOW);
+
+ PHL_DrawTextBoldCentered("SPRITES", 320, 560 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("BUSTER", 320, 576 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("PROGRAM", 320, 640 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("BUSTER", 320, 656 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("MUSIC", 320, 720 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("MATAJUUROU", 320, 736 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("TEST PLAYER", 320, 800 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("ZAC", 320, 816 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("- SPECIAL THANKS -", 320, 912 - viewY, YELLOW);
+
+ PHL_DrawTextBoldCentered("QUADRUPLE D", 320, 992 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("SANDMAN", 320, 1008 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("KBGM", 320, 1072 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("KR.SHIN", 320, 1088 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("KBGMPLAYER", 320, 1152 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("NARUTO", 320, 1168 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("SOUND EFFECT", 320, 1232 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("OSABISHIYUUKI", 320, 1248 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("EDGE", 320, 1312 - viewY, YELLOW);
+ PHL_DrawTextBoldCentered("TAKABO", 320, 1328 - viewY, WHITE);
+
+ PHL_DrawTextBoldCentered("THE END", 320, maxViewY - 284 - viewY, YELLOW);
+ PHL_DrawSurfacePart(300, maxViewY - 256 - viewY, (int)imageIndex * 40, 280, 40, 80, images[imgHero]);
+ }
+
+ PHL_EndDrawing();
+ }
+
+ }
+
+ forceGameExit = 1;
+
+}
+
+//Black screen between screens
+void screenTransition()
+{
+ char timer = 15;
+
+ while (PHL_MainLoop() && timer > 0)
+ {
+ PHL_StartDrawing();
+
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+ timer -= 1;
+
+ PHL_EndDrawing();
+ }
+
+ if (autoSave == 1) {
+ writeSave(savename);
+ }
+}
+
+void enterDoor()
+{
+ //Is not leaving boss room prematurely
+ bossFlag = 0;
+
+ level = lastDoor->warplevel;
+
+ screenX = lastDoor->warpcoords % 12;
+ screenY = lastDoor->warpcoords / 12;
+
+ herox = lastDoor->warpx;
+ heroy = lastDoor->warpy;
+
+ PHL_StopMusic();
+ PHL_FreeMusic(bgmMusic);
+
+ if (level == 0) {
+ //Free uncommon images
+ PHL_FreeSurface(images[imgMisc2040]);
+ PHL_FreeSurface(images[imgMisc6020]);
+ PHL_FreeSurface(images[imgDark]);
+ }else{
+ bgmMusic = PHL_LoadMusic("midi/start", 0);
+ PHL_PlayMusic(bgmMusic);
+
+ int timer = 125;
+ while (PHL_MainLoop() && timer > 0)
+ {
+ timer -= 1;
+
+ PHL_StartDrawing();
+
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+ drawTextCentered(dungeon[level - 1], 320, 216);
+
+ PHL_EndDrawing();
+ }
+
+ PHL_StopMusic();
+ PHL_FreeMusic(bgmMusic);
+
+ loadUncommonImages();
+ }
+
+ //Reload tileset
+ PHL_FreeSurface(images[imgTiles]);
+ images[imgTiles] = PHL_LoadQDA(tilesetStrings[level]);
+
+ bgmMusic = PHL_LoadMusic(musicStrings[level], 1);
+
+ changeScreen(0, 0);
+
+ PHL_PlayMusic(bgmMusic);
+}
+
+void loadScreen()
+{
+ //Stop music if you leave a boss room early
+ if (bossFlag == 1) {
+ PHL_StopMusic();
+ PHL_FreeMusic(bgmMusic);
+
+ bgmMusic = PHL_LoadMusic(musicStrings[level], 1);
+ PHL_PlayMusic(bgmMusic);
+ }
+
+ bossDefeatedFlag = bossFlag = 0;
+ roomDarkness = 0;
+
+ screenTransition();
+
+ int fileNum = stage[level][(screenY * 12) + screenX];
+
+ //Cycle through this process twice. Once for the backgroud, and one for the foreground
+ int cycle = 0;
+ for (cycle = 0; cycle < 2; cycle++)
+ {
+ //Build file string
+ char toChar[4];
+ sprintf(toChar, "%03d", fileNum);
+
+ char dest[80];
+ strcpy(dest, "");
+ #ifdef _3DS
+ strcat(dest, "romfs:/map/");
+ #elif defined(__amigaos4__) || defined(__MORPHOS__)
+ strcat(dest, "PROGDIR:data/map/");
+ #elif defined(_SDL)
+ strcat(dest, "data/map/");
+ #else
+ strcat(dest, "romfs/map/");
+ #endif
+ strcat(dest, toChar);
+
+ //load background on first pass
+ if (cycle == 0) {
+ strcat(dest, "a");
+ }
+ strcat(dest, ".map");
+
+ //Read file
+ FILE* file;
+ if ((file = fopen(dest, "rb")))
+ {
+ char* memblock;
+ int size;
+
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ memblock = (char*)malloc(size);
+ fseek(file, 0, SEEK_SET);
+ if(fread(memblock, 1, size, file) != size)
+ printf("Warning, could not read %s correctly\n", dest);
+ fclose(file);
+
+ //Load data
+ int count = 162; //Level data starts 118
+ int xx, yy;
+ int valx = 0, valy = 0;
+ int raw;
+ for (yy = 0; yy < 12; yy++) {
+ for (xx = 0; xx < 16; xx++) {
+ raw = (unsigned)memblock[count];
+ valx = raw & 0x0F;
+ valy = raw & 0xF0;
+ valy >>= 4;
+
+ if (cycle == 0) {
+ background.tileX[xx][yy] = valx;
+ background.tileY[xx][yy] = valy;
+ }else if (cycle == 1) {
+ foreground.tileX[xx][yy] = valx;
+ foreground.tileY[xx][yy] = valy;
+
+ collisionTiles[xx][yy] = getTileType(valx, valy);
+ //Breakable blocks
+ if (valy == 11 && (valx == 0 || valx == 1 || valx == 2)) {
+ int secret = 0;
+ if (valx == 2) {
+ secret = 1;
+ }
+ createDestroyable(xx * 40, yy * 40, secret);
+ }
+
+ //Lava
+ if (valx == 2 && valy == 1) {
+ createEffect(10, xx * 40, yy * 40);
+ foreground.tileX[xx][yy] = 0;
+ foreground.tileY[xx][yy] = 0;
+ }
+
+ //Water
+ if (valx == 6 && valy == 1) {
+ createEffect(11, xx * 40, yy * 40);
+ foreground.tileX[xx][yy] = 0;
+ foreground.tileY[xx][yy] = 0;
+ }
+ }
+
+ count += 2;
+ }
+ count += 12;
+ }
+
+ free(memblock);
+
+ }else{
+ PHL_ErrorScreen("Map file was not found");
+ }
+ }
+ PHL_UpdateBackground(background, foreground);
+
+ //Load file
+ //Build file string
+ char toChar[4];
+ sprintf(toChar, "%03d", fileNum);
+
+ char dest[30];
+ #ifdef _3DS
+ strcpy(dest, "romfs:/obj/");
+ #elif defined(__amigaos4__) || defined(__MORPHOS__)
+ strcpy(dest, "PROGDIR:data/obj/");
+ #elif defined(_SDL)
+ strcpy(dest, "data/obj/");
+ #else
+ strcpy(dest, "romfs/obj/");
+ #endif
+
+
+ //Add a 0 if needed
+ /*
+ if (fileNum < 100) {
+ strcat(dest, "0");
+ }
+ */
+ strcat(dest, toChar);
+ strcat(dest, ".dat");
+
+ FILE* file;
+ if ((file = fopen(dest, "rb"))) {
+ unsigned char* memblock;
+ int size;
+
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ memblock = (unsigned char*)malloc(size);
+ fseek(file, 0, SEEK_SET);
+ if(fread(memblock, 1, size, file) != size)
+ printf("Warning: could not read %s correctly\n", dest);
+
+ int count = 0;
+ while (count < size) {
+ int type = memblock[count];
+
+ if (type <= 10)
+ {
+ if (type == 0 || type == 9) { //Blue/Red Slime
+ createSlime(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count + 4]);
+ }
+ else if (type == 1) { //Bat (grey/red)
+ createBat(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3]);
+ }
+ else if (type == 2) { //Slug
+ createSlug(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3]);
+ }
+ else if (type == 3) { //Knight
+ createKnight(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3]);
+ }
+ else if (type == 4) { //Rhyno head
+ createHead(0, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count+4], memblock[count+5]);
+ }
+ else if (type == 5) { //Dragon head
+ createHead(2, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count+4], memblock[count+5]);
+ }
+ else if (type == 6) { //Goblin/medusa head
+ createHead(1, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count+4], memblock[count+5]);
+ }
+ else if (type == 7) { //Demon head
+ createHead(3, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count+4], memblock[count+5]);
+ }
+ else if (type == 10) { //Fireball head
+ createHead(4, memblock[count+1] * 20, memblock[count+2] * 20, 1, memblock[count+3], memblock[count+4]);
+ }
+ }
+
+ else if (type <= 20)
+ {
+ if (type == 11) { //Poison Gas
+ createGas(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 12) { //Flying skull
+ createSkull(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 13) { //Fish
+ createFish(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 14) { //Water Jumper
+ createWaterJumper(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3], memblock[count+4], memblock[count+5]);
+ }
+ else if (type == 15) { //Podoboo
+ createPodoboo(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3], memblock[count+4]);
+ }
+ else if (type == 16) { //Thwomp
+ createThwomp(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3], memblock[count+4], memblock[count+5], memblock[count+6]);
+ }
+ else if (type == 17) { //Skeleton
+ createSkeleton(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 18) { //Ghoul
+ createGhoul(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 19) { //Seal
+ createSeal(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ else if (type == 20) { //Jellyfish
+ createJellyfish(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ }
+
+ else if (type <= 30)
+ {
+ if (type == 21) { //Wizard
+ createWizard(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ else if (type == 22) { //Pendulum
+ createPendulum(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 24) { //Bee
+ createBee(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 25) { //Air Jar
+ //createJar(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3], memblock[count+4]);
+ createHead(5, memblock[count + 1] * 20, memblock[count + 2] * 20, 0, memblock[count+3], memblock[count+4]);
+ }
+ else if (type == 26) { //Boar
+ createBoar(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ else if (type == 27) { //Fire Wheel
+ createFirewheel(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 28) { //Rock Golem
+ createGolem(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 29) { //Poison Knight
+ createPoisonknight(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ else if (type == 30) { //Electricity doggy
+ createDog(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ }
+
+ else if (type < 40)
+ {
+ if (type == 31) {
+ createBoomknight(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ else if (type == 32) {
+ createPumpkinenemy(memblock[count+1]*20, memblock[count+2]*20);
+ }
+ }
+
+ else if (type < 50)
+ {
+ //Bosses
+ if (type == 40) {
+ createDodo(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 41) {
+ createBatboss(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 42) {
+ createCrab(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 43) {
+ createGyra(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 44) {
+ createLolidra(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 45) {
+ createDevil(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 46) {
+ createGarm(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ else if (type == 47) {
+ createHydra(memblock[count+1] * 20);
+ }
+ }
+
+ else if (type <= 60)
+ {
+ //Objects
+ if (type == 50) { //Moving platforms
+ createPlatform(0, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3] * 20, memblock[count + 4] * 20, memblock[count + 5], memblock[count+6]);
+ }
+ else if (type == 51) { //Loose block
+ createPlatform(1, memblock[count + 1] * 20, memblock[count + 2] * 20, 0, 0, 0, memblock[count+3]);
+ }
+ else if (type == 52) { //Locked Block
+ createLockBlock(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 53) { //Gate
+ createGate(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 54) { //Statue
+ createStatue(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 55) { //Megaman block
+ createPlatform(2, memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count + 4], 0, 0);
+ }
+ else if (type == 56) { //Electric gate
+ createShockgate(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3]);
+ }
+ else if (type == 57) { //Hydra platform
+ createPlatform(3, memblock[count + 1] * 20, memblock[count + 2] * 20, 0, 0, 0, 0);
+ }
+ }
+
+ else if (type < 70)
+ {
+
+ }
+
+ else/* if (type <= 80)*/
+ {
+ if (type == 70) { //Breakable Block
+ createDestroyable(memblock[count+1] * 20, memblock[count+2] * 20, 1);
+ }
+ else if (type == 71) { //Secret Trigger
+ createSecretTrigger(memblock[count+1], memblock[count+2], memblock[count+3]);
+ }
+ else if (type == 73) { //Chests
+ createChest(memblock[count + 1] * 20, memblock[count + 2] * 20, memblock[count + 3], memblock[count + 4]);
+ }
+ else if (type == 74) { //Save Points
+ createSavePoint(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 75) { //door
+ createDoor(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3], memblock[count+4], memblock[count+5] * 20, memblock[count+6] * 20, memblock[count+7]);
+ }
+ else if (type == 76) { //Light Switch
+ createSwitch(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 77) { //Floor Button
+ createFloorPad(memblock[count+1]*20, memblock[count+2]*20, memblock[count+3]);
+ }
+ else if (type == 78) {
+ roomDarkness = 1;
+ }
+ else if (type == 79) { //Ladder Spawner
+ createLadder(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 80) { //Generator
+ createGenerator(memblock[count+1] * 20, memblock[count+2] * 20, memblock[count+3]);
+ }
+ else if (type == 81) { //Crown
+ createCrown(memblock[count+1] * 20, memblock[count+2] * 20);
+ }
+ }
+ count += 16;
+ }
+
+ free(memblock);
+ fclose(file);
+ }
+
+}
+
+void drawHud()
+{
+ //Repress certain screen altering variables
+ int tempDark = roomDarkness;
+ roomDarkness = 0;
+
+ int tempQuake = quakeTimer;
+ quakeTimer = 0;
+
+ //Change HUD position
+ int drawy = 8;
+ {
+ if (heroy <= 100) {
+ drawy = 400;
+ }
+ }
+
+ //Move scrolling health bar
+ {
+ if (drawhp > herohp) {
+ drawhp -= 1;
+ }
+ if (drawhp < herohp) {
+ drawhp += 1;
+ }
+ }
+
+ //Main image
+ {
+ PHL_DrawSurfacePart(8, drawy, 0, 0, 368, 64, images[imgHud]);
+ }
+
+ //Health bar
+ {
+ PHL_RGB hpbarc = PHL_NewRGB(128, 255, 0);
+ if (getHeroPoisoned() > 0) {
+ hpbarc = PHL_NewRGB(255, 128, 255);
+ }
+
+ PHL_DrawRect(76, drawy + 8, maxhp * 2, 6, PHL_NewRGB(255, 0, 0));
+ PHL_DrawRect(76, drawy + 8, drawhp * 2, 6, hpbarc);
+ }
+
+ //Ammo counter
+ {
+ char c[10];
+ sprintf(c, "%02d", heroAmmo);
+ PHL_DrawTextBold(c, 74, drawy + 36, WHITE);
+ }
+
+ //Draw weapon icon
+ {
+ int wx = 32 * (heroWeapon + 1);
+ if (hasWeapon[heroWeapon] == 0) {
+ wx = 0;
+ }
+ PHL_DrawSurfacePart(24, drawy + 16, wx, 64, 32, 32, images[imgHud]);
+ }
+
+ //Restore screen altering variables
+ {
+ quakeTimer = tempQuake;
+ roomDarkness = tempDark;
+ }
+}
+
+void freeArrays()
+{
+ int i;
+ for (i = 0; i < MAX_EFFECTS; i++) {
+ effectDestroy(i);
+ }
+
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ objectDestroy(i);
+ }
+
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ enemyDestroy(i);
+ }
+
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ weaponDestroy(i);
+ }
+
+ for (i = 0; i < MAX_PLATFORMS; i++) {
+ platformDestroy(i);
+ }
+}
+
+void changeScreen(int dx, int dy)
+{
+ roomSecret = 0;
+
+ freeArrays();
+
+ screenX += dx;
+ screenY += dy;
+ loadScreen();
+
+ writeSave(savename);
+}
+
+int getTileType(int valx, int valy) {
+ int result = 0;
+
+ if (valy == 11 && valx == 8) {
+ result = 3; //Ladder Top
+ }else
+ if (valy == 1 && valx == 1) {
+ result = 5; //Lava
+ }else
+ if (valy > 7) {
+ result = 1; //Solid
+ }else
+ //specifics
+ if (valy == 0 && (valx == 3 || valx == 5)) {
+ result = 2; //Ladders
+ }else
+ if (valy == 1 && valx == 5) {
+ result = 4; //Water
+ }else
+ if (valx == 0 && (valy == 1 || valy == 2)) {
+ result = 6; //Spikes
+ }else
+ if (valy == 11 && (valx == 0 || valx == 1 || valx == 2)) {
+ result = 1; //Breakable solid block
+ }
+
+ if (level == 4 && valy == 3 && (valx == 0 || valx == 1 || valx == 2)) {
+ result = 6; //Spikes
+ }
+
+ return result;
+}
+
+//Save file load/save
+int writeSave(char* fname)
+{
+ int result = 0;
+ //mkdir("data");
+ FILE* f;
+
+ char fullPath[4096];
+ strcpy(fullPath, "");
+ #ifdef _3DS
+ strcat(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #elif _KOLIBRI
+ strcat(fullPath, KOS_HCL_SAVES_PATH);
+ #endif
+ strcat(fullPath, fname);
+
+ if ( (f = fopen(fullPath, "wb")) ) {
+ int size = 4548;
+ unsigned char* memblock = (unsigned char*)malloc(size);
+ memset(memblock, 0, size);
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ #define D 3
+ #else
+ #define D 0
+ #endif
+ memblock[0x0+D] = herohp;
+ memblock[0x4+D] = maxhp;
+ memblock[0x8+D] = heroAmmo;
+ memblock[0x0C+D] = maxAmmo;
+
+ if (heroWeapon == -1) {
+ memblock[0x10] = 0;
+ }else{
+ memblock[0x10] = heroWeapon;
+ }
+ memblock[0x14] = 1; //Unknown, but always resets to 1
+
+ int i;
+ for (i = 0; i < 60; i++) {
+ memblock[(0x3FC) + i] = flags[i];
+ }
+
+ for (i = 0; i < 5; i++) {
+ memblock[(0x7E4) + i] = hasWeapon[i];
+ }
+
+ int itemorder[28] = { 0x7F6, 0x7FA, 0x7F9, 0x7F8, 0x7F1, 0x7F3, 0x7F2,
+ 0x7FB, 0x7ED, 0x7EF, 0x7EE, 0x7F0, 0x7EC, 0x7F4,
+ 0x7F7, 0x7F5, 0x7EA, 0x7EB, 0x7FF, 0x803, 0x804,
+ 0x7FE, 0x802, 0x805, 0x800, 0x7FD, 0x7FC, 0x801 };
+
+ for (i = 0; i < 28; i++) {
+ memblock[itemorder[i]] = hasItem[i];
+ }
+
+ for (i = 0; i < 8; i++) {
+ memblock[(0x806) + i] = hasKey[i];
+ }
+
+ int writeHerox = herox;
+ int writeHeroy = heroy;
+ memcpy(&memblock[0x11B0], &writeHerox, 4);
+ memcpy(&memblock[0x11B4], &writeHeroy, 4);
+
+ if (getHeroDirection() == 1) {
+ memblock[0x11C0+D] = 0;
+ }else{
+ memblock[0x11C0+D] = 1;
+ }
+
+ memblock[0x11B8+D] = level;
+
+ //Screen
+ memblock[0x11BC+D] = (screenX) + (screenY * 12);
+
+ //Time
+ memcpy(&memblock[0x11AC], &playTime, 4);
+
+ fwrite(memblock, 1, size, f);
+
+ free(memblock);
+
+ result = 1;
+ fclose(f);
+ }
+ #ifdef EMSCRIPTEN
+ EM_ASM(
+ //persist changes
+ FS.syncfs(false,function (err) {
+ assert(!err);
+ });
+ );
+ #endif
+
+ return result;
+}
+
+void loadSave(char* fname)
+{
+ FILE* f;
+
+ char fullPath[128];
+ strcpy(fullPath, "");
+ #ifdef _3DS
+ strcat(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #endif
+ strcat(fullPath, fname);
+
+ if ((f = fopen(fullPath, "rb"))) {
+ //Reminder: read order matters
+ unsigned long loadTemp = 0;
+ int tmp;
+ //Hero HP
+ tmp = fread(&loadTemp, 4, 1, f);
+ herohp = loadTemp;
+ drawhp = herohp;
+
+ //Max HP
+ tmp = fread(&loadTemp, 4, 1, f);
+ maxhp = loadTemp;
+
+ //Ammo
+ tmp = fread(&loadTemp, 4, 1, f);
+ heroAmmo = loadTemp;
+
+ //Max Ammo
+ tmp = fread(&loadTemp, 4, 1, f);
+ maxAmmo = loadTemp;
+
+ int loadedWeapon = 0;
+ tmp = fread(&loadedWeapon, 1, 1, f);
+
+ //Read Flags
+ fseek(f, 0x3FC, SEEK_SET);
+ int i;
+ for (i = 0; i < 60; i++) {
+ tmp = fread(&flags[i], 1, 1, f);
+ }
+
+ //Read weapons
+ fseek(f, 0x7E4, SEEK_SET);
+ for (i = 0; i < 5; i++) {
+ tmp = fread(&hasWeapon[i], 1, 1, f);
+ }
+
+ heroWeapon = -1;
+ if (hasWeapon[loadedWeapon] == 1) {
+ heroWeapon = loadedWeapon;
+ }
+
+ //Read items
+ int itemorder[28] = { 16, 17, 12, 8, 10, 9, 11,
+ 4, 6, 5, 13, 15, 0, 14,
+ 3, 2, 1, 7, 26, 25, 21,
+ 18, 24, 27, 22, 19, 20, 23 };
+ fseek(f, 0x7EA, SEEK_SET);
+ for (i = 0; i < 28; i++) {
+ tmp = fread(&hasItem[itemorder[i]], 1, 1, f);
+ }
+
+ //Read keys
+ for (i = 0; i < 8; i++) {
+ tmp = fread(&hasKey[i], 1, 1, f);
+ }
+
+ fseek(f, 0x11AC, SEEK_SET);
+ tmp = fread(&playTime, 4, 1, f);
+
+ //fseek(f, 4540, SEEK_SET);
+ //Hero X and Y
+ tmp = fread(&loadTemp, 4, 1, f);
+ herox = loadTemp;
+ tmp = fread(&loadTemp, 4, 1, f);
+ heroy = loadTemp;
+
+ //Level
+ tmp = fread(&loadTemp, 4, 1, f);
+ level = loadTemp;
+
+ //Screen coords
+ tmp = fread(&loadTemp, 4, 1, f);
+ screenX = (loadTemp) % 12;
+ screenY = ((int)(loadTemp) / 12);
+
+ //Direction
+ tmp = fread(&loadTemp, 4, 1, f);
+ if (loadTemp == 0) {
+ setHeroDirection(1);
+ }else{
+ setHeroDirection(-1);
+ }
+ fclose(f);
+ #undef D
+ }
+
+}
+
+int fileExists(char* fpath)
+{
+ int result = 0;
+
+ char fullPath[128];
+ strcpy(fullPath, "");
+ #ifdef _3DS
+ strcat(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #endif
+ strcat(fullPath, fpath);
+
+ FILE* f;
+ if ( (f = fopen(fullPath, "rb")) ) {
+ result = 1;
+ fclose(f);
+ }
+
+ return result;
+}
+
+void playSecret()
+{
+ PHL_StopMusic();
+ secretTimer = 210;
+}
+
+void secretCountdown()
+{
+ if (secretTimer > 0) {
+ secretTimer -= 1;
+ if (secretTimer <= 0) {
+ PHL_StopMusic();
+ if (bossFlag == 0 && bossDefeatedFlag == 0) {
+ PHL_PlayMusic(bgmMusic);
+ }
+ }else if (secretTimer == 180) {
+ PHL_PlayMusic(bgmSecret);
+ }
+ }
+}
+
+int getDrawHP()
+{
+ return drawhp;
+}
+
+void setDrawHP(int val)
+{
+ drawhp = val;
+}
+
+int getLevel()
+{
+ return level;
+}
+
+void setBossRoom()
+{
+ bossFlag = 1;
+ PHL_StopMusic();
+ secretTimer = 0;
+
+ PHL_FreeMusic(bgmMusic);
+ if (level != 8) {
+ bgmMusic = PHL_LoadMusic("midi/boss", 1);
+ }else{
+ bgmMusic = PHL_LoadMusic("midi/lastboss", 1);
+ }
+ PHL_PlayMusic(bgmMusic);
+}
+
+void setAutoSave(char val)
+{
+ autoSave = val;
+}
+
+char getAutoSave()
+{
+ return autoSave;
+}
+
+void loadUncommonImages()
+{
+ //Seal Toungs
+ if (level == 4) {
+ images[imgMisc2040] = PHL_LoadQDA("chr20x40.BMP");
+ }
+ //Darkness
+ if (level == 5) {
+ images[imgDark] = PHL_LoadQDA("dark.bmp");
+ }
+ //Dragon Flame
+ if (level == 7 || level == 8) {
+ images[imgMisc6020] = PHL_LoadQDA("chr60x20.bmp");
+ }
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/game.h b/contrib/games/hydracastlelabyrinth/src/game.h
new file mode 100644
index 0000000000..d162ae321a
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/game.h
@@ -0,0 +1,237 @@
+#ifndef GAME_H
+#define GAME_H
+
+#include "PHL.h"
+#include "enemy.h"
+#include "enemies/slime.h"
+#include "enemies/bat.h"
+#include "enemies/slug.h"
+#include "enemies/knight.h"
+#include "enemies/heads.h"
+#include "enemies/gas.h"
+#include "enemies/skull.h"
+#include "enemies/fish.h"
+#include "enemies/waterjumper.h"
+#include "enemies/podoboo.h"
+#include "enemies/thwomp.h"
+#include "enemies/dodo.h"
+#include "enemies/batboss.h"
+#include "enemies/crab.h"
+#include "enemies/skeleton.h"
+#include "enemies/ghoul.h"
+#include "enemies/seal.h"
+#include "enemies/jellyfish.h"
+#include "enemies/wizard.h"
+#include "enemies/pendulum.h"
+#include "enemies/gyra.h"
+#include "enemies/lolidra.h"
+#include "enemies/bee.h"
+#include "enemies/devil.h"
+#include "enemies/firewheel.h"
+#include "enemies/boar.h"
+#include "enemies/golem.h"
+#include "enemies/garm.h"
+#include "enemies/poisonknight.h"
+#include "enemies/dog.h"
+#include "enemies/boomknight.h"
+#include "enemies/pumpkin.h"
+#include "enemies/hydra.h"
+#include "object.h"
+#include "effect.h"
+#include "weapon.h"
+#include "platform.h"
+
+#define TITLE 0
+#define GAME 1
+#define INVENTORY 2
+#define OPTIONS 3
+#define SAVING 4
+#define LEVELSTART 5
+#define GETITEM 6
+
+//Sound channels
+#define CHN_MUSIC 0
+#define CHN_SOUND 1 //Various sounds, like menus and fanfares
+#define CHN_HERO 2
+#define CHN_WEAPONS 3
+#define CHN_ENEMIES 4
+#define CHN_EFFECTS 5
+
+extern Door* lastDoor;
+
+extern int secretTimer;
+extern int levelStartTimer;
+extern int saveTimer;
+
+extern int quakeTimer;
+
+extern int bellFlag;
+extern int bossFlag;
+extern int bossDefeatedFlag;
+
+extern char roomDarkness;
+
+//Used for item get message
+extern int itemGotX;
+extern int itemGotY;
+
+extern int roomSecret;
+
+extern int collisionTiles[16][12];
+
+//Playtime in frames. At 60 frames per second can hold ~828 1/2 days worth of playtime if my math isn't shit
+extern unsigned long playTime;
+
+//Inventory
+extern unsigned char hasWeapon[5];
+extern unsigned char hasItem[28];
+extern unsigned char hasKey[8];
+
+//Save data flags
+extern unsigned char flags[60];
+
+extern PHL_Background background,
+ foreground;
+
+//Game assets
+extern PHL_Surface images[15];
+extern PHL_Music bgmMusic;
+extern PHL_Music bgmSecret;
+extern PHL_Music bgmGameover;
+extern PHL_Sound sounds[43];
+
+#define MAX_WEAPONS 5
+extern Weapon* weapons[MAX_WEAPONS];
+
+#define MAX_OBJECTS 40
+extern Object* objects[MAX_OBJECTS];
+
+#define MAX_ENEMIES 20
+extern Enemy* enemies[MAX_ENEMIES];
+
+#define MAX_EFFECTS 30
+extern Effect* effects[MAX_EFFECTS];
+
+#define MAX_PLATFORMS 10
+extern Platform* platforms[MAX_PLATFORMS];
+
+//Graphic names
+#define imgTiles 0
+#define imgEnemies 1
+#define imgHud 2
+#define imgMisc20 3
+#define imgMisc32 4
+#define imgHero 5
+#define imgItems 6
+#define imgExplosion 7
+#define imgBoss 8
+#define imgMisc2040 9
+#define imgFontKana 10
+#define imgBoldFont 11
+#define imgDark 12
+#define imgMisc6020 13
+#define imgTitle01 14
+
+//Sound names
+#define sndBee01 0
+#define sndBell01 1
+#define sndBom01 2
+#define sndBom02 3
+#define sndBom03 4
+#define sndDoor00 5
+#define sndFire01 6
+#define sndGas01 7
+#define sndGet01 8
+#define sndGet02 9
+#define sndHit01 10
+#define sndHit02 11
+#define sndHit03 12
+#define sndHit04 13
+#define sndHit05 14
+#define sndHit06 15
+#define sndHit07 16
+#define sndJump01 17
+#define sndJump02 18
+#define sndNg 19
+#define sndOk 20
+#define sndPi01 21
+#define sndPi02 22
+#define sndPi03 23
+#define sndPi04 24
+#define sndPi05 25
+#define sndPi06 26
+#define sndPi07 27
+#define sndPi08 28
+#define sndPi09 29
+#define sndPi10 30
+#define sndPower01 31
+#define sndPower02 32
+#define sndShot01 33
+#define sndShot02 34
+#define sndShot03 35
+#define sndShot04 36
+#define sndShot05 37
+#define sndShot06 38
+#define sndShot07 39
+#define sndStep01 40
+#define sndWater01 41
+#define sndWolf01 42
+
+#ifdef _SDL
+extern char savename[4096];
+extern char savemap[4096];
+#else
+#define savename "data/save.tmp"
+#define savemap "map/018.map"
+#endif
+
+#ifdef _KOLIBRI
+#define KOS_HCL_SAVES_PATH "/tmp0/1/.hydracastlelabyrinth"
+#define KOS_TMP_DIR "/tmp0/1"
+#endif
+
+void loadImages();
+void freeImages();
+void loadResources();
+void freeResources();
+
+void game();
+
+void gameSetup();
+void gameCleanup();
+
+void enterDoor();
+#ifdef EMSCRIPTEN
+void getItemSetup(int itemNum);
+int getItemEMStep();
+#else
+void getItem(int itemNum);
+#endif
+void saveScreen();
+
+void gameEnding();
+
+//void enterDoor(Door* d);
+void loadScreen();
+
+void changeScreen(int dx, int dy);
+
+int writeSave(char* fname);
+void loadSave(char* fname);
+
+int fileExists(char* fpath);
+
+void playSecret();
+void secretCountdown();
+
+int getDrawHP();
+void setDrawHP(int val);
+
+int getLevel();
+
+void setBossRoom();
+
+void setAutoSave(char val);
+char getAutoSave();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/hero.c b/contrib/games/hydracastlelabyrinth/src/hero.c
new file mode 100644
index 0000000000..7a8ed26873
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/hero.c
@@ -0,0 +1,1415 @@
+#include "hero.h"
+#include "game.h"
+#include
+#include "weapon.h"
+#include "platform.h"
+#include
+
+//State constants
+const char NORMAL = 0;
+const char SLASH = 1;
+const char HIT = 2;
+const char LADDER = 3;
+const char STONE = 4;
+const char CHARGE = 5;
+//#define GETITEM 6
+const char DOOR = 7;
+const char DEATH = 8;
+const char QUAKE = 9;
+
+int state;
+
+double herox, heroy;
+double herohp, maxhp;
+int heroAmmo, maxAmmo;
+int heroWeapon;
+
+Mask heroMask;
+Mask shieldMask;
+
+void updateMask();
+void heroChangeScreen(int dx, int dy);
+
+int herodir = 1;
+
+int canCharge = 0;
+int canJump = 0;
+int onground = 0;
+int heldUp = 0;
+
+const double GRAVITY = 0.3;
+const double CLIMBSPEED = 1.2;
+const double CLIMBSPEEDPOWER = 2.0;
+
+double vsp = 0;
+double hsp = 0;
+double imageIndex = 0;
+double jumpspd = 7.5;
+
+
+int invincible = 0;
+int timer = 0;
+int chargeTimer = 0;
+int shieldTimer = 0; //Holds up shield if this is 0
+int stun = 0;
+int stunTimer = 0;
+
+int poisoned = 0;
+int stoneTimer = 0;
+int stoneState = 0;
+int stoneDir = 1;
+
+int inWater = 0;
+int drownTimer = 0;
+
+void heroSetup()
+{
+ state = NORMAL;
+ herodir = 1;
+
+ herox = 320;
+ heroy = 320;
+ vsp = 0;
+ hsp = 0;
+ imageIndex = 0;
+ //climbspd = 1;
+
+ invincible = 0;
+ timer = 0;
+ chargeTimer = 0;
+ shieldTimer = 0;
+
+ poisoned = 0;
+ stoneTimer = 0;
+ stoneState = 0;
+ stoneDir = 1;
+
+ herohp = 128;
+ maxhp = 128;
+ heroAmmo = 0;
+ maxAmmo = 99;
+ heroWeapon = -1;
+
+ heroMask.unused = 0;
+ heroMask.circle = 0;
+ heroMask.w = 24;
+ heroMask.h = 26;
+
+ onground = 0;
+ canJump = 0;
+
+ heroy += 1;
+ if (checkTileCollision(1, getHeroMask()) == 1 || checkTileCollision(3, getHeroMask()) == 1) {
+ onground = 1;
+ if (hasItem[12] == 1) {
+ canJump = 1;
+ }
+ }
+ heroy -= 1;
+
+ shieldMask.unused = 1;
+ shieldMask.circle = 0;
+ shieldMask.w = 24;
+ shieldMask.h = 24;
+ shieldMask.x = 0;
+ shieldMask.y = 0;
+
+ inWater = -1;
+}
+
+int heroStep()
+{
+ int result = -1;
+
+ //set HP limits
+ {
+ if (herohp > maxhp) {
+ herohp = maxhp;
+ }
+
+ if (herohp < 0) {
+ herohp = 0;
+ }
+ }
+
+ heldUp = btnUp.held;
+
+ //Counters
+ {
+ if (invincible > 0) {
+ invincible -= 1;
+ }
+ }
+
+ //Scripted states
+ if (state == DOOR) {
+ //Remove some status conditions
+ stun = 0;
+ poisoned = 0;
+ inWater = 0;
+
+ //Animate
+ imageIndex += 0.2;
+
+ //Done walking
+ if (imageIndex >= 10) {
+ enterDoor();
+ state = NORMAL;
+ }
+ }
+
+ else if (state == DEATH) {
+ stun = 0;
+ stunTimer = 0;
+ poisoned = 0;
+
+ //Animate
+ {
+ imageIndex += 0.3;
+ if (imageIndex >= 4) {
+ imageIndex -= 4;
+ }
+
+ //blinking
+ if (timer >= 90) {
+ invincible = 1;
+ }else{
+ invincible = 0;
+ }
+ }
+
+ timer += 1;
+
+ //Poof
+ if (timer == 90) {
+ createEffect(2, herox - 32, heroy - 12);
+ }
+ //Play Music
+ if (timer == 150) {
+ PHL_PlayMusic(bgmGameover);
+ }
+
+ //End game over screen prematurly
+ if (timer > 150 && btnStart.pressed == 1) {
+ btnStart.pressed = 0;
+ timer = 630;
+ }
+
+ //Reset game
+ if (timer == 630) {
+ /*
+ FILE* f;
+ if ((f = fopen("data/save.tmp", "rb"))) {
+ remove("data/save.tmp");
+ }
+ fclose(f);
+ */
+ PHL_StopMusic();
+ result = 1;
+ }
+ }
+
+ //Uncontrollable states, but can move
+ else {
+ char canGrav = 1;
+ double grav = GRAVITY;
+
+ if (state == CHARGE) {
+ canGrav = 0;
+ shieldTimer = 10;
+ vsp = 0;
+
+ //Charge start (rear back)
+ {
+ if (timer == 0) {
+ imageIndex = 0;
+ hsp = -2 * herodir;
+ }
+ }
+
+ //Friction
+ {
+ double fric = 0.3;
+
+ if (hsp < 0) {
+ hsp += fric;
+ if (hsp >= 0) {
+ hsp = 0;
+ }
+ }else if (hsp > 0) {
+ hsp -= fric;
+ if (hsp <= 0) {
+ hsp = 0;
+ }
+ }
+ }
+
+ timer += 1;
+
+ //Forward charge start
+ {
+ if (timer == 15) {
+ invincible = 35;
+ hsp = 7 * herodir;
+ PHL_PlaySound(sounds[sndShot01], CHN_WEAPONS);
+ }
+ }
+
+ //Animation
+ {
+ if (timer > 15) {
+ imageIndex = 1;
+ }
+ if (timer > 19) {
+ imageIndex = 2;
+ }
+ if (timer > 21) {
+ imageIndex = 3;
+ }
+ if (timer == 59) {
+ imageIndex = 4;
+ }
+ }
+
+ //Stop
+ if (timer == 30) {
+ hsp = 0;
+ }
+
+ //End state
+ if (timer >= 60) {
+ state = NORMAL;
+ }
+
+ }
+
+ else if (state == HIT) {
+ grav = GRAVITY - 0.05;
+
+ //timer
+ {
+ timer -= 1;
+ if (timer < 0) {
+ timer = 0;
+ }
+ }
+
+ //Animate
+ {
+ imageIndex += 0.33;
+ if (imageIndex >= 2) {
+ imageIndex -= 2;
+ }
+ }
+
+ if (onground == 1) {
+ hsp = 0;
+ }
+
+ //End hit state
+ {
+ if (onground == 1 && vsp == 0 && timer == 0) {
+ state = NORMAL;
+ invincible = 60;
+ }
+ }
+
+ }
+
+ else if (state == STONE) {
+ grav = GRAVITY - 0.05;
+
+ if (stoneState != 2) {
+ stoneTimer -= 1;
+ //Setup break free animation
+ if (stoneTimer <= 0) {
+ stoneTimer = 0;
+ stoneState = 2;
+ imageIndex = 0;
+
+ createRockSmash(herox, heroy + 20);
+ herodir = stoneDir;
+ }
+ }
+
+ //Animate
+ imageIndex += 0.16;
+
+ //Frozen state flashes
+ if (stoneState != 2 && imageIndex >= 2) {
+ imageIndex = 0;
+ }
+
+ //In air
+ if (stoneState == 0) {
+ if (onground == 0) {
+ //hsp = -(herodir * 2);
+ }else{
+ stoneState = 1;
+ createEffect(9, herox, heroy + 20);
+ createEffect(9, herox, heroy + 20);
+ }
+ }
+ //On ground
+ else if (stoneState == 1) {
+ hsp = 0;
+ if (btnFaceDown.pressed == 1) {
+ stoneTimer -= 30;
+ createEffect(9, herox, heroy + 20);
+ }
+ }
+ //Break free animation
+ else if (stoneState == 2) {
+ imageIndex += 0.16;
+
+ if ((int)imageIndex == 3) {
+ createEffect(8, herox - 32, heroy - 22);
+ imageIndex += 0.5;
+ }
+
+ if (imageIndex >= 17) {
+ state = NORMAL;
+ stoneState = 0;
+ }
+ }
+ }
+
+ else if (state == QUAKE) {
+ grav = GRAVITY - 0.05;
+ hsp = 0;
+
+ if (onground == 1) {
+ PHL_PlaySound(sounds[sndPi02], CHN_HERO);
+ if (timer == 0) {
+ vsp = -2 - grav;
+ onground = 0;
+ }
+ else if (timer == 1) {
+ vsp = -1 - grav;
+ onground = 0;
+ }
+ else if (timer == 2) {
+ vsp = -0.5 - grav;
+ onground = 0;
+ }
+ else if (timer == 3) {
+ state = NORMAL;
+ vsp = 0;
+ }
+ timer += 1;
+ }
+ }
+
+ //Controllable states
+ else {
+ char canWalk = 1;
+
+ if (state == NORMAL) {
+ //Timers
+ {
+ if (shieldTimer > 0) {
+ shieldTimer -= 1;
+ }
+ }
+
+ //Change direction with buttons
+ {
+ if (btnLeft.held == 1) {
+ herodir = -1;
+ }
+ if (btnRight.held == 1) {
+ herodir = 1;
+ }
+ }
+
+ //Jumping
+ {
+ if (btnFaceDown.pressed == 1) {
+ if (onground == 1 || canJump == 1) {
+ if (onground == 0) {
+ canJump = 0;
+ }
+ vsp = -jumpspd;
+ onground = 0;
+ PHL_PlaySound(sounds[sndJump01], CHN_HERO);
+ }
+ }
+
+ //cancel jump
+ if (vsp < 0 && btnFaceDown.released == 1) {
+ vsp = 0;
+ }
+ }
+
+ //Animate
+ {
+ if (onground == 1 && hsp != 0) {
+ imageIndex += 0.1;
+ if (imageIndex >= 2) {
+ imageIndex -= 2;
+ }
+ }
+ }
+
+ //Charging
+ {
+ if (canCharge == 1 && btnFaceLeft.held == 1) {
+ chargeTimer += 1;
+ //Create Effects
+ if (chargeTimer >= 10 && chargeTimer < 66 && ((chargeTimer - 10) % 8) == 0) {
+ createEffect(6, herox, heroy + 20);
+ }
+
+ if (chargeTimer == 70) {
+ PHL_PlaySound(sounds[sndPower01], CHN_SOUND);
+ }
+ }
+
+ if (canCharge == 1 && chargeTimer >= 70 && btnFaceLeft.released == 1) {
+ state = CHARGE;
+ timer = 0;
+ imageIndex = 0;
+ addWeapon(SWORD, herox, heroy);
+ }
+ }
+
+ //Attack
+ {
+ if (stun == 0) {
+ //Slash
+ if (btnFaceLeft.pressed == 1) {
+ state = SLASH;
+ imageIndex = 0;
+ PHL_PlaySound(sounds[sndShot01], CHN_WEAPONS);
+ addWeapon(SWORD, herox, heroy);
+ }
+
+ //Weapon
+ if (btnFaceRight.pressed == 1) {
+ if (heroWeapon != -1) {
+ addWeapon(heroWeapon, (int)herox - 20, (int)heroy);
+ }
+ }
+ }
+ }
+
+ //Grabbing Ladder
+ {
+ //Grab ladder
+ if (btnUp.held == 1) {
+ PHL_Rect collide = getTileCollisionXY(2, herox, heroy + 20);
+ if (collide.x != -1) {
+ state = LADDER;
+ canWalk = 0;
+ hsp = 0;
+ vsp = 0;
+ herox = collide.x + 20;
+ }
+ }
+
+ //Climb down onto ladder
+ else if (onground == 1 && btnDown.held == 1) {
+ PHL_Rect collide = getTileCollisionXY(3, herox, heroy + 40);
+ if (collide.x != -1) {
+ state = LADDER;
+ canWalk = 0;
+ hsp = 0;
+ vsp = 0;
+ herox = collide.x + 20;
+ heroy += 1;
+ }
+ }
+ }
+
+ }
+
+ else if (state == SLASH) {
+ shieldTimer = 10;
+
+ //Can move in air, not on the ground
+ if (onground == 1) {
+ canWalk = 0;
+ hsp = 0;
+ }
+
+ //Animate
+ {
+ double imgspd = 0.25;
+
+ if (imageIndex < 1) {
+ imgspd = 0.25;
+ }else if (imageIndex < 2) {
+ imgspd = 0.34;
+ }else if (imageIndex < 3) {
+ imgspd = 0.34;
+ }else if (imageIndex < 4) {
+ imgspd = 0.125;
+ }else if (imageIndex < 5) {
+ imgspd = 0.5;
+ }
+
+ imageIndex += imgspd;
+ }
+
+ //Finish slash
+ {
+ if (imageIndex >= 5) {
+ state = NORMAL;
+ canCharge = hasItem[17]; //Has red scroll
+ chargeTimer = 0;
+ }
+ }
+
+ }
+
+ else if (state == LADDER) {
+ onground = 0;
+ canWalk = 0;
+ canGrav = 0;
+ hsp = 0;
+ vsp = 0;
+
+ //Generate final climb speed
+ double climbspd = CLIMBSPEED;
+ {
+ //Has power bracelet
+ if (hasItem[4] == 1) {
+ climbspd = CLIMBSPEEDPOWER;
+ }
+
+ //Stun slows climb speed
+ if (stun > 0) {
+ climbspd /= 2;
+ }
+ }
+
+ //Get up/down axis
+ int yaxis = btnDown.held - btnUp.held;
+
+ //Animate
+ if (yaxis != 0) {
+ imageIndex += 0.125;
+
+ //Limit imageIndex
+ if (imageIndex >= 8) {
+ imageIndex -= 8;
+ }
+ }
+
+ //Movement
+ heroy += climbspd * yaxis;
+
+ //Touch ground
+ {
+ if (yaxis == 1) {
+ PHL_Rect collide = getTileCollision(1, getHeroMask());
+ if (collide.x != -1) {
+ state = NORMAL;
+ heroy = collide.y - 40;
+ imageIndex = 0;
+ }
+ }
+ }
+
+ //Off of ladder
+ {
+ if (yaxis != 0) {
+ if (checkTileCollision(2, getHeroMask()) == 0 && checkTileCollision(3, getHeroMask()) == 0) {
+ state = NORMAL;
+ if (btnDown.held == 1) {
+ onground = 0;
+ }
+ }
+ }
+ }
+ }
+
+ //Walking
+ {
+ if (canWalk == 1) {
+ int xaxis = btnRight.held - btnLeft.held;
+ hsp = 3 * xaxis;
+ }
+ }
+
+ //Cancel jump
+ {
+ if (vsp < 0 && btnFaceDown.released == 1) {
+ vsp = 0;
+ }
+ }
+
+ //Earthquake
+ {
+ if (hasItem[11] == 0) { //Does not have amulete
+ if (quakeTimer > 0 && onground == 1) {
+ state = QUAKE;
+ vsp = -3 - grav;
+ timer = 0;
+ PHL_PlaySound(sounds[sndPi02], CHN_HERO);
+ }
+ }
+ }
+
+ }
+
+ //Movement
+ {
+ //Used to prevent glitching out on ladder tops
+ int precheckladder = checkTileCollision(3, getHeroMask());
+
+ //Horizontal movement
+ {
+ if (hsp != 0) {
+ double finalhsp = hsp;
+ //Slow when climbing and stunned
+ {
+ if ( (inWater == 1 && hasItem[5] == 0) || stun == 1) {
+ finalhsp /= 4;
+ }
+ }
+
+ //Speed up movement in water
+ {
+ if (inWater == 1 && hasItem[5] == 1) { //Has fins
+ finalhsp = (finalhsp / 3) * 2;
+ }
+ }
+
+ //Move
+ herox += finalhsp;
+
+ //Stay within screen during boss fight
+ {
+ if (bossFlag == 1) {
+ if (herox < 10) {
+ herox = 10;
+ }
+ if (herox > 630) {
+ herox = 630;
+ }
+ }
+ }
+
+ //Collide with wall
+ {
+ PHL_Rect collide = getTileCollision(1, getHeroMask());
+ if (collide.x == -1 && precheckladder == 0) {
+ collide = getTileCollision(3, getHeroMask());
+ }
+
+ //Did collide
+ if (collide.x != -1) {
+ if (hsp > 0) {
+ herox = collide.x - (heroMask.w / 2);
+ }else if (hsp < 0) {
+ herox = collide.x + 40 + (heroMask.w / 2);
+ }
+
+ if (state == STONE) {
+ herodir *= -1;
+ }
+ }
+ }
+
+ //Check if walked off ledge
+ if (vsp >= 0) {
+ heroy += 1;
+
+ if ( checkTileCollision(1, getHeroMask()) //Solid ground
+ || (hasItem[13] == 1 && checkTileCollision(5, getHeroMask())) //Has red shoes
+ || (precheckladder == 0 && checkTileCollision(3, getHeroMask())) ) //Ladder tops
+ {}else{
+ onground = 0;
+ }
+
+ heroy -= 1;
+ }
+ }
+ }
+
+ //Vertical Movement
+ {
+ //Gravity
+ if (canGrav == 1 && onground == 0) {
+ int maxVsp = 8;
+
+ //Water slows movement
+ if (inWater == 1) {
+ grav *= 0.5;
+ maxVsp *= 0.5;
+ }
+
+ vsp += grav;
+ if (vsp > maxVsp) {
+ vsp = maxVsp;
+ }
+ }
+
+ //Vertical Movement
+ {
+ double tempVsp = vsp;
+ char landed = 0;
+
+ //Water slows movement
+ if (inWater == 1 || stun == 1) {
+ tempVsp *= 0.5;
+ }
+
+ //Movement
+ heroy += tempVsp;
+
+ //Colliding with floor/ceiling
+ {
+ PHL_Rect collide = getTileCollision(1, getHeroMask());
+ if (collide.x == -1&& precheckladder == 0) {
+ collide = getTileCollision(3, getHeroMask());
+ }
+ if (collide.x == -1 && hasItem[13] == 1) { //has red shoes
+ collide = getTileCollision(5, getHeroMask());
+ }
+
+ if (collide.x != -1) {
+ //Collide with floor
+ if (vsp > 0) {
+ heroy = collide.y - 40;
+ vsp = 0;
+
+ onground = 1;
+ if (hasItem[12] == 1) { //Has blue boots
+ canJump = 1;
+ }
+
+ landed = 1;
+ }
+
+ //Collide with ceiling
+ else if (vsp < 0) {
+ heroy = collide.y + 40 - (40 - heroMask.h);
+ }
+ }
+
+ else{
+ //Jumpthrough/moving platforms
+ if (vsp >= 0) {
+ int i;
+ for (i = 0; i < MAX_PLATFORMS; i++) {
+ if (platforms[i] != NULL) {
+ int onPlatTop = 0;
+ if (herox - (heroMask.w / 2) > platforms[i]->mask.x + platforms[i]->mask.w || herox + (heroMask.w / 2) < platforms[i]->mask.x) {
+ }
+ else{
+ if (platforms[i]->y == heroy + 40 && vsp >= 0) {
+ onPlatTop = 1;
+ }
+ }
+
+ if (onPlatTop == 1 || checkCollision(getHeroMask(), platforms[i]->mask) == 1) {
+ heroMask.y -= vsp;
+ if (onPlatTop == 1 || checkCollision(heroMask, platforms[i]->mask) == 0) {
+ heroy = platforms[i]->mask.y - 40;
+ if (vsp != 0) {
+ landed = 1;
+ }
+ vsp = 0;
+ onground = 1;
+ if (hasItem[12] == 1) {
+ canJump = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Land on ground after a hit
+ if (landed == 1 && (state == HIT || state == STONE)) {
+ timer = 60;
+ PHL_PlaySound(sounds[sndHit01], CHN_HERO);
+
+ createEffectExtra(3, herox - 30, heroy + 8, -1, 0, 0);
+ createEffectExtra(3, herox - 10, heroy + 8, 1, 0, 0);
+ }
+
+ }
+
+ }
+
+ }
+
+ //Water stuff
+ {
+ //Drown/bubble
+ if (inWater == 1) {
+ drownTimer -= 1;
+ if (drownTimer <= 0) {
+ drownTimer = 60;
+ if (hasItem[6] == 0) {
+ herohp -= 4;
+ }
+ createEffect(12, herox, heroy + 20);
+ PHL_PlaySound(sounds[sndPi06], CHN_SOUND);
+ }
+ }
+
+ //Splash
+ if (checkTileCollision(4, getHeroMask())) {
+ if (inWater == 0) {
+ drownTimer = 60;
+ //Splash effect
+ createSplash(herox, heroy);
+ }
+ inWater = 1;
+ }else{
+ if (checkTileCollision(6, getHeroMask()) == 0) {
+ if (inWater == 1) {
+ //Splash effect
+ createSplash(herox, heroy);
+ }
+ inWater = 0;
+ }
+ }
+ }
+
+ //Poison
+ {
+ if (poisoned > 0) {
+ poisoned -= 1;
+ if (poisoned % 20 == 0) {
+ herohp -= 1;
+ createEffect(7, herox, heroy);
+ }
+ }
+ }
+
+ //Switch weapon
+ {
+ int axis = btnR.pressed - btnL.pressed;
+
+ if (axis != 0) {
+ PHL_PlaySound(sounds[sndPi01], CHN_SOUND);
+
+ int i;
+ for (i = 1; i <= 5; i++) {
+ int thisweapon = heroWeapon + (i * axis);
+
+ if (thisweapon >= 5) {
+ thisweapon -= 5;
+ }
+ if (thisweapon < 0) {
+ thisweapon += 5;
+ }
+
+ if (hasWeapon[thisweapon] == 1) {
+ heroWeapon = thisweapon;
+ i = 6;
+ }
+ }
+ }
+ }
+
+ //Collide with lava
+ {
+ heroy -= 20;
+ if (checkTileCollision(5, getHeroMask())) {
+ herohp = 0;
+ setDrawHP(0);
+ }
+ heroy += 20;
+ }
+
+ //Collide with spikes
+ {
+ PHL_Rect spike = getTileCollision(6, getHeroMask());
+ if (spike.x != -1) {
+ Mask spikeMask;
+ spikeMask.circle = spikeMask.unused = 0;
+ spikeMask.x = spike.x + 10;
+ spikeMask.y = spike.y + 10;
+ spikeMask.w = spikeMask.h = 20;
+
+ if (checkCollision(spikeMask, getHeroMask())) {
+ heroHit(15, spike.x + 20);
+ }
+ }
+ }
+
+ //Death
+ {
+ if (getDrawHP() <= 0) { //Based on the hud's opinion on player's health, apparently
+ state = DEATH;
+ timer = 0;
+ imageIndex = 0;
+ PHL_StopMusic();
+ PHL_PlaySound(sounds[sndHit02], CHN_HERO);
+ }
+ }
+ }
+
+ //Manage charge
+ {
+ if (state != NORMAL) {
+ canCharge = 0;
+ chargeTimer = 0;
+ }
+
+ if (canCharge == 1) {
+ if (btnFaceLeft.held == 0 && btnFaceLeft.pressed == 0) {
+ canCharge = 0;
+ }
+ }
+ }
+
+ //Screen transitions
+ {
+ if (herox < -20) {
+ herox = 620;
+ heroChangeScreen(-1, 0);
+ }
+ else if (herox > 660) {
+ herox = 20;
+ heroChangeScreen(1, 0);
+ }
+ else if (state == LADDER && heroy < -40) {
+ heroy = 440;
+ heroChangeScreen(0, -1);
+ }
+ else if (heroy > 480) {
+ heroy = 0;
+ heroChangeScreen(0, 1);
+ }
+ }
+
+ return result;
+}
+
+void heroChangeScreen(int dx, int dy)
+{
+ vsp = 0;
+ chargeTimer = 0;
+ canCharge = 0;
+ if (hasItem[12] == 1) {
+ canJump = 1;
+ }
+ if (state == HIT || state == SLASH || state == CHARGE) {
+ state = NORMAL;
+ }
+
+ //Force a black screen
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+ PHL_ForceScreenUpdate();
+
+ changeScreen(dx, dy);
+}
+
+
+void heroDraw()
+{
+ int cropX = 0, cropY = 0;
+ int drawShield = 0;
+
+ if (state == DOOR) {
+ cropY = 160;
+ cropX = (int)imageIndex * 40;
+ }
+
+
+ else if (state == GETITEM) {
+ int animation[7] = {0, 1, 2, 3, 2, 0, 1};
+ cropY = 40;
+ cropX = 320 + (animation[(int)imageIndex] * 40);
+ }
+
+ //Climbing
+ else if (state == LADDER) {
+ cropX = 80;
+ cropY = 80;
+ int animation[8] = {0, 1, 2, 1, 0, 3, 4, 3};
+ cropX += 40 * animation[(int)floor(imageIndex)];
+ }
+
+
+ else if (state == NORMAL)
+ {
+ if (onground == 1) {
+ //Walking
+ if (hsp != 0) {
+ cropX = floor(imageIndex) * 40;
+ if (herodir == -1) {
+ cropX += 80;
+ }
+ }
+
+ //Standing
+ else{
+ imageIndex = 0;
+ cropX = 0;
+ cropY = 0;
+ if (hasItem[14] == 1 && shieldTimer <= 0) {
+ drawShield = 1;
+ cropY = 120;
+ if (herodir == -1) {
+ cropX += 40;
+ }
+ if (heldUp == 1) {
+ cropX += 80;
+ }
+ }else{
+ if (herodir == -1) {
+ cropX += 80;
+ }
+ }
+ }
+ }else{
+ //Jumping/falling
+ if (vsp < 0) {
+ imageIndex = 0;
+ }else{
+ imageIndex = 1;
+ }
+ cropX = 160 + (40 * imageIndex);
+ if (herodir == -1) {
+ cropX += 80;
+ }
+ }
+ }else if (state == SLASH)
+ {
+ //Sword Slash
+ int animation[5] = {0, 1, 2, 2, 0};
+
+ cropY = 40;
+ cropX = 40 * animation[(int)floor(imageIndex)];
+ if (herodir == -1) {
+ cropX += 120;
+ }
+ }
+ else if (state == CHARGE) {
+ int animation[5] = {0, 1, 2, 2, 0};
+ cropY = 40;
+ cropX = animation[(int)imageIndex] * 40;
+ if (herodir == -1) {
+ cropX += 120;
+ }
+ }
+ else if (state == HIT) {
+ int thisImage = 12;
+
+ if (onground == 0) {
+ thisImage = 8;
+ }
+
+ if (state == STONE) {
+ thisImage = 28;
+ }
+
+ thisImage += (int)imageIndex;
+ if (herodir == -1) {
+ thisImage += 2;
+ }
+
+ cropX = 40 * thisImage;
+
+ while (cropX >= 640) {
+ cropX -= 640;
+ cropY += 40;
+ }
+ }
+ else if (state == STONE) {
+ cropY = 40;
+
+ if (stoneState == 0 || stoneState == 1) { //In air/on ground
+ int thisImage = (int)imageIndex;
+ if (stoneDir == -1) {
+ thisImage += 2;
+ }
+
+ cropX = 480 + (thisImage * 40);
+ }
+ else if (stoneState == 2) { //Break free
+ int animation[17] = {0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 1, 0};
+ cropX = 320 + (animation[(int)imageIndex] * 40);
+ }
+ }
+ else if (state == DEATH) {
+ if (timer >= 130) {
+ char tempDark = roomDarkness;
+ roomDarkness = 0;
+
+ PHL_DrawTextBold("GAME OVER", 248, 240, YELLOW);
+
+ roomDarkness = tempDark;
+ }else{
+ int frame = 0;
+ if (herodir == 1) {
+ int animation[4] = {0, 3, 6, 9};
+ frame = animation[(int)imageIndex];
+ }
+ if (herodir == -1) {
+ int animation[4] = {2, 1, 4, 11};
+ frame = animation[(int)imageIndex];
+ }
+ cropX = frame * 40;
+ }
+ }
+ else if (state == QUAKE) {
+ cropY = 80;
+ if (herodir == -1) {
+ cropX = 40;
+ }
+ }
+
+ if ((state == HIT && invincible % 6 < 3) || invincible % 2 == 0) {
+ PHL_DrawSurfacePart(herox - 20, heroy, cropX, cropY, 40, 40, images[imgHero]);
+ if (drawShield == 1) {
+ int scx = 320; //Shield crop x
+ int sdx = herox - 2, sdy = heroy + 10; //Shield draw x/y
+ if (herodir == -1) {
+ sdx -= 36;
+ scx += 40;
+ }
+ if (heldUp == 1) {
+ scx += 80;
+ sdy -= 26;
+ sdx -= 8 * herodir;
+ }
+ PHL_DrawSurfacePart(sdx, sdy, scx, 240, 40, 40, images[imgHero]);
+ }
+ }
+
+ //Draw stun effect
+ if (stun == 1) {
+ int frame = (int)(((300 - stunTimer) % 32) / 4);
+ if (frame == 0) {
+ PHL_PlaySound(sounds[sndHit05], CHN_SOUND);
+ }
+
+ int animation[8] = {0, 1, 2, 1, 0, -1, -1, -1};
+
+ if (animation[frame] != -1) {
+ PHL_DrawSurfacePart(herox - 32, heroy - 12, 384 + (animation[frame] * 64), 64, 64, 64, images[imgMisc32]);
+ }
+
+ if (stunTimer <= 0) {
+ stun = 0;
+ PHL_PlaySound(sounds[sndPower01], CHN_SOUND);
+ }else{
+ stunTimer -= 1;
+ }
+ }
+
+ //PHL_DrawRect(mask.x, mask.y, mask.w, mask.h, PHL_NewRGB(0x00, 0x00, 0xFF));
+ updateMask();
+ //PHL_DrawMask(shieldMask);
+}
+
+void updateMask()
+{
+ heroMask.x = herox - 12;
+ heroMask.y = heroy + 14;
+
+ //Update shield mask
+ {
+ shieldMask.unused = 1;
+ if (hasItem[14] == 1) { //has shield
+ if (state == NORMAL && onground == 1 && hsp == 0 && shieldTimer == 0) {
+ shieldMask.unused = 0;
+
+ //Shield held in front
+ if (heldUp == 0) {
+ shieldMask.w = 14;
+ shieldMask.h = 20;
+ shieldMask.x = herox + 10;
+ shieldMask.y = heroy + 20;
+ if (herodir == -1) {
+ shieldMask.x -= 34;
+ }
+ }
+
+ //Shield above head
+ else{
+ shieldMask.w = 24;
+ shieldMask.h = 8;
+ shieldMask.x = herox - 2;
+ shieldMask.y = heroy - 2;
+ if (herodir == -1) {
+ shieldMask.x -= 20;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+int heroHit(int damage, int centerx)
+{
+ if (state != HIT && state != DEATH && state != DOOR && (invincible <= 0 || (state == STONE && invincible == 60))) {
+ if (state != STONE || (state == STONE && stoneState != 2)) {
+ PHL_PlaySound(sounds[sndHit02], CHN_HERO);
+ herohp -= damage;
+
+ vsp = -4;
+ onground = 0;
+
+ if (herox - centerx > 0) {
+ herodir = -1;
+ hsp = herodir * -2;
+ }
+
+ if (herox - centerx < 0) {
+ herodir = 1;
+ hsp = herodir * -2;
+ }
+
+ if (state != STONE) {
+ state = HIT;
+ }
+
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void heroPoison()
+{
+ if (hasItem[8] != 1) { //Does not have poison resistance ring
+ if (poisoned <= 0) {
+ poisoned = 300;
+ }
+ }
+}
+
+void heroStone()
+{
+ //if (((state != HIT && state != DEATH && state != DOOR ) || (state == STONE && stoneState != 2)) && invincible <= 0) {
+ if (state != HIT && state != DEATH && state != DOOR && (invincible <= 0 || (state == STONE && invincible == 60))) {
+ if (state != STONE || (state == STONE && stoneState != 2)) {
+ if (hasItem[9] != 1) { //Does not have green ring
+ if (state == STONE) {
+ herodir = stoneDir;
+ }
+ setHeroState(STONE);
+ }
+ }
+ }
+}
+
+//Get-ers and set-ers
+Mask getHeroMask()
+{
+ updateMask();
+ return heroMask;
+}
+
+int getHeroState()
+{
+ return state;
+}
+
+void setHeroState(int s)
+{
+ state = s;
+
+ //Special cases
+ if (s == GETITEM) {
+ heldUp = 0;
+ //timer = 0;
+ //subPosition = GETITEM;
+ }
+
+ if (s == DOOR) {
+ imageIndex = 0;
+ }
+
+ if (s == STONE) {
+ if (stoneTimer <= 0) {
+ stoneTimer = 350;
+ }
+ stoneState = 0;
+ invincible = 60;
+ stoneDir = herodir;
+ }
+}
+
+int getHeroInvincible()
+{
+ return invincible;
+}
+
+int getHeroDirection()
+{
+ return herodir;
+}
+
+void setHeroDirection(int d)
+{
+ herodir = d;
+}
+
+double getHeroImageIndex()
+{
+ return imageIndex;
+}
+
+void setHeroImageIndex(double index)
+{
+ imageIndex = index;
+}
+
+double getHeroVsp()
+{
+ return vsp;
+}
+
+double getHeroHsp()
+{
+ return hsp;
+}
+
+void setHeroHsp(double newHsp)
+{
+ hsp = newHsp;
+}
+
+void setHeroVsp(double newVsp)
+{
+ vsp = newVsp;
+}
+
+int getHeroOnground()
+{
+ return onground;
+}
+
+void setHeroOnground(int val)
+{
+ onground = val;
+}
+
+void setHeroTimer(int t)
+{
+ timer = t;
+}
+
+int getHeroPoisoned()
+{
+ return poisoned;
+}
+
+void heroStun()
+{
+ if (hasItem[10] == 0) { //Does not have cloak
+ stun = 1;
+ if (stunTimer <= 0) {
+ stunTimer = 300;
+ }
+ }
+}
+
+void setHeroCanjump(int set)
+{
+ canJump = set;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/hero.h b/contrib/games/hydracastlelabyrinth/src/hero.h
new file mode 100644
index 0000000000..00f1732895
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/hero.h
@@ -0,0 +1,55 @@
+#ifndef HERO_H
+#define HERO_H
+
+#include "PHL.h"
+#include "collision.h"
+
+extern double herox, heroy;
+extern double herohp, maxhp;
+extern int heroAmmo, maxAmmo;
+extern int heroWeapon;
+
+extern Mask heroMask;
+extern Mask shieldMask;
+
+void heroSetup();
+void heroCleanup();
+int heroStep();
+void heroDraw();
+
+int heroHit(int damage, int centerx);
+
+void heroPoison();
+void heroStone();
+
+Mask getHeroMask();
+
+int getHeroState();
+void setHeroState(int s);
+
+int getHeroInvincible();
+
+int getHeroDirection();
+void setHeroDirection(int d);
+
+double getHeroImageIndex();
+void setHeroImageIndex(double index);
+
+double getHeroVsp();
+double getHeroHsp();
+
+void setHeroHsp(double newHsp);
+void setHeroVsp(double newVsp);
+
+int getHeroOnground();
+void setHeroOnground(int val);
+
+void setHeroTimer(int t);
+
+int getHeroPoisoned();
+
+void heroStun();
+
+void setHeroCanjump(int set);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/ini.c b/contrib/games/hydracastlelabyrinth/src/ini.c
new file mode 100644
index 0000000000..fd5d1881e5
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/ini.c
@@ -0,0 +1,395 @@
+#include "ini.h"
+#include "game.h"
+#include "options.h"
+#include "PHL.h"
+#include
+#include
+#include "text.h"
+#ifdef EMSCRIPTEN
+#include
+#endif
+
+//char* getFileLocation();
+char* trimString(char* orig);
+
+void screenLoad(char* first, char* second);
+void sizeLoad(char* first, char* second);
+void blurLoad(char* first, char* second);
+void xbrzLoad(char* first, char* second);
+void languageLoad(char* first, char* second);
+void autosaveLoad(char* first, char* second);
+void musictypeLoad(char* first, char* second);
+void musicvolumeLoad(char* first, char* second);
+
+void iniInit()
+{
+ //Build filepath
+ char fullPath[128];
+ {
+ #ifdef _SDL
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ strcpy(fullPath, "PROGDIR:.hydracastlelabyrinth/");
+ #elif defined(EMSCRIPTEN)
+ strcpy(fullPath, "hcl_data/");
+ #elif defined(_KOLIBRI)
+ strcpy(fullPath, KOS_HCL_SAVES_PATH"/");
+ #else
+ strcpy(fullPath, getenv("HOME"));
+ strcat(fullPath, "/.hydracastlelabyrinth/");
+ #endif
+ #elif defined(_3DS)
+ strcpy(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #else
+ strcpy(fullPath, "");
+ #endif
+ strcat(fullPath, "system.ini");
+ }
+
+ FILE* f;
+
+ if ( (f = fopen(fullPath, "rt")) )
+ {
+ //File exists - read it
+ fclose(f);
+ loadSettings();
+ }else{
+ //File does not exists - create it (with default hardcoded settings)
+ saveSettings();
+ }
+
+}
+
+void saveSettings()
+{
+ //Build filepath
+ char fullPath[128];
+ {
+ #ifdef _SDL
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ strcpy(fullPath, "PROGDIR:.hydracastlelabyrinth/");
+ #elif defined(EMSCRIPTEN)
+ strcpy(fullPath, "hcl_data/");
+ #elif defined(_KOLIBRI)
+ strcpy(fullPath, KOS_HCL_SAVES_PATH"/");
+ #else
+ strcpy(fullPath, getenv("HOME"));
+ strcat(fullPath, "/.hydracastlelabyrinth/");
+ #endif
+ #elif defined(_3DS)
+ strcpy(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #else
+ strcpy(fullPath, "");
+ #endif
+ strcat(fullPath, "system.ini");
+ }
+
+ FILE* f;
+
+ if ( (f = fopen(fullPath, "wt")) )
+ {
+ fprintf(f, "[disp]");
+ #ifdef _3DS
+ //Screen
+ fprintf(f, "\r\nscreen=");
+ if (activeScreen->screen == GFX_BOTTOM) {
+ fprintf(f, "bottom");
+ }else{
+ fprintf(f, "top");
+ }
+ #endif
+
+ #ifdef _PSP
+ //Screen Size
+ fprintf(f, "\r\nsize=");
+ if (getScreenSize() == 1) {
+ fprintf(f, "1");
+ }
+ else if (getScreenSize() == 2) {
+ fprintf(f, "2");
+ }
+ else {
+ fprintf(f, "0");
+ }
+
+ //Screen Blur
+ fprintf(f, "\r\nblur=");
+ if (getBlur() == 1) {
+ fprintf(f, "on");
+ }else{
+ fprintf(f, "off");
+ }
+ #endif
+ #ifdef _SDL
+ //xBRZ Scaling
+ fprintf(f, "\r\nxbrz=");
+ if (getXBRZ() == 1) {
+ fprintf(f, "on");
+ }else{
+ fprintf(f, "off");
+ }
+ #endif
+
+ fprintf(f, "\r\n[system]");
+
+ //Language
+ fprintf(f, "\r\nlanguage=");
+ if (getLanguage() == 0) {
+ fprintf(f, "jp");
+ }
+ if (getLanguage() == 1) {
+ fprintf(f, "en");
+ }
+
+ //Autosave
+ fprintf(f, "\r\nautosave=");
+ if (getAutoSave() == 1) {
+ fprintf(f, "on");
+ }else{
+ fprintf(f, "off");
+ }
+
+ #ifdef _SDL
+ fprintf(f, "\r\n[audio]");
+ fprintf(f, "\r\nmusic_type=%s", getMusicType()?"ogg":"midi");
+ fprintf(f, "\r\nmusic=%d", music_volume);
+ // Audio
+ #endif
+ fclose(f);
+ }
+ #ifdef EMSCRIPTEN
+ EM_ASM(
+ FS.syncfs(false,function () {
+ Module.print("File sych'd")
+ });
+ );
+ #endif
+}
+
+void loadSettings()
+{
+ //Build filepath
+ char fullPath[128];
+ {
+ #ifdef _SDL
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ strcpy(fullPath, "PROGDIR:.hydracastlelabyrinth/");
+ #elif defined(EMSCRIPTEN)
+ strcpy(fullPath, "hcl_data/");
+ #elif defined(_KOLIBRI)
+ strcat(fullPath, KOS_HCL_SAVES_PATH"/");
+ #else
+ strcpy(fullPath, getenv("HOME"));
+ strcat(fullPath, "/.hydracastlelabyrinth/");
+ #endif
+ #elif defined(_3DS)
+ strcpy(fullPath, "sdmc:/3ds/appdata/HydraCastleLabyrinth/");
+ #else
+ strcpy(fullPath, "");
+ #endif
+ strcat(fullPath, "system.ini");
+ }
+
+ FILE* f;
+
+ if ( (f = fopen(fullPath, "rt")) )
+ {
+ char line[80];
+
+ while ( (fgets(line, 80, f) != NULL) )
+ {
+ char* lineptr = line;
+ lineptr = trimString(lineptr);
+
+ if (lineptr != NULL) {
+ //Ignore category lines
+ if (lineptr[0] != '[')
+ {
+ //Check if it has a = delimiter first
+ int i;
+ for (i = 0; i < 80; i++) {
+ if (line[i] == '=')
+ {
+ //Begin line splitting
+ char* half;
+ if ( (half = strsep(&lineptr, "=")) != NULL)
+ {
+ //first half
+ char* fhalf = half;
+
+ if ( (half = strsep(&lineptr, "=")) != NULL) {
+ //Second half
+ char* shalf = half;
+
+ //Load options
+ screenLoad(fhalf, shalf);
+ sizeLoad(fhalf, shalf);
+ blurLoad(fhalf, shalf);
+ xbrzLoad(fhalf, shalf);
+ languageLoad(fhalf, shalf);
+ autosaveLoad(fhalf, shalf);
+ musictypeLoad(fhalf, shalf);
+ musicvolumeLoad(fhalf, shalf);
+ }
+ }
+
+ //End
+ i = 81;
+ }
+ }
+ }
+ }
+ }
+ fclose(f);
+ }
+
+}
+
+//Build file path
+/*
+char* getFileLocation()
+{
+ char fullPath[128];
+ strcpy(fullPath, "");
+ #ifdef _CIA
+ strcat(fullPath, "sdmc:/3ds/HydraCastleLabyrinth/");
+ #endif
+ strcat(fullPath, "system.ini");
+
+ return fullPath;
+}
+*/
+
+char* trimString(char* orig)
+{
+ char* output = orig;
+
+ int i, r = 0;
+ for (i = 0; i < strlen(orig); i++) {
+ if (orig[i] != ' ' && orig[i] != '\n' && orig[i] != '\r') {
+ output[r] = orig[i];
+ r++;
+ }
+ }
+
+ orig[r] = 0;
+
+ return output;
+}
+
+void screenLoad(char* first, char* second)
+{
+ #ifdef _3DS
+ if (strcmp(first, "screen") == 0) {
+ if (strcmp(second, "top") == 0) {
+ swapScreen(GFX_TOP, GFX_LEFT);
+ }
+ if (strcmp(second, "bottom") == 0) {
+ swapScreen(GFX_BOTTOM, GFX_LEFT);
+ }
+ }
+ #endif
+}
+
+void sizeLoad(char* first, char* second)
+{
+ #ifdef _PSP
+ if (strcmp(first, "size") == 0) {
+ if (second[0] == '0') {
+ //fprintf(debug, "\nsize is 0");
+ setScreenSize(0);
+ }
+ if (second[0] == '1') {
+ //fprintf(debug, "\nsize is 1");
+ setScreenSize(1);
+ }
+ if (second[0] == '2') {
+ //fprintf(debug, "\nsize is 2");
+ setScreenSize(2);
+ }
+ }
+ #endif
+}
+
+void blurLoad(char* first, char* second)
+{
+ #ifdef _PSP
+ if (strcmp(first, "blur") == 0) {
+ if (strcmp(second, "on") == 0) {
+ //fprintf(debug, "\nblur is on");
+ //oslSetBilinearFilter(1);
+ setBlur(1);
+ }
+ if (strcmp(second, "off") == 0) {
+ //fprintf(debug, "\nblur is off");
+ //oslSetBilinearFilter(0);
+ setBlur(0);
+ }
+ }
+ #endif
+}
+
+void xbrzLoad(char* first, char* second)
+{
+ #ifdef _SDL
+ if (strcmp(first, "xbrz") == 0) {
+ if (strcmp(second, "on") == 0) {
+ setXBRZ(1);
+ }
+ if (strcmp(second, "off") == 0) {
+ setXBRZ(0);
+ }
+ }
+ #endif
+}
+
+void languageLoad(char* first, char* second)
+{
+ if (strcmp(first, "language") == 0) {
+ if (strcmp(second, "en") == 0) {
+ setLanguage(ENGLISH);
+ }
+ if (strcmp(second, "jp") == 0) {
+ setLanguage(JAPANESE);
+ }
+ }
+}
+
+void autosaveLoad(char* first, char* second)
+{
+ if (strcmp(first, "autosave") == 0) {
+ if (strcmp(second, "on") == 0) {
+ //fprintf(debug, "\nautosave is on");
+ setAutoSave(1);
+ }
+ if (strcmp(second, "off") == 0) {
+ //fprintf(debug, "\nautosave is off");
+ setAutoSave(0);
+ }
+ }
+}
+
+void musicvolumeLoad(char* first, char* second)
+{
+ #ifdef _SDL
+ if (strcmp(first, "music") == 0) {
+ if (second[0] >= '0' && second[0] <= '4') {
+ music_volume = second[0]-'0';
+ PHL_MusicVolume(0.25f * music_volume);
+ }
+ }
+ #endif
+}
+
+void musictypeLoad(char* first, char* second)
+{
+ #ifdef _SDL
+ if (strcmp(first, "music_type") == 0) {
+ if (strcmp(second, "ogg") == 0) {
+ setMusicType(1);
+ }
+ if (strcmp(second, "midi") == 0) {
+ setMusicType(0);
+ }
+ }
+ #endif
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/ini.h b/contrib/games/hydracastlelabyrinth/src/ini.h
new file mode 100644
index 0000000000..c97fd01cbd
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/ini.h
@@ -0,0 +1,10 @@
+#ifndef INI_H
+#define INI_H
+
+//Functions to handle the "system.ini" file
+void iniInit();
+
+void saveSettings();
+void loadSettings();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/inventory.c b/contrib/games/hydracastlelabyrinth/src/inventory.c
new file mode 100644
index 0000000000..52ed1abebd
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/inventory.c
@@ -0,0 +1,180 @@
+#include "inventory.h"
+#include "PHL.h"
+#include "game.h"
+#include "text.h"
+
+int cursorX = 0;
+int cursorY = 0;
+
+#ifdef EMSCRIPTEN
+static char tempDark;
+void inventorySetup()
+{
+ tempDark = roomDarkness;
+ roomDarkness = 0;
+
+ PHL_PlaySound(sounds[sndPi04], CHN_SOUND);
+}
+int inventoryEMStep()
+{
+ int result = -1;
+ PHL_MainLoop();
+
+ PHL_StartDrawing();
+ PHL_ScanInput();
+
+ if (inventoryStep() == 1) {
+ result = 0;
+ }
+
+ inventoryDraw();
+
+ PHL_EndDrawing();
+
+ if(!result)
+ roomDarkness = tempDark;
+ return result;
+}
+#else
+
+void inventory()
+{
+ char tempDark = roomDarkness;
+ roomDarkness = 0;
+
+ char loop = 1;
+ PHL_PlaySound(sounds[sndPi04], CHN_SOUND);
+ while (PHL_MainLoop() && loop == 1)
+ {
+ PHL_StartDrawing();
+ PHL_ScanInput();
+
+ if (inventoryStep() == 1) {
+ loop = 0;
+ }
+
+ inventoryDraw();
+
+ PHL_EndDrawing();
+ }
+
+ roomDarkness = tempDark;
+}
+#endif
+int inventoryStep()
+{
+ secretCountdown();
+
+ //Input
+ char playsnd = 0;
+
+ if (btnRight.pressed - btnLeft.pressed != 0) {
+ cursorX += btnRight.pressed - btnLeft.pressed;
+ playsnd = 1;
+ }
+ if (btnDown.pressed - btnUp.pressed != 0) {
+ cursorY += btnDown.pressed - btnUp.pressed;
+ playsnd = 1;
+ }
+
+ if (playsnd == 1) {
+ PHL_PlaySound(sounds[sndPi01], CHN_SOUND);
+ }
+
+ //Limit cursor
+ if (cursorX < 0) { cursorX = 6; }
+ if (cursorX > 6) { cursorX = 0; }
+ if (cursorY < 0) { cursorY = 3; }
+ if (cursorY > 3) { cursorY = 0; }
+
+ if (btnStart.pressed == 1 || btnDecline.pressed == 1)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+void inventoryDraw()
+{
+ //Black background
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+
+ //Labels
+ PHL_DrawTextBold("SUB WEAPON", 16, 16, YELLOW);
+ PHL_DrawTextBold("ITEM", 16, 96, YELLOW);
+ PHL_DrawTextBold("KEY", 16, 320, YELLOW);
+
+ //Blue rectangles
+ int i, a, cx, cy;
+ //Weapons
+ for (i = 0; i < 5; i++) {
+ PHL_DrawRect(18 + (48 * i), 34, 44, 44, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(20 + (48 * i), 36, 40, 40, PHL_NewRGB(119, 166, 219));
+ if (hasWeapon[i] == 1) {
+ cx = (i + 1) * 40;
+ }else{
+ cx = 0;
+ }
+ PHL_DrawSurfacePart(20 + (48 * i), 36, cx, 0, 40, 40, images[imgItems]);
+ }
+ //Items
+ int count = 0;
+ int imageOrder[28] = { 13, 17, 16, 15, 8, 10, 9,
+ 18, 4, 6, 5, 7, 3, 11,
+ 14, 12, 1, 2, 22, 26, 27,
+ 21, 25, 28, 23, 20, 19, 24 };
+
+ for (i = 0; i < 4; i++) {
+ for (a = 0; a < 7; a++) {
+ PHL_DrawRect(18 + (48 * a), 114 + (48 * i), 44, 44, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(20 + (48 * a), 116 + (48 * i), 40, 40, PHL_NewRGB(119, 166, 219));
+
+ if (hasItem[count] == 0) {
+ cx = 0;
+ cy = 0;
+ }else{
+ cy = 0;
+ cx = (imageOrder[count] + 6) * 40;
+ while (cx >= 640) {
+ cy += 40;
+ cx -= 640;
+ }
+ }
+
+ PHL_DrawSurfacePart(20 + (48 * a), 116 + (48 * i), cx, cy, 40, 40, images[imgItems]);
+ count++;
+ }
+ }
+ //Keys
+ for (i = 0; i < 8; i++) {
+ PHL_DrawRect(18 + (48 * i), 338, 44, 44, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(20 + (48 * i), 340, 40, 40, PHL_NewRGB(119, 166, 219));
+ if (hasKey[i] == 1) {
+ cx = 120 + (i * 40);
+ cy = 80;
+ }else{
+ cx = 0;
+ cy = 0;
+ }
+ PHL_DrawSurfacePart(20 + (48 * i), 340, cx, cy, 40, 40, images[imgItems]);
+ }
+
+ //Text box
+ PHL_DrawRect(16, 400, 606, 46, PHL_NewRGB(255, 255, 255));
+ PHL_DrawRect(18, 402, 602, 42, PHL_NewRGB(0, 0, 255));
+
+ //Text
+ if (hasItem[cursorX + (cursorY * 7)] == 1) {
+ int drawX = 32, drawY = 402;
+
+ //Draw item name
+ drawX = drawText(itemName[cursorX + (cursorY * 7) + 5], drawX, drawY);
+ //Draw item description
+ drawX = drawCharacter(6, 0, drawX, drawY);
+ drawText(itemDescription[cursorX + (cursorY * 7)], drawX, drawY);
+ }
+
+ //Cursor
+ PHL_DrawSurfacePart(16 + (cursorX * 48), 112 + (cursorY * 48), 0, 96, 48, 48, images[imgHud]);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/inventory.h b/contrib/games/hydracastlelabyrinth/src/inventory.h
new file mode 100644
index 0000000000..76834cc1ff
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/inventory.h
@@ -0,0 +1,13 @@
+#ifndef INVENTORY_H
+#define INVENTORY_H
+
+#ifdef EMSCRIPTEN
+void inventorySetup();
+int inventoryEMStep();
+#else
+void inventory();
+#endif
+int inventoryStep();
+void inventoryDraw();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/main.c b/contrib/games/hydracastlelabyrinth/src/main.c
new file mode 100644
index 0000000000..02703efa88
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/main.c
@@ -0,0 +1,182 @@
+#include "PHL.h"
+#include "game.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef ODROID
+#define _XTYPEDEF_MASK
+#include
+#endif
+#ifdef EMSCRIPTEN
+#include
+#endif
+
+#ifdef __amigaos4__
+static const char* __attribute__((used)) stackcookie = "$STACK: 1000000";
+#endif
+#ifdef __MORPHOS__
+unsigned long __stack = 1000000;
+#endif
+
+#ifdef _KOLIBRI
+extern char* dirname(char*);
+extern void setcwd(char*);
+#endif
+
+void createSaveLocations()
+{
+ //Force create save data folders
+ #ifdef _3DS
+ //3DS builds
+ mkdir("sdmc:/3ds", 0777);
+ mkdir("sdmc:/3ds/appdata", 0777);
+ mkdir("sdmc:/3ds/appdata/HydraCastleLabyrinth", 0777);
+ mkdir("sdmc:/3ds/appdata/HydraCastleLabyrinth/data", 0777);
+ mkdir("sdmc:/3ds/appdata/HydraCastleLabyrinth/map", 0777);
+ #elif defined(_SDL)
+ char buff[4096];
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ strcpy(buff,"PROGDIR:.hydracastlelabyrinth");
+ #elif defined(EMSCRIPTEN)
+ strcpy(buff, "hcl_data");
+ #elif defined (_KOLIBRI)
+ mkdir(KOS_HCL_SAVES_PATH, 777);
+ #else
+ strcpy(buff, getenv("HOME"));
+ strcat(buff, "/.hydracastlelabyrinth");
+ #endif
+ // if exist first?
+ #ifndef _KOLIBRI
+ struct stat sb;
+ if(!(stat(buff, &sb)==0 && S_ISDIR(sb.st_mode)))
+ mkdir(buff, 0777);
+ #endif
+ #else
+ //psp, wii
+ mkdir("/data", 0777);
+ mkdir("/map", 0777);
+ #endif
+}
+
+#ifdef EMSCRIPTEN
+int fileSynched = 0;
+#endif
+
+int main(int argc, char **argv)
+{
+ //Setup
+ #ifdef _KOLIBRI
+ setcwd(dirname(argv[0]));
+ #endif
+
+ #ifdef EMSCRIPTEN
+ // that HEAP32 on &fileSynched looks like a hack, but I needed a way to be sure the DB is read before reading the ini files
+ EM_ASM_INT({
+ FS.mkdir('hcl_data');
+ FS.mount(IDBFS,{},'hcl_data');
+ Module.print("Will import permanent storage");
+ FS.syncfs(true, function() {
+ Module.print("Permanent storage imported");
+ HEAP32[$0>>2] = 1;
+ });
+ }, &fileSynched);
+ #endif
+ #ifdef _3DS
+ sdmcInit();
+ osSetSpeedupEnable(false);
+ #endif
+ #ifdef _SDL
+ if ( SDL_Init(SDL_INIT_VIDEO) < 0) {
+ SDL_Delay(5000);
+ exit(EXIT_FAILURE);
+ }
+ #if defined(PANDORA) || defined(PYRA) || defined(CHIP) || defined(ODROID)
+ wantFullscreen = 1;
+ #else
+ wantFullscreen = 0;
+ #endif
+ #ifdef CHIP
+ screenScale = 1;
+ #elif defined(BITTBOY)
+ screenScale = 1;
+ #elif defined(PYRA)
+ //screenScale = 3;
+ desktopFS = 1;
+ #elif defined(ODROID)
+ desktopFS = 1;
+ #else
+ screenScale = 2;
+ #endif
+ useJoystick = 1;
+ // get command line arguments
+ for (int i=1; icurrent_h;
+ screenW = infos->current_w;
+ #endif
+ } else {
+ screenW = 320 * screenScale;
+ screenH = 240 * screenScale;
+ }
+ printf("Hydra Castle Labyrinth, %s %dx%d scale=x%d%s, using Joystick=%d\n", (wantFullscreen || desktopFS)?"Fullscreen":"Windowed", screenW, screenH, screenScale, getXBRZ()?" xBRZ":"", useJoystick);
+ #endif
+
+ srand(time(NULL));
+ createSaveLocations();
+
+ game();
+
+ //System specific cleanup
+ #ifdef _PSP
+ sceKernelExitGame();
+ #endif
+
+ #ifdef _3DS
+ sdmcExit();
+ #endif
+
+ return 0;
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/misc.c b/contrib/games/hydracastlelabyrinth/src/misc.c
new file mode 100644
index 0000000000..cf10bc6eee
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/misc.c
@@ -0,0 +1,92 @@
+#include
+#include
+
+void *memrchr(const void *m, int c, size_t n)
+{
+ const unsigned char *s = (const unsigned char*)m;
+ c = (unsigned char)c;
+ while (n--) if (s[n]==c) return (void *)(s+n);
+ return 0;
+}
+
+void setcwd(char* path){
+ __asm__ __volatile__(
+ "int $0x40"
+ ::"a"(30), "b"(1), "c"(path)
+ :"memory"
+ );
+}
+
+char *dirname(char *path)
+{
+ static const char dot[] = ".";
+ char *last_slash;
+ last_slash = path != NULL ? strrchr (path, '/') : NULL;
+ if (last_slash != NULL && last_slash != path && last_slash[1] == '\0')
+ {
+ char *runp;
+ for (runp = last_slash; runp != path; --runp)
+ if (runp[-1] != '/')
+ break;
+ if (runp != path)
+ last_slash = (char*)memrchr((void*)path, '/', runp - path);
+ }
+ if (last_slash != NULL)
+ {
+ char *runp;
+ for (runp = last_slash; runp != path; --runp)
+ if (runp[-1] != '/')
+ break;
+ if (runp == path)
+ {
+ if (last_slash == path + 1)
+ ++last_slash;
+ else
+ last_slash = path + 1;
+ }
+ else
+ last_slash = runp;
+ last_slash[0] = '\0';
+ }
+ else
+ path = (char *) dot;
+ return path;
+}
+
+#pragma pack(push,1)
+typedef struct{
+ unsigned p00;
+ union{
+ uint64_t p04;
+ struct {
+ unsigned p04dw;
+ unsigned p08dw;
+ };
+ };
+ unsigned p12;
+ union {
+ unsigned p16;
+ const char *new_name;
+ void *bdfe;
+ void *buf16;
+ const void *cbuf16;
+ };
+ char p20;
+ const char *p21;
+}ksys70_t;
+
+
+int kos_mkdir(const char *path, unsigned v)
+{
+ int status;
+ ksys70_t dir_opt;
+ dir_opt.p00 = 9;
+ dir_opt.p21 = path;
+ __asm__ __volatile__(
+ "int $0x40"
+ :"=a"(status)
+ :"a"(70), "b"(&dir_opt)
+ :"memory"
+ );
+ return status;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/object.c b/contrib/games/hydracastlelabyrinth/src/object.c
new file mode 100644
index 0000000000..947f428e5f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/object.c
@@ -0,0 +1,1532 @@
+#include "object.h"
+#include "game.h"
+#include "hero.h"
+#include
+#include
+#include "enemies/slug.h"
+#include "game.h"
+#include
+
+void nullFunction(void* v);
+
+void ammoStep(Ammo* a);
+void ammoDraw(Ammo* a);
+
+void destroyableStep(Destroyable* d);
+
+void secretTriggerStep(SecretTrigger* s);
+
+void chestStep(Chest* c);
+void chestDraw(Chest* c);
+
+void savePointStep(SavePoint* s);
+void savePointDraw(SavePoint* s);
+
+void doorStep(Door* d);
+void doorDraw(Door* d);
+
+void lockBlockStep(LockBlock* l);
+void lockBlockDraw(LockBlock* l);
+
+void switchStep(Switch* s);
+int switchActivate(Switch* s);
+void switchResult(Switch* s);
+void switchDraw(Switch* s);
+
+void gateStep(Gate* g);
+void gateDraw(Gate* g);
+
+void statueStep(Statue* s);
+void statueDraw(Statue* s);
+
+void buttonStep(FloorPad* f);
+void buttonDraw(FloorPad* f);
+
+void ladderStep(Ladder* l);
+void ladderActivate(int x, int y);
+
+void generatorStep(Generator* g);
+void generatorDraw(Generator* g);
+
+void shockgateStep(Shockgate* s);
+void shockgateDraw(Shockgate* s);
+
+void crownStep(Crown* c);
+void crownDraw(Crown* c);
+
+void nullFunction(void* v)
+{
+ //Wow, it's literally nothing!
+}
+
+void objectDestroy(int id)
+{
+ if (objects[id] != NULL) {
+ if (objects[id]->data != NULL) {
+ free(objects[id]->data);
+ }
+ objects[id]->data = NULL;
+
+ free(objects[id]);
+ }
+ objects[id] = NULL;
+}
+
+//Ammo/Health
+void spawnCollectable(int x, int y)
+{
+ int num = (rand() % 100) + 1;
+ int result = -1;
+
+ int heartchance = 15;
+ int ammochance = 10;
+
+ if (hasItem[3] == 1) { //Has golden seed
+ heartchance *= 2;
+ ammochance *= 2;
+ }
+
+ if (num <= heartchance) {
+ result = 1; //Heart
+ }
+ else if (num > heartchance && num <= heartchance + ammochance) {
+ result = 0; //Ammo
+ }
+
+ //result = rand() % 2;
+
+ if (result != -1) {
+ createAmmo(x, y, result);
+ }
+}
+
+void createAmmo(int x, int y, int type)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Ammo* a = malloc(sizeof *a);
+ a->id = i;
+
+ a->x = x;
+ a->y = y;
+ a->type = type;
+
+ a->vsp = -2.5;
+ a->grav = 0.2;
+ a->blink = 30;
+ a->canLand = 0;
+ a->bounce = 0;
+
+ o->data = a;
+ o->objectStep = ammoStep;
+ o->objectDraw = ammoDraw;
+ o->type = -1;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void ammoStep(Ammo* a)
+{
+ char dead = 0;
+
+ //Flashing animation
+ {
+ if (a->blink > 0) {
+ a->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 2;
+ mask.h = 1;
+ mask.x = a->x - (mask.w / 2);
+ mask.y = a->y + (40 - mask.h);
+ }
+
+ //Movement
+ {
+ a->y += a->vsp;
+ a->vsp += a->grav;
+
+ mask.y = a->y + (40 - mask.h);
+ }
+
+ //Destroy if it falls in a pit
+ {
+ if (a->y > 480) {
+ dead = 1;
+ }
+ }
+
+ //Falling
+ if (a->vsp >= 0) {
+ //Inside of a block
+ if (a->canLand == 0) {
+ if (checkTileCollision(1, mask) == 0 && checkTileCollision(3, mask) == 0) {
+ a->canLand = 1;
+ }
+ }
+
+ //Land on ground
+ if (a->canLand == 1) {
+ PHL_Rect collide = getTileCollision(1, mask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, mask);
+ }
+ if (collide.x != -1) {
+
+ a->y = collide.y - 40;
+ a->vsp = 0;
+
+ //Bounce
+ if (a->bounce <= 2) {
+ double bounceVsp[3] = {-2, -1, 0};
+
+ if (a->bounce > 2) {
+ a->bounce = 2;
+ }
+
+ a->vsp = bounceVsp[a->bounce];
+ a->bounce += 1;
+ PHL_PlaySound(sounds[sndPi02], 2);
+ }
+ }
+ }
+ }else{
+ a->canLand = 0;
+ }
+
+ //Setup hero collision mask
+ {
+ mask.w = 20;
+ mask.h = 32;
+ //Heart
+ if (a->type == 1) {
+ mask.w = 28;
+ mask.h = 26;
+ }
+ mask.x = a->x - (mask.w / 2);
+ mask.y = a->y + (40 - mask.h);
+ }
+
+ //Collect
+ {
+ if (a->blink <= 0 && checkCollision(mask, heroMask)) {
+ //Ammo
+ if (a->type == 0) {
+ heroAmmo += 5;
+ if (heroAmmo > maxAmmo) {
+ heroAmmo = maxAmmo;
+ }
+ }
+ //Heart
+ else if (a->type == 1) {
+ herohp += 10;
+ if (herohp > 128) {
+ herohp = 128;
+ }
+ }
+
+ dead = 1;
+ PHL_PlaySound(sounds[sndGet02], 2);
+ }
+ }
+
+ //Destroy object
+ {
+ if (dead == 1) {
+ objectDestroy(a->id);
+ }
+ }
+
+}
+
+void ammoDraw(Ammo* a)
+{
+ if (a->blink % 2 == 0) {
+ int cropX[2] = {40, 0};
+
+ PHL_DrawSurfacePart(a->x - 20, a->y + 2, cropX[a->type], 120, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Destroyable Block
+void createDestroyable(int x, int y, int secret)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Destroyable* d = malloc(sizeof *d);
+ d->id = i;
+
+ d->x = x;
+ d->y = y;
+ d->secret = secret;
+
+ d->hp = 3;
+ //d->invulnerable = 0;
+
+ d->mask.x = x;
+ d->mask.y = y;
+ d->mask.w = d->mask.h = 40;
+ d->mask.unused = d->mask.circle = 0;
+
+ o->data = d;
+ o->objectStep = destroyableStep;
+ o->objectDraw = nullFunction;
+ o->type = 3;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void destroyableStep(Destroyable* d)
+{
+ //if (d->invulnerable <= 0) {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown <= 0) {
+ if (checkCollision(d->mask, weapons[i]->weaponMask)) {
+ if (hasItem[0] == 1) { //Has copper pick
+ d->hp -= 1;
+ if (hasItem[1] == 0) {
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }else{ //Has silver pick
+ d->hp -= 2;
+ }
+ }
+
+ //d->invulnerable = 15;
+ weaponHit(weapons[i]);
+
+ if (d->hp <= 0) {
+ createRockSmash(d->x + 20, d->y + 20);
+ int sx = d->x / 40;
+ int sy = d->y / 40;
+ foreground.tileX[sx][sy] = 0;
+ foreground.tileY[sx][sy] = 0;
+ collisionTiles[sx][sy] = 0;
+ PHL_UpdateBackground(background, foreground);
+
+ if (d->secret == 0) {
+ spawnCollectable(d->x + 20, d->y);
+ }else if (d->secret == 1) {
+ roomSecret = 1;
+ }
+
+ objectDestroy(d->id);
+
+ }
+
+ if (hasItem[0] == 0) {
+ PHL_PlaySound(sounds[sndHit03], 1);
+ }
+ }
+ }
+ }
+ }
+ /*}else{
+ d->invulnerable -= 1;
+ }
+ */
+}
+
+//Secret Trigger
+void createSecretTrigger(int type, int enemyType, int flag)
+{
+ if (flag != 0 && flags[flag] == 1) {
+ roomSecret = 1;
+ }else{
+
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ SecretTrigger* s = malloc(sizeof *s);
+ s->id = i;
+ s->flag = flag;
+
+ s->type = type;
+ s->enemyType = enemyType;
+
+ o->data = s;
+ o->objectStep = secretTriggerStep;
+ o->objectDraw = nullFunction;
+ o->type = -1;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+}
+
+void secretTriggerStep(SecretTrigger* s)
+{
+ int i, result = 1;
+ for (i = 0; i < MAX_ENEMIES; i++) {
+ if (s->type == 0) { //Destroy all enemies
+ if (enemies[i] != NULL && enemies[i]->type != -1) {
+ i = MAX_ENEMIES;
+ result = 0;
+ }
+ }
+ else if (s->type == 1) { //Destroy one type of enemy
+ if (enemies[i] != NULL && enemies[i]->type == s->enemyType) {
+ i = MAX_ENEMIES;
+ result = 0;
+ }
+ }
+ else{ //No trigger, only activate on creation
+ result = 0;
+ }
+ }
+
+ if (result == 1) {
+ if (s->flag != 0) {
+ flags[s->flag] = 1;
+ }
+ roomSecret = 1;
+ objectDestroy(s->id);
+ }
+}
+
+//Chest
+void createChest(int x, int y, int item, int secret)
+{
+ //Don't create if the player already has the item
+ int dospawn = 1;
+ if (item <= 4) {
+ if (hasWeapon[item] == 1) { dospawn = 0; }
+ }
+ else if (item <= 32) {
+ if (hasItem[item - 5] == 1) { dospawn = 0; }
+ }
+ else if (item <= 40) {
+ if (hasKey[item - 33] == 1) { dospawn = 0; }
+ }
+
+ if (dospawn == 1) {
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Chest* c = malloc(sizeof *c);
+ c->id = i;
+
+ c->x = x;
+ c->y = y;
+ c->item = item;
+ c->secret = secret;
+ c->timer = 10;
+
+ c->visible = 1;
+ if (secret == 1 && roomSecret == 0) { //Assume secret trigger is loaded before chest
+ c->visible = 0;
+ if (hasItem[2] == 1) { //Has Bell
+ PHL_PlaySound(sounds[sndBell01], CHN_SOUND);
+ //bellFlag = 1;
+ }
+ }
+
+ c->mask.x = x;
+ c->mask.y = y;
+ c->mask.w = c->mask.h = 40;
+ c->mask.unused = c->mask.circle = 0;
+
+ o->data = c;
+ o->objectStep = chestStep;
+ o->objectDraw = chestDraw;
+ o->type = 4;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+}
+
+#ifdef EMSCRIPTEN
+extern int em_state;
+#endif
+void chestStep(Chest* c)
+{
+ if (c->visible == 1) {
+ c->timer -= 1;
+ if (c->timer <= 0) {
+ createEffectExtra(5, c->x + (rand() % 40) + 1, c->y + 4 + (rand() % 40) + 1, 0, 0, 0);
+ c->timer = 12;
+ }
+
+ if (btnUp.pressed == 1) {
+ if (getHeroOnground() == 1 && checkCollisionXY(c->mask, herox, heroy + 20) == 1) {
+ PHL_PlaySound(sounds[sndGet02], CHN_HERO);
+ if (c->item <= 4) {
+ hasWeapon[c->item] = 1;
+ itemGotX = 40 + (c->item * 40);
+ itemGotY = 0;
+ }
+ else if (c->item <= 32) {
+ hasItem[c->item - 5] = 1;
+ int itemorder[28] = { 12, 16, 15, 14, 7, 9, 8,
+ 17, 3, 5, 4, 6, 2, 10,
+ 13, 11, 0, 1, 21, 25, 26,
+ 20, 24, 27, 22, 19, 18, 23 };
+ itemGotX = 280 + (itemorder[c->item - 5] * 40);
+ itemGotY = 0;
+ while (itemGotX >= 640) {
+ itemGotX -= 640;
+ itemGotY += 40;
+ }
+ }
+ else if (c->item <= 40) {
+ hasKey[c->item - 33] = 1;
+ itemGotX = 120 + ((c->item - 33) * 40);
+ itemGotY = 80;
+ }
+
+ //Fix no 2nd jump immediatly after getting boots
+ if (c->item == 17) {
+ setHeroCanjump(1);
+ }
+
+ //setHeroState(6); //Set hero state to GETITEM
+ int saveItem = c->item;
+
+ objectDestroy(c->id);
+ #ifdef EMSCRIPTEN
+ getItemSetup(saveItem);
+ em_state = 50;
+ #else
+ getItem(saveItem);
+ #endif
+ }
+ }
+ }
+ else {
+ if (roomSecret == 1) {
+ c->visible = 1;
+ playSecret();
+ }
+ //Visible if the boss is already defeated
+ if (bossDefeatedFlag == 1) {
+ c->visible = 1;
+ }
+ }
+}
+
+void chestDraw(Chest* c)
+{
+ if (c->visible == 1) {
+ int dx = 520, dy = 0;
+
+ if (c->item > 32) {
+ dx = 240;
+ dy = 120;
+ }
+
+ PHL_DrawSurfacePart(c->x, c->y, dx, dy, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Save point
+void createSavePoint(int x, int y, int hidden)
+{
+ if (hidden == 0 || hasKey[7] == 1) {
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ SavePoint* s = malloc(sizeof *s);
+ s->id = i;
+
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+
+ s->mask.x = x + 6;
+ s->mask.y = y;
+ s->mask.w = 28;
+ s->mask.h = 40;
+ s->mask.unused = s->mask.circle = 0;
+
+ o->data = s;
+ o->objectStep = savePointStep;
+ o->objectDraw = savePointDraw;
+ o->type = -1;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+}
+
+void savePointStep(SavePoint* s)
+{
+ s->imageIndex += 0.15;
+ if (s->imageIndex >= 4) {
+ s->imageIndex -= 4;
+ }
+
+ if (btnUp.pressed == 1 && getHeroOnground() == 1) {
+ if (checkCollisionXY(s->mask, herox, heroy)) {
+ saveScreen();
+ }
+ }
+}
+
+void savePointDraw(SavePoint* s)
+{
+ PHL_DrawSurfacePart(s->x, s->y, (int)s->imageIndex * 40, 320, 40, 40, images[imgMisc20]);
+}
+
+//Door
+unsigned char unlockedDoor[8] = {0, 2, 6, 7, 32, 22, 39, 48};
+
+void createDoor(int x, int y, int level, int coords, int warpx, int warpy, int secret)
+{
+ if (level != 8 || hasKey[7] == 1) {
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Door* d = malloc(sizeof *d);
+ d->id = i;
+
+ d->x = x;
+ d->y = y;
+
+ d->visible = 1;
+ d->secret = secret;
+ if (d->secret == 1) {
+ d->visible = 0;
+ }
+
+ d->open = 0;
+ if (level == 0) {
+ d->open = 1;
+ }else{
+ d->open = flags[unlockedDoor[level-1]];
+ }
+
+ d->warplevel = level;
+ d->warpcoords = coords;
+ d->warpx = warpx;
+ d->warpy = warpy;
+
+
+ d->mask.x = x + 6;
+ d->mask.y = y;
+ d->mask.w = 28;
+ d->mask.h = 40;
+ d->mask.unused = d->mask.circle = 0;
+
+ o->data = d;
+ o->objectStep = doorStep;
+ o->objectDraw = doorDraw;
+ o->type = -1;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+}
+
+void doorStep(Door* d)
+{
+ if (d->visible == 1) {
+ if (btnUp.pressed == 1 && getHeroOnground() == 1 && getHeroState() != 6) {
+ if (checkCollisionXY(d->mask, herox, heroy)) {
+ if (d->open == 0) {
+ if (hasKey[d->warplevel - 1] == 1) {
+ d->open = 1;
+ flags[unlockedDoor[d->warplevel - 1]] = 1;
+ //doorUnlocked[d->warplevel-1] = 1;
+ PHL_PlaySound(sounds[sndDoor00], CHN_SOUND);
+ }
+ }else{
+ //Setup Door event
+ herox = d->x + 20;
+ heroy = d->y;
+
+ lastDoor = d;
+ setHeroState(7); //Set state to DOOR
+ PHL_PlaySound(sounds[sndStep01], CHN_HERO);
+ }
+ }
+ }
+ }else{
+ if (bossDefeatedFlag == 1 && hasKey[getLevel()]) {
+ d->visible = 1;
+ }
+ //Display key and door if the boss didn't show up
+ else if (d->secret == 1 && bossFlag == 0) {
+ bossFlag = 1;
+ bossDefeatedFlag = 1;
+ PHL_StopMusic();
+ }
+ }
+}
+
+void doorDraw(Door* d)
+{
+ if (d->visible == 1) {
+ PHL_DrawSurfacePart(d->x, d->y, 600 - (40 * d->open), 0, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Lock Block
+void createLockBlock(int x, int y, int flag)
+{
+ if (flags[flag] == 0) {
+
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ LockBlock* l = malloc(sizeof *l);
+
+ l->id = i;
+ l->x = x;
+ l->y = y;
+
+ l->invincible = 0;
+
+ int tx = x / 40;
+ int ty = y / 40;
+ l->tile = collisionTiles[tx][ty];
+ collisionTiles[tx][ty] = 1;
+
+ l->flag = flag;
+
+ o->data = l;
+ o->objectStep = lockBlockStep;
+ o->objectDraw = lockBlockDraw;
+ o->type = -1;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+
+ }
+}
+
+void lockBlockStep(LockBlock* l)
+{
+ //Collide with weapons
+ if (l->invincible <= 0) {
+ Mask mask;
+ mask.circle = mask.unused = 0;
+ mask.x = l->x;
+ mask.y = l->y;
+ mask.w = mask.h = 40;
+
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ l->invincible = 15;
+ //Sound
+ weaponHit(weapons[i]);
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }else{
+ l->invincible -= 1;
+ }
+
+ if (roomSecret == 1) {
+ playSecret();
+
+ int tx = l->x / 40;
+ int ty = l->y / 40;
+ collisionTiles[tx][ty] = l->tile;
+ flags[l->flag] = 1;
+ //Destroy
+ objectDestroy(l->id);
+ /*
+ free(objects[l->id]);
+ objects[l->id] = NULL;
+ */
+ }
+}
+
+void lockBlockDraw(LockBlock* l)
+{
+ PHL_DrawSurfacePart(l->x, l->y, 120, 400, 40, 40, images[imgMisc20]);
+}
+
+
+//Green light switch
+void createSwitch(int x, int y, int flag)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Switch* s = malloc(sizeof *s);
+
+ s->id = i;
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+
+ s->flag = flag;
+ s->activated = 0;
+
+ if (flags[s->flag] == 1) {
+ switchResult(s);
+ s->activated = 1;
+ }
+
+ o->data = s;
+ o->objectStep = switchStep;
+ o->objectDraw = switchDraw;
+ o->type = 76;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void switchStep(Switch* s)
+{
+ if (s->activated == 0) {
+ if (btnUp.pressed == 1) {
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = 16;
+ mask.h = 30;
+ mask.x = s->x + 12;
+ mask.y = s->y + 10;
+
+ if (checkCollision(mask, getHeroMask())) {
+ if (switchActivate(s) == 1) {
+ PHL_PlaySound(sounds[sndPi02], CHN_SOUND);
+ playSecret();
+ }
+ }
+ }
+ }else{
+ s->imageIndex += 0.2;
+ if (s->imageIndex >= 3) {
+ s->imageIndex -= 2;
+ }
+ }
+}
+
+int switchActivate(Switch* s)
+{
+ int success = 0;
+
+ if ((s->flag == 24 && hasItem[23] == 1) || //Switch in level 2
+ (s->flag == 26 && hasItem[24] == 1)) { //Switch in level 6
+ success = 1;
+ }
+
+ //Switches in level 7
+ if ((s->flag == 44 && hasItem[25] == 1) || //Left Switch
+ (s->flag == 43 && hasItem[26] == 1))
+ {
+ PHL_PlaySound(sounds[sndPi02], CHN_SOUND);
+ s->activated = 1;
+ flags[s->flag] = 1;
+
+ if (flags[44] == 1 && flags[43] == 1) {
+ flags[45] = 1;
+ roomSecret = 1;
+ success = 1;
+ }
+ }
+
+ if (success == 1) {
+ switchResult(s);
+ s->activated = 1;
+ flags[s->flag] = 1;
+ }
+
+ return success;
+}
+
+void switchResult(Switch* s)
+{
+ if (s->flag == 24) { //Switch in level 2
+ createPlatform(0, 320, 240, 320, 360, 1, 0);
+ flags[23] = 1;
+ }
+
+ if (s->flag == 26) { //Switch in level 6
+ roomSecret = 1;
+ }
+}
+
+void switchDraw(Switch* s)
+{
+ PHL_DrawSurfacePart(s->x, s->y, 240 + (40 * (int)s->imageIndex), 560, 40, 40, images[imgMisc20]);
+}
+
+//Gates
+void createGate(int x, int y, int col)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Gate* g = malloc(sizeof *g);
+ g->id = i;
+
+ g->x = x;
+ g->y = y;
+
+ g->col = col;
+
+ g->imageIndex = 0;
+ g->timer = 0;
+ g->open = 0;
+
+ //g->invincible = 0;
+
+ o->data = g;
+ o->objectStep = gateStep;
+ o->objectDraw = gateDraw;
+ o->type = 53;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void gateStep(Gate* g)
+{
+ //Animate
+ {
+ if (g->open == 1) {
+ if (g->imageIndex < 4) {
+ g->imageIndex += 0.1;
+ }
+ }
+ }
+
+ //Not (fully) opened
+ if (g->imageIndex < 4)
+ {
+ //Setup Mask
+ Mask mask;
+ {
+ mask.unused = mask.circle = 0;
+ mask.x = g->x + 11;
+ mask.y = g->y;
+ mask.w = 18;
+ mask.h = 40;
+ }
+
+ //Collide with player
+ {
+ if (checkCollision(mask, getHeroMask())) {
+ if (getHeroHsp() < 0) {
+ herox = mask.x + mask.w + (getHeroMask().w / 2);
+ }
+ else if (getHeroHsp() > 0) {
+ herox = mask.x - (getHeroMask().w / 2);
+ }
+ }
+ }
+
+ //Collide with weapons
+ {
+ if (g->open == 0) {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+
+ //Is a sword
+ if (weapons[i]->type == 5) {
+ if ((g->col == 0 && hasItem[20] == 1) || (g->col == 1 && hasItem[19] == 1)) {
+ g->open = 1;
+ PHL_PlaySound(sounds[sndDoor00], CHN_SOUND);
+ }
+ }
+
+ weaponHit(weapons[i]);
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+}
+
+void gateDraw(Gate* g)
+{
+ int cx = (int)g->imageIndex * 40,
+ cy = 520;
+
+ if (g->col == 0) { //Red Gate
+ cx += 200;
+ }
+
+ PHL_DrawSurfacePart(g->x, g->y, cx, cy, 40, 40, images[imgMisc20]);
+}
+
+//Statue
+void createStatue(int x, int y, int type)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Statue* s = malloc(sizeof *s);
+ s->id = i;
+
+ s->x = x;
+ s->y = y;
+
+ s->type = type;
+
+ s->invincible = 0;
+ s->hp = 3;
+
+ o->data = s;
+ o->objectStep = statueStep;
+ o->objectDraw = statueDraw;
+ o->type = 54;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void statueStep(Statue* s)
+{
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = 40;
+ mask.h = 40;
+ mask.x = s->x + ((40 - mask.w) / 2);
+ mask.y = s->y;
+
+ //Collide with hero
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ if (getHeroHsp() < 0) {
+ herox = mask.x + mask.w + (getHeroMask().w / 2) + 1;
+ }
+ if (getHeroHsp() > 0) {
+ herox = mask.x - (getHeroMask().w / 2);
+ }
+ }
+
+ //Collide with weapons
+ if (s->invincible <= 0) {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ s->invincible = 15;
+ //Sound
+ weaponHit(weapons[i]);
+
+ //Break if you have the right item
+ if ((s->type == 0 && hasItem[22] == 1) || (s->type == 1 && hasItem[21] == 1)) {
+ s->hp -= 1;
+
+ if (s->hp <= 0) {
+ createRockSmash(s->x + 20, s->y + 20);
+ objectDestroy(s->id);
+ }
+ }else{
+ PHL_PlaySound(sounds[sndHit03], CHN_WEAPONS);
+ }
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }else{
+ s->invincible -= 1;
+ }
+}
+
+void statueDraw(Statue* s)
+{
+ int cx = 200,
+ cy = 400;
+
+ if (s->type == 1) {
+ cx += 200;
+ }
+
+ PHL_DrawSurfacePart(s->x, s->y, cx, cy, 40, 40, images[imgMisc20]);
+}
+
+//Button
+void createFloorPad(int x, int y, int flag)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ char found = 0;
+
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ FloorPad* f = malloc(sizeof *f);
+ f->id = i;
+
+ f->x = x;
+ f->y = y;
+
+ f->pressed = 0;
+ f->flag = flag;
+
+ if (flag != 0 && flags[flag] == 1) {
+ roomSecret = 1;
+ }
+
+ o->data = f;
+ o->objectStep = buttonStep;
+ o->objectDraw = buttonDraw;
+ o->type = 77;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ found = 1;
+ }
+
+ if (found == 1) {
+ //Create a breakable block if it's covered by a solid block
+ int roundx = x / 40,
+ roundy = y / 40;
+
+ if (collisionTiles[roundx][roundy] == 1) {
+ createDestroyable(x, y, 2);
+ }
+ }
+ }
+}
+
+void buttonStep(FloorPad* f)
+{
+ if (f->pressed == 0) {
+ if (getHeroVsp() > 0) {
+ Mask mask;
+ mask.unused = mask.circle = 0;
+ mask.w = 16;
+ mask.h = 10;
+ mask.x = f->x + ((40 - mask.w) /2);
+ mask.y = f->y + (40 - mask.h);
+
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ f->pressed = 1;
+ PHL_PlaySound(sounds[sndHit02], CHN_SOUND);
+
+ //Check if there are other buttons unpressed
+ int found = 0;
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] != NULL) {
+ if (objects[i]->type == 77) {
+ FloorPad* btemp = objects[i]->data;
+ if (btemp->pressed == 0) {
+ found = 1;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+ }
+
+ //Activate flag
+ if (found == 0) {
+ roomSecret = 1;
+
+ if (f->flag != 0 && flags[f->flag] == 0) {
+ flags[f->flag] = 1;
+ }
+ }
+ }
+
+ }
+ }
+}
+
+void buttonDraw(FloorPad* f)
+{
+ char covered = 0;
+
+ int roundx = f->x / 40,
+ roundy = f->y / 40;
+
+ //Covered by breakable block
+ if (collisionTiles[roundx][roundy] == 1) {
+ covered = 1;
+ }
+
+ if (covered == 0) {
+ PHL_DrawSurfacePart(f->x, f->y, 160 + (f->pressed * 40), 320, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Ladder
+void createLadder(int x, int y, int flag)
+{
+ if (flags[flag] == 1) {
+ ladderActivate(x, y);
+ }else{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Ladder* l = malloc(sizeof *l);
+ l->id = i;
+
+ l->x = x;
+ l->y = y;
+
+ l->flag = flag;
+
+ if (getLevel() != 6) {
+ if (hasItem[2] == 1) { //Has Bell
+ PHL_PlaySound(sounds[sndBell01], CHN_SOUND);
+ }
+ }
+
+ o->data = l;
+ o->objectStep = ladderStep;
+ o->objectDraw = nullFunction;
+ o->type = 79;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+}
+
+void ladderStep(Ladder* l)
+{
+ if (roomSecret == 1) {
+ playSecret();
+ flags[l->flag] = 1;
+ ladderActivate(l->x, l->y);
+ objectDestroy(l->id);
+ }
+}
+
+void ladderActivate(int x, int y)
+{
+ y = (int)(y / 40);
+ x = (int)(x / 40);
+
+ while (y >= 0) {
+ foreground.tileX[x][y] = 3;
+ foreground.tileY[x][y] = 0;
+
+ collisionTiles[x][y] = 2;
+ y -= 1;
+ }
+
+ PHL_UpdateBackground(background, foreground);
+}
+
+//Generator
+void createGenerator(int x, int y, int flag)
+{
+ if (flags[flag] == 0) {
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Generator* g = malloc(sizeof *g);
+ g->id = i;
+ g->hp = 3;
+ g->blink = 0;
+
+ g->x = x;
+ g->y = y;
+
+ g->imageIndex = 0;
+
+ g->flag = flag;
+
+ o->data = g;
+ o->objectStep = generatorStep;
+ o->objectDraw = generatorDraw;
+ o->type = 80;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+
+}
+
+void generatorStep(Generator* g)
+{
+ //Animate
+ {
+ g->imageIndex += 0.33;
+ if (g->imageIndex >= 2) {
+ g->imageIndex -= 2;
+ }
+
+ if (g->blink > 0) {
+ g->blink -= 1;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 24;
+ mask.h = 40;
+ mask.x = g->x + ((40 - mask.w) / 2);
+ mask.y = g->y;
+ }
+
+ //Weapon collision
+ {
+ int i;
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->cooldown == 0) {
+ if (checkCollision(mask, weapons[i]->weaponMask)) {
+ weaponHit(weapons[i]);
+
+ g->hp -= 1;
+ g->blink = 15;
+
+ i = MAX_WEAPONS;
+ }
+ }
+ }
+ }
+ }
+
+ //Destroy
+ {
+ if (g->hp <= 0) {
+ createRockSmash(g->x + 20, g->y + 20);
+ flags[g->flag] = 1;
+ //if no generators exist
+ {
+ char found = 0;
+
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (i != g->id && objects[i] != NULL) {
+ if (objects[i]->type == 80) {
+ found = 1;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+
+ if (found == 0) {
+ roomSecret = 1;
+ playSecret();
+ }
+ }
+ objectDestroy(g->id);
+ }
+ }
+
+}
+
+void generatorDraw(Generator* g)
+{
+ if (g->blink % 2 == 0) {
+ PHL_DrawSurfacePart(g->x, g->y, 80 + ((int)g->imageIndex * 40), 600, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Electric gate
+void createShockgate(int x, int y, int flag)
+{
+ if (flags[flag] == 0) {
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Shockgate* s = malloc(sizeof *s);
+ s->id = i;
+
+ s->x = x;
+ s->y = y;
+
+ s->imageIndex = 0;
+
+ s->flag = flag;
+
+ o->data = s;
+ o->objectStep = shockgateStep;
+ o->objectDraw = shockgateDraw;
+ o->type = 56;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+ }
+
+}
+
+void shockgateStep(Shockgate* s)
+{
+ //Animate
+ {
+ s->imageIndex += 0.33;
+ if (s->imageIndex >= 4) {
+ s->imageIndex -= 4;
+ }
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 40 * 3;
+ mask.h = 8;
+ mask.x = s->x;
+ mask.y = s->y + ((40 - mask.h) / 2);
+ }
+
+ //Hero Collision
+ {
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ heroy = mask.y - 40;
+ setHeroVsp(0);
+ }
+ }
+
+ //Destroy
+ if (roomSecret == 1) {
+ flags[s->flag] = 1;
+ objectDestroy(s->id);
+ }
+}
+
+void shockgateDraw(Shockgate* s)
+{
+ int i;
+ for (i = 0; i < 3; i++) {
+ PHL_DrawSurfacePart(s->x + (40 * i), s->y, 80 + ((int)s->imageIndex * 40), 560, 40, 40, images[imgMisc20]);
+ }
+}
+
+//Crown
+void createCrown(int x, int y)
+{
+ int i;
+ for (i = 0; i < MAX_OBJECTS; i++) {
+ if (objects[i] == NULL) {
+ Object* o = malloc(sizeof *o);
+ Crown* c = malloc(sizeof *c);
+ c->id = i;
+
+ c->x = x;
+ c->y = y;
+
+ c->ystart = c->y;
+ c->bobRot = 0;
+
+ c->imageIndex = 0;
+
+ c->visible = 0;
+ c->timer = 0;
+
+ o->data = c;
+ o->objectStep = crownStep;
+ o->objectDraw = crownDraw;
+ o->type = 57;
+
+ objects[i] = o;
+ i = MAX_OBJECTS;
+ }
+ }
+}
+
+void crownStep(Crown* c)
+{
+ if (c->visible == 0) {
+ if (roomSecret == 1) {
+ c->visible = 1;
+ playSecret();
+ }
+ }else{
+ //Animate
+ {
+ c->imageIndex += 0.33;
+ if (c->imageIndex >= 2) {
+ c->imageIndex -= 2;
+ }
+
+ //Movement
+ c->bobRot += 6;
+ if (c->bobRot >= 360) {
+ c->bobRot -= 360;
+ }
+ c->y = c->ystart + sin(c->bobRot * 3.14159 / 180) * 5;
+ }
+
+ //Sparkle
+ c->timer -= 1;
+ if (c->timer <= 0) {
+ createEffectExtra(5, c->x + (rand() % 40) + 1, c->y + 4 + (rand() % 40) + 1, 0, 0, 0);
+ c->timer = 12;
+ }
+
+ //Setup Mask
+ Mask mask;
+ {
+ mask.circle = mask.unused = 0;
+ mask.w = 15;
+ mask.h = 11;
+ mask.x = c->x + 20 - (mask.w / 2);
+ mask.y = c->y + 20 - (mask.h / 2);
+ }
+
+ if (checkCollision(mask, getHeroMask()) == 1) {
+ objectDestroy(c->id);
+ gameEnding();
+ }
+
+ }
+}
+
+void crownDraw(Crown* c)
+{
+ if (c->visible == 1) {
+ int cropX = (int)c->imageIndex * 40;
+
+ PHL_DrawSurfacePart(c->x, c->y, cropX, 200, 40, 40, images[imgHero]);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/object.h b/contrib/games/hydracastlelabyrinth/src/object.h
new file mode 100644
index 0000000000..8da299a317
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/object.h
@@ -0,0 +1,181 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+#include "collision.h"
+
+typedef struct {
+ void* data; //Specific object struct
+ void (*objectStep)();
+ void (*objectDraw)();
+ int type;
+} Object;
+
+void objectDestroy(int id);
+
+//Health/Ammo collectables
+typedef struct {
+ int id, type; //0 for ammo, 1 for heart
+ double x, y,
+ vsp, grav;
+
+ int blink, canLand, bounce;
+} Ammo;
+
+void spawnCollectable(int x, int y);
+void createAmmo(int x, int y, int type);
+
+//Destroyable blocks
+typedef struct {
+ int id;
+ int x, y;
+ int hp/*, invulnerable*/;
+ int secret;
+ Mask mask;
+} Destroyable;
+
+void createDestroyable(int x, int y, int secret);
+
+//Secret Trigger
+typedef struct {
+ int id, flag;
+ int type, enemyType;
+} SecretTrigger;
+
+void createSecretTrigger(int type, int enemyType, int flag);
+
+//Chest
+typedef struct {
+ int id;
+ int x, y;
+ int item, secret;
+ int visible;
+ int timer;
+
+ Mask mask;
+} Chest;
+
+void createChest(int x, int y, int item, int secret);
+
+//Save point
+typedef struct {
+ int id;
+ int x, y;
+ double imageIndex;
+
+ Mask mask;
+} SavePoint;
+
+void createSavePoint(int x, int y, int hidden);
+
+//Door
+typedef struct {
+ int id;
+ int x, y;
+ int open, secret, visible;
+
+ int warplevel, warpcoords;
+ int warpx, warpy;
+
+ Mask mask;
+} Door;
+
+void createDoor(int x, int y, int level, int coords, int warpx, int warpy, int secret);
+
+//Lock Block
+typedef struct {
+ int id, flag;
+ int x, y;
+ int tile;
+ int invincible;
+} LockBlock;
+
+void createLockBlock(int x, int y, int flag);
+
+//Light Switch
+typedef struct {
+ int id, flag;
+ int x, y;
+ int activated;
+ double imageIndex;
+} Switch;
+
+void createSwitch(int x, int y, int flag);
+
+//Blue/Red Gates
+typedef struct {
+ int id;
+ int x, y;
+ int col;
+ int timer, open;
+ //int invincible;
+ double imageIndex;
+} Gate;
+
+void createGate(int x, int y, int col);
+
+//Statue
+typedef struct {
+ int id;
+ int x, y;
+ int type;
+ int invincible;
+ int hp;
+} Statue;
+
+void createStatue(int x, int y, int type);
+
+//Button
+typedef struct {
+ int id;
+ int x, y;
+ int flag;
+ int pressed;
+} FloorPad;
+
+void createFloorPad(int x, int y, int flag);
+
+//Ladder
+typedef struct {
+ int id;
+ int x, y;
+ int flag;
+} Ladder;
+
+void createLadder(int x, int y, int flag);
+
+//Generator
+typedef struct {
+ int id;
+ int hp;
+ int blink;
+ int x, y;
+ double imageIndex;
+ int flag;
+} Generator;
+
+void createGenerator(int x, int y, int flag);
+
+//Electric gate
+typedef struct {
+ int id;
+ int x, y;
+ double imageIndex;
+ int flag;
+} Shockgate;
+
+void createShockgate(int x, int y, int flag);
+
+//Ending crown
+typedef struct {
+ int id;
+ int x, ystart;
+ double y;
+ double bobRot;
+ double imageIndex;
+ int timer;
+ char visible;
+} Crown;
+
+void createCrown(int x, int y);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/options.c b/contrib/games/hydracastlelabyrinth/src/options.c
new file mode 100644
index 0000000000..c2e0659e9d
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/options.c
@@ -0,0 +1,381 @@
+#include
+#include "options.h"
+#include "PHL.h"
+#include "game.h"
+#include "ini.h"
+#include "text.h"
+
+char page = 0;
+int optCursor = 0;
+int lastOption = -1;
+#ifdef _SDL
+int musicType = 1;
+
+int getMusicType()
+{
+ return musicType;
+}
+void setMusicType(int t)
+{
+#ifndef _KOLIBRI
+ if(t!=musicType)
+ {
+ musicType = t;
+ // restart music...
+ }
+#else
+ musicType = 1; /* MIDI NOT WORK! */
+#endif
+}
+#endif
+
+#ifdef EMSCRIPTEN
+static char tempDark;
+static int emOnly;
+void optionsSetup(int only)
+{
+ tempDark = roomDarkness;
+ roomDarkness = 0;
+ emOnly = only;
+ page = only?1:0;
+}
+
+int optionsEMStep()
+{
+ int loop = 1;
+ PHL_MainLoop();
+
+ PHL_StartDrawing();
+
+ optionsDraw();
+
+ PHL_ScanInput();
+ int result = optionsStep();
+
+ PHL_EndDrawing();
+
+ if (page == 0 && result != -1 && result != 2) {
+ loop = 0;
+ }
+ if (emOnly && page==0) {
+ loop = 0;
+ }
+
+ if(!loop) roomDarkness = tempDark;
+
+ return (!loop)?result:-1;
+}
+#else
+
+int options(int only)
+{
+ char tempDark = roomDarkness;
+ roomDarkness = 0;
+
+ int result = -1;
+ char loop = 1;
+
+ if(only) page=1;
+
+ while (PHL_MainLoop() && loop == 1)
+ {
+ PHL_StartDrawing();
+
+ optionsDraw();
+
+ PHL_ScanInput();
+ result = optionsStep();
+
+ PHL_EndDrawing();
+
+ if (page == 0 && result != -1 && result != 2) {
+ loop = 0;
+ }
+ if (only && page==0) {
+ loop = 0;
+ }
+ }
+
+ roomDarkness = tempDark;
+
+ return result;
+}
+#endif
+int optionsStep()
+{
+ int result = -1;
+
+ secretCountdown();
+
+ //input
+ if (btnDown.pressed == 1) {
+ optCursor += 1;
+ PHL_PlaySound(sounds[sndPi01], CHN_SOUND);
+
+ }else if (btnUp.pressed == 1) {
+ optCursor -= 1;
+ PHL_PlaySound(sounds[sndPi01], CHN_SOUND);
+ }
+
+ //First screen (continue/reset/exit)
+ if (page == 0) {
+ //Limit cursor
+ if (optCursor >= 4) {
+ optCursor = 0;
+ }
+
+ if (optCursor < 0) {
+ optCursor = 3;
+ }
+
+ if (btnAccept.pressed == 1 || btnStart.pressed == 1) {
+ result = optCursor;
+ if (result == 1) {
+ PHL_StopMusic();
+ }
+ if (result == 2) {
+ page = 1;
+ optCursor = 0;
+ }
+
+ //No blip on game exit
+ if (optCursor != 3) {
+ PHL_PlaySound(sounds[sndOk], CHN_SOUND);
+ }
+ }
+
+ if (btnSelect.pressed == 1) {
+ result = 0;
+ PHL_PlaySound(sounds[sndOk], CHN_SOUND);
+ }
+ }
+ //Actual options screen
+ else if (page == 1) {
+ //Limit cursor
+ if (optCursor > lastOption) {
+ optCursor = 0;
+ }
+
+ if (optCursor < 0) {
+ optCursor = lastOption;
+ }
+
+ if (btnAccept.pressed == 1 || btnStart.pressed == 1) {
+ //Universal settings
+ //Language
+ if (optCursor == 0) {
+ if (getLanguage() == JAPANESE) {
+ setLanguage(ENGLISH);
+ }else{
+ setLanguage(JAPANESE);
+ }
+ }
+
+ //Autosave
+ if (optCursor == 1) {
+ if (getAutoSave() == 0) {
+ setAutoSave(1);
+ }else{
+ setAutoSave(0);
+ }
+ }
+
+ #ifdef _3DS
+ if (optCursor == 2) {
+ if (activeScreen->screen == GFX_TOP) {
+ swapScreen(GFX_BOTTOM, GFX_LEFT);
+ }else{
+ swapScreen(GFX_TOP, GFX_LEFT);
+ }
+ }
+ #endif
+
+ #ifdef _PSP
+ if (optCursor == 2) {
+ if (getScreenSize() == 0) {
+ setScreenSize(1);
+ }else if (getScreenSize() == 1) {
+ setScreenSize(2);
+ }else{
+ setScreenSize(0);
+ }
+ }
+
+ //Blur
+ if (optCursor == 3) {
+ if (getBlur() == 0) {
+ setBlur(1);
+ }else{
+ setBlur(0);
+ }
+ }
+ #endif
+
+ #ifdef _SDL
+ // Music type
+ if(optCursor == 2) {
+ if(getMusicType() == 0)
+ setMusicType(1);
+ else
+ setMusicType(0);
+ }
+ // Music volume
+ if(optCursor == 3) {
+ music_volume = (music_volume+1)%5;
+ PHL_MusicVolume(0.25f * music_volume);
+ }
+ // xBRZ
+ if (optCursor == 4) {
+ if (getXBRZ() == 0) {
+ setXBRZ(1);
+ }else{
+ setXBRZ(0);
+ }
+ }
+ #endif
+
+ //Back
+ if (optCursor == lastOption) {
+ page = 0;
+ }
+ }
+ else if (btnDecline.pressed == 1) {
+ page = 0;
+ }
+
+ if (page == 0) {
+ saveSettings();
+ }
+ }
+
+ return result;
+}
+
+void optionsDraw()
+{
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+
+ if (page == 0)
+ {
+ PHL_DrawTextBold("CONTINUE", 254, 144, YELLOW);
+ PHL_DrawTextBold("RESET", 278, 176, YELLOW);
+ PHL_DrawTextBold("OPTIONS", 262, 208, YELLOW);
+ PHL_DrawTextBold("EXIT", 286, 240, YELLOW);
+
+ PHL_DrawTextBold("<", 232, 144 + (32 * optCursor), RED);
+ PHL_DrawTextBold(">", 390, 144 + (32 * optCursor), RED);
+ }
+ else if (page == 1)
+ {
+ int xleft = 216;
+ int xright = xleft + 160;
+
+ int ydrawstart = 144;
+ int ydraw = ydrawstart;
+ int ystep = 32;
+ int optioncount = 0;
+
+ //Language
+ PHL_DrawTextBold("LANGUAGE", xleft, ydraw, YELLOW);
+ if (getLanguage() == 1) {
+ PHL_DrawTextBold("EN", xright, ydraw, YELLOW);
+ }
+ if (getLanguage() == 0) {
+ PHL_DrawTextBold("JP", xright, ydraw, YELLOW);
+ }
+
+ ydraw += ystep;
+ optioncount++;
+
+ //AutoSave
+ PHL_DrawTextBold("SAFE SAVE", xleft, ydraw, YELLOW);
+ if (getAutoSave() == 1) {
+ PHL_DrawTextBold("ON", xright, ydraw, YELLOW);
+ }else{
+ PHL_DrawTextBold("OFF", xright, ydraw, YELLOW);
+ }
+
+ ydraw += ystep;
+ optioncount++;
+
+ #ifdef _3DS
+ //Screen
+ PHL_DrawTextBold("SCREEN", xleft, ydraw, YELLOW);
+ if (activeScreen->screen == GFX_TOP) {
+ PHL_DrawTextBold("TOP", xright, ydraw, YELLOW);
+ }else{
+ PHL_DrawTextBold("BOTTOM", xright, ydraw, YELLOW);
+ }
+
+ ydraw += ystep;
+ optioncount++;
+ #endif
+
+ #ifdef _PSP
+ //Screen Size
+ PHL_DrawTextBold("SCREEN", xleft, ydraw, YELLOW);
+ if (getScreenSize() == 0) {
+ PHL_DrawTextBold("1:1", xright, ydraw, YELLOW);
+ }
+ else if (getScreenSize() == 1) {
+ PHL_DrawTextBold("FILL", xright, ydraw, YELLOW);
+ }
+ else if (getScreenSize() == 2) {
+ PHL_DrawTextBold("FULL", xright, ydraw, YELLOW);
+ }
+
+ ydraw += ystep;
+ optioncount++;
+
+ //Blur
+ PHL_DrawTextBold("BLUR", xleft, ydraw, YELLOW);
+ if (getBlur() == 1) {
+ PHL_DrawTextBold("ON", xright, ydraw, YELLOW);
+ }
+ else{
+ PHL_DrawTextBold("OFF", xright, ydraw, YELLOW);
+ }
+
+ ydraw += ystep;
+ optioncount++;
+ #endif
+
+ #ifdef _SDL
+ // Music type
+ PHL_DrawTextBold("MUSIC", xleft, ydraw, YELLOW);
+ if (getMusicType() == 1) {
+ PHL_DrawTextBold("OGG", xright, ydraw, YELLOW);
+ }
+ else{
+ PHL_DrawTextBold("MIDI", xright, ydraw, YELLOW);
+ }
+ ydraw += ystep;
+ optioncount++;
+ // Music volume
+ PHL_DrawTextBold("MUSIC", xleft, ydraw, YELLOW);
+ char buff[50];
+ sprintf(buff, "%d%%", music_volume*25);
+ PHL_DrawTextBold(buff, xright, ydraw, YELLOW);
+ ydraw += ystep;
+ optioncount++;
+ // xBRZ scaling
+ PHL_DrawTextBold("XBRZ", xleft, ydraw, YELLOW);
+ if (getXBRZ() == 1) {
+ PHL_DrawTextBold("ON", xright, ydraw, YELLOW);
+ }
+ else{
+ PHL_DrawTextBold("OFF", xright, ydraw, YELLOW);
+ }
+ ydraw += ystep;
+ optioncount++;
+ #endif
+
+ PHL_DrawTextBold("BACK", xleft, ydraw, YELLOW);
+
+ if (lastOption == -1) {
+ lastOption = optioncount;
+ }
+
+ PHL_DrawTextBold(">", xleft - 32, ydrawstart + (ystep * optCursor), RED);
+ }
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/options.h b/contrib/games/hydracastlelabyrinth/src/options.h
new file mode 100644
index 0000000000..e2b47ff39f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/options.h
@@ -0,0 +1,18 @@
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#ifdef EMSCRIPTEN
+void optionsSetup(int only);
+int optionsEMStep();
+#else
+int options(int only);
+#endif
+
+int optionsStep();
+void optionsDraw();
+#ifdef _SDL
+int getMusicType();
+void setMusicType(int t);
+#endif
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/platform.c b/contrib/games/hydracastlelabyrinth/src/platform.c
new file mode 100644
index 0000000000..4435aa7ec1
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/platform.c
@@ -0,0 +1,223 @@
+#include "platform.h"
+#include "game.h"
+#include "PHL.h"
+#include "hero.h"
+#include
+
+void createPlatform(int type, int xstart, int ystart, int xend, int yend, int spd, int secret)
+{
+ int i;
+ for (i = 0; i < MAX_PLATFORMS; i++) {
+ if (platforms[i] == NULL) {
+ Platform* p = malloc(sizeof *p);
+ p->id = i;
+ p->type = type;
+
+ p->x = p->xstart = xstart;
+ p->y = p->ystart = ystart;
+
+ p->xend = xend;
+ p->yend = yend;
+
+ p->timer = 0;
+
+ p->secret = secret;
+
+ if (roomSecret == 1) {
+ p->secret = 0;
+ }
+
+ p->visible = 1;
+ if (p->secret == 1) {
+ p->visible = 0;
+ }
+
+ if (type == 1) {
+ p->xend = p->x + 1;
+ p->yend = p->y;
+ }
+
+ p->state = 0;
+ p->spd = spd;
+
+ p->mask.circle = 0;
+ p->mask.unused = 0;
+ if (p->secret == 1) {
+ p->mask.unused = 1;
+ }
+ p->mask.x = xstart;
+ p->mask.y = ystart;
+ p->mask.w = p->mask.h = 40;
+
+ platforms[i] = p;
+ i = MAX_PLATFORMS;
+ }
+ }
+}
+
+void platformStep(Platform* p)
+{
+ char isSolid = p->visible;
+
+ //Megaman blocks
+ if (p->type == 2) {
+ int myStep = p->xend,
+ maxSteps = p->yend;
+
+ p->timer += 1;
+ if (p->timer >= maxSteps * 60 + 60) {
+ p->timer = 0;
+ }
+
+ //Play sound
+ if (p->timer == myStep * 60) {
+ PHL_PlaySound(sounds[sndPi03], CHN_SOUND);
+ }
+
+ if (p->timer > myStep * 60 && p->timer <= (myStep * 60) + 60) {
+ isSolid = 1;
+ p->visible = 1;
+
+ //Blink effect
+ if (p->timer > myStep * 60 + 30) {
+ p->visible = p->timer % 2;
+ }
+ }else{
+ isSolid = 0;
+ p->mask.unused = 1;
+ p->visible = 0;
+
+ //Fall off platform
+ if (getHeroMask().x > p->mask.x + p->mask.w || getHeroMask().x + getHeroMask().w < p->mask.x) {
+ }else if (heroy == p->y - 40 && getHeroVsp() >= 0) {
+ setHeroOnground(0);
+ }
+ }
+ }
+
+ if (isSolid == 1) {
+ p->mask.unused = 0;
+
+ int targx = p->xend,
+ targy = p->yend;
+
+ if (p->state == 1) {
+ targx = p->xstart;
+ targy = p->ystart;
+ }
+
+ //Check if the player is standing on top
+ int isontop = 0;
+ int isabove = 0;
+ if (getHeroMask().x > p->mask.x + p->mask.w || getHeroMask().x + getHeroMask().w < p->mask.x) {
+ }else if (heroy == p->y - 40 && getHeroVsp() >= 0) {
+ isontop = 1;
+ }else if (heroy < p->y - 40) {
+ isabove = 1;
+ }
+
+ //Move platform
+ if (p->y != targy) {
+ if (p->y < targy) {
+ p->y += p->spd;
+ if (isontop == 1) {
+ heroy += p->spd;
+ }
+ }else{
+ p->y -= p->spd;
+ if (isontop == 1) {
+ heroy -= p->spd;
+ }else{
+ p->mask.y = p->y;
+ if (checkCollision(p->mask, getHeroMask()) && isabove == 1) {
+ heroy = p->y - 40;
+ }
+ }
+ }
+ }
+
+ if (p->x != targx) {
+ if (p->x < targx) {
+ p->x += p->spd;
+ if (isontop == 1) {
+ herox += p->spd;
+ }
+ }else{
+ p->x -= p->spd;
+ if (isontop == 1) {
+ herox -= p->spd;
+ }
+ }
+ }
+
+ if (p->x == targx && p->y == targy) {
+ if (p->state == 0) {
+ p->state = 1;
+ }else{
+ p->state = 0;
+ }
+ }
+
+ if (p->type == 0) //Moving platform
+ {
+
+ }
+ else if (p->type == 1) { //Loose block
+ if (p->spd != 0) {
+ p->timer -= 1;
+ if (p->timer <= 0) {
+ createRockSmash(p->x + 20, p->y + 20);
+ if (isontop == 1) {
+ setHeroOnground(0);
+ }
+ platformDestroy(p->id);
+ /*createEffectExtra(4, p->x, p->y, -1, 0, 0);
+ createEffectExtra(4, p->x, p->y, -1, 0, 1);
+ createEffectExtra(4, p->x, p->y, 1, 0, 0);
+ createEffectExtra(4, p->x, p->y, 1, 0, 1);
+ */
+ }
+ }
+
+ if (p->spd == 0 && isontop == 1) {
+ p->spd = 2;
+ p->timer = 30;
+ }
+ }
+
+ //Update Mask
+ p->mask.x = p->x;
+ p->mask.y = p->y;
+ }else{
+ //p->mask.unused = 1;
+ if (p->secret == 1) {
+ if (roomSecret == 1) {
+ p->mask.unused = 0;
+ p->visible = 1;
+ p->secret = 0;
+ playSecret();
+ }
+ }
+ }
+}
+
+void platformDraw(Platform* p)
+{
+ if (p->visible == 1) {
+ int cropX = p->type * 40;
+
+ if (p->type == 3) {
+ cropX = 9 * 40;
+ }
+
+ PHL_DrawSurfacePart(p->x, p->y, cropX, 400, 40, 40, images[imgMisc20]);
+ }
+}
+
+void platformDestroy(int id)
+{
+ if (platforms[id] != NULL) {
+ free(platforms[id]);
+ }
+ platforms[id] = NULL;
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/platform.h b/contrib/games/hydracastlelabyrinth/src/platform.h
new file mode 100644
index 0000000000..8375d46ec5
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/platform.h
@@ -0,0 +1,26 @@
+#ifndef PLATFORM_H
+#define PLATFORM_H
+
+#include "collision.h"
+
+typedef struct {
+ int id, type; //0 = moving platform
+ double x, y;
+ int xstart, ystart;
+ int xend, yend;
+ int state;
+ double spd;
+ int timer;
+ int secret, visible;
+
+ Mask mask;
+} Platform;
+
+void createPlatform(int type, int xstart, int ystart, int xend, int yend, int spd, int secret);
+
+void platformStep(Platform* p);
+void platformDraw(Platform* p);
+
+void platformDestroy(int id);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/qda.c b/contrib/games/hydracastlelabyrinth/src/qda.c
new file mode 100644
index 0000000000..5b46761b81
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/qda.c
@@ -0,0 +1,72 @@
+#include "qda.h"
+#include
+#include
+#include
+#if defined(__amigaos4__) || defined(__MORPHOS__)
+#include "amigaos.h"
+#endif
+
+QDAHeader headers[29];
+//Load headers for each image
+//Returns: 0 = file not found | 1 = success | 2 = Invalid file
+int initQDA()
+{
+ int result = 0;
+ FILE* f;
+
+ char fullPath[80];
+ #ifdef _SDL
+ strcpy(fullPath, "data/");
+ #else
+ strcpy(fullPath, "");
+ #endif
+ #ifdef _3DS
+ strcat(fullPath, "romfs:/");
+ #endif
+ strcat(fullPath, "bmp.qda");
+
+ if ( (f = fopen(fullPath, "rb")) ) {
+ result = 1;
+
+ //Read header data into memory
+ int allHeadersSize = 0x1F5C;
+ unsigned char* QDAFile = (unsigned char*)malloc(allHeadersSize);
+ int tmp = fread(QDAFile, allHeadersSize, 1, f);
+ (void)tmp;
+
+ //Check if QDA file is valid
+ {
+ if (QDAFile[4] == 0x51 && QDAFile[5] == 0x44 && QDAFile[6] == 0x41 && QDAFile[7] == 0x30) {
+ //Load headers separately
+ {
+ int numofsheets = 29;
+ int headerSize = 0x10C;
+
+ int i;
+ for (i = 0; i < numofsheets; i++) {
+ //memcpy(&headers[i], &QDAFile[0x100 + (i * headerSize)], sizeof(QDAHeader));
+ int offset = 256 + (i * headerSize);
+ memcpy(&headers[i].offset, &QDAFile[offset], 4);
+ memcpy(&headers[i].size, &QDAFile[offset + 4], 4);
+ memcpy(&headers[i].bytes, &QDAFile[offset + 8], 4);
+ memcpy(&headers[i].fileName, &QDAFile[offset + 12], 0x100);
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ BE32(&headers[i].offset);
+ BE32(&headers[i].size);
+ BE32(&headers[i].bytes);
+ #endif
+ }
+ }
+ }else{
+ result = 2;
+ }
+ }
+
+ //Cleanup
+ free(QDAFile);
+ }
+
+ fclose(f);
+
+ return result;
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/qda.h b/contrib/games/hydracastlelabyrinth/src/qda.h
new file mode 100644
index 0000000000..7f8c46edce
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/qda.h
@@ -0,0 +1,14 @@
+#ifndef QDA_H
+#define QDA_H
+
+typedef struct {
+ unsigned long offset;
+ unsigned long size;
+ unsigned long bytes;
+ unsigned char* fileName[256];
+} QDAHeader;
+
+extern QDAHeader headers[29]; //names, offsets, and sizes of each sheet
+int initQDA();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/audio.c b/contrib/games/hydracastlelabyrinth/src/sdl/audio.c
new file mode 100644
index 0000000000..f5d710cb0a
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/audio.c
@@ -0,0 +1,83 @@
+#include "audio.h"
+#include "../options.h"
+#include
+
+int music_volume = 4;
+
+void PHL_AudioInit()
+{
+ SDL_InitSubSystem(SDL_INIT_AUDIO);
+ #ifndef __MORPHOS__
+ Mix_Init(MIX_INIT_OGG); // midi is on by default
+ #endif
+ Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096);
+
+ PHL_MusicVolume(0.25f * music_volume);
+}
+
+void PHL_AudioClose()
+{
+ Mix_CloseAudio();
+ #ifndef __MORPHOS__
+ Mix_Quit();
+ #endif
+}
+
+//Same as PHL_LoadSound, but expects a file name without extension
+PHL_Music PHL_LoadMusic(char* fname, int loop)
+{
+ PHL_Music ret;
+ ret.loop = loop;
+ char buff[4096];
+ strcpy(buff, "data/");
+ strcat(buff, fname);
+ strcat(buff, getMusicType()?".ogg":".mid");
+ ret.snd = Mix_LoadMUS(buff);
+ return ret;
+}
+
+PHL_Sound PHL_LoadSound(char* fname)
+{
+ char buff[4096];
+ strcpy(buff, "data/");
+ strcat(buff, fname);
+ return Mix_LoadWAV(buff);
+}
+
+void PHL_MusicVolume(float vol)
+{
+ Mix_VolumeMusic(SDL_MIX_MAXVOLUME*vol);
+}
+
+void PHL_PlayMusic(PHL_Music snd)
+{
+ if(snd.snd)
+ Mix_PlayMusic(snd.snd, snd.loop?-1:0);
+}
+
+void PHL_PlaySound(PHL_Sound snd, int channel)
+{
+ Mix_PlayChannel(channel, snd, 0);
+}
+
+void PHL_StopMusic()
+{
+ Mix_HaltMusic();
+}
+
+void PHL_StopSound(PHL_Sound snd, int channel)
+{
+ Mix_HaltChannel(channel);
+}
+
+void PHL_FreeMusic(PHL_Music snd)
+{
+ if(snd.snd)
+ Mix_FreeMusic(snd.snd);
+ snd.snd = NULL;
+}
+
+void PHL_FreeSound(PHL_Sound snd)
+{
+ Mix_FreeChunk(snd);
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/audio.h b/contrib/games/hydracastlelabyrinth/src/sdl/audio.h
new file mode 100644
index 0000000000..65da57cfb8
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/audio.h
@@ -0,0 +1,35 @@
+#ifndef SDLAUDIO_H
+#define SDLAUDIO_H
+
+#include
+#include
+
+//#define PHL_Music Mix_Music*
+#define PHL_Sound Mix_Chunk*
+typedef struct
+{
+ int loop;
+ Mix_Music* snd;
+
+} PHL_Music;
+
+extern int music_volume;
+
+void PHL_AudioInit();
+void PHL_AudioClose();
+
+void PHL_MusicVolume(float vol);
+
+PHL_Music PHL_LoadMusic(char* fname, int loop); //Same as PHL_LoadSound, but expects a file name without extension
+PHL_Sound PHL_LoadSound(char* fname);
+
+void PHL_PlayMusic(PHL_Music snd);
+void PHL_PlaySound(PHL_Sound snd, int channel);
+
+void PHL_StopMusic();
+void PHL_StopSound(PHL_Sound snd, int channel);
+
+void PHL_FreeMusic(PHL_Music snd);
+void PHL_FreeSound(PHL_Sound snd);
+
+#endif
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/graphics.c b/contrib/games/hydracastlelabyrinth/src/sdl/graphics.c
new file mode 100644
index 0000000000..49ca7d2736
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/graphics.c
@@ -0,0 +1,352 @@
+#include
+#include "../PHL.h"
+#include "../game.h"
+#include "../qda.h"
+#include "graphics.h"
+#include "scale.h"
+#include
+#include
+
+#if defined(__amigaos4__) || defined(__MORPHOS__)
+#include "../amigaos.h"
+#endif
+
+SDL_Surface* screen = NULL;
+SDL_Surface* drawbuffer = NULL;
+SDL_Surface* backbuffer = NULL;
+
+int wantFullscreen = 0;
+int screenScale = 2;
+int desktopFS = 0;
+
+int deltaX = 0;
+int deltaY = 0;
+
+int screenW = 640;
+int screenH = 480;
+
+int drawscreen = 0;
+
+int xbrz = 0;
+
+static uint32_t tframe;
+
+extern void Input_InitJoystick();
+extern void Input_CloseJoystick();
+
+int getXBRZ()
+{
+ return xbrz;
+}
+
+void setXBRZ(int active)
+{
+#ifdef _KOLIBRI
+ xbrz = 0; // Problems with "xBRZ". Temporarily not used.
+#else
+ if(active) active = 1;
+ if(xbrz==active) return;
+ xbrz = active;
+
+ // try to reload everything, but boss ressource will not be reloaded
+ freeImages();
+ loadImages();
+#endif
+}
+
+
+SDL_Color PHL_NewRGB(uint8_t r, uint8_t g, uint8_t b)
+{
+ SDL_Color ret = {.r = r, .b = b, .g = g};
+ return ret;
+}
+
+void PHL_GraphicsInit()
+{
+ SDL_ShowCursor(SDL_DISABLE);
+
+ Input_InitJoystick();
+ #ifdef __MORPHOS__
+ uint32_t flags = SDL_SWSURFACE;
+ #else
+ uint32_t flags = SDL_HWSURFACE|SDL_DOUBLEBUF;
+ #endif
+ if(wantFullscreen || desktopFS)
+ flags |= SDL_FULLSCREEN;
+ screen = SDL_SetVideoMode((desktopFS)?0:screenW, (desktopFS)?0:screenH, 0, flags);
+ if(desktopFS)
+ {
+ const SDL_VideoInfo* infos = SDL_GetVideoInfo();
+ screenH = infos->current_h;
+ screenW = infos->current_w;
+
+ if(screenW/320 < screenH/240)
+ screenScale = screenW/320;
+ else
+ screenScale = screenH/240; // automatic guess the scale
+ deltaX = (screenW-320*screenScale)/2;
+ deltaY = (screenH-240*screenScale)/2;
+ }
+
+ SDL_WM_SetCaption("Hydra Castle Labyrinth", NULL);
+
+ drawbuffer = screen;
+ drawscreen = 1;
+ backbuffer = SDL_CreateRGBSurface(0, 320*screenScale, 240*screenScale, 32, 0, 0, 0, 0);
+ tframe = SDL_GetTicks();
+}
+
+void PHL_GraphicsExit()
+{
+ Input_CloseJoystick();
+ SDL_FreeSurface(backbuffer);
+ SDL_Quit();
+}
+
+void PHL_StartDrawing()
+{
+ PHL_ResetDrawbuffer();
+}
+void PHL_EndDrawing()
+{
+ //implement some crude frameskiping, limited to 2 frame skip
+ static int skip = 0;
+ uint32_t tnext = tframe + 1000/60;
+ if (SDL_GetTicks()>tnext && skip<2) {
+ tframe += 1000/60;
+ skip++;
+ return;
+ }
+
+ // handle black borders
+ if(deltaX) {
+ SDL_Rect rect = {0, 0, deltaX, screenH};
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+ rect.x = screenW - deltaX -1;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+ }
+ if(deltaY) {
+ SDL_Rect rect = {0, 0, screenW, deltaY};
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+ rect.y = screenH - deltaY -1;
+ SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
+ }
+
+ SDL_Flip(screen);
+ while((tframe = SDL_GetTicks())format, r, g, b));
+}
+
+PHL_Surface PHL_NewSurface(int w, int h)
+{
+ if(getXBRZ())
+ return SDL_CreateRGBSurface(0, w, h, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
+ else
+ return SDL_CreateRGBSurface(0, w, h, 32, 0, 0, 0, 0);
+}
+void PHL_FreeSurface(PHL_Surface surf)
+{
+ SDL_FreeSurface(surf);
+}
+
+//PHL_Surface PHL_LoadBMP(char* fname);
+PHL_Surface PHL_LoadBMP(int index)
+{
+ SDL_Surface* surf;
+
+ FILE* f;
+ if ( (f = fopen("data/bmp.qda", "rb")) ) {
+ uint8_t* QDAFile = (uint8_t*)malloc(headers[index].size);
+ fseek(f, headers[index].offset, SEEK_SET);
+ int tmp = fread(QDAFile, 1, headers[index].size, f);
+ fclose(f);
+
+ uint16_t w, h;
+
+ //Read data from header
+ memcpy(&w, &QDAFile[18], 2);
+ memcpy(&h, &QDAFile[22], 2);
+ #if defined(__amigaos4__) || defined(__MORPHOS__)
+ BE16(&w); BE16(&h);
+ #endif
+
+ surf = PHL_NewSurface(w * screenScale, h * screenScale);
+ //surf = PHL_NewSurface(200, 200);
+
+ //Load Palette
+ int dx, dy;
+ int count = 0;
+
+ if(getXBRZ()) {
+#ifndef _KOLIBRI
+ Uint32 palette[20][18];
+
+ for (dx = 0; dx < 20; dx++) {
+ for (dy = 0; dy < 16; dy++) {
+ palette[dx][dy] = 255<<24 | ((Uint32)QDAFile[54 + count])<<0 | ((Uint32)QDAFile[54 + count + 1])<<8 | ((Uint32)QDAFile[54 + count + 2])<<16;
+ count += 4;
+ }
+ }
+ Uint32* tmp = NULL;
+ tmp = (Uint32*)malloc(w*h*screenScale*4);
+ Uint32 transp;
+ for (dx = 0; dx < w; dx++) {
+ for (dy = 0; dy < h; dy++) {
+
+ int pix = dx + w * dy;
+ int px = QDAFile[1078 + pix] / 16;
+ int py = QDAFile[1078 + pix] % 16;
+ //Get transparency from first palette color
+ if (dx == 0 && dy == 0) {
+ //Darkness special case
+ if (index == 27)
+ transp = 255<<24;
+ else
+ transp = palette[0][0];
+ }
+
+ Uint32 c = palette[px][py];
+ if(c==transp)
+ c=0;
+ tmp[(h-1-dy)*w+dx] = c;
+ }
+ }
+
+ xbrz_scale((void*)tmp, (void*)surf->pixels, w, h, screenScale);
+ free(tmp);
+#endif
+ } else {
+ PHL_RGB palette[20][18];
+
+ for (dx = 0; dx < 20; dx++) {
+ for (dy = 0; dy < 16; dy++) {
+ palette[dx][dy].b = QDAFile[54 + count];
+ palette[dx][dy].g = QDAFile[54 + count + 1];
+ palette[dx][dy].r = QDAFile[54 + count + 2];
+ count += 4;
+ }
+ }
+ for (dx = 0; dx < w; dx++) {
+ for (dy = 0; dy < h; dy++) {
+
+ int pix = dx + w * dy;
+ int px = QDAFile[1078 + pix] / 16;
+ int py = QDAFile[1078 + pix] % 16;
+ //Get transparency from first palette color
+ if (dx == 0 && dy == 0) {
+ //Darkness special case
+ if (index == 27) {
+ SDL_SetColorKey(surf, SDL_SRCCOLORKEY|SDL_RLEACCEL, SDL_MapRGB(surf->format, 0x00, 0x00, 0x00));
+ }else{
+ SDL_SetColorKey(surf, SDL_SRCCOLORKEY|SDL_RLEACCEL, SDL_MapRGB(surf->format, palette[0][0].r, palette[0][0].g, palette[0][0].b));
+ }
+ }
+
+ PHL_RGB c = palette[px][py];
+ //PHL_DrawRect(dx * 2, dy * 2, 2, 2, c);
+ SDL_Rect rect = {dx * screenScale, (h-1-dy) * screenScale, screenScale, screenScale};
+ SDL_FillRect(surf, &rect, SDL_MapRGB(surf->format, c.r, c.g, c.b));
+ }
+ }
+ }
+ free(QDAFile);
+ }
+
+ return surf;
+}
+
+void PHL_DrawRect(int x, int y, int w, int h, SDL_Color col)
+{
+ SDL_Rect rect = {x*screenScale/2 + (drawscreen?deltaX:0), y*screenScale/2 + (drawscreen?deltaY:0), w*screenScale/2, h*screenScale/2};
+
+ SDL_FillRect(drawbuffer, &rect, SDL_MapRGB(drawbuffer->format, col.r, col.g, col.b));
+}
+
+void PHL_DrawSurface(double x, double y, PHL_Surface surface)
+{
+ if (quakeTimer > 0) {
+ int val = quakeTimer % 4;
+ if (val == 0) {
+ y -= 1;
+ } else if (val == 2) {
+ y += 1;
+ }
+ }
+
+ SDL_Rect offset;
+ offset.x = x*screenScale/2 + (drawscreen?deltaX:0);
+ offset.y = y*screenScale/2 + (drawscreen?deltaY:0);
+
+ SDL_BlitSurface(surface, NULL, drawbuffer, &offset);
+}
+void PHL_DrawSurfacePart(double x, double y, int cropx, int cropy, int cropw, int croph, PHL_Surface surface)
+{
+ if (quakeTimer > 0) {
+ int val = quakeTimer % 4;
+ if (val == 0) {
+ y -= (screenScale==1)?2:1;
+ }else if (val == 2) {
+ y += (screenScale==1)?2:1;
+ }
+ }
+
+ SDL_Rect crop, offset;
+ crop.x = cropx*screenScale/2;
+ crop.y = cropy*screenScale/2;
+ crop.w = cropw*screenScale/2;
+ crop.h = croph*screenScale/2;
+
+ offset.x = x*screenScale/2 + (drawscreen?deltaX:0);
+ offset.y = y*screenScale/2 + (drawscreen?deltaY:0);
+
+ SDL_BlitSurface(surface, &crop, drawbuffer, &offset);
+}
+
+void PHL_DrawBackground(PHL_Background back, PHL_Background fore)
+{
+ PHL_DrawSurface(0, 0, backbuffer);
+}
+void PHL_UpdateBackground(PHL_Background back, PHL_Background fore)
+{
+ PHL_SetDrawbuffer(backbuffer);
+
+ int xx, yy;
+
+ for (yy = 0; yy < 12; yy++)
+ {
+ for (xx = 0; xx < 16; xx++)
+ {
+ //Draw Background tiles
+ PHL_DrawSurfacePart(xx * 40, yy * 40, back.tileX[xx][yy] * 40, back.tileY[xx][yy] * 40, 40, 40, images[imgTiles]);
+
+ //Only draw foreground tile if not a blank tile
+ if (fore.tileX[xx][yy] != 0 || fore.tileY[xx][yy] != 0) {
+ PHL_DrawSurfacePart(xx * 40, yy * 40, fore.tileX[xx][yy] * 40, fore.tileY[xx][yy] * 40, 40, 40, images[imgTiles]);
+ }
+ }
+ }
+
+ PHL_ResetDrawbuffer();
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/graphics.h b/contrib/games/hydracastlelabyrinth/src/sdl/graphics.h
new file mode 100644
index 0000000000..e3105e9df2
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/graphics.h
@@ -0,0 +1,78 @@
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include
+
+#define PHL_Surface SDL_Surface*
+
+#define PHL_RGB SDL_Color
+
+typedef struct {
+ int tileX[16][12];
+ int tileY[16][12];
+} PHL_Background;
+
+/*
+typedef struct {
+ unsigned int r, g, b;
+} PHL_RGB;
+*/
+/*
+typedef struct {
+ OSL_IMAGE* pxdata;
+ int width;
+ int height;
+ PHL_RGB colorKey;
+} PHL_Surface;
+*/
+extern PHL_Surface screen;
+
+extern int wantFullscreen;
+extern int screenScale;
+extern int desktopFS;
+
+extern int deltaX;
+extern int deltaY;
+
+extern int screenW;
+extern int screenH;
+
+SDL_Color PHL_NewRGB(uint8_t r, uint8_t g, uint8_t b);
+/*
+{
+ SDL_Color ret = {.r = r, .b = b, .g = g};
+ return ret;
+}
+*/
+void PHL_GraphicsInit();
+void PHL_GraphicsExit();
+
+void PHL_StartDrawing();
+void PHL_EndDrawing();
+
+void PHL_ForceScreenUpdate();
+
+void PHL_SetDrawbuffer(PHL_Surface surf);
+void PHL_ResetDrawbuffer();
+
+//PHL_RGB PHL_NewRGB(int r, int g, int b);
+void PHL_SetColorKey(PHL_Surface surf, int r, int g, int b);
+
+PHL_Surface PHL_NewSurface(int w, int h);
+void PHL_FreeSurface(PHL_Surface surf);
+
+//PHL_Surface PHL_LoadBMP(char* fname);
+PHL_Surface PHL_LoadBMP(int index);
+
+void PHL_DrawRect(int x, int y, int w, int h, SDL_Color col);
+
+void PHL_DrawSurface(double x, double y, PHL_Surface surface);
+void PHL_DrawSurfacePart(double x, double y, int cropx, int cropy, int cropw, int croph, PHL_Surface surface);
+
+void PHL_DrawBackground(PHL_Background back, PHL_Background fore);
+void PHL_UpdateBackground(PHL_Background back, PHL_Background fore);
+
+int getXBRZ();
+void setXBRZ(int active);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/input.c b/contrib/games/hydracastlelabyrinth/src/sdl/input.c
new file mode 100644
index 0000000000..7002055f66
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/input.c
@@ -0,0 +1,218 @@
+#include "input.h"
+#include
+
+Button btnUp = {0}, btnDown = {0}, btnLeft = {0}, btnRight = {0};
+Button btnFaceUp = {0}, btnFaceDown = {0}, btnFaceLeft = {0}, btnFaceRight = {0};
+Button btnL = {0}, btnR = {0};
+Button btnStart = {0}, btnSelect = {0};
+Button btnAccept = {0}, btnDecline = {0};
+int axisX = 0, axisY = 0;
+
+int bUp = 0, bDown = 0, bLeft = 0, bRight = 0;
+int bFaceUp = 0, bFaceDown = 0, bFaceLeft = 0, bFaceRight = 0;
+int bR = 0, bL = 0;
+int bStart = 0, bSelect = 0;
+int bAccept = 0, bDecline = 0;
+int jUp = 0, jDown = 0, jLeft = 0, jRight = 0;
+int jFaceUp = 0, jFaceDown = 0, jFaceLeft = 0, jFaceRight = 0;
+int jR = 0, jL = 0;
+int jStart = 0, jSelect = 0;
+int jAccept = 0, jDecline = 0;
+
+SDL_Joystick *joy1 = NULL;
+
+int useJoystick = 1;
+
+void Input_InitJoystick()
+{
+ int n = SDL_NumJoysticks();
+ if (n) {
+ joy1 = SDL_JoystickOpen(0);
+ SDL_JoystickEventState(SDL_ENABLE);
+ printf("Using %s\n", SDL_JoystickName(0));
+ } else {
+ joy1 = NULL;
+ }
+}
+
+void Input_CloseJoystick()
+{
+ if(joy1)
+ SDL_JoystickClose(joy1);
+ joy1 = NULL;
+}
+
+void Input_KeyEvent(SDL_Event* evt)
+{
+ int w = (evt->type==SDL_KEYDOWN)?1:0;
+ switch(evt->key.keysym.sym)
+ {
+ case SDLK_UP: bUp = w; break;
+ case SDLK_DOWN: bDown = w; break;
+ case SDLK_LEFT: bLeft = w; break;
+ case SDLK_RIGHT: bRight = w; break;
+#if defined(PANDORA) || defined(PYRA)
+ case SDLK_PAGEUP: bFaceUp = w; break;
+ case SDLK_PAGEDOWN: bFaceDown = w; break;
+ case SDLK_END: bFaceLeft = w; break; // reversing, so (B) is sword
+ case SDLK_HOME: bFaceRight = w; break;
+ case SDLK_RCTRL: bR = w; break;
+ case SDLK_RSHIFT: bL = w; break;
+ case SDLK_LCTRL: bSelect = w; break;
+ case SDLK_LALT: bStart = w; break;
+#elif defined(CHIP)
+ case SDLK_MINUS: bFaceUp = w; break;
+ case SDLK_o: bFaceDown = w; break;
+ case SDLK_0: bFaceLeft = w; break;
+ case SDLK_EQUALS: bFaceRight = w; break;
+ case SDLK_1: bR = w; break;
+ case SDLK_2: bL = w; break;
+ case SDLK_SPACE: bSelect = w; break;
+ case SDLK_RETURN: bStart = w; break;
+#elif defined(BITTBOY)
+ case SDLK_MINUS: bFaceUp = w; break;
+ case SDLK_LCTRL: bFaceDown = w; break; // A - jump
+ case SDLK_SPACE: bFaceLeft = w; break; // B - slash
+ case SDLK_LALT: bFaceRight = w; break; // TA - secondary
+ case SDLK_LSHIFT: bR = w; break; // TB - switch
+ case SDLK_ESCAPE: bSelect = w; break; // select - menu
+ case SDLK_RETURN: bStart = w; break; // start - inventory
+ case SDLK_RCTRL: bSelect = w; break; // reset - menu
+#elif defined(GAMESHELL)
+ case SDLK_i: bFaceUp = w; break;
+ case SDLK_k: bFaceDown = w; break; //jump
+ case SDLK_j: bFaceLeft = w; break; //slash
+ case SDLK_u: bFaceRight = w; break; //secondary weapon
+ case SDLK_SPACE: bR = w; break; //switch weapon
+ // case SDLK_w: bL = w; break; //switch weapon
+ // case SDLK_SPACE: bSelect = w; break;
+ case SDLK_ESCAPE: bSelect = w; break;
+ case SDLK_RETURN: bStart = w; break;
+#else
+ case SDLK_e: bFaceUp = w; break;
+ case SDLK_x: bFaceDown = w; break;
+ case SDLK_s: bFaceLeft = w; break;
+ case SDLK_d: bFaceRight = w; break;
+ case SDLK_r: bR = w; break;
+ case SDLK_w: bL = w; break;
+ case SDLK_SPACE: bSelect = w; break;
+ case SDLK_ESCAPE: bSelect = w; break;
+ case SDLK_RETURN: bStart = w; break;
+#endif
+ }
+}
+
+void Input_JoyAxisEvent(SDL_Event* evt) {
+ if(evt->jaxis.which!=0)
+ return;
+ #define DEADZONE 32
+ if(evt->jaxis.axis==0) {
+ int v = (evt->jaxis.value)/256;
+ if(v>-DEADZONE & vjaxis.axis==1) {
+ int v = (evt->jaxis.value)/256;
+ if(v>-DEADZONE & vjbutton.which!=0)
+ return;
+ int w = (evt->type==SDL_JOYBUTTONDOWN)?1:0;
+/* XBox 360 based mapping here,
+ (would be better to switch to SDL2.0)
+ btn SDL1.2 SDL2.0
+ ---------------------------
+ A 0 10
+ B 1 11
+ X 2 12
+ Y 3 13
+ Home N/A 14
+ LB 4 8
+ RB 5 9
+ LT N/A N/A (axis)
+ RT N/A N/A (axis)
+ Select 6 5
+ Start 7 4
+ L3 9 6
+ R3 10 7
+ DPad Up N/A 0
+ DPad Down N/A 1
+ DPad Left N/A 2
+ DPad Right N/A 3
+*/
+ switch(evt->jbutton.button)
+ {
+ case 0: jFaceDown = w; break;
+ case 1: jFaceLeft = w; break;
+ case 2: jFaceRight = w; break;
+ case 3: jFaceUp = w; break;
+ case 4: jL = w; break;
+ case 5: jR = w; break;
+ case 6: jSelect = w; break;
+ case 7: jStart = w; break;
+ /*case 12:jUp = w; break;
+ case 13:jDown = w; break;
+ case 14:jLeft = w; break;
+ case 15:jRight = w; break;*/
+ }
+}
+
+void Input_JoyHatEvent(SDL_Event* evt) {
+ if(evt->jhat.which!=0)
+ return;
+ if(evt->jhat.hat!=0)
+ return;
+ int v=evt->jhat.value;
+ jUp = v&SDL_HAT_UP;
+ jDown = v&SDL_HAT_DOWN;
+ jLeft = v&SDL_HAT_LEFT;
+ jRight = v&SDL_HAT_RIGHT;
+}
+
+void updateKey(Button* btn, int state)
+{
+ if (state) {
+ if (btn->held == 1) {
+ btn->pressed = 0;
+ }else{
+ btn->pressed = 1;
+ }
+ btn->held = 1;
+ btn->released = 0;
+ }else{
+ if (btn->held == 1) {
+ btn->released = 1;
+ }else{
+ btn->released = 0;
+ }
+ btn->held = 0;
+ btn->pressed = 0;
+ }
+}
+
+void PHL_ScanInput()
+{
+ updateKey(&btnUp, bUp|jUp|(axisY<0));
+ updateKey(&btnDown, bDown|jDown|(axisY>0));
+ updateKey(&btnLeft, bLeft|jLeft|(axisX<0));
+ updateKey(&btnRight, bRight|jRight|(axisX>0));
+
+ updateKey(&btnStart, bStart|jStart);
+ updateKey(&btnSelect, bSelect|jSelect);
+
+ updateKey(&btnL, bL|jL);
+ updateKey(&btnR, bR|jR);
+
+ updateKey(&btnFaceLeft, bFaceLeft|jFaceLeft);
+ updateKey(&btnFaceDown, bFaceDown|jFaceDown);
+ updateKey(&btnFaceRight, bFaceRight|jFaceRight);
+
+ updateKey(&btnAccept, bFaceLeft|jFaceLeft);
+ updateKey(&btnDecline, bFaceDown|jFaceDown);
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/input.h b/contrib/games/hydracastlelabyrinth/src/sdl/input.h
new file mode 100644
index 0000000000..bb1c0edf9f
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/input.h
@@ -0,0 +1,22 @@
+#ifndef INPUT_H
+#define INPUT_H
+
+#include
+
+typedef struct {
+ int pressed,
+ held,
+ released;
+} Button;
+
+extern Button btnUp, btnDown, btnLeft, btnRight;
+extern Button btnFaceUp, btnFaceDown, btnFaceLeft, btnFaceRight;
+extern Button btnL, btnR;
+extern Button btnStart, btnSelect;
+extern Button btnAccept, btnDecline;
+extern int axisX, axisY;
+extern int useJoystick;
+
+void PHL_ScanInput();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/joystick_stub.c b/contrib/games/hydracastlelabyrinth/src/sdl/joystick_stub.c
new file mode 100644
index 0000000000..6c0a3644d0
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/joystick_stub.c
@@ -0,0 +1,153 @@
+/// JOYSTICK STUB FOR Wolfenstein 3D port to KolibriOS
+/// Ported by maxcodehack and turbocat2001
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @file SDL_joystick.h
+ * @note In order to use these functions, SDL_Init() must have been called
+ * with the SDL_INIT_JOYSTICK flag. This causes SDL to scan the system
+ * for joysticks, and load appropriate drivers.
+ */
+
+/** The joystick structure used to identify an SDL joystick */
+struct _SDL_Joystick;
+typedef struct _SDL_Joystick SDL_Joystick;
+
+/* Function prototypes */
+/**
+ * Count the number of joysticks attached to the system
+ */
+ int SDL_NumJoysticks(void){};
+
+/**
+ * Get the implementation dependent name of a joystick.
+ *
+ * This can be called before any joysticks are opened.
+ * If no name can be found, this function returns NULL.
+ */
+ const char * SDL_JoystickName(int device_index){};
+
+/**
+ * Open a joystick for use.
+ *
+ * @param[in] device_index
+ * The index passed as an argument refers to
+ * the N'th joystick on the system. This index is the value which will
+ * identify this joystick in future joystick events.
+ *
+ * @return This function returns a joystick identifier, or NULL if an error occurred.
+ */
+ SDL_Joystick * SDL_JoystickOpen(int device_index){};
+
+/**
+ * Returns 1 if the joystick has been opened, or 0 if it has not.
+ */
+ int SDL_JoystickOpened(int device_index){};
+
+/**
+ * Get the device index of an opened joystick.
+ */
+ int SDL_JoystickIndex(SDL_Joystick *joystick){};
+
+/**
+ * Get the number of general axis controls on a joystick
+ */
+ int SDL_JoystickNumAxes(SDL_Joystick *joystick){};
+
+/**
+ * Get the number of trackballs on a joystick
+ *
+ * Joystick trackballs have only relative motion events associated
+ * with them and their state cannot be polled.
+ */
+ int SDL_JoystickNumBalls(SDL_Joystick *joystick){};
+
+/**
+ * Get the number of POV hats on a joystick
+ */
+ int SDL_JoystickNumHats(SDL_Joystick *joystick){};
+
+/**
+ * Get the number of buttons on a joystick
+ */
+ int SDL_JoystickNumButtons(SDL_Joystick *joystick){};
+
+/**
+ * Update the current state of the open joysticks.
+ *
+ * This is called automatically by the event loop if any joystick
+ * events are enabled.
+ */
+ void SDL_JoystickUpdate(void){};
+
+/**
+ * Enable/disable joystick event polling.
+ *
+ * If joystick events are disabled, you must call SDL_JoystickUpdate()
+ * yourself and check the state of the joystick when you want joystick
+ * information.
+ *
+ * @param[in] state The state can be one of SDL_QUERY, SDL_ENABLE or SDL_IGNORE.
+ */
+ int SDL_JoystickEventState(int state){};
+
+/**
+ * Get the current state of an axis control on a joystick
+ *
+ * @param[in] axis The axis indices start at index 0.
+ *
+ * @return The state is a value ranging from -32768 to 32767.
+ */
+ int SDL_JoystickGetAxis(SDL_Joystick *joystick, int axis){};
+
+/**
+ * @name Hat Positions
+ * The return value of SDL_JoystickGetHat() is one of the following positions:
+ */
+/*@{*/
+#define SDL_HAT_CENTERED 0x00
+#define SDL_HAT_UP 0x01
+#define SDL_HAT_RIGHT 0x02
+#define SDL_HAT_DOWN 0x04
+#define SDL_HAT_LEFT 0x08
+#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP)
+#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN)
+#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP)
+#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN)
+/*@}*/
+
+/**
+ * Get the current state of a POV hat on a joystick
+ *
+ * @param[in] hat The hat indices start at index 0.
+ */
+ int SDL_JoystickGetHat(SDL_Joystick *joystick, int hat){};
+
+/**
+ * Get the ball axis change since the last poll
+ *
+ * @param[in] ball The ball indices start at index 0.
+ *
+ * @return This returns 0, or -1 if you passed it invalid parameters.
+ */
+ int SDL_JoystickGetBall(SDL_Joystick *joystick, int ball, int *dx, int *dy){};
+
+/**
+ * Get the current state of a button on a joystick
+ *
+ * @param[in] button The button indices start at index 0.
+ */
+ int SDL_JoystickGetButton(SDL_Joystick *joystick, int button){};
+
+/**
+ * Close a joystick previously opened with SDL_JoystickOpen()
+ */
+ void SDL_JoystickClose(SDL_Joystick *joystick){};
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/scale.cpp b/contrib/games/hydracastlelabyrinth/src/sdl/scale.cpp
new file mode 100644
index 0000000000..8077662b8c
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/scale.cpp
@@ -0,0 +1,14 @@
+#include "scale.h"
+#include "../xBRZ/xbrz.h"
+#include
+
+void xbrz_scale(void* src, void* dst, int width, int height, int scale)
+{
+ if(scale==1) {
+ memcpy(dst, src, width*height*4);
+ } else if (scale>1 && scale <= 6) {
+ xbrz::scale(scale, (const uint32_t*)src, (uint32_t*)dst, width, height, xbrz::ColorFormat::ARGB);
+ } else {
+ xbrz::nearestNeighborScale((const uint32_t*)src, width, height, (uint32_t*)dst, width*scale, height*scale);
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/scale.h b/contrib/games/hydracastlelabyrinth/src/sdl/scale.h
new file mode 100644
index 0000000000..1734c30d35
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/scale.h
@@ -0,0 +1,14 @@
+#ifndef _SCALE_H_
+#define _SCALE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void xbrz_scale(void* src, void* dst, int width, int height, int scale);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_SCALE_H_
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/system.c b/contrib/games/hydracastlelabyrinth/src/sdl/system.c
new file mode 100644
index 0000000000..1c9afd8982
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/system.c
@@ -0,0 +1,56 @@
+#include
+#include "system.h"
+
+
+char quitGame = 0;
+
+void Input_KeyEvent(SDL_Event* evt);
+void Input_JoyEvent(SDL_Event* evt);
+void Input_JoyAxisEvent(SDL_Event* evt);
+void Input_JoyHatEvent(SDL_Event* evt);
+
+int PHL_MainLoop()
+{
+ SDL_Event evt;
+ while(SDL_PollEvent(&evt))
+ {
+ switch(evt.type)
+ {
+ case SDL_QUIT:
+ quitGame = 1;
+ return 0;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ Input_KeyEvent(&evt);
+ break;
+ case SDL_JOYAXISMOTION:
+ Input_JoyAxisEvent(&evt);
+ break;
+ case SDL_JOYHATMOTION:
+ Input_JoyHatEvent(&evt);
+ break;
+ case SDL_JOYBUTTONDOWN:
+ case SDL_JOYBUTTONUP:
+ Input_JoyEvent(&evt);
+ break;
+ }
+ }
+ if (quitGame == 1)
+ {
+ return 0;
+ }
+ return 1;
+}
+void PHL_ConsoleInit()
+{
+
+}
+void PHL_GameQuit()
+{
+ quitGame = 1;
+}
+
+void PHL_ErrorScreen(char* message)
+{
+ fprintf(stderr, "%s\n", message);
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/sdl/system.h b/contrib/games/hydracastlelabyrinth/src/sdl/system.h
new file mode 100644
index 0000000000..68d8c65267
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/sdl/system.h
@@ -0,0 +1,15 @@
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include
+#include "../PHL.h"
+
+extern char quitGame;
+
+int PHL_MainLoop();
+void PHL_ConsoleInit();
+void PHL_GameQuit();
+
+void PHL_ErrorScreen(char* message);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/stagedata.c b/contrib/games/hydracastlelabyrinth/src/stagedata.c
new file mode 100644
index 0000000000..3366438aef
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/stagedata.c
@@ -0,0 +1,93 @@
+#include "stagedata.h"
+
+int stage[9][96] = {
+ //Overworld
+ { -1, -1, 20, 21, 22, 23, 24, 25, 26, 27, -1, -1,
+ -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1,
+ -1, -1, 36, 37, 38, 39, 40, 41, 42, 43, -1, -1,
+ -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1,
+ -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, -1, -1,
+ -1, -1, 60, 61, 62, 63, 64, 65, 66, 67, -1, -1,
+ -1, -1, 68, 69, 70, 71, 72, 73, 74, 75, -1, -1,
+ -1, -1, 76, 77, 78, 79, 80, 81, 82, 83, -1, -1 },
+
+ //Level 1
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 84, 85, 86, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 87, 88, 89, 90, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 91, 92, 93, 94, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 2
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 95, -1, -1, -1, 96, -1, -1, -1, -1,
+ -1, -1, -1, 97, 98, 99, 100, 101, -1, -1, -1, -1,
+ -1, -1, -1, 102, 103, 104, 105, 106, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 3
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 108, -1, 109, 110, 111, -1, 112, -1, -1, -1,
+ -1, -1, 113, 114, 115, 107, 116, 117, 118, -1, -1, -1,
+ -1, -1, -1, -1, 119, 120, 121, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 122, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 4
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 129, 130, 131, 132, -1, 135, 136, -1,
+ -1, -1, 123, 124, 125, 126, 127, 128, 133, 134, -1, -1,
+ -1, -1, 137, 138, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 139, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 140, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 5
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 159, -1, 162, -1, -1, -1, -1, -1, -1,
+ -1, -1, 158, 160, 161, 163, 164, -1, -1, -1, -1, -1,
+ -1, -1, -1, 165, 166, 167, -1, -1, -1, -1, -1, -1,
+ -1, -1, 168, 169, 170, 171, 172, -1, -1, -1, -1, -1,
+ -1, -1, -1, 173, -1, 174, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 6
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 156, 157, -1, -1, -1,
+ -1, -1, -1, -1, 149, 150, 154, 155, -1, -1, -1, -1,
+ -1, -1, 151, -1, 152, 153, -1, -1, -1, -1, -1, -1,
+ -1, -1, 141, 142, 143, 144, 145, 146, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 147, 148, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 7
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 188, 189, 190, 191, -1, -1, -1, -1, -1,
+ -1, -1, -1, 182, 183, 184, 185, -1, -1, -1, -1, -1,
+ -1, 186, 175, 176, 177, 178, 179, 180, 181, -1, -1, -1,
+ -1, 187, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+
+ //Level 8
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 192, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 195, 194, 193, 198, 199, -1, -1, -1, -1,
+ -1, -1, -1, 196, 197, 202, 201, 200, -1, -1, -1, -1,
+ -1, -1, -1, 205, 204, 203, 208, 209, -1, -1, -1, -1,
+ -1, -1, -1, 206, 207, 212, 211, 210, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 213, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }
+ };
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/stagedata.h b/contrib/games/hydracastlelabyrinth/src/stagedata.h
new file mode 100644
index 0000000000..9be297ab55
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/stagedata.h
@@ -0,0 +1,6 @@
+#ifndef STAGEDATA_H
+#define STAGEDATA_H
+
+extern int stage[9][96];
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/template.html b/contrib/games/hydracastlelabyrinth/src/template.html
new file mode 100644
index 0000000000..7b21a78a99
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/template.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+ Hydra Castle Labyrinth
+
+
+
+
+
+ {{{ SCRIPT }}}
+
+
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/text.c b/contrib/games/hydracastlelabyrinth/src/text.c
new file mode 100644
index 0000000000..fcfb428081
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/text.c
@@ -0,0 +1,213 @@
+#include "text.h"
+#include
+#include
+#include
+#include "PHL.h"
+#include "game.h"
+
+char gameLanguage;
+Message* saving;
+Message* saveError[3];
+Message* itemName[41];
+Message* found;
+Message* itemDescription[28];
+Message* dungeon[8];
+
+void loadMessage(Message* m, FILE* f);
+void trimMessage(Message* m);
+
+void textInit()
+{
+ gameLanguage = JAPANESE;
+
+ saving = (Message*)malloc(sizeof(Message));
+
+ int i;
+ for (i = 0; i < 3; i++) {
+ saveError[i] = (Message*)malloc(sizeof(Message));
+ }
+
+ for (i = 0; i < 8; i++) {
+ dungeon[i] = (Message*)malloc(sizeof(Message));
+ }
+
+ for (i = 0; i < 41; i++) {
+ itemName[i] = (Message*)malloc(sizeof(Message));
+ }
+
+ found = (Message*)malloc(sizeof(Message));
+
+ for (i = 0; i < 28; i++) {
+ itemDescription[i] = (Message*)malloc(sizeof(Message));
+ }
+
+ //printf("\nText init");
+}
+
+void textFree()
+{
+ free(saving);
+
+ int i;
+ for (i = 0; i < 3; i++) {
+ free(saveError[i]);
+ }
+
+ for (i = 0; i < 8; i++) {
+ free(dungeon[i]);
+ }
+
+ for (i = 0; i < 41; i++) {
+ free(itemName[i]);
+ }
+
+ free(found);
+
+ for (i = 0; i < 28; i++) {
+ free(itemDescription[i]);
+ }
+}
+
+void loadText()
+{
+ FILE* f;
+
+ char fullPath[30];
+ #ifdef _3DS
+ strcpy(fullPath, "romfs:/");
+ #elif defined(_SDL)
+ strcpy(fullPath, "data/");
+ #else
+ strcpy(fullPath, "romfs/");
+ #endif
+
+ if (gameLanguage == ENGLISH) {
+ strcat(fullPath, "en.dat");
+ }else{
+ strcat(fullPath, "jp.dat");
+ }
+
+ //printf("\n");
+ //printf(fullPath);
+
+ if ( (f = fopen(fullPath, "rb")) )
+ {
+ //printf("\ntext.dat found");
+
+ //Load saving message
+ loadMessage(saving, f);
+
+ //printf("\n%d", saving->length);
+
+ //Load save error message
+ int i;
+ for (i = 0; i < 3; i++) {
+ loadMessage(saveError[i], f);
+ }
+
+ //Load dungeon intros
+ for (i = 0; i < 8; i++) {
+ loadMessage(dungeon[i], f);
+ }
+
+ //Load item names
+ for (i = 0; i < 41; i++) {
+ loadMessage(itemName[i], f);
+ }
+
+ //Found!
+ loadMessage(found, f);
+
+ //Load item descriptions
+ for (i = 0; i < 28; i++) {
+ loadMessage(itemDescription[i], f);
+ }
+
+ }else{
+ //printf("\ntext.dat was not found");
+ }
+
+ fclose(f);
+}
+
+//Returns the next character X position
+int drawText(Message* m, int x, int y)
+{
+ int textw = 20;
+ int texth = 32;
+
+ int dx = x;
+
+ int i;
+ for (i = 0; i < m->length; i++) {
+ PHL_DrawSurfacePart(dx, y, m->x[i] * textw, m->y[i] * texth, textw, texth, images[imgFontKana]);
+ dx += textw - 2;
+ }
+
+ return dx;
+}
+
+int drawCharacter(int cx, int cy, int x, int y)
+{
+ int textw = 20;
+ int texth = 32;
+
+ PHL_DrawSurfacePart(x, y, cx * textw, cy * texth, textw, texth, images[imgFontKana]);
+
+ return x + textw - 2;
+}
+
+void drawTextCentered(Message* m, int x, int y)
+{
+ int textw = 20;
+ int texth = 32;
+
+ x -= (m->length / 2) * (textw - 2);
+
+ int i;
+ for (i = 0; i < m->length; i++) {
+ PHL_DrawSurfacePart(x + (i * (textw - 2)), y, m->x[i] * textw, m->y[i] * texth, textw, texth, images[imgFontKana]);
+ }
+}
+
+void setLanguage(char lan)
+{
+ gameLanguage = lan;
+ loadText();
+}
+
+char getLanguage()
+{
+ return gameLanguage;
+}
+
+void loadMessage(Message* m, FILE* f)
+{
+ unsigned char* buffer = (unsigned char*)malloc(sizeof(unsigned char) * 64);
+
+ int cnt = fread(buffer, 1, 64, f);
+
+ int i;
+ for (i = 0; i < cnt; i+=2) {
+ m->x[i/2] = buffer[i];
+ m->y[i/2] = buffer[i+1];
+ }
+ trimMessage(m);
+
+ free(buffer);
+}
+
+void trimMessage(Message* m)
+{
+ m->length = 32;
+
+ int i;
+ for (i = 31; i >= 0; i--)
+ {
+ if (m->x[i] == 0 && m->y[i] == 0) {
+ m->length -= 1;
+ }else{
+ i = -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/text.h b/contrib/games/hydracastlelabyrinth/src/text.h
new file mode 100644
index 0000000000..1be84102e1
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/text.h
@@ -0,0 +1,34 @@
+#ifndef TEXT_HEADER
+#define TEXT_HEADER
+
+#define JAPANESE 0
+#define ENGLISH 1
+
+extern char gameLanguage;
+
+typedef struct {
+ unsigned char x[32];
+ unsigned char y[32];
+ char length;
+} Message;
+
+extern Message* saving;
+extern Message* saveError[3];
+extern Message* itemName[41];
+extern Message* found;
+extern Message* itemDescription[28];
+extern Message* dungeon[8];
+
+void textInit();
+void textFree();
+
+void loadText();
+
+int drawText(Message* m, int x, int y);
+int drawCharacter(int cx, int cy, int x, int y);
+void drawTextCentered(Message* m, int x, int y);
+
+void setLanguage(char lan);
+char getLanguage();
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/titlescreen.c b/contrib/games/hydracastlelabyrinth/src/titlescreen.c
new file mode 100644
index 0000000000..f85de4f62b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/titlescreen.c
@@ -0,0 +1,142 @@
+#include "titlescreen.h"
+#include "game.h"
+#include
+#include "text.h"
+
+int tempsave = 0;
+int cursor = 0;
+
+void titleScreenSetup();
+
+int titleScreenStep();
+void titleScreenDraw();
+
+#ifdef EMSCRIPTEN
+int titleEMStep()
+{
+ PHL_MainLoop();
+ //Get input
+ PHL_ScanInput();
+
+ //Titlescreen step
+ int result = titleScreenStep();
+
+ //Draw titlescreen
+ PHL_StartDrawing();
+
+ titleScreenDraw();
+
+ if (result != -1)
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+
+ PHL_EndDrawing();
+
+ return result;
+}
+#endif
+
+int titleScreen()
+{
+ titleScreenSetup();
+
+ char loop = 1;
+ int result = -1;
+
+ while (PHL_MainLoop() && loop == 1)
+ {
+ //__asm__("int3");
+ //Get input
+ PHL_ScanInput();
+
+ //Titlescreen step
+ result = titleScreenStep();
+
+ //Draw titlescreen
+ PHL_StartDrawing();
+
+ titleScreenDraw();
+
+ if (result != -1) {
+ loop = 0;
+ //Force screen to black
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+ }
+ PHL_EndDrawing();
+ }
+
+ return result;
+}
+
+void titleScreenSetup()
+{
+ cursor = 0;
+
+ //Move cursor if save file exists
+ if ( fileExists(savemap) ) {
+ cursor = 1;
+ }
+
+ //Check if temp save file exists
+ tempsave = 0;
+ if ( fileExists(savename) ) {
+ #ifndef EMSCRIPTEN
+ tempsave = 1;
+ #endif
+ cursor = 1;
+ }
+}
+
+int titleScreenStep()
+{
+ //Move cursor
+ if (btnDown.pressed == 1 || btnSelect.pressed == 1) {
+ cursor += 1;
+ if (cursor > 3) {
+ cursor = 0;
+ }
+ PHL_PlaySound(sounds[sndPi01], 1);
+ }
+
+ if (btnUp.pressed == 1) {
+ cursor -= 1;
+ if (cursor < 0) {
+ cursor = 3;
+ }
+ PHL_PlaySound(sounds[sndPi01], 1);
+ }
+
+ //Selection
+ if (btnAccept.pressed == 1 || btnStart.pressed == 1) {
+ PHL_PlaySound(sounds[sndOk], 1);
+ return cursor;
+ }
+
+ return -1;
+}
+
+void titleScreenDraw()
+{
+ //Blackdrop
+ PHL_DrawRect(0, 0, 640, 480, PHL_NewRGB(0, 0, 0));
+
+ if (tempsave == 0) {
+ //Title image
+ PHL_DrawSurfacePart(168, 72, 0, 0, 304, 168, images[imgTitle01]);
+ }else{
+ //Save error message
+ drawTextCentered(saveError[0], 320, 80);
+ drawTextCentered(saveError[1], 320, 80 + 50);
+ drawTextCentered(saveError[2], 320, 80 + 96);
+ }
+
+ //Cursor
+ PHL_DrawSurfacePart(228, 264 + (cursor * 32), 4, 176, 184, 32, images[imgTitle01]);
+
+ //Text
+ PHL_DrawTextBold("NEW GAME", 256, 272, YELLOW);
+ PHL_DrawTextBold("LOAD GAME", 248, 304, YELLOW);
+ PHL_DrawTextBold("OPTIONS", 264, 336, YELLOW);
+ PHL_DrawTextBold("EXIT", 288, 368, YELLOW);
+ PHL_DrawTextBold("(C) 2011 E.HASHIMOTO", 160, 410, WHITE);
+ PHL_DrawTextBold("GPL2 PTITSEB", 224, 442, WHITE);
+}
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/titlescreen.h b/contrib/games/hydracastlelabyrinth/src/titlescreen.h
new file mode 100644
index 0000000000..1e532f7e4c
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/titlescreen.h
@@ -0,0 +1,11 @@
+#ifndef TITLESCREEN_H
+#define TITLESCREEN_H
+
+#ifdef EMSCRIPTEN
+void titleScreenSetup();
+int titleEMStep();
+#else
+int titleScreen();
+#endif
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/weapon.c b/contrib/games/hydracastlelabyrinth/src/weapon.c
new file mode 100644
index 0000000000..1d27e01cc5
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/weapon.c
@@ -0,0 +1,602 @@
+#include "weapon.h"
+#include
+#include
+#include
+#include "hero.h"
+#include "PHL.h"
+#include "game.h"
+#include "object.h"
+
+void updateWeaponMask(Weapon* w);
+
+void addWeapon(int type, int x, int y)
+{
+ if (heroAmmo > 0 || type == SWORD) {
+ int i, weaponcount = 0;
+
+ //Count
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] != NULL) {
+ if (weapons[i]->type != SWORD) {
+ weaponcount += 1;
+ }
+ }
+ }
+
+ if (type == BOMB) { //Bombs have one lower limit
+ weaponcount += 1;
+ }
+
+ if (type == SWORD || weaponcount < (2 + hasItem[15])) { //3 projectiles if have green scroll
+
+ if (type != SWORD) {
+ PHL_PlaySound(sounds[sndShot01], CHN_WEAPONS);
+ }
+
+ for (i = 0; i < MAX_WEAPONS; i++) {
+ if (weapons[i] == NULL) {
+ Weapon* w = malloc(sizeof *w);
+ w->id = i;
+ w->type = type;
+
+ w->x = x;
+ w->y = y;
+
+ w->hsp = 0;
+ w->vsp = 0;
+
+ w->grav = 0;
+ w->imageIndex = 0;
+
+ w->power = 1;
+ w->timer = 0;
+ w->state = 0;
+ w->cooldown = 0;
+ w->hitflag = 0;
+
+ w->dir = getHeroDirection();
+
+ w->weaponMask.circle = 0;
+ w->weaponMask.unused = 0;
+ w->weaponMask.x = 0;
+ w->weaponMask.y = 0;
+ w->weaponMask.w = 10;
+ w->weaponMask.h = 10;
+
+ if (w->type == SWORD)
+ {
+ w->weaponMask.unused = 1;
+ }else if (w->type == ARROW)
+ {
+ w->hsp = 8 * getHeroDirection();
+ w->weaponMask.y += 16;
+ w->weaponMask.w = 32;
+ w->weaponMask.h = 6;
+ }else if (w->type == AXE)
+ {
+ w->hsp = 2 * getHeroDirection();
+ w->vsp = -6;
+ w->grav = 0.2;
+ w->weaponMask.w = w->weaponMask.h = 24;
+ w->y += (40 - getHeroMask().h) - ((40 - w->weaponMask.h) / 2);
+ w->weaponMask.x = w->x + ((40 - w->weaponMask.w) / 2);
+ w->weaponMask.y = w->y + ((40 - w->weaponMask.h) / 2);
+
+ }else if (w->type == BOOMERANG)
+ {
+ w->x += 20;
+ w->y += 20;
+ updateWeaponMask(w);
+ w->weaponMask.circle = 1;
+ w->weaponMask.w = 16;
+
+ w->hsp = 6 * getHeroDirection();
+ w->vsp = getHeroDirection(); //Vsp is used for starting direction
+ w->timer = 120;
+ }else if (w->type == FIREBALL)
+ {
+ w->vsp = -2;
+ w->grav = 0.1;
+
+ w->hsp = 2 * getHeroDirection();
+
+ w->x += 20;
+ w->y += 20;
+
+ w->weaponMask.circle = 1;
+ w->weaponMask.w = 15;
+ updateWeaponMask(w);
+
+ Mask tempMask;
+ tempMask.circle = tempMask.unused = 0;
+ tempMask.y = w->y - w->weaponMask.w;
+ tempMask.x = w->x - w->weaponMask.w;
+ tempMask.w = tempMask.h = w->weaponMask.w * 2;
+
+ PHL_Rect collide = getTileCollision(1, tempMask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, tempMask);
+ }
+ if (collide.x != -1) {
+ w->y = collide.y + 40 + w->weaponMask.w;
+ }
+
+ }else if (w->type == BOMB)
+ {
+ w->dir = 0;
+ w->vsp = -2;
+ w->grav = 0.1;
+ w->hsp = getHeroDirection();
+
+ w->weaponMask.unused = 1;
+
+ Mask tempMask;
+ tempMask.y = w->y + 8;
+ tempMask.x = w->x + 8;
+ tempMask.w = tempMask.h = 24;
+ tempMask.circle = tempMask.unused = 0;
+
+ PHL_Rect collide = getTileCollision(1, tempMask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, tempMask);
+ }
+ if (collide.x != -1) {
+ w->y = collide.y + 40 - 8;
+ }
+ }
+
+ updateWeaponMask(w);
+
+ weapons[i] = w;
+ i = MAX_WEAPONS;
+
+ if (type != SWORD) {
+ heroAmmo -= 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+//When a weapon lands a hit
+void weaponHit(Weapon* w)
+{
+ //w->cooldown = 15;
+ w->hitflag = 1;
+
+ if (w->type == SWORD) {
+ createEffect(1, w->weaponMask.x + (w->weaponMask.w / 2) - 10 + (rand() % 20) - 10, w->weaponMask.y + (w->weaponMask.h / 2) - 10 + (rand() % 20) - 10);
+ PHL_PlaySound(sounds[sndHit02], CHN_WEAPONS);
+ }else if (w->type == ARROW || w->type == AXE) {
+ PHL_PlaySound(sounds[sndPi02], CHN_WEAPONS);
+ createEffect(1, w->x, w->y);
+ weaponDestroy(w->id);
+ }else if (w->type == BOOMERANG) {
+ PHL_PlaySound(sounds[sndPi02], CHN_WEAPONS);
+ createEffect(1, w->x, w->y - 40 + (rand() % 40) + 1);
+ }else if (w->type == FIREBALL) {
+ PHL_PlaySound(sounds[sndPi02], CHN_WEAPONS);
+ createEffect(1, w->x - 20, w->y - 20);
+ //weaponDestroy(w->id);
+ }else if (w->type == BOMB) {
+
+ }
+}
+
+void weaponStep(Weapon* w)
+{
+ char dead = 0;
+
+ if (w->cooldown > 0) {
+ w->cooldown -= 1;
+ }
+
+ if (w->hitflag == 1) {
+ w->hitflag = 0;
+ w->cooldown = 15;
+ }
+
+ if (w->type == SWORD)
+ {
+ double imgind = getHeroImageIndex();
+ if ((getHeroState() != 1 && getHeroState() != 5) || imgind >= 4) { //Not slashing
+ dead = 1;
+ }
+
+ w->x = herox;
+ w->y = heroy;
+ updateWeaponMask(w);
+
+ }else if (w->type == ARROW)
+ {
+ //Yes, this does have to be done before movement
+ //Destroy if out of view or collides with wall
+ if (w->x > 640 || w->x + 40 < 0 || checkTileCollision(1, w->weaponMask) == 1) {
+ weaponHit(w);
+ }
+
+ w->x += w->hsp;
+ updateWeaponMask(w);
+ }else if (w->type == AXE)
+ {
+ w->y += w->vsp;
+ w->vsp += w->grav;
+ updateWeaponMask(w);
+
+ PHL_Rect collide = getTileCollisionWeapon(1, w->weaponMask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, w->weaponMask);
+ }
+
+ if (collide.x != -1) {
+ if (w->vsp <= 0) {
+ w->y = collide.y + 40 - ((40 - w->weaponMask.h) / 2);
+ w->vsp = 0;
+ }else{
+ weaponHit(w);
+ return;
+ }
+ }
+
+ w->x += w->hsp;
+ updateWeaponMask(w);
+
+ collide = getTileCollisionWeapon(1, w->weaponMask);
+
+ if (collide.x != -1) {
+ if (w->hsp > 0) {
+ w->x = collide.x - 40 + ((40 - w->weaponMask.w) / 2);
+ }else{
+ w->x = collide.x + 40 - ((40 - w->weaponMask.w) / 2);
+ }
+ }
+
+ //Animate
+ {
+ w->imageIndex += 0.33;
+ if (w->imageIndex >= 8) {
+ w->imageIndex -= 8;
+ }
+ }
+
+ if (w->x > 640 || w->x + 40 < 0 || w->y > 520) {
+ dead = 1;
+ }
+ }else if (w->type == BOOMERANG)
+ {
+ w->x += w->hsp;
+ w->hsp -= 0.125 * (w->vsp); //Vsp is recycled to be the starting direction
+ if (w->hsp >= 6) {
+ w->hsp = 6;
+ }
+ if (w->hsp <= -6) {
+ w->hsp = -6;
+ }
+
+ w->dir = 1;
+ if (w->hsp < 0) {
+ w->dir = -1;
+ }
+
+ w->y = heroy + 20;
+ updateWeaponMask(w);
+
+ w->imageIndex -= (0.33 * w->vsp);
+ if (w->imageIndex < 0) {
+ w->imageIndex += 8;
+ }
+ if (w->imageIndex >= 8) {
+ w->imageIndex -= 8;
+ }
+
+ w->timer -= 1;
+ if (w->timer <= 0) {
+ createEffect(5, w->x, w->y);
+ dead = 1;
+ }
+ }else if (w->type == FIREBALL)
+ {
+ //Move vertically
+ w->y += w->vsp;
+ w->vsp += w->grav;
+
+ Mask tempMask;
+ tempMask.circle = tempMask.unused = 0;
+ tempMask.y = w->y - w->weaponMask.w;
+ tempMask.w = tempMask.h = w->weaponMask.w * 2;
+ tempMask.x = w->x - w->weaponMask.w;
+
+ //Check vertical collision
+ PHL_Rect collide = getTileCollisionWeapon(1, tempMask);
+ if (collide.x == -1) {
+ collide = getTileCollisionWeapon(3, tempMask);
+ }
+
+ if (collide.x != -1) {
+ if (w->vsp <= 0) {
+ w->y = collide.y + 40 + w->weaponMask.w;
+ w->vsp = 0;
+ }else{
+ w->y = collide.y - w->weaponMask.w;
+ if (w->timer == 0) {
+ w->vsp = -2;
+ PHL_PlaySound(sounds[sndPi02], 2);
+ }else if (w->timer == 1) {
+ w->vsp = -1;
+ PHL_PlaySound(sounds[sndPi02], 2);
+ }else{
+ weaponHit(w);
+ dead = 1;
+ }
+ w->timer += 1;
+ }
+
+ tempMask.y = w->y - w->weaponMask.w;
+ }
+
+ //Move horizontally
+ w->x += w->hsp;
+ tempMask.x = w->x - w->weaponMask.w;
+
+ //Check horizontal collision
+ collide = getTileCollisionWeapon(1, tempMask);
+ if (collide.x != -1) {
+ if (w->hsp > 0) {
+ w->x = collide.x - (tempMask.w / 2) ;
+ }else{
+ w->x = collide.x + 40 + (tempMask.w / 2);
+ }
+ w->hsp *= -1;
+ tempMask.x = w->x - w->weaponMask.w;
+
+ w->dir = 1;
+ if (w->hsp < 0) {
+ w->dir = -1;
+ }
+ }
+
+ updateWeaponMask(w);
+
+ //animate
+ {
+ w->imageIndex += 0.5;
+ if (w->imageIndex >= 8) {
+ w->imageIndex -= 8;
+ }
+ }
+
+ if (w->x > 640 || w->x + 40 < 0 || w->y > 520) {
+ dead = 1;
+ }
+ }else if (w->type == BOMB)
+ {
+ if (w->state == 0) { //Bouncing bomb
+ Mask tempMask;
+ tempMask.y = w->y + 8;
+ tempMask.w = tempMask.h = 24;
+ tempMask.circle = tempMask.unused = 0;
+
+ w->x += w->hsp;
+ tempMask.x = w->x + 8;
+ PHL_Rect collide = getTileCollision(1, tempMask);
+ if (collide.x != -1) {
+ if (w->hsp > 0) {
+ w->x = collide.x - 40 + 8;
+ }else{
+ w->x = collide.x + 40 - 8;
+ }
+ w->hsp *= -1;
+ tempMask.x = w->x + 8;
+ }
+
+ w->imageIndex -= (0.33 * w->hsp);
+ if (w->imageIndex < 0) {
+ w->imageIndex += 8;
+ }
+ if (w->imageIndex >= 8) {
+ w->imageIndex -= 8;
+ }
+
+ w->y += w->vsp;
+ w->vsp += w->grav;
+
+ tempMask.y = w->y + 8;
+ collide = getTileCollision(1, tempMask);
+ if (collide.x == -1) {
+ collide = getTileCollision(3, tempMask);
+ }
+ if (collide.x != -1) {
+ if (w->vsp <= 0) {
+ w->y = collide.y + 40 - 8;
+ w->vsp = 0;
+ }else{
+ w->y = collide.y - 40 + 8;
+ if (w->timer == 0) {
+ w->vsp = -2;
+ PHL_PlaySound(sounds[sndPi02], CHN_WEAPONS);
+ }else if (w->timer == 1) {
+ w->vsp = -1;
+ PHL_PlaySound(sounds[sndPi02], CHN_WEAPONS);
+ }else{
+ w->state = 1;
+ w->imageIndex = 0;
+ w->weaponMask.unused = 0;
+ PHL_PlaySound(sounds[sndBom03], CHN_WEAPONS);
+ }
+ w->timer += 1;
+ }
+ }
+
+ updateWeaponMask(w);
+
+ if (w->x > 640 || w->x + 40 < 0 || w->y > 520) {
+ //weaponDestroy(w->id);
+ dead = 1;
+ }
+ }
+ else if (w->state == 1) { //Explosion
+ updateWeaponMask(w);
+
+ if (checkCollision(getHeroMask(), w->weaponMask) == 1) {
+ heroHit(20, w->x + 20);
+ }
+
+ w->imageIndex += 0.34;
+ if (w->imageIndex >= 11) {
+ //weaponDestroy(w->id);
+ dead = 1;
+ }
+ }
+ }
+
+ if (dead == 1) {
+ weaponDestroy(w->id);
+ }
+}
+
+void weaponDraw(Weapon* w)
+{
+ if (w->type == SWORD) {
+ //PHL_DrawMask(w->weaponMask);
+
+ //Draw Sword
+ double imgind = getHeroImageIndex();
+ int dir = getHeroDirection();
+ if (imgind < 4) {
+ int swordx = 0, swordy = 0;
+ int scropx = 40 * (int)floor(imgind),
+ scropy = 240;
+ if (imgind < 1) {
+ swordy = -16;
+ swordx = -8;
+ }else if (imgind < 2) {
+ swordy = -8;
+ swordx = 24;
+ }else if (imgind < 4) {
+ swordy = 14;
+ swordx = 26;
+ }
+
+ if (dir == -1) {
+ swordx *= -1;
+ scropx += 160;
+ }
+
+ if (getHeroInvincible() % 2 == 0) {
+ PHL_DrawSurfacePart(herox - 20 + swordx, heroy + swordy, scropx, scropy, 40, 40, images[imgHero]);
+ }
+
+ //PHL_DrawSurfacePart(w->x, w->y, 0, 240, 40, 40, images[imgHero]);
+ }
+
+ }
+ else if (w->type == ARROW)
+ {
+ int dx = 240;
+ if (w->hsp <= 0) {
+ dx += 40;
+ }
+ PHL_DrawSurfacePart(w->x, w->y, dx, 200, 40, 40, images[imgMisc20]);
+ }else if (w->type == AXE)
+ {
+ int dx = 0;
+ if (w->hsp <= 0) {
+ dx = 320;
+ }
+ PHL_DrawSurfacePart(w->x, w->y, dx + ((int)w->imageIndex * 40), 240, 40, 40, images[imgMisc20]);
+ }else if (w->type == BOOMERANG)
+ {
+ PHL_DrawSurfacePart(w->x - 20, w->y - 20, 320 + ((int)w->imageIndex * 40), 160, 40, 40, images[imgMisc20]);
+ }else if (w->type == FIREBALL)
+ {
+ int dx = 0;
+ if (w->hsp <= 0) {
+ dx = 320;
+ }
+ PHL_DrawSurfacePart(w->x - 20, w->y - 20, dx + ((int)w->imageIndex * 40), 280, 40, 40, images[imgMisc20]);
+ }else if (w->type == BOMB)
+ {
+ if (w->state == 0) {
+ PHL_DrawSurfacePart(w->x, w->y, (int)w->imageIndex * 40, 160, 40, 40, images[imgMisc20]);
+ }
+ else if (w->state == 1) {
+ //PHL_DrawMask(w->weaponMask);
+
+ int cx = (int)w->imageIndex * 128;
+ int cy = 0;
+ while (cx >= 640) {
+ cx -= 640;
+ cy += 96;
+ }
+ PHL_DrawSurfacePart(w->weaponMask.x, w->weaponMask.y, cx, cy, 128, 96, images[imgExplosion]);
+ }
+ }
+ //PHL_DrawMask(w->weaponMask);
+}
+
+void updateWeaponMask(Weapon* w)
+{
+ if (w->type == SWORD) {
+ //Sword mask
+ //swordMask.unused = 0;
+ double imgind = getHeroImageIndex();
+ int dir = getHeroDirection();
+ w->weaponMask.unused = 0;
+
+ if (imgind < 1) {
+ /*w->weaponMask.w = 8;
+ w->weaponMask.h = 24;
+ w->weaponMask.x = herox - 4 + (dir * -8); //herox - 20 + (direction * -8) + 16
+ w->weaponMask.y = heroy - 8; //heroy - 16 + 8
+ */
+ w->weaponMask.unused = 1;
+ }else if (imgind < 2) {
+ w->weaponMask.w = 32;
+ w->weaponMask.h = 38;
+ w->weaponMask.x = herox - 20 + (dir * 28) + 4;
+ w->weaponMask.y = heroy - 6;
+ }else if (imgind < 3) {
+ w->weaponMask.w = 24;
+ w->weaponMask.h = 20;
+ w->weaponMask.x = herox - 20 + (dir * 26) + 8;
+ w->weaponMask.y = heroy + 18;
+ }else if (imgind < 4) {
+ w->weaponMask.w = 24;
+ w->weaponMask.h = 6;
+ w->weaponMask.x = herox - 20 + (dir * 26) + 8;
+ w->weaponMask.y = heroy + 30;
+ }
+ }else if (w->type == ARROW) {
+ w->weaponMask.x = w->x;
+ w->weaponMask.y = w->y + 16;
+ if (w->hsp > 0) {
+ w->weaponMask.x += 8;
+ }
+
+ }else if (w->type == AXE) {
+ w->weaponMask.x = w->x + ((40 - w->weaponMask.w) / 2);
+ w->weaponMask.y = w->y + ((40 - w->weaponMask.h) / 2);
+ }else if (w->type == BOOMERANG) {
+ w->weaponMask.x = w->x;
+ w->weaponMask.y = w->y;
+ }else if (w->type == FIREBALL) {
+ w->weaponMask.x = w->x;
+ w->weaponMask.y = w->y;
+ }else if (w->type == BOMB) {
+ if (w->state == 1) { //Update mask on explosion
+ w->weaponMask.x = w->x - 44;
+ w->weaponMask.y = w->y - 44 - 8;
+ w->weaponMask.w = 128;
+ w->weaponMask.h = 90; //Hits blocks below
+ }
+ }
+}
+
+void weaponDestroy(int id)
+{
+ if (weapons[id] != NULL) {
+ free(weapons[id]);
+ }
+ weapons[id] = NULL;
+}
diff --git a/contrib/games/hydracastlelabyrinth/src/weapon.h b/contrib/games/hydracastlelabyrinth/src/weapon.h
new file mode 100644
index 0000000000..5f47be6b92
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/weapon.h
@@ -0,0 +1,34 @@
+#ifndef WEAPON_H
+#define WEAPON_H
+
+#include "collision.h"
+
+#define ARROW 0
+#define AXE 1
+#define BOOMERANG 2
+#define FIREBALL 3
+#define BOMB 4
+#define SWORD 5
+
+typedef struct {
+ int id, type;
+
+ double x, y;
+ double vsp, hsp;
+ double grav;
+ double imageIndex;
+ int dir;
+
+ int power, timer, state, cooldown, hitflag;
+
+ Mask weaponMask;
+} Weapon;
+
+void addWeapon(int type, int x, int y);
+void weaponStep(Weapon* w);
+void weaponDraw(Weapon* w);
+
+void weaponHit(Weapon* w);
+void weaponDestroy(int id);
+
+#endif
\ No newline at end of file
diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/Changelog.txt b/contrib/games/hydracastlelabyrinth/src/xBRZ/Changelog.txt
new file mode 100644
index 0000000000..1ebcc6dbb8
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/Changelog.txt
@@ -0,0 +1,51 @@
+===========
+|Changelog|
+===========
+
+xBRZ 1.5 [2017-08-07]
+---------------------
+Added RGB conversion routines
+
+
+xBRZ 1.4 [2015-07-25]
+---------------------
+Added 6xBRZ scaler
+Create color distance buffer lazily
+
+
+xBRZ 1.3 [2015-04-03]
+---------------------
+Improved ARGB performance by 15%
+Fixed alpha channel gradient bug
+
+
+xBRZ 1.2 [2014-11-21]
+---------------------
+Further improved performance by over 30%
+
+
+xBRZ 1.1 [2014-11-02]
+---------------------
+Support images with alpha channel
+Improved color analysis
+
+
+xBRZ 1.0 [2013-02-11]
+---------------------
+Fixed xBRZ scaler compiler issues for GCC
+
+
+xBRZ 0.2 [2012-12-11]
+---------------------
+Added 5xBRZ scaler
+Optimized xBRZ scaler performance by factor 3
+Further improved image quality of xBRZ scaler
+
+
+xBRZ 0.1 [2012-09-26]
+---------------------
+Initial release:
+- scale while preserving small image features
+- support multithreading
+- support 64-bit architectures
+- support processing image slices
diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/License.txt b/contrib/games/hydracastlelabyrinth/src/xBRZ/License.txt
new file mode 100644
index 0000000000..ea09e3734b
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/License.txt
@@ -0,0 +1,621 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.cpp b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.cpp
new file mode 100644
index 0000000000..812dc6a5aa
--- /dev/null
+++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.cpp
@@ -0,0 +1,1174 @@
+// ****************************************************************************
+// * This file is part of the HqMAME project. It is distributed under *
+// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// * *
+// * Additionally and as a special exception, the author gives permission *
+// * to link the code of this program with the MAME library (or with modified *
+// * versions of MAME that use the same license as MAME), and distribute *
+// * linked combinations including the two. You must obey the GNU General *
+// * Public License in all respects for all of the code used other than MAME. *
+// * If you modify this file, you may extend this exception to your version *
+// * of the file, but you are not obligated to do so. If you do not wish to *
+// * do so, delete this exception statement from your version. *
+// ****************************************************************************
+
+#pragma GCC diagnostic ignored "-Wunused-function"
+#include "xbrz.h"
+#include "xbrz_tools.h"
+#include
+#include
+#include
+#include
+#ifndef NDEBUG
+#define NDEBUG
+#endif
+
+using namespace xbrz;
+
+
+namespace
+{
+template inline
+uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
+{
+ static_assert(0 < M && M < N && N <= 1000, "");
+
+ auto calcColor = [](unsigned char colFront, unsigned char colBack) -> unsigned char {
+ return (colFront * M + colBack * (N - M)) / N;
+ };
+
+ return makePixel(calcColor(getRed (pixFront), getRed (pixBack)),
+ calcColor(getGreen(pixFront), getGreen(pixBack)),
+ calcColor(getBlue (pixFront), getBlue (pixBack)));
+}
+
+
+template inline
+uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate color between two colors with alpha channels (=> NO alpha blending!!!)
+{
+ static_assert(0 < M && M < N && N <= 1000, "");
+
+ const unsigned int weightFront = getAlpha(pixFront) * M;
+ const unsigned int weightBack = getAlpha(pixBack) * (N - M);
+ const unsigned int weightSum = weightFront + weightBack;
+ if (weightSum == 0)
+ return 0;
+
+ auto calcColor = [=](unsigned char colFront, unsigned char colBack)
+ {
+ return static_cast((colFront * weightFront + colBack * weightBack) / weightSum);
+ };
+
+ return makePixel(static_cast(weightSum / N),
+ calcColor(getRed (pixFront), getRed (pixBack)),
+ calcColor(getGreen(pixFront), getGreen(pixBack)),
+ calcColor(getBlue (pixFront), getBlue (pixBack)));
+}
+
+
+//inline
+//double fastSqrt(double n)
+//{
+// __asm //speeds up xBRZ by about 9% compared to std::sqrt which internally uses the same assembler instructions but adds some "fluff"
+// {
+// fld n
+// fsqrt
+// }
+//}
+//
+
+
+#ifdef _MSC_VER
+ #define FORCE_INLINE __forceinline
+#elif defined __GNUC__
+ #define FORCE_INLINE __attribute__((always_inline)) inline
+#else
+ #define FORCE_INLINE inline
+#endif
+
+
+enum RotationDegree //clock-wise
+{
+ ROT_0,
+ ROT_90,
+ ROT_180,
+ ROT_270
+};
+
+//calculate input matrix coordinates after rotation at compile time
+template
+struct MatrixRotation;
+
+template
+struct MatrixRotation
+{
+ static const size_t I_old = I;
+ static const size_t J_old = J;
+};
+
+template //(i, j) = (row, col) indices, N = size of (square) matrix
+struct MatrixRotation
+{
+ static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation!
+ static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old;//
+};
+
+
+template
+class OutputMatrix
+{
+public:
+OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width
+ out_(out),
+ outWidth_(outWidth) {
+}
+
+template
+uint32_t& ref() const
+{
+ static const size_t I_old = MatrixRotation::I_old;
+ static const size_t J_old = MatrixRotation::J_old;
+ return *(out_ + J_old + I_old * outWidth_);
+}
+
+private:
+uint32_t* out_;
+const int outWidth_;
+};
+
+
+template inline
+T square(T value) {
+ return value * value;
+}
+
+
+
+inline
+double distRGB(uint32_t pix1, uint32_t pix2)
+{
+ const double r_diff = static_cast(getRed (pix1)) - getRed (pix2);
+ const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2);
+ const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2);
+
+ //euklidean RGB distance
+ return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff));
+}
+
+
+inline
+double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight)
+{
+ //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion
+ //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first!
+ const int r_diff = static_cast(getRed (pix1)) - getRed (pix2);//we may delay division by 255 to after matrix multiplication
+ const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); //
+ const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double!
+
+ //const double k_b = 0.0722; //ITU-R BT.709 conversion
+ //const double k_r = 0.2126; //
+ const double k_b = 0.0593; //ITU-R BT.2020 conversion
+ const double k_r = 0.2627; //
+ const double k_g = 1 - k_b - k_r;
+
+ const double scale_b = 0.5 / (1 - k_b);
+ const double scale_r = 0.5 / (1 - k_r);
+
+ const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff;//[!], analog YCbCr!
+ const double c_b = scale_b * (b_diff - y);
+ const double c_r = scale_r * (r_diff - y);
+
+ //we skip division by 255 to have similar range like other distance functions
+ return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r));
+}
+
+
+inline
+double distYCbCrBuffered(uint32_t pix1, uint32_t pix2)
+{
+ //30% perf boost compared to plain distYCbCr()!
+ //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB
+ static const std::vector diffToDist = []
+ {
+ std::vector tmp;
+
+ for (uint32_t i = 0; i < 256 * 256 * 256; ++i) //startup time: 114 ms on Intel Core i5 (four cores)
+ {
+ const int r_diff = getByte<2>(i) * 2 - 0xFF;
+ const int g_diff = getByte<1>(i) * 2 - 0xFF;
+ const int b_diff = getByte<0>(i) * 2 - 0xFF;
+
+ const double k_b = 0.0593; //ITU-R BT.2020 conversion
+ const double k_r = 0.2627; //
+ const double k_g = 1 - k_b - k_r;
+
+ const double scale_b = 0.5 / (1 - k_b);
+ const double scale_r = 0.5 / (1 - k_r);
+
+ const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff;//[!], analog YCbCr!
+ const double c_b = scale_b * (b_diff - y);
+ const double c_r = scale_r * (r_diff - y);
+
+ tmp.push_back(static_cast(std::sqrt(square(y) + square(c_b) + square(c_r))));
+ }
+ return tmp;
+ } ();
+
+ //if (pix1 == pix2) -> 8% perf degradation!
+ // return 0;
+ //if (pix1 < pix2)
+ // std::swap(pix1, pix2); -> 30% perf degradation!!!
+#if 1
+ const int r_diff = static_cast(getRed (pix1)) - getRed (pix2);
+ const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2);
+ const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2);
+
+ return diffToDist[(((r_diff + 0xFF) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte
+ (((g_diff + 0xFF) / 2) << 8) |
+ (( b_diff + 0xFF) / 2)];
+#else //not noticeably faster:
+ const int r_diff_tmp = ((pix1 & 0xFF0000) + 0xFF0000 - (pix2 & 0xFF0000)) / 2;
+ const int g_diff_tmp = ((pix1 & 0x00FF00) + 0x00FF00 - (pix2 & 0x00FF00)) / 2; //slightly reduce precision (division by 2) to squeeze value into single byte
+ const int b_diff_tmp = ((pix1 & 0x0000FF) + 0x0000FF - (pix2 & 0x0000FF)) / 2;
+
+ return diffToDist[(r_diff_tmp & 0xFF0000) | (g_diff_tmp & 0x00FF00) | (b_diff_tmp & 0x0000FF)];
+#endif
+}
+
+
+enum BlendType
+{
+ BLEND_NONE = 0,
+ BLEND_NORMAL, //a normal indication to blend
+ BLEND_DOMINANT, //a strong indication to blend
+ //attention: BlendType must fit into the value range of 2 bit!!!
+};
+
+struct BlendResult
+{
+ BlendType
+ /**/ blend_f, blend_g,
+ /**/ blend_j, blend_k;
+};
+
+
+struct Kernel_4x4 //kernel for preprocessing step
+{
+ uint32_t
+ /**/ a, b, c, d,
+ /**/ e, f, g, h,
+ /**/ i, j, k, l,
+ /**/ m, n, o, p;
+};
+
+/*
+ input kernel area naming convention:
+ -----------------
+ | A | B | C | D |
+ ----|---|---|---|
+ | E | F | G | H | //evaluate the four corners between F, G, J, K
+ ----|---|---|---| //input pixel is at position F
+ | I | J | K | L |
+ ----|---|---|---|
+ | M | N | O | P |
+ -----------------
+ */
+template
+FORCE_INLINE //detect blend direction
+BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType"
+{
+ BlendResult result = {};
+
+ if ((ker.f == ker.g &&
+ ker.j == ker.k) ||
+ (ker.f == ker.j &&
+ ker.g == ker.k))
+ return result;
+
+ auto dist = [&](uint32_t pix1, uint32_t pix2) {
+ return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight);
+ };
+
+ const int weight = 4;
+ double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + weight * dist(ker.j, ker.g);
+ double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + weight * dist(ker.f, ker.k);
+
+ if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8
+ {
+ const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk;
+ if (ker.f != ker.g && ker.f != ker.j)
+ result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL;
+
+ if (ker.k != ker.j && ker.k != ker.g)
+ result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL;
+ }
+ else if (fk < jg)
+ {
+ const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg;
+ if (ker.j != ker.f && ker.j != ker.k)
+ result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL;
+
+ if (ker.g != ker.f && ker.g != ker.k)
+ result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL;
+ }
+ return result;
+}
+
+struct Kernel_3x3
+{
+ uint32_t
+ /**/ a, b, c,
+ /**/ d, e, f,
+ /**/ g, h, i;
+};
+
+#define DEF_GETTER(x) template uint32_t inline get_ ## x(const Kernel_3x3 &ker) { return ker.x; }
+//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token
+DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c)
+DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f)
+DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i)
+#undef DEF_GETTER
+
+#define DEF_GETTER(x, y) template <> inline uint32_t get_ ## x(const Kernel_3x3 &ker) { return ker.y; }
+DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a)
+DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b)
+DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c)
+#undef DEF_GETTER
+
+#define DEF_GETTER(x, y) template <> inline uint32_t get_ ## x(const Kernel_3x3 &ker) { return ker.y; }
+DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g)
+DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d)
+DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a)
+#undef DEF_GETTER
+
+#define DEF_GETTER(x, y) template <> inline uint32_t get_ ## x(const Kernel_3x3 &ker) { return ker.y; }
+DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i)
+DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h)
+DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g)
+#undef DEF_GETTER
+
+
+//compress four blend types into a single byte
+inline BlendType getTopL (unsigned char b) {
+ return static_cast(0x3 & b);
+}
+inline BlendType getTopR (unsigned char b) {
+ return static_cast(0x3 & (b >> 2));
+}
+inline BlendType getBottomR(unsigned char b) {
+ return static_cast(0x3 & (b >> 4));
+}
+inline BlendType getBottomL(unsigned char b) {
+ return static_cast(0x3 & (b >> 6));
+}
+
+inline void setTopL (unsigned char& b, BlendType bt) {
+ b |= bt;
+} //buffer is assumed to be initialized before preprocessing!
+inline void setTopR (unsigned char& b, BlendType bt) {
+ b |= (bt << 2);
+}
+inline void setBottomR(unsigned char& b, BlendType bt) {
+ b |= (bt << 4);
+}
+inline void setBottomL(unsigned char& b, BlendType bt) {
+ b |= (bt << 6);
+}
+
+inline bool blendingNeeded(unsigned char b) {
+ return b != 0;
+}
+
+template inline
+unsigned char rotateBlendInfo(unsigned char b) {
+ return b;
+}
+template <> inline unsigned char rotateBlendInfo(unsigned char b) {
+ return ((b << 2) | (b >> 6)) & 0xff;
+}
+template <> inline unsigned char rotateBlendInfo(unsigned char b) {
+ return ((b << 4) | (b >> 4)) & 0xff;
+}
+template <> inline unsigned char rotateBlendInfo(unsigned char b) {
+ return ((b << 6) | (b >> 2)) & 0xff;
+}
+
+
+#ifndef NDEBUG
+int debugPixelX = -1;
+int debugPixelY = 12;
+__declspec(thread) bool breakIntoDebugger = false;
+#endif
+
+
+/*
+ input kernel area naming convention:
+ -------------
+ | A | B | C |
+ ----|---|---|
+ | D | E | F | //input pixel is at position E
+ ----|---|---|
+ | G | H | I |
+ -------------
+ */
+template
+FORCE_INLINE //perf: quite worth it!
+void blendPixel(const Kernel_3x3& ker,
+ uint32_t* target, int trgWidth,
+ unsigned char blendInfo, //result of preprocessing all four corners of pixel "e"
+ const xbrz::ScalerCfg& cfg)
+{
+#define a get_a(ker)
+#define b get_b(ker)
+#define c get_c(ker)
+#define d get_d(ker)
+#define e get_e(ker)
+#define f get_f(ker)
+#define g get_g(ker)
+#define h get_h(ker)
+#define i get_i(ker)
+
+#ifndef NDEBUG
+ if (breakIntoDebugger)
+ __debugbreak(); //__asm int 3;
+#endif
+
+ const unsigned char blend = rotateBlendInfo(blendInfo);
+
+ if (getBottomR(blend) >= BLEND_NORMAL)
+ {
+ auto eq = [&](uint32_t pix1, uint32_t pix2) {
+ return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight) < cfg.equalColorTolerance;
+ };
+ auto dist = [&](uint32_t pix1, uint32_t pix2) {
+ return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight);
+ };
+
+ const bool doLineBlend = [&]() -> bool
+ {
+ if (getBottomR(blend) >= BLEND_DOMINANT)
+ return true;
+
+ //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes
+ if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90� corners
+ return false;
+ if (getBottomL(blend) != BLEND_NONE && !eq(e, c))
+ return false;
+
+ //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes")
+ if (!eq(e, i) && eq(g, h) && eq(h, i) && eq(i, f) && eq(f, c))
+ return false;
+
+ return true;
+ } ();
+
+ const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color
+
+ OutputMatrix out(target, trgWidth);
+
+ if (doLineBlend)
+ {
+ const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9
+ const double hc = dist(h, c); //
+
+ const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g;
+ const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c;
+
+ if (haveShallowLine)
+ {
+ if (haveSteepLine)
+ Scaler::blendLineSteepAndShallow(px, out);
+ else
+ Scaler::blendLineShallow(px, out);
+ }
+ else
+ {
+ if (haveSteepLine)
+ Scaler::blendLineSteep(px, out);
+ else
+ Scaler::blendLineDiagonal(px, out);
+ }
+ }
+ else
+ Scaler::blendCorner(px, out);
+ }
+
+#undef a
+#undef b
+#undef c
+#undef d
+#undef e
+#undef f
+#undef g
+#undef h
+#undef i
+}
+
+
+template //scaler policy: see "Scaler2x" reference implementation
+void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz::ScalerCfg& cfg, int yFirst, int yLast)
+{
+ yFirst = std::max(yFirst, 0);
+ yLast = std::min(yLast, srcHeight);
+ if (yFirst >= yLast || srcWidth <= 0)
+ return;
+
+ const int trgWidth = srcWidth * Scaler::scale;
+
+ //"use" space at the end of the image as temporary buffer for "on the fly preprocessing": we even could use larger area of
+ //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing
+ const int bufferSize = srcWidth;
+ unsigned char* preProcBuffer = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - bufferSize;
+ std::fill(preProcBuffer, preProcBuffer + bufferSize, '\0');
+ static_assert(BLEND_NONE == 0, "");
+
+ //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending
+ //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition!
+ if (yFirst > 0)
+ {
+ const int y = yFirst - 1;
+
+ const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0);
+ const uint32_t* s_0 = src + srcWidth * y;//center line
+ const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1);
+ const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1);
+
+ for (int x = 0; x < srcWidth; ++x)
+ {
+ const int x_m1 = std::max(x - 1, 0);
+ const int x_p1 = std::min(x + 1, srcWidth - 1);
+ const int x_p2 = std::min(x + 2, srcWidth - 1);
+
+ Kernel_4x4 ker = {}; //perf: initialization is negligible
+ ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible
+ ker.b = s_m1[x];
+ ker.c = s_m1[x_p1];
+ ker.d = s_m1[x_p2];
+
+ ker.e = s_0[x_m1];
+ ker.f = s_0[x];
+ ker.g = s_0[x_p1];
+ ker.h = s_0[x_p2];
+
+ ker.i = s_p1[x_m1];
+ ker.j = s_p1[x];
+ ker.k = s_p1[x_p1];
+ ker.l = s_p1[x_p2];
+
+ ker.m = s_p2[x_m1];
+ ker.n = s_p2[x];
+ ker.o = s_p2[x_p1];
+ ker.p = s_p2[x_p2];
+
+ const BlendResult res = preProcessCorners(ker, cfg);
+ /*
+ preprocessing blend result:
+ ---------
+ | F | G | //evalute corner between F, G, J, K
+ ----|---| //input pixel is at position F
+ | J | K |
+ ---------
+ */
+ setTopR(preProcBuffer[x], res.blend_j);
+
+ if (x + 1 < bufferSize)
+ setTopL(preProcBuffer[x + 1], res.blend_k);
+ }
+ }
+ //------------------------------------------------------------------------------------
+
+ for (int y = yFirst; y < yLast; ++y)
+ {
+ uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access
+
+ const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0);
+ const uint32_t* s_0 = src + srcWidth * y;//center line
+ const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1);
+ const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1);
+
+ unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position
+
+ for (int x = 0; x < srcWidth; ++x, out += Scaler::scale)
+ {
+#ifndef NDEBUG
+ breakIntoDebugger = debugPixelX == x && debugPixelY == y;
+#endif
+ //all those bounds checks have only insignificant impact on performance!
+ const int x_m1 = std::max(x - 1, 0); //perf: prefer array indexing to additional pointers!
+ const int x_p1 = std::min(x + 1, srcWidth - 1);
+ const int x_p2 = std::min(x + 2, srcWidth - 1);
+
+ Kernel_4x4 ker4 = {}; //perf: initialization is negligible
+
+ ker4.a = s_m1[x_m1]; //read sequentially from memory as far as possible
+ ker4.b = s_m1[x];
+ ker4.c = s_m1[x_p1];
+ ker4.d = s_m1[x_p2];
+
+ ker4.e = s_0[x_m1];
+ ker4.f = s_0[x];
+ ker4.g = s_0[x_p1];
+ ker4.h = s_0[x_p2];
+
+ ker4.i = s_p1[x_m1];
+ ker4.j = s_p1[x];
+ ker4.k = s_p1[x_p1];
+ ker4.l = s_p1[x_p2];
+
+ ker4.m = s_p2[x_m1];
+ ker4.n = s_p2[x];
+ ker4.o = s_p2[x_p1];
+ ker4.p = s_p2[x_p2];
+
+ //evaluate the four corners on bottom-right of current pixel
+ unsigned char blend_xy = 0; //for current (x, y) position
+ {
+ const BlendResult res = preProcessCorners(ker4, cfg);
+ /*
+ preprocessing blend result:
+ ---------
+ | F | G | //evalute corner between F, G, J, K
+ ----|---| //current input pixel is at position F
+ | J | K |
+ ---------
+ */
+ blend_xy = preProcBuffer[x];
+ setBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence!
+
+ setTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1)
+ preProcBuffer[x] = blend_xy1; //store on current buffer position for use on next row
+
+ blend_xy1 = 0;
+ setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column
+
+ if (x + 1 < bufferSize) //set 3rd known corner for (x + 1, y)
+ setBottomL(preProcBuffer[x + 1], res.blend_g);
+ }
+
+ //fill block of size scale * scale with the given color
+ fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale, Scaler::scale);
+ //place *after* preprocessing step, to not overwrite the results while processing the the last pixel!
+
+ //blend four corners of current pixel
+ if (blendingNeeded(blend_xy)) //good 5% perf-improvement
+ {
+ Kernel_3x3 ker3 = {}; //perf: initialization is negligible
+
+ ker3.a = ker4.a;
+ ker3.b = ker4.b;
+ ker3.c = ker4.c;
+
+ ker3.d = ker4.e;
+ ker3.e = ker4.f;
+ ker3.f = ker4.g;
+
+ ker3.g = ker4.i;
+ ker3.h = ker4.j;
+ ker3.i = ker4.k;
+
+ blendPixel(ker3, out, trgWidth, blend_xy, cfg);
+ blendPixel(ker3, out, trgWidth, blend_xy, cfg);
+ blendPixel(ker3, out, trgWidth, blend_xy, cfg);
+ blendPixel(ker3, out, trgWidth, blend_xy, cfg);
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------------
+
+template
+struct Scaler2x : public ColorGradient
+{
+ static const int scale = 2;
+
+ template //bring template function into scope for GCC
+ static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) {
+ ColorGradient::template alphaGrad(pixBack, pixFront);
+ }
+
+
+ template
+ static void blendLineShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+ }
+
+ template
+ static void blendLineSteep(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ }
+
+ template
+ static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<1, 0>(), col);
+ alphaGrad<1, 4>(out.template ref<0, 1>(), col);
+ alphaGrad<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR
+ }
+
+ template
+ static void blendLineDiagonal(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 2>(out.template ref<1, 1>(), col);
+ }
+
+ template
+ static void blendCorner(uint32_t col, OutputMatrix& out)
+ {
+ //model a round corner
+ alphaGrad<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366
+ }
+};
+
+
+template
+struct Scaler3x : public ColorGradient
+{
+ static const int scale = 3;
+
+ template //bring template function into scope for GCC
+ static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) {
+ ColorGradient::template alphaGrad(pixBack, pixFront);
+ }
+
+
+ template
+ static void blendLineShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+
+ alphaGrad<3, 4>(out.template ref(), col);
+ out.template ref() = col;
+ }
+
+ template
+ static void blendLineSteep(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ out.template ref<2, scale - 1>() = col;
+ }
+
+ template
+ static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<2, 0>(), col);
+ alphaGrad<1, 4>(out.template ref<0, 2>(), col);
+ alphaGrad<3, 4>(out.template ref<2, 1>(), col);
+ alphaGrad<3, 4>(out.template ref<1, 2>(), col);
+ out.template ref<2, 2>() = col;
+ }
+
+ template
+ static void blendLineDiagonal(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 8>(out.template ref<1, 2>(), col); //conflict with other rotations for this odd scale
+ alphaGrad<1, 8>(out.template ref<2, 1>(), col);
+ alphaGrad<7, 8>(out.template ref<2, 2>(), col); //
+ }
+
+ template
+ static void blendCorner(uint32_t col, OutputMatrix& out)
+ {
+ //model a round corner
+ alphaGrad<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598
+ //alphaGrad<7, 256>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale
+ //alphaGrad<7, 256>(out.template ref<1, 2>(), col); //0.02826017254
+ }
+};
+
+
+template
+struct Scaler4x : public ColorGradient
+{
+ static const int scale = 4;
+
+ template //bring template function into scope for GCC
+ static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) {
+ ColorGradient::template alphaGrad(pixBack, pixFront);
+ }
+
+
+ template
+ static void blendLineShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+
+ alphaGrad<3, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+
+ out.template ref() = col;
+ out.template ref() = col;
+ }
+
+ template
+ static void blendLineSteep(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col);
+
+ out.template ref<2, scale - 1>() = col;
+ out.template ref<3, scale - 1>() = col;
+ }
+
+ template
+ static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<3, 4>(out.template ref<3, 1>(), col);
+ alphaGrad<3, 4>(out.template ref<1, 3>(), col);
+ alphaGrad<1, 4>(out.template ref<3, 0>(), col);
+ alphaGrad<1, 4>(out.template ref<0, 3>(), col);
+
+ alphaGrad<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR
+
+ out.template ref<3, 3>() = col;
+ out.template ref<3, 2>() = col;
+ out.template ref<2, 3>() = col;
+ }
+
+ template
+ static void blendLineDiagonal(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 2>(out.template ref(), col);
+ alphaGrad<1, 2>(out.template ref(), col);
+ out.template ref() = col;
+ }
+
+ template
+ static void blendCorner(uint32_t col, OutputMatrix& out)
+ {
+ //model a round corner
+ alphaGrad<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563
+ alphaGrad< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501
+ alphaGrad< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501
+ }
+};
+
+
+template
+struct Scaler5x : public ColorGradient
+{
+ static const int scale = 5;
+
+ template //bring template function into scope for GCC
+ static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) {
+ ColorGradient::template alphaGrad(pixBack, pixFront);
+ }
+
+
+ template
+ static void blendLineShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+
+ alphaGrad<3, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+
+ out.template ref() = col;
+ out.template ref() = col;
+ out.template ref() = col;
+ out.template ref() = col;
+ }
+
+ template
+ static void blendLineSteep(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+ alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col);
+
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col);
+
+ out.template ref<2, scale - 1>() = col;
+ out.template ref<3, scale - 1>() = col;
+ out.template ref<4, scale - 1>() = col;
+ out.template ref<4, scale - 2>() = col;
+ }
+
+ template
+ static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+
+ alphaGrad<2, 3>(out.template ref<3, 3>(), col);
+
+ out.template ref<2, scale - 1>() = col;
+ out.template ref<3, scale - 1>() = col;
+ out.template ref<4, scale - 1>() = col;
+
+ out.template ref() = col;
+ out.template ref() = col;
+ }
+
+ template
+ static void blendLineDiagonal(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 8>(out.template ref(), col);//conflict with other rotations for this odd scale
+ alphaGrad<1, 8>(out.template ref(), col);
+ alphaGrad<1, 8>(out.template ref(), col); //
+
+ alphaGrad<7, 8>(out.template ref<4, 3>(), col);
+ alphaGrad<7, 8>(out.template ref<3, 4>(), col);
+
+ out.template ref<4, 4>() = col;
+ }
+
+ template
+ static void blendCorner(uint32_t col, OutputMatrix& out)
+ {
+ //model a round corner
+ alphaGrad<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088
+ alphaGrad<23, 100>(out.template ref<4, 3>(), col); //0.2306749731
+ alphaGrad<23, 100>(out.template ref<3, 4>(), col); //0.2306749731
+ //alphaGrad<1, 64>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale
+ //alphaGrad<1, 64>(out.template ref<2, 4>(), col); //0.01676812367
+ }
+};
+
+
+template
+struct Scaler6x : public ColorGradient
+{
+ static const int scale = 6;
+
+ template //bring template function into scope for GCC
+ static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) {
+ ColorGradient::template alphaGrad(pixBack, pixFront);
+ }
+
+
+ template
+ static void blendLineShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+
+ alphaGrad<3, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+
+ out.template ref() = col;
+ out.template ref() = col;
+ out.template ref() = col;
+ out.template ref() = col;
+
+ out.template ref() = col;
+ out.template ref() = col;
+ }
+
+ template
+ static void blendLineSteep(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+ alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col);
+
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col);
+ alphaGrad<3, 4>(out.template ref<5, scale - 3>(), col);
+
+ out.template ref<2, scale - 1>() = col;
+ out.template ref<3, scale - 1>() = col;
+ out.template ref<4, scale - 1>() = col;
+ out.template ref<5, scale - 1>() = col;
+
+ out.template ref<4, scale - 2>() = col;
+ out.template ref<5, scale - 2>() = col;
+ }
+
+ template
+ static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out)
+ {
+ alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col);
+ alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col);
+ alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col);
+ alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col);
+
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<1, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+ alphaGrad<3, 4>(out.template ref(), col);
+
+ out.template ref<2, scale - 1>() = col;
+ out.template ref<3, scale - 1>() = col;
+ out.template ref<4, scale - 1>() = col;
+ out.template ref<5, scale - 1>() = col;
+
+ out.template ref<4, scale - 2>() = col;
+ out.template ref<5, scale - 2>() = col;
+
+ out.template ref