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 + + + +
+ +
Loading...
+
+ + {{{ 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() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaGrad<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaGrad<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaGrad< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaGrad< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + +//------------------------------------------------------------------------------------ + +struct ColorDistanceRGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + return distYCbCrBuffered(pix1, pix2); + + //if (pix1 == pix2) //about 4% perf boost + // return 0; + //return distYCbCr(pix1, pix2, luminanceWeight); + } +}; + +struct ColorDistanceARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0; + const double a2 = getAlpha(pix2) / 255.0; + /* + Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] + + 1. if a1 = a2, distance should be: a1 * distYCbCr() + 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 + 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() + */ + + //return std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2); + //=> following code is 15% faster: + const double d = distYCbCrBuffered(pix1, pix2); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + + //alternative? return std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2))); + } +}; + + +struct ColorGradientRGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientRGB(pixFront, pixBack); + } +}; + +struct ColorGradientARGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientARGB(pixFront, pixBack); + } +}; +} + + +void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + switch (colFmt) + { + case ColorFormat::ARGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case ColorFormat::RGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + } + assert(false); +} + + +bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance) +{ + switch (colFmt) + { + case ColorFormat::ARGB: + return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + + case ColorFormat::RGB: + return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + } + assert(false); + return false; +} + + +void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + SliceType::TARGET, 0, trgHeight, [](uint32_t pix) { + return pix; + }); +} diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.h b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.h new file mode 100644 index 0000000000..b83df4e9c4 --- /dev/null +++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz.h @@ -0,0 +1,73 @@ +// **************************************************************************** +// * 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. * +// **************************************************************************** + +#ifndef XBRZ_HEADER_3847894708239054 +#define XBRZ_HEADER_3847894708239054 + +#include //size_t +#include //uint32_t +#include + +#include "xbrz_config.h" + +namespace xbrz +{ +/* + ------------------------------------------------------------------------- + | xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | + ------------------------------------------------------------------------- + using a modified approach of xBR: + http://board.byuu.org/viewtopic.php?f=10&t=2248 + - new rule set preserving small image features + - highly optimized for performance + - support alpha channel + - support multithreading + - support 64-bit architectures + - support processing image slices + - support scaling up to 6xBRZ + */ + +enum class ColorFormat //from high bits -> low bits, 8 bit per channel +{ + RGB, //8 bit for each red, green, blue, upper 8 bits unused + ARGB, //including alpha channel, BGRA byte order on little-endian machines +}; + +/* + -> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only + -> support for source/target pitch in bytes! + -> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + CAVEAT: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + in the target image data if you are using multiple threads for processing each enlarged slice! + + THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process at least 8-16 rows + */ +void scale(size_t factor, //valid range: 2 - 6 + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + ColorFormat colFmt, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight); + + +//parameter tuning +bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance); +} + +#endif diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_config.h b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_config.h new file mode 100644 index 0000000000..a38ea013d6 --- /dev/null +++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_config.h @@ -0,0 +1,33 @@ +// **************************************************************************** +// * 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. * +// **************************************************************************** + +#ifndef XBRZ_CONFIG_HEADER_284578425345 +#define XBRZ_CONFIG_HEADER_284578425345 + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz +{ +struct ScalerCfg +{ + double luminanceWeight = 1; + double equalColorTolerance = 30; + double dominantDirectionThreshold = 3.6; + double steepDirectionThreshold = 2.2; + double newTestAttribute = 0;//unused; test new parameters +}; +} + +#endif \ No newline at end of file diff --git a/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_tools.h b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_tools.h new file mode 100644 index 0000000000..c5819cea2a --- /dev/null +++ b/contrib/games/hydracastlelabyrinth/src/xBRZ/xbrz_tools.h @@ -0,0 +1,177 @@ +// **************************************************************************** +// * 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. * +// **************************************************************************** + +#ifndef XBRZ_TOOLS_H_825480175091875 +#define XBRZ_TOOLS_H_825480175091875 + +#include +#include +#include + + +namespace xbrz +{ +template inline +unsigned char getByte(uint32_t val) { + return static_cast((val >> (8 * N)) & 0xff); +} + +inline unsigned char getAlpha(uint32_t pix) { + return getByte<3>(pix); +} +inline unsigned char getRed (uint32_t pix) { + return getByte<2>(pix); +} +inline unsigned char getGreen(uint32_t pix) { + return getByte<1>(pix); +} +inline unsigned char getBlue (uint32_t pix) { + return getByte<0>(pix); +} + +inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { + return (a << 24) | (r << 16) | (g << 8) | b; +} +inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { + return (r << 16) | (g << 8) | b; +} + +inline uint32_t rgb555to888(uint16_t pix) { + return ((pix & 0x7C00) << 9) | ((pix & 0x03E0) << 6) | ((pix & 0x001F) << 3); +} +inline uint32_t rgb565to888(uint16_t pix) { + return ((pix & 0xF800) << 8) | ((pix & 0x07E0) << 5) | ((pix & 0x001F) << 3); +} + +inline uint16_t rgb888to555(uint32_t pix) { + return static_cast(((pix & 0xF80000) >> 9) | ((pix & 0x00F800) >> 6) | ((pix & 0x0000F8) >> 3)); +} +inline uint16_t rgb888to565(uint32_t pix) { + return static_cast(((pix & 0xF80000) >> 8) | ((pix & 0x00FC00) >> 5) | ((pix & 0x0000F8) >> 3)); +} + + +template inline +Pix* byteAdvance(Pix* ptr, int bytes) +{ + using PixNonConst = typename std::remove_cv::type; + using PixByte = typename std::conditional::value, char, const char>::type; + + static_assert(std::is_integral::value, "Pix* is expected to be cast-able to char*"); + + return reinterpret_cast(reinterpret_cast(ptr) + bytes); +} + + +//fill block with the given color +template inline +void fillBlock(Pix* trg, int pitch, Pix col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + + +enum class SliceType +{ + SOURCE, + TARGET, +}; + +template +void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(PixSrc)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + switch (st) + { + case SliceType::SOURCE: + //nearest-neighbor (going over source image - fast for upscaling, since source is read only once + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrg_first = ( y * trgHeight + srcHeight - 1) / srcHeight;//=ceil(y * trgHeight / srcHeight) + const int yTrg_last = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight;//=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrg_last - yTrg_first; + + if (blockHeight > 0) + { + const PixSrc* srcLine = byteAdvance(src, y * srcPitch); + /**/ PixTrg* trgLine = byteAdvance(trg, yTrg_first * trgPitch); + int xTrg_first = 0; + + for (int x = 0; x < srcWidth; ++x) + { + const int xTrg_last = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrg_last - xTrg_first; + if (blockWidth > 0) + { + xTrg_first = xTrg_last; + + const auto trgPix = pixCvrt(srcLine[x]); + fillBlock(trgLine, trgPitch, trgPix, blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } + break; + + case SliceType::TARGET: + //nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + PixTrg* trgLine = byteAdvance(trg, y * trgPitch); + const int ySrc = srcHeight * y / trgHeight; + const PixSrc* srcLine = byteAdvance(src, ySrc * srcPitch); + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = pixCvrt(srcLine[xSrc]); + } + } + break; + } +} +} + +#endif //XBRZ_TOOLS_H_825480175091875