From: Miriam Ruiz Date: Wed, 3 Dec 2008 10:57:32 +0000 (+0100) Subject: Imported kball_final_src_16dec2004.tar.gz X-Git-Url: https://repo.or.cz/w/kball.git/commitdiff_plain/287fe02ec7407cbb1a12e33ccba52ab577b92a46 Imported kball_final_src_16dec2004.tar.gz Downloaded from http://kball.sourceforge.net/ Author: Bruno Diaz Sphere mapping code: Martijn "amarillion" van Iersel --- 287fe02ec7407cbb1a12e33ccba52ab577b92a46 diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..7a70f38 --- /dev/null +++ b/CHANGES @@ -0,0 +1,31 @@ +19:46 Viernes, 3 de Diciembre de 2004 +07:46 p.m. 3/12/04 (d/m/y) +-- New speech sounds for game over and won screen +-- Fixed some engine bugs +-- New levels +-- Removed the "demo" message +-- Entering final stage of release +-- F11 key toggles show or not fps + +-------------------------------------------------------------------------- + +04:50 p.m. Viernes, 20 de Agosto de 2004 +04:51 p.m. 20/08/04 +-- Lots of new stuff, like MUSIC, LEVELS, etc +-- 15 original music tunes +-- 30 original levels in 3 campaigns (easy,medium,hard) +-- Code bugfixes +-- Recompiled for Windows using GCC 3.2.3 and Allegro 4.1.15 WIP +-- WIP second release + +-------------------------------------------------------------------------- + +11:13 p.m. Martes, 27 de Julio de 2004 +11:13 p.m. 27/07/04 +-- Lots of new stuff, fixed stuff, etc +-- Sound! +-- Compiled with GCC 3.2.3, and Allegro 4.1.14 WIP for Win32 (now it needs alleg41.dll) + +-------------------------------------------------------------------------- + +WIP first internal version -- 10/December/2003 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..44cc8d5 --- /dev/null +++ b/INSTALL @@ -0,0 +1,29 @@ +KBall +----- + +This are instructions to compile the program from source code. + +NOTE: for compiling the source code, you need a C++ compiler such as GNU GCC, DUMB, and the Allegro Game Library. + +Get and proper install the following software: + +* Allegro game library - Version 4.1.15 or better! - http://alleg.sf.net/ +* DUMB music library - Version 0.9.2 or better! - http://dumb.sf.net/ +* GNU GCC - http://gcc.gnu.org/ + +Compilation instructions : + +-- DJGPP users (DOS version of the game): + + fix.bat djgpp + make + +-- Mingw users (Windows version of the game): + + fix.bat mingw + make + +-- Linux users: + + ./fix.sh linux + make diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b1d67e --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +This program is distributed under The MIT License + +Copyright (c) 2003, 2004, Kronoman +In loving memory of my father +Made in Argentina + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..33a7150 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +KBall + +A game of skill and reflexes + +Developed in Argentina by Kronoman +"In loving memory of my father". + +Copyright (c) 2003, 2004, Kronoman + +Released under The MIT license + +Home page (get updates, new levels, etc here) + +http://kball.sourceforge.net/ diff --git a/TODO b/TODO new file mode 100644 index 0000000..3a42ff4 --- /dev/null +++ b/TODO @@ -0,0 +1,38 @@ +TODO + +- Check speed of algorithms, use gprof for that + Read http://linuxgazette.net/100/vinayak.html for more info! + +- When you lose on campaign mode, you must be able to CONTINUE (10...9...8...etc) + +- Add extra ball prize (++lives) + +- Add training, easy, medium, hard levels + (change speed/friction of ball, in training score must be always zero, + and the player will have a 'hand brake') + +- Add levels + +- Campaign mode [ ALMOST DONE - LACKS HI SCORE TABLE ] + +- Add tile palette to map editor, for easy selection + +- More tiles, like arrows, walls, etc + +- Bounce of ball against corner walls is wrong, must be improved! + +- Add alpha transparency to tiles (so we can make glass, etc) + +- Rotation of the ball is not accurate (sometimes it rotates backwards) + This problem is called 'gimbal lock' <-- SOLUCION AL PROBLEMA: GOOGLE! + +- Add a console to modify/see vars on the fly + +- Installer for Windows, (for Linux too?, check Loki installer...) + +- It compiles at random under Linux, sometimes it works, sometimes not. (WTF??) + +- Add more gameplay (power ups and other arcade-like stuff) + +- Test Map button for Map Editor + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..48f1035 --- /dev/null +++ b/VERSION @@ -0,0 +1,3 @@ +Final release +2.53 am - 16 Diciembre 2004 +2.53 am - 16/Dec/2004 diff --git a/bin/backgr.dat b/bin/backgr.dat new file mode 100644 index 0000000..f86d8b4 Binary files /dev/null and b/bin/backgr.dat differ diff --git a/bin/gui.dat b/bin/gui.dat new file mode 100644 index 0000000..9bad8a8 Binary files /dev/null and b/bin/gui.dat differ diff --git a/bin/intro.dat b/bin/intro.dat new file mode 100644 index 0000000..44bbbcd Binary files /dev/null and b/bin/intro.dat differ diff --git a/bin/levels/1.map b/bin/levels/1.map new file mode 100644 index 0000000..bbc847e Binary files /dev/null and b/bin/levels/1.map differ diff --git a/bin/levels/10.map b/bin/levels/10.map new file mode 100644 index 0000000..df4c76f Binary files /dev/null and b/bin/levels/10.map differ diff --git a/bin/levels/11.map b/bin/levels/11.map new file mode 100644 index 0000000..d653e58 Binary files /dev/null and b/bin/levels/11.map differ diff --git a/bin/levels/12.map b/bin/levels/12.map new file mode 100644 index 0000000..c5e9b0c Binary files /dev/null and b/bin/levels/12.map differ diff --git a/bin/levels/14.map b/bin/levels/14.map new file mode 100644 index 0000000..2e70721 Binary files /dev/null and b/bin/levels/14.map differ diff --git a/bin/levels/15.map b/bin/levels/15.map new file mode 100644 index 0000000..aa9ed29 Binary files /dev/null and b/bin/levels/15.map differ diff --git a/bin/levels/2.map b/bin/levels/2.map new file mode 100644 index 0000000..18d6c7c Binary files /dev/null and b/bin/levels/2.map differ diff --git a/bin/levels/3.map b/bin/levels/3.map new file mode 100644 index 0000000..0d03b3a Binary files /dev/null and b/bin/levels/3.map differ diff --git a/bin/levels/4.map b/bin/levels/4.map new file mode 100644 index 0000000..331e1de Binary files /dev/null and b/bin/levels/4.map differ diff --git a/bin/levels/5.map b/bin/levels/5.map new file mode 100644 index 0000000..90f5360 Binary files /dev/null and b/bin/levels/5.map differ diff --git a/bin/levels/6.map b/bin/levels/6.map new file mode 100644 index 0000000..690ec24 Binary files /dev/null and b/bin/levels/6.map differ diff --git a/bin/levels/7.map b/bin/levels/7.map new file mode 100644 index 0000000..87e9d8d Binary files /dev/null and b/bin/levels/7.map differ diff --git a/bin/levels/8.map b/bin/levels/8.map new file mode 100644 index 0000000..84f80b6 Binary files /dev/null and b/bin/levels/8.map differ diff --git a/bin/levels/9.map b/bin/levels/9.map new file mode 100644 index 0000000..17b1aa2 Binary files /dev/null and b/bin/levels/9.map differ diff --git a/bin/levels/deluxe.fmp b/bin/levels/deluxe.fmp new file mode 100644 index 0000000..9d06021 Binary files /dev/null and b/bin/levels/deluxe.fmp differ diff --git a/bin/levels/easy.fmp b/bin/levels/easy.fmp new file mode 100644 index 0000000..036d435 Binary files /dev/null and b/bin/levels/easy.fmp differ diff --git a/bin/levels/hard.fmp b/bin/levels/hard.fmp new file mode 100644 index 0000000..05d0c0d Binary files /dev/null and b/bin/levels/hard.fmp differ diff --git a/bin/levels/medium.fmp b/bin/levels/medium.fmp new file mode 100644 index 0000000..3d98029 Binary files /dev/null and b/bin/levels/medium.fmp differ diff --git a/bin/levels/remove.me b/bin/levels/remove.me new file mode 100644 index 0000000..bbdbf99 --- /dev/null +++ b/bin/levels/remove.me @@ -0,0 +1 @@ +Remove me diff --git a/bin/levels/trainer.fmp b/bin/levels/trainer.fmp new file mode 100644 index 0000000..2c03225 Binary files /dev/null and b/bin/levels/trainer.fmp differ diff --git a/bin/music_l.dat b/bin/music_l.dat new file mode 100644 index 0000000..edd303e Binary files /dev/null and b/bin/music_l.dat differ diff --git a/bin/remove.me b/bin/remove.me new file mode 100644 index 0000000..99e8d22 --- /dev/null +++ b/bin/remove.me @@ -0,0 +1 @@ +This file can be safely removed \ No newline at end of file diff --git a/bin/sprites.dat b/bin/sprites.dat new file mode 100644 index 0000000..f97bb94 Binary files /dev/null and b/bin/sprites.dat differ diff --git a/bin/tileset.dat b/bin/tileset.dat new file mode 100644 index 0000000..bb13533 Binary files /dev/null and b/bin/tileset.dat differ diff --git a/doc/design.txt b/doc/design.txt new file mode 100644 index 0000000..57d42d9 --- /dev/null +++ b/doc/design.txt @@ -0,0 +1,103 @@ +KBall +----- + +Some design notes about the game + +By Kronoman +Copyright (c) 2004, Kronoman +In loving memory of my father + +Document started 14/01/2004 (D/M/Y) + +Levels +------ + +The levels are stored inside a directory named "levels" under the directory where the game's executable is. +There is two kind of levels, single levels, and level campaigns. + +Single level +------------ + +Is a single level, named [filename].map ; must be done using the built-in level editor. + +Level campaign +-------------- + +Is a Allegro grabber datafile, named [filename].fmp, that contains many single levels as objects ; must be named in numbers, like "1_MAP", "2_MAP", etc. +Each level name is a number+"_MAP" ([n]_MAP), like '1_MAP', '2_MAP', etc. +The program will load them in sequence starting at '1_MAP', and will end when it don't found the next level (thus, the campaign is over, and you win). + +Datafiles +--------- + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[sprites.dat] + +Has all data about standard sprites of the game, sounds, fonts and miscellaneous images that appear during the game. + +Must have the following objects: +BALL -- ball sprite, is a texture of 64x64 that will be mapped to a ball +BALL_MASK_SHADOW -- ball shadow sprite, to be blitted with 50% blend over mapped sphere to give shadowed look +GAME_SCORE_FONT -- this font is used to write the score on screen during game, and also, the prizes remaining; MUST contain letters, is also used to display messages on screen. +GAME_TIME_FONT -- this font is used to write the time on screen during game +GAME_MESSAGES_FONT -- this font is used to write special messages on screen like 'PAUSE', 'WON THE LEVEL', 'LOST A BALL', etc. + +GAME_OVER_BMP -- bitmap to show when game over. +GAME_OVER_WAV -- wav to play when game over +WON_BMP -- bitmap to show when player won the campaign game. +WON_WAV -- wav to play when player won campaign game. + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[gui.dat] + +Has all the data about GUI (fonts, menu backgrounds, music, etc) + +MENU_FONT_BIG -- font big for menus +MENU_FONT_MEDIUM -- font medium for menus +MENU_FONT_SMALL -- font small for menus +MENU_BMP_BACKGROUND -- bitmap for menu background + +ABOUT_BACKGROUND -- bitmap for about background. +ABOUT_TEXT -- text object with text to display on about dialog. +ABOUT_BACK_SOUND -- sound that will loop in background of about dialog. + +MENU_MUSIC_XM -- music for menu, must be a XM type object, a XM music file. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[tileset.dat] + +Has the tile set used in the map (walls, floors, etc) +TILE_SET_CFG_TXT -- This is a text object, with the configuration of each tile of the tilset. This object is *mandatory* +Also, you must include all objects referenced in TILE_SET_CFG_TXT (bitmaps, sounds, etc) + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[backgr.dat] + +Has all the backgrounds for game levels. +Can be up to 256 backgrounds, 0..255. +The name of each bitmap object must be: +B[0...255]_BMP --> in example: B0_BMP, B1_BMP...B255_BMP +Each bitmap must be tileable, so it looks OK in the background. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[music_l.dat] + +Has all the music for the game levels. +Can be up to 256 music tones, 0..255. +The name for each music object must be: +M[0..255]_XM --> in example : M0_XM, M1_XM, M2_XM +The music format _must_ be FastTracker's XM +Each object must be a binary object of type "XM " + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[intro.dat] + +This is a very special file, has the introduction of the game +Must be exactly like this (all is hard coded, sorry) +1_BMP Pictures of Kronoman, must be of 320xXXX pixels +2_BMP +3_BMP +4_BMP +5_BMP +LOGO_KRONOMAN_BMP Logo of Kronoman +KRONOMAN_WAV Kronoman sound +INTRO_TXT text for the intro with pictures + diff --git a/doc/doing_campaign.txt b/doc/doing_campaign.txt new file mode 100644 index 0000000..17a3e20 --- /dev/null +++ b/doc/doing_campaign.txt @@ -0,0 +1,18 @@ +To do a campaign from a bunch of levels, you need to do: + +dat -c1 -s1 -t MAP -a campaign.fmp *.map + +dat is the utility that came with Allegro. + +NOTE: the maps MUST be named: +1.map +2.map +3.map +etc + +so in the datafile, they will be named: + +1_MAP +2_MAP +3_MAP +etc diff --git a/doc/manual/kball_manual_en.pdf b/doc/manual/kball_manual_en.pdf new file mode 100644 index 0000000..4a0e2e8 Binary files /dev/null and b/doc/manual/kball_manual_en.pdf differ diff --git a/doc/manual/kball_manual_es.pdf b/doc/manual/kball_manual_es.pdf new file mode 100644 index 0000000..b3c01ba Binary files /dev/null and b/doc/manual/kball_manual_es.pdf differ diff --git a/fix.bat b/fix.bat new file mode 100644 index 0000000..98628e2 --- /dev/null +++ b/fix.bat @@ -0,0 +1,92 @@ +@echo off + +echo KBall Source Code +echo. +echo KBall's website: http://kball.sf.net/ + + +if [%1] == [linux] goto linux +if [%1] == [djgpp] goto djgpp +if [%1] == [mingw32] goto mingw32 +if [%1] == [test] goto test +goto help + + +:test +REM Does a test to see if the platform can compile +REM You first need to configure the platform +if exist target.os goto targetok + echo Before testing, you first must configure your platform. +goto help + +:targetok +echo Testing, please wait... +make test +if not errorlevel 0 goto testfail +if not exist test.run goto testfail + + echo. + echo * SUCESS * + echo Congratulations, the test compiled! + echo. + echo NOTE: You need Allegro 4.1.15 or better to compile KBall + +goto testdone + +:testfail + echo. + echo * ERROR * + echo. + echo The compilation returned a error! + echo Check that: + echo (*) You have all compiler tools installed (gcc,make,etc...) + echo (*) You have Allegro 4.1.15 or better properly installed (http://alleg.sf.net/) + echo (*) You have DUMB 0.9.2 or better properly installed (http://dumb.sf.net/) + echo. + +:testdone + echo Cleaning the test... + make cleantest + +goto done + +:djgpp +echo Configuring for DOS/djgpp... +echo # Warning! This file will be overwritten by configuration routines! > target.os +echo TARGET=DJGPP>> target.os +goto done + + +:mingw32 +echo Configuring for Windows/Mingw32... +echo # Warning! This file will be overwritten by configuration routines! > target.os +echo TARGET=MINGW32>> target.os +goto done + + +:linux +echo Configuring for Linux/GCC... +echo # Warning! This file will be overwritten by configuration routines! > target.os +echo TARGET=LINUX>> target.os +goto done + + +:help +echo Usage: fix platform +echo. +echo Where platform is one of: djgpp, mingw32 or linux. +echo. +echo NOTICE: +echo You can also call: fix test +echo to check if your system can compile this programs. +echo. +echo To compile KBall you need Allegro 4.1.15 or better, and DUMB 0.9.2 or better +echo http://alleg.sf.net/ +echo http://dumb.sf.net/ +echo. +goto end + +:done +echo Done! + +:end diff --git a/fix.sh b/fix.sh new file mode 100755 index 0000000..26b6f50 --- /dev/null +++ b/fix.sh @@ -0,0 +1,78 @@ +#!/bin/sh +echo "KBall Source Code" +echo +echo "KBall's website: http://kball.sf.net/" + +# REMEMBER TO ALTER THIS TEST TO SUIT YOUR NEEDS!!! +proc_test() +{ + # You first need to configure the platform + if [ ! -e target.os ]; then + echo "Before test, you first must configure your platform." + proc_help; + else + echo Testing, please wait... + make test + + if [ $? -eq 0 -a -e test.run ]; then + echo + echo "* SUCESS *" + echo "Congratulations, the test compiled!" + echo + echo "NOTE: You need Allegro 4.1.15 or better to compile KBall" + else + echo + echo "* ERROR *" + echo + echo "The compilation returned a error or can't be runned!" + echo "Check that:" + echo "(*) You have all compiler tools installed (gcc,make,etc...)" + echo "(*) You have Allegro 4.1.15 or better properly installed (http://alleg.sf.net/)" + echo "(*) You have DUMB 0.9.2 or better properly installed (http://dumb.sf.net/)" + echo + fi + + echo "Cleaning the test..." + make cleantest + fi +} + +proc_help() +{ + echo "Usage: fix platform" + echo + echo "Where platform is one of: djgpp, mingw32 or linux. " + echo + echo "NOTICE:" + echo "You can also call: fix test" + echo "to check if your system can compile this programs." + echo + echo "To compile KBall you need Allegro 4.1.15 or better, and DUMB 0.9.2 or better" + echo "http://alleg.sf.net/" + echo "http://dumb.sf.net/" + echo +} + +proc_fix() +{ + echo "Configuring for $1..." + + if [ "$2" != "none" ]; then + echo "# Warning! This file will be overwritten by configuration routines!" > target.os + echo "TARGET=$2" >> target.os + fi +} + + +# prepare for the given platform. + +case "$1" in + "djgpp" ) proc_fix "DOS (djgpp)" "DJGPP";; + "mingw32" ) proc_fix "Windows (Mingw32)" "MINGW32";; + "linux" ) proc_fix "Linux (GCC)" "LINUX";; + "test" ) proc_test;; + "help" ) proc_help;; + * ) proc_help;; +esac + +echo "Done!" diff --git a/include/backgbmp.h b/include/backgbmp.h new file mode 100644 index 0000000..ecdcb81 --- /dev/null +++ b/include/backgbmp.h @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------ +// backgbmp.h +// System for handling level backgrounds +// +// +// NOTE ; SYSTEM VERY SLOW, IMPROVE IT WITH A CACHE, ETC! +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#ifndef BACKGBMP_H +#define BACKGBMP_H + +#include + + +// background +class CBackground +{ + public: + CBackground(); + ~CBackground(); + + BITMAP *get_background(char *filename, int index); // gets the background from file, or a default background otherwise + void free_memory(); // releases memory and returns to default background + + private: + BITMAP *bmp_in_ram; + DATAFILE *data_loaded; + BITMAP *bmp_default; + + // cache stuff + char file_loaded[1024]; // wich file we have in memory + int index_loaded; + + +}; + +#endif diff --git a/include/cball.h b/include/cball.h new file mode 100644 index 0000000..6ef54e9 --- /dev/null +++ b/include/cball.h @@ -0,0 +1,93 @@ +// ------------------------------------------------------------------ +// cball.h +// ------------------------------------------------------------------ +// This class is the ball of the player +// By Kronoman - In loving memory of my father +// Copyright (c) 2003,2004, Kronoman +// ------------------------------------------------------------------ + +#ifndef CBALL_H +#define CBALL_H + +#include + +#include "tmap.h" // I need to know the map where I move +#include "control.h" // the controller class +#include "partmang.h" // particle manager +#include "particle.h" +#include "sound.h" + +// values that "int CBall::update_logic()" may return + // all OK + #define CBALL_IS_FINE 0 + // I'm so dead :'( + #define CBALL_IS_DEAD 1 + // I'm over the level's exit ;^D + #define CBALL_EXIT_LEVEL 2 + + +// lives by default for SINGLE level +#define BALL_LIVES 3 +// lives by default for campaign mode +#define BALL_LIVES_CAMPAIGN 7 + +// acceleration +#define BALL_SPEED_X 0.65 +#define BALL_SPEED_Y 0.65 +#define BALL_SPEED_Z 0.35 + +// radius of the ball (diameter will be this size * 2 +#define BALL_RADIUS 25 + +// max speed in any axis +#define BALL_MSPEED 10 + +// friction +#define N_BALL_SPEED_X 0.1 +#define N_BALL_SPEED_Y 0.1 +// gravity +#define N_BALL_SPEED_Z 0.15 + +// max Z value of the ball (jump limit) +#define BALL_MAX_Z 15.0 + +// min value of Z, below here, is DEAD +#define BALL_MIN_Z -15.0 + +class CBall +{ + public: + CBall(); + ~CBall(); + + void draw(BITMAP *bmp, int sx, int sy); // draw the ball + + int update_logic(); // updates the logic, returns status of the ball + + // -- Data members (all public) -- + // this vars are public to let easy access to them, (yes, I know that this broke the encapsulation) + + float x, y, z, dx, dy, dz; // pos x,y,z ; speed dx, dy, dz + + BITMAP *spr; // sprite (texture of sphere, will be the size of texture too) + BITMAP *spr_shadow; // shadow mask for the sphere texture + + BITMAP *spr_cache; // cache for sphere rotaded sprite + + float anglex, angley; // angle of rotation of the sprite (to animate it) + + int lives; // lives left + long int score; // score + + CTMap *ctmap; // pointer to the map where the ball moves, the ball NEEDS to know the map + + CParticleManager *particle_manager; // pointer to game's particle manager, the ball NEEDS to add particles when get pickups, etc + + CController control; // controller for gameplay + + CSoundWrapper soundw; // sound system +}; + +#endif + + diff --git a/include/control.h b/include/control.h new file mode 100644 index 0000000..13ad6d7 --- /dev/null +++ b/include/control.h @@ -0,0 +1,121 @@ +// ----------------------------------------------------------------------- +// Controller wrapper +// control.cpp +// ----------------------------------------------------------------------- +// This class handles a generic controller. +// Simulates a 8 way gamepad with 6 buttons using keyboard,joystick,mouse +// By Kronoman - In loving memory of my father +// Copyright (c) 2003 - Released under the MIT license +// 08-NOV-2003 +// +// ** NOTE ** +// Modified 04-MAR-2004 to add customize control interface. +// This class is arcane, I have a better implementation. +// +// We will use this in this game, in future games, we will use the new implementation. +// +// **** NOTICE **** +// THIS PARTICULAR GAME, KBALL DOES NOT NEED BUTTONS, SO IS DISABLED IN INTERACTIVE CONFIGURATION! +// ** NOTICE ** I CHANGED SOME DATA TO PUBLIC DUE TO KBALL REQUIREMENTS! +// ----------------------------------------------------------------------- + +#ifndef KCONTROL_H +#define KCONTROL_H + +#include + + +// This are the return values of the controller +// they are returned as a bit mask +// So, all joystick input is DIGITAL +#define KC_NONE 0 +#define KC_UP 1 +#define KC_DOWN 2 +#define KC_LEFT 4 +#define KC_RIGHT 8 + +// buttons (there is room left, for adding more axis in future) +#define KC_BTN1 256 +#define KC_BTN2 512 +#define KC_BTN3 1024 +#define KC_BTN4 2048 +#define KC_BTN5 4096 +#define KC_BTN6 8192 + +class CController +{ + public: + CController(); + ~CController(); + + // Set functions + // ------------- + + // keyboard + void set_default_keyboard(); // we have a 'default' configuration for keyboard + void set_keyboard_par(int value, int index); // this sets one value for key_val[] array + void set_use_keyboard(bool use); // set if you want to use keyboard input or not + void interactive_configuration_keyboard(FONT *font, int color); // to interactive configure keyboard + + // mouse + void set_mouse_sens(int s); // set mouse sensitiviness + void set_use_mouse(bool use); // use the mouse? + void interactive_configuration_mouse(FONT *font, int color); // interactive configure mouse + + // joystick + void set_joystick_number(int n); // wich joystick you want to input? + void set_use_joystick(bool use); // use the joystick? + void interactive_configuration_joystick(FONT *font, int color); // interactive configure joystick + + // Get functions + // ------------- + + // this is the one that does the actual input from user + int do_input_poll(); + + int get_keyboard_par(int value, int index); // get keyboard key, -1 on error (bad index passed) + bool get_use_keyboard() { return this->use_keyboard; } + + bool get_use_mouse() { return this->use_mouse; } + int get_mouse_sens() { return this->mouse_sens; } + + bool get_use_joystick() { return this->use_joystick; } + int get_joystick_number() { return this->joy_num; } + + int get_controller_id() { return this->controller_id; } // unique controller ID (assigned by creation) + + // Config saving stuff + // NOTICE: you have to _previously_ call to allegro's set_config_file + void save_configuration_of_controller(char *cfg_section); // save current configuration in a file (cfg_section = section) + void load_configuration_of_controller(char *cfg_section); // load configuration from a config file (cfg_section = section) + + // Static members (available from the class itself) + // -------------- + + static int get_controller_count() { return CController::controller_count; } + + // ** NOTICE ** I CHANGED SOME DATA TO PUBLIC DUE TO KBALL REQUIREMENTS! + bool use_keyboard; // want to use keyboard input? (default=true) + bool use_joystick; // want to use joystick? (default=false) + bool use_mouse; // use mouse input? (default=true) + + private: + // keyboard stuff + int key_val[15]; // keys to input: 0..3= up,down,left,right | 4..7= reserved | 8..13= buttons | 14= reserved + + + // joystick stuff + int joy_num; // wich joystick to use? 0..num_joysticks (default=0) + + + // mouse stuff + int mouse_sens; // square of 'dead' until mouse movement is detected; (default 10, smaller is more sens) + + // ID of controller + int controller_id; // controller ID: automated (useful for saving configuration, you can have different sections, well, use it for something... ) + + // static members, available to all + static int controller_count; +}; + +#endif diff --git a/include/cwdata.h b/include/cwdata.h new file mode 100644 index 0000000..77acc72 --- /dev/null +++ b/include/cwdata.h @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------ +// cwdata.h +// ------------------------------------------------------------------ +// This is a wrapper over a datafile. +// Basically, it wraps a datafile in resources like bitmaps, +// sound and fonts in a way that can be requested +// and used by the program, just requesting them by name +// ------------------------------------------------------------------ +// By Kronoman +// In loving memory of my father +// Copyright (c) 2003, Kronoman +// ------------------------------------------------------------------ +// Upgraded in January 2004, based on skin.cpp of my simple GUI manager +// ------------------------------------------------------------------ +#ifndef CWDATAFILE_H +#define CWDATAFILE_H + +// Allegro +#include + +// STL stuff +#include // sweet key-data container +#include +#include +using namespace std; + + +// This is the type of data that I use for the Map that cache the datafile resources +typedef map DatafileCacheMap; + +class CWDatafile +{ + public: + CWDatafile(); + CWDatafile(const char *filename); + + ~CWDatafile(); + + void nuke_datafile(); // this frees the memory used by the datafile (and the cache, so, it resets the datafile) + + bool load_datafile(const char *filename); // this loads a datafile from a datafile in hard disk, returns TRUE if fails + + void do_cache(); // this do the map cache of resources, is automatically called when needed + + void *get_resource_dat(const string resource_name); // This is BETTER, returns directly the data or NULL on error + void *get_resource_dat(const char *resource_name); // This is BETTER, returns directly the data or NULL on error + + DATAFILE *get_resource(const string resource_name); // The hot stuff: get resources, or NULL on error (or exception, if set) + DATAFILE *get_resource(const char *resource_name); + + DATAFILE *get_whole_datafile(); // This returns a pointer to the whole loaded DATAFILE (in case that you need it for something) + + void dump_debug_datafile_data(); // debug function, shows all loaded on console output + + // for all the class + static void set_die_on_failure(bool b) { CWDatafile::die_on_failure = b; } // die on failure? + + private: + DATAFILE *datafile; // datafile in RAM + DatafileCacheMap datafile_cache_map; // STL map that does the cache of the datafile resources + + // for all the class + static bool die_on_failure; // if a error ocurs, kill the app? (die with error message) +}; + +#endif diff --git a/include/filehelp.h b/include/filehelp.h new file mode 100644 index 0000000..f44ae39 --- /dev/null +++ b/include/filehelp.h @@ -0,0 +1,19 @@ +// -------------------------------------------------------- +// filehelp.h +// -------------------------------------------------------- +// Copyright (c) 2003, 2004, Kronoman +// In loving memory of my father +// -------------------------------------------------------- +// I use this for reach the datafile searching in common paths +// Is specially useful for datafiles in Windows, because most +// of the time Windows don't start the program in the executable path, +// and the program is unable to find the datafile. +// -------------------------------------------------------- + + +#ifndef FILEHELP_H +#define FILEHELP_H + +char *where_is_the_filename(char *buffer, const char *filename); + +#endif diff --git a/include/gamemenu.h b/include/gamemenu.h new file mode 100644 index 0000000..30c1040 --- /dev/null +++ b/include/gamemenu.h @@ -0,0 +1,49 @@ +// ----------------------------------------------- +// gamemenu.h +// ----------------------------------------------- +// This has the menu of the Kball game +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#ifndef GAMEMENU_H +#define GAMEMENU_H + +#include + +#include // for showing time, and also, for a little surprise ;P + +#include "cwdata.h" // datafile wrapper +#include "qmenu.h" // menu system +#include "gkernel.h" // main game loop +#include "mapedit.h" // built in map editor -:^D +#include "gerror.h" // error reporting +#include "partmang.h" // particle manager, for nice particle effects of background +#include "particle.h" // many particle types +#include "mytracer.h" // this shit keeps crashing, I need to debug it... dammit! +#include "sound.h" // sound system + +class CGameMenu +{ + public: + CGameMenu(); + ~CGameMenu(); + + void do_main_menu(); + void do_file_level_selector(); + void do_options_menu(); + void do_about_stuff(); + + private: + BITMAP *menu_back; // doble buffer bitmap for menu + CWDatafile menu_datafile; // datafile for menu data + CMapEditor map_editor; // amazing map editor(r)(c)(tm) + CMyTracer mtracer; // tracer for debug log + CGameKernel game_kernel; // Game kernel :^O + CSoundWrapper soundw; // sound system +}; + + void do_main_menu(); + void do_file_level_selector(); +#endif diff --git a/include/gerror.h b/include/gerror.h new file mode 100644 index 0000000..764b983 --- /dev/null +++ b/include/gerror.h @@ -0,0 +1,22 @@ +/* +----------------------------------------------- +Generic Allegro Project Template +By Kronoman - July 2003 +Copyright (c) 2003, Kronoman +In loving memory of my father +----------------------------------------------- +gerror.h +----------------------------------------------- +Error messages +----------------------------------------------- +*/ +#ifndef GERROR_H +#define GERROR_H + +#include +#include /* for the variable argument list */ +#include /* for use of malloc */ + +void raise_error(AL_CONST char *msg, ...); + +#endif diff --git a/include/gkernel.h b/include/gkernel.h new file mode 100644 index 0000000..3b91bd9 --- /dev/null +++ b/include/gkernel.h @@ -0,0 +1,90 @@ +// ----------------------------------------------- +// gkernel.h +// ----------------------------------------------- +// Game main loop and kernel +// ----------------------------------------------- +// By Kronoman - July 2003 +// Updated to C++ class in January 2004 +// In loving memory of my father +// ----------------------------------------------- +#ifndef GKERNEL_H +#define GKERNEL_H + +#include +#include "cball.h" // player ball +#include "tmap.h" // tile map +#include "cwdata.h" // datafile handler +#include "partmang.h" // particle manager +#include "backgbmp.h" // background manager +#include "mytracer.h" // this shit keeps crashing, I need to debug it... dammit! +#include "sound.h" // amazing sound system :o +#include "stats.h" // game statistics +#include "musiclvl.h" // music + +// timer update ratio, in BPS +#define GKERNEL_FPSQ 30 + +// special flag for when the user aborts the game with ESC +#define GKERNEL_USER_FINISHED_GAME -666 + + +// This class has the game kernel. +class CGameKernel +{ + public: + CGameKernel(); + ~CGameKernel(); + + // this are the prefered method to start and play a game session + int play_a_single_level(char *level_filename); + void play_a_full_campaign(char *level_filename); + + // from here, stuff is almost internal only, because they are pretty 'low level'; altough may come handy + void init(); // initializes game (sets timers, loads data, etc) + void shutdown(); // shuts down game (unsets timers, unloads data, etc) + + int game_loop(); // this is the main game loop; will return CBALL_IS_DEAD, or CBALL_EXIT_LEVEL (dead, or won level) + int update_logic(); // this updates 1 logic update of game ; will return CBALL_IS_DEAD, CBALL_IS_FINE, or CBALL_EXIT_LEVEL + + void update_screen(); // this updates the screen + + void load_level_file(const char *file); // loads a level from a file, and sets the game ready to play on that level -- *MUST* BE CALLED BEFORE STARTING THE GAME LOOP! + + BITMAP *tile_bmp_to_screen_size(BITMAP *bmp); // tiles a bitmap to screen size, basically its purpose is to tile the level's background + + CBall player_ball; // ball of the player, I need to touch this from 'outside' ; basically, set the live ammount before each game + + CGameStats stats; // game statistics, I may need to touch them from 'outside' + + private: + + bool game_over; // game over? + CTMap game_map; // tile map loaded (with his own tile set) + CParticleManager particle_manager; // particle manager + CWDatafile main_data_file; // DATAFILE loaded + + BITMAP *dbuffer; // doble buffer + BITMAP *backdropbmp; // background bitmap for current level + CBackground background_loader; // loader system for level's background from datafile + + + FONT *game_time_font; // font for showing time left on game + FONT *game_score_font; // font for showing score on game + FONT *game_messages_font; // font for showing messages on game + + CSoundWrapper soundw; // sound system + CMusicLvl music_loader; // loader system for music from datafile + + + // current level file name (so we can reload it when player loses, etc) + char current_level_file_name[1024]; + + // for all the class data + static int timer_installed_count; // how many times we installed the timer; when this is 0, remove/install timers + + // debug tracer + CMyTracer mtracer; // my tracer to debug this crap +}; + +#endif + diff --git a/include/intro.h b/include/intro.h new file mode 100644 index 0000000..a632383 --- /dev/null +++ b/include/intro.h @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------ +// intro.h +// ------------------------------------------------------------------ +// This is the intro / exit secuence for the game - everything hardcoded, sorry +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ +#ifndef KBALL_INTRO_H +#define KBALL_INTRO_H + +#include + +void kball_do_the_intro(); +void kball_do_the_exit(); + +#endif diff --git a/include/mapedit.h b/include/mapedit.h new file mode 100644 index 0000000..b3c1f33 --- /dev/null +++ b/include/mapedit.h @@ -0,0 +1,61 @@ +// ----------------------------------------------- +// mapedit.h +// ----------------------------------------------- +// Built-in map editor (totally l33t!) +// ----------------------------------------------- +// By Kronoman - July 2003 - December 2003 +// In loving memory of my father +// ----------------------------------------------- + +#ifndef MAPEDIT_H +#define MAPEDIT_H + +#include + +#include "tmap.h" // tile map class +#include "gerror.h" // to show any error that may arise +#include "misc_def.h" +#include "mytracer.h" +#include "backgbmp.h" + + +class CMapEditor +{ + public: + CMapEditor(); + ~CMapEditor(); + + void start_map_editor(); + + private: + void map_editor_help_message(); + + void redraw_the_tile_map(int layer, bool grid); + + void do_change_mouse_cursor(); + + void kprint(char *msg); // helper for printing messages on screen + + + int tx, ty; // current scroll in the map + int ts; // current tile selected from the tile set (1..255) + int tl; // current layer of map selected 0..MAP_LAYERS-1 (MAP_LAYERS in tmap.h) + bool draw_grid; // draw grid? + + CTMap game_map; // game map + CBackground background; // backgrounds availables + + // two bitmaps for internal use + BITMAP *dbuffer; // doble buffer + BITMAP *mcursor; // mouse cursor + + // helper function for text print + int ykprint, ckprint; + + // debugger + CMyTracer mtracer; +}; + +#endif + + diff --git a/include/misc_def.h b/include/misc_def.h new file mode 100644 index 0000000..ef6d0a3 --- /dev/null +++ b/include/misc_def.h @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------ +// Some miscellaneous definitions +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2003, Kronoman +// ------------------------------------------------------------------ + +#ifndef KRONO_MISC_DEF +#define KRONO_MISC_DEF + +// this file has the tile set for the map +#define TILESET_FILE "tileset.dat" +// this file has the sprites, sound, backgrounds, etc +#define SPRITES_FILE "sprites.dat" + +#endif + diff --git a/include/musiclvl.h b/include/musiclvl.h new file mode 100644 index 0000000..cd6ee13 --- /dev/null +++ b/include/musiclvl.h @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------ +// musiclvl.h +// System for handling level's music +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ +#ifndef MUSICLVL_H +#define MUSICLVL_H + +#include +#include // DUMB : http://dumb.sf.net/ + +// music level loader +class CMusicLvl +{ + public: + CMusicLvl(); + ~CMusicLvl(); + + DUH *get_music(char *filename, int index); // gets the music from file, or a default music (NULL) otherwise + void free_memory(); // releases memory and returns to default music + + private: + DUH *music_in_ram; + DATAFILE *data_loaded; + + // cache stuff + char file_loaded[1024]; // wich file we have in memory + int index_loaded; + +}; + +#endif diff --git a/include/mytracer.h b/include/mytracer.h new file mode 100644 index 0000000..42e180c --- /dev/null +++ b/include/mytracer.h @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------ +// mytracer.h +// ------------------------------------------------------------------ +// This implements a tracer for debugging the game +// Basically, it records events on disk, so we can trace where the hell +// the bastard is crashing! +// ------------------------------------------------------------------ +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ------------------------------------------------------------------ + +#ifndef MYTRACER_H +#define MYTRACER_H + +#include +using namespace std; + +// yeah, sue me, I'm using C functions (I have my reasons, so, be nice and STFU) +#include +#include // for the variable argument list + +// file to save the trace (filename, 8.3 chars to keep compatibility with DOS) +#define TRACE_SAVE_IN_FILE "tracelog.txt" + +class CMyTracer +{ + public: + CMyTracer(); + ~CMyTracer(); + + void add(string txt); + void add(const char *msg, ...); // format like printf :O + + void reset(); // reset the file where we are tracing (OVERWRITES THE FILE!) + + // NOTE: this is configuration for all class objects (notice the 'static') + static bool DISABLE_TRACE; // define this to true to DISABLE the logging +}; + + +#endif diff --git a/include/particle.h b/include/particle.h new file mode 100644 index 0000000..eeed73c --- /dev/null +++ b/include/particle.h @@ -0,0 +1,144 @@ +// ----------------------------------------------- +// particle.h +// ----------------------------------------------- +// Many particle types, designed to be used with +// my particle manager. +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#ifndef PARTICLE_H +#define PARTICLE_H + +#include + +#include +using namespace std; + +#include "partmang.h" // particle manager + +// ----------------------------------------------- +// Spark particle, this looks like a spark (duh) +// ----------------------------------------------- +class CSparkParticle : public CBaseParticle +{ + public: + CSparkParticle() : CBaseParticle() { scale_spark = 1; }; + CSparkParticle(float x1, float y1, float dx1, float dy1, int color1, int life1) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { scale_spark = 1; }; + CSparkParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, int s_spark) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { scale_spark = s_spark; }; + ~CSparkParticle() { }; + + void render_particle(BITMAP *bmp, int xd, int yd); + + // data + int scale_spark; // this is the scale of the spark, defaults to 1 (bigger = bigger spark) +}; + +// ----------------------------------------------- +// Circle fill particle +// ----------------------------------------------- +class CCircleParticle : public CBaseParticle +{ + public: + CCircleParticle() : CBaseParticle() { radius = rand()%3+1; }; + CCircleParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, int r) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { radius = r; }; + ~CCircleParticle() { }; + + void render_particle(BITMAP *bmp, int xd, int yd); + + // data + int radius; // radius of circle +}; + +// ----------------------------------------------- +// Rect fill particle +// ----------------------------------------------- +class CRectParticle : public CBaseParticle +{ + public: + CRectParticle() : CBaseParticle() { size = rand()%3+1; }; + CRectParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, int s) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { size = s; }; + ~CRectParticle() { }; + + void render_particle(BITMAP *bmp, int xd, int yd); + + // data + int size; // size of rect +}; + + +// ----------------------------------------------- +// Text particle, renders text on the particle +// ----------------------------------------------- +class CTextParticle : public CBaseParticle +{ + public: + CTextParticle() : CBaseParticle() { text_to_show = "Krono Rulez!" ; font_text = font; }; + CTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, string txt) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { text_to_show = txt; font_text = font; }; + CTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, string txt, FONT *fnt) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { text_to_show = txt; font_text = fnt; }; + CTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, char *txt, FONT *fnt) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { text_to_show = txt; font_text = fnt; }; + ~CTextParticle() {}; + + void render_particle(BITMAP *bmp, int xd, int yd); + + // data of particle + string text_to_show; // text to show (duh!) + FONT *font_text; // font of text +}; + +// ----------------------------------------------- +// *Explosive* TEXT particle, renders text on the particle +// when the text particle deads, "explodes", +// that means that will spawn base particles (same color as text) +// ----------------------------------------------- +class CExplosiveTextParticle : public CTextParticle +{ + public: + CExplosiveTextParticle() : CTextParticle() {}; + CExplosiveTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, string txt) : CTextParticle( x1, y1, dx1, dy1, color1, life1, txt) { }; + CExplosiveTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, string txt, FONT *fnt) : CTextParticle( x1, y1, dx1, dy1, color1, life1, txt, fnt) { }; + CExplosiveTextParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, char *txt, FONT *fnt) : CTextParticle( x1, y1, dx1, dy1, color1, life1, txt, fnt) { }; + ~CExplosiveTextParticle() {}; + + + bool update_logic(CParticleManager &particle_manager); +}; + +// ----------------------------------------------- +// Bitmap particle -- draws a bitmap on the particle +// ----------------------------------------------- +class CBitmapParticle : public CBaseParticle +{ + public: + CBitmapParticle() : CBaseParticle() { spr = NULL; }; + CBitmapParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, BITMAP *spr1) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { spr = spr1; }; + ~CBitmapParticle() { }; + + void render_particle(BITMAP *bmp, int xd, int yd); + + // data + BITMAP *spr; +}; + +// ----------------------------------------------- +// Rotating Bitmap particle -- draws a bitmap on the particle (ideal for a debris/spark/etc) +// ----------------------------------------------- +class CRotoBitmapParticle : public CBaseParticle +{ + public: + CRotoBitmapParticle() : CBaseParticle() { spr = NULL; angle = 0.0; angle_speed = 0.0; }; + CRotoBitmapParticle(float x1, float y1, float dx1, float dy1, int color1, int life1, BITMAP *spr1, float ang, float ang_s) : CBaseParticle( x1, y1, dx1, dy1, color1, life1) { spr = spr1; angle = ang; angle_speed = ang_s; }; + ~CRotoBitmapParticle() { }; + + void render_particle(BITMAP *bmp, int xd, int yd); + bool update_logic(CParticleManager &particle_manager); + + // data + BITMAP *spr; + float angle; + float angle_speed; +}; + + +#endif diff --git a/include/partmang.h b/include/partmang.h new file mode 100644 index 0000000..6f6075f --- /dev/null +++ b/include/partmang.h @@ -0,0 +1,71 @@ +// ----------------------------------------------- +// partmang.h +// ----------------------------------------------- +// Particle manager, to control particles in the game +// Also, implementation of base particle +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#ifndef PARTMANG_H +#define PARTMANG_H + +#include + +#include // STL container for the particles (yeah, sue me, I'm not doing my own linked list :P) +#include +using namespace std; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// The base particle, a dot +// This particle must be used as base +// for making other types of particles, using +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class CParticleManager; // I need this here, so my particle 'knows' the manager and can send messages to him :D + +class CBaseParticle +{ + public: + CBaseParticle(); + CBaseParticle(float x1, float y1, float dx1, float dy1, int color1, int life1); + virtual ~CBaseParticle(); + + // the update logic receives the manager, so the particle can add new particles, if desired + virtual bool update_logic(CParticleManager &particle_manager); // updates particle's logic, must return TRUE if particle is dead (particle WILL be _deleted_ from memory by manager if dead) + + virtual void render_particle(BITMAP *bmp, int xd, int yd); // renders particle on bmp, displaced by x,y + + // all particle properties are public, for easy modification (yeah, I know, poor OO design, don't bug me) + float x, y, dx, dy; // particle x,y, and direction (acceleration in x,y) + int color; // particle color (in Allegro's makecol format) + int life; // remaining life of particle +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// The particle manager +// This handles a bunch of particles +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class CParticleManager +{ + public: + CParticleManager(); + ~CParticleManager(); + + void nuke_particle_list(); // this will free all memory used by particles, and particles itself. + + void add_particle(CBaseParticle *node); // adds a particle to manager (the particle WILL be nuked automatically when the manager object gets deleted) + + void update_logic(); // updates logic of all particles + + void render(BITMAP *bmp, int x, int y); // renders particles on bmp, displaced by x,y + + // some public data + float add_x, add_y; // displace particles on x, y, this can be used to simulate wind/gravity effects (is add to x,y of all particles) + float add_dx, add_dy; // this is add to dx,dy of all particles (can be used to simulate acceleration, etc) + + private: + list particle_list; // storage for pointers to particles that I must manage +}; + +#endif diff --git a/include/qmenu.h b/include/qmenu.h new file mode 100644 index 0000000..a97b5ad --- /dev/null +++ b/include/qmenu.h @@ -0,0 +1,110 @@ +// ----------------------------------------------- +// qmenu.h +// ----------------------------------------------- +// Quick menu system for my games/etc +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +// From hell they came!! arghhhh!! + +#ifndef QMENU_H +#define QMENU_H + +#include + +#include +#include // STL container for the menu items +#include +using namespace std; + +#include "control.h" // I use my generic controller to control the menu system ;^D + +// how many beats by second should the menu manager handle ? (default = 30) +// this is used for double buffer stuff mainly, and also for timing the calls to the optional callback routine +#define BPS_OF_MENU_MANAGER 30 + +// This class models a quick menu system +class CQMenu +{ + public: + CQMenu(); + ~CQMenu(); + + void clear_menu_list(); // resets menu list to empty + + int add_item_to_menu(string item); // adds a selectable item to the menu, returns the index of the item added, or -1 on error (menu full) + int add_item_to_menu(char *item) { return add_item_to_menu(string(item)); }; + + int do_the_menu(int selected); // starts the menu loop until the user selects one, will return the index of the item selected by the user (starts at 0). + int do_the_menu(void) { return do_the_menu(0); } // overload + + // set functions + void set_font_s(FONT *f); // sets font to render (selected) + void set_font_ns(FONT *f); // sets font to render (not selected) + + void set_fg_color_ns(int fg); // sets fg color (not selected) + void set_bg_color_ns(int bg); // sets bg color (not selected) + void set_fg_color_s(int fg); // sets fg color (selected) + void set_bg_color_s(int bg); // sets bg color (selected) + void set_xywh(int x, int y, int w, int h); // set pos of the menu + void set_to_bitmap(BITMAP *b); + void set_back_bitmap(BITMAP *b); + void set_callback_drawer(void (*c)(CQMenu &d, bool do_logic)); + + // get functions + FONT *get_font_s(); + FONT *get_font_ns(); + int get_fg_color_ns(); + int get_bg_color_ns(); + int get_fg_color_s(); + int get_bg_color_s(); + int get_menu_item_count(); + int get_x(); + int get_y(); + int get_w(); + int get_h(); + BITMAP *get_to_bitmap(); + BITMAP *get_back_bitmap(); + string get_menu_item_text(int item_index); // will return string of item + + // timing helpers (global timer) + int get_time_counter(); + unsigned long int get_big_timer_counter(); + + // the controller object is public so you can control it directly to suit your needs + CController control; // controller for menu control + + // some public values that control the display of the menu itself + int item_y_separation; // pixels to separate each item in 'Y' + int text_align; // alignment of text of items: default = left (normal), 1 = right, 2 = center around mx, 3 = justify in mw space + private: + FONT *menu_font_s; // pointer to font to render the menu items (selected) + FONT *menu_font_ns; // pointer to font to render the menu items (not selected) + + int menu_fg_color_ns; // color of foreground of the text (item NOT selected) + int menu_bg_color_ns; // color of background of the text (-1 for transparent) (item NOT selected) + + int menu_fg_color_s; // color of foreground of the text (item SELECTED) + int menu_bg_color_s; // color of background of the text (-1 for transparent) (item SELECTED) + + int mx, my, mw, mh; // x,y,w,h of the menu (position for drawing) + + BITMAP *to_bitmap; // bitmap where the menu must be drawn in each draw update (usually 'screen') + BITMAP *back_bitmap; // background bitmap of the menu, will be put at 0,0 of "to_bitmap" on each redraw + + // this is a callback function that can be called in each logic and draw update + // will be pased a reference to the caller object (to get bitmap, etc) + // do_logic will have TRUE if you need to update the logic + // do_logic will have FALSE if you need to render the graphics + // this scheme lets you keep a constant rate of animations + void (*callback)(CQMenu &d, bool do_logic); + + // data contained + vector menu_item_text; // item menu container + +}; + + +#endif diff --git a/include/savescrs.h b/include/savescrs.h new file mode 100644 index 0000000..d61749c --- /dev/null +++ b/include/savescrs.h @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------ +// savescrs.h +// ------------------------------------------------------------------ +// This saves screenshoots of the current screen +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#ifndef SAVESCRS_H +#define SAVESCRS_H + +#include +void save_screenshoot(char *name, int hex_start, char *extension); + +#endif diff --git a/include/sound.h b/include/sound.h new file mode 100644 index 0000000..b903ea4 --- /dev/null +++ b/include/sound.h @@ -0,0 +1,70 @@ +// ----------------------------------------------- +// Sound System +// By Kronoman +// Copyright (c) 2004, Kronoman +// In loving memory of my father +// ----------------------------------------------- +// sound.h +// ----------------------------------------------- +// This system wraps around Allegro and DUMB, +// so I can use the high level sound routines, +// global adjust volume, disable volume, etc +// ----------------------------------------------- +#ifndef _KRONO_SOUND_H +#define _KRONO_SOUND_H + +#include // Allegro : http://alleg.sf.net/ +#include // DUMB : http://dumb.sf.net/ + +class CSoundWrapper +{ + public: + CSoundWrapper(); + ~CSoundWrapper(); + + // digital samples + int play_sample(const SAMPLE *spl, int vol, int pan, int freq, int loop); // digital sample playing + void set_volume_d(int v); // volume digital + int get_volume_d(); + + // music + void music_load(DUH *dat); // call this before playing, dat should be a pointer to a music object of a datafile + + void music_start(); // start playing + + void music_pause(); // pause playback + void music_resume(); // pause playback + + void music_poll(); // _must_ be called at regular intervals to hear the music + + void music_stop(); // stop playing + + void set_volume_m(int v); // volume music + int get_volume_m(); + + // for all the object + void set_enabled(bool s); + bool is_enabled(); + + // for all the class + static void global_set_volume(int v); + static int global_get_volume(); + + static void global_set_enabled(bool s); + static bool global_is_enabled(); + + private: + // for this object + bool enabled; // enable this object player (if false, nothing will be played) + int volume_d; // volume, 0 to 255 + int volume_m; // volume music, 0 to 255 + + // for all the class + static bool global_enabled; // enables the sound system ? + static int global_volume; // global volume, 0 to 255 + + DUH *duh; + AL_DUH_PLAYER *dp; +}; + +#endif diff --git a/include/sphermap.h b/include/sphermap.h new file mode 100644 index 0000000..ea58b75 --- /dev/null +++ b/include/sphermap.h @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------- +// sphermap.h +// ----------------------------------------------------------------------- +// Code to render a sphere +// From Pixelate #11, article written by Martijn 'amarillion' van Iersel +// ----------------------------------------------------------------------- +// Modified by Kronoman to suit the needs of the game. +// ----------------------------------------------------------------------- + +#ifndef SPHERMAP_H + +#include + +// awful hack to use fixed, seems that my compiler don't like fixed type data :P +#ifndef fixed + #warning Awful hack in sphermap.h - please remember to check it =) + #define fixed long int +#endif + +void get_planet_rotation_matrix (MATRIX *, fixed , fixed , fixed ); +void mapped_sphere_ex (BITMAP *target, int cx, int cy, int r, BITMAP *map, MATRIX *rotmat); + +#endif diff --git a/include/stats.h b/include/stats.h new file mode 100644 index 0000000..e02864e --- /dev/null +++ b/include/stats.h @@ -0,0 +1,40 @@ +// ----------------------------------------------- +// stats.h +// ----------------------------------------------- +// Statistics of gameplay for KBall +// ----------------------------------------------- +// By Kronoman +// Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#ifndef KBALL_STATS_H +#define KBALL_STATS_H + +#include + +class CGameStats +{ + public: + CGameStats(); + ~CGameStats(); + + void reset(); // reset stats + + + void print(BITMAP *bmp, int y, int color_fg, int color_bg, FONT *f); // print stats + + void add_time(int h, int m, int s); // use this to update time, will keep it ok + + // time stats (total time of gameplay) + int h, s, m; + + // score + long int score; + + // balls lost + int blost; +}; + +#endif + diff --git a/include/tmap.h b/include/tmap.h new file mode 100644 index 0000000..8c0b3e2 --- /dev/null +++ b/include/tmap.h @@ -0,0 +1,111 @@ +// ------------------------------------------------------------------ +// tmap.h +// ------------------------------------------------------------------ +// This handles the tile map for the ball game +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2003, Kronoman +// ------------------------------------------------------------------ + +#ifndef TMAP_H +#define TMAP_H + +#include + +#include "cwdata.h" // For data handling/loading of tile data + +// square map size (fixed, sorry ; also, don't get this > 256 because I use chars to determine numbers :P) +#define TMAP_SIZE 256 + +// sprite size of each tile +#define TMAPS_W 64 +#define TMAPS_H 64 + +// map layers (currently, leave this at 2) +#define MAP_LAYERS 2 + +// ============================================================================ +// This is a single tile class (defines different kinds of tiles, +// each one of this is 1 tile class) +// ============================================================================ +class CTileClass +{ + public: + CTileClass(); + ~CTileClass(); + void draw(BITMAP *bmp, int x, int y, bool draw_grid, int layer); // draws the tile on bmp at x,y (pixel coordinates) + + void load_from_config(int nt, CWDatafile *data); // this loads the tile 'nt' (1.255) from a previously set config + void save_to_config(int nt); // this saves parameters to a previously set config *FILE* (not memory pointer) + + // all data public + BITMAP *spr; // sprite + char spr_name[512]; // this is to keep sprite name, is just to let the thing save itself when needed (as in a map editor) + + SAMPLE *sound; // sound ; will be played *only* if: a) is a prize and is pickup ; b) is a wall and ball bounces + char sound_name[512]; // this is to keep sound name, is just to let the thing save itself when needed (as in a map editor) + + // physic propertys + bool exit_level; // this kind of tile is exit level (default = false) + bool solid; // this is totally solid -> IS a WALL? (the ball bounces against it, check bounce factor) (default = false) + float bounce_factor; // a value _positive_ that is 'bounce factor', when solid = true, (default = 0.9) + float adx, ady,adz; // when the ball goes over this tile, this is _added_ to the dx,dy,dz values of the ball (default = 0.0) + float mdx, mdy,mdz; // when the ball goes over this tile, this is _multiplied_ to the dx,dy,dz values of the ball (default = 1.0) + + // prize propertys, this are for when the tile belongs to 'prize' layer + int score; // score on pickup + bool indispensable; // we must pickup it to pass the level + bool is_a_prize; // this tile is a 'prize' or just a decoration tile +}; + +// ============================================================================ +// This handles a tile map +// ============================================================================ +class CTMap +{ + public: + CTMap(); + ~CTMap(); + + bool get_tile_walkable(int x, int y, int layer); // this serves for validation purposes (for the ball), will return true if the tile is walkable, false otherwise (outside map counts as non walkable) + + int get_tile_type(int x, int y, int layer); // this returns the tile type (1..255), or -1 if outside map, or 0 if empty (if empty, or outside map, ball should fall free to death) + + void set_tile_type(int x, int y, int layer, int value); // this sets the tile type at x,y (0..255) x,y are coordinates of the matrix + + bool load_map_from_file(const char *filename); // load tile map from file, return true if FAILS, false otherwise + bool save_map_to_file(const char *filename); // save tile map to file, return true if FAILS, false otherwise + + bool load_tile_set_from_file(char *filename); // loads a tile set from a DATAFILE (filename is the name of the datafile), return true if FAILS, false otherwise + bool save_tile_set_config_to_file(char *filename); // saves the tile set configuration to a text file (not the bitmaps!) + + void empty_the_map(); // resets all map to 0s + + void draw_map(BITMAP *bmp, int ix, int iy, int iw, int ih, int layer, bool draw_grid); // draws a zone of the map, coordinates in pixels + + void free_memory(); // releases the memory, auto called on destructor + + void update_logic(); // updates the logic of the map (animations, time remaining, etc) + + // -- data, all public for faster access -- + int tile_map[TMAP_SIZE][TMAP_SIZE][MAP_LAYERS]; // tile map loaded, is divided in layers, 0 = floor, 1 = prizes, DON'T USE MORE (is like this for future ampliation only) + + CTileClass tile_set[256]; // tile class loaded (each tilem[][] is a index in this array) ; NOTE: 0 is reserved for empty tile index!!!, so this goes 1..255 for valid tiles + + CWDatafile tile_set_data; // this is the data loaded from file (has the sprites, etc of the tile_set array) + + int background_index; // ID index of background, ranges from 0...255 + int music_index; // ID index of the music, ranges from 0..255 + + int prize_map_indispensable; // ammount of indispensable items on map (if > 0, you can't leave the map) + int time_m; // minutes (0..255) left to try to collect items and reach exit + int time_s; // seconds (0..59) left to try to collect items and reach exit + int timer_tick_rate; // set this to measure timer tick rate, otherwise, the time will run wild (ex: if update logic is called 30 times by second, then set this to 30) + int curr_tick; // internal current tick measurer, when reaches 0, time_s --; + + int sxp, syp; // starting position for the player (is the first 2 bytes of the map file, 0..255, 0..255) +}; + +#endif + + diff --git a/include/ui_misc.h b/include/ui_misc.h new file mode 100644 index 0000000..4971df0 --- /dev/null +++ b/include/ui_misc.h @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------ +// ui_misc.h +// ------------------------------------------------------------------ +// This are some miscellaneous helper functions, mainly used by the kernel UI interface +// ------------------------------------------------------------------ +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ------------------------------------------------------------------ + +#ifndef UI_MISC_H +#define UI_MISC_H + +#include +void ui_misc_dark_bmp(BITMAP *bmp); +void ui_misc_text_out_shadow(BITMAP *bmp, FONT *f, const char *s, int x, int y, int cs, int ct); +void ui_misc_wait_for_input(); + +#endif diff --git a/makefile b/makefile new file mode 100644 index 0000000..4fd7b81 --- /dev/null +++ b/makefile @@ -0,0 +1,107 @@ +# ------------------------------------------------------------------------ +# KBall makefile +# By Kronoman +# Copyright (c) 2003, 2004 +# Thanks to Schwarzung for the help on making the original makefile system. +# ------------------------------------------------------------------------ + +# This has the target platform defined, this is modified by fix.bat or fix.sh +include target.os + +# Suggested by GNU Coding Stardards +SHELL = /bin/sh + +# =============================================== +# Target binary name without extension +BINARY = kball + +# Source directory +SRCDIR = src + +# Include directory +INCDIR = include + +# Source code suffix (.c, .cpp, etc) +SRCSUF = .cpp + +# Simple source code test file (must be in same dir as makefile for now) :( +# The extension will be taken from SRCSUF, don't put it! +TESTFILE = test +# =============================================== + + +# ----------------------------- +# -- Platform specific stuff -- +# ----------------------------- + +# ------------------ +# DJGPP target +# ------------------ +ifeq ($(TARGET),DJGPP) + +PLATFORMDIR=djgpp + +# compiler to invoque +GCC = gxx + +# Binary file suffix +BINSUF = dos.exe +# object suffix +OBJSUF = .o + +# If you need extra link options (like more librarys, add to LFLAGS var) +LFLAGS = -s -laldmb -ldumb -lalleg + +# Compiler flags +CFLAGS = -I$(INCDIR) -Wall -O3 +endif + +# ------------------ +# MingW32 +# ------------------ +ifeq ($(TARGET),MINGW32) + +PLATFORMDIR=mingw32 + +GCC = g++ + +# Binary file suffix +BINSUF = _w32.exe +OBJSUF = .o + +# If you need extra link options (like more librarys, add to LFLAGS var) +LFLAGS = -Wl,--subsystem,windows -s -laldmb -ldumb -lalleg + +# Compiler flags +CFLAGS = -I$(INCDIR) -Wall -O3 +endif + +# ------------------ +# Linux +# ------------------ +ifeq ($(TARGET),LINUX) + +PLATFORMDIR=linux + +GCC = g++ + +# Binary file suffix +BINSUF = _linux.bin +OBJSUF = .o + +# If you need extra link options (like more librarys, add to LFLAGS var) +LFLAGS = -s -laldmb -ldumb `allegro-config --libs` + +# Compiler flags +CFLAGS = -I$(INCDIR) -Wall -O3 +endif + +# --------------------------------- +# -- Platform non-specific stuff -- +# --------------------------------- + +OBJDIR = obj/$(PLATFORMDIR) +BINDIR = bin + +# -- The rules for build are in this file -- +include makefile.all diff --git a/makefile.all b/makefile.all new file mode 100644 index 0000000..c1b8ae9 --- /dev/null +++ b/makefile.all @@ -0,0 +1,47 @@ +# KBall Makefile +# Thanks to Schwarzung for help. +# Copyright (c) 2003-2005, Kronoman + +TEMP = $(wildcard $(SRCDIR)/*$(SRCSUF)) +FILES = $(if $(TEMP), $(TEMP), $(error No source code found!)) +OBJS = $(addprefix $(OBJDIR)/,$(addsuffix $(OBJSUF), $(basename $(notdir $(FILES) ) ) ) ) + +# main target, all project +.PHONY: all +all: $(BINDIR)/$(BINARY)$(BINSUF) + +$(BINDIR)/$(BINARY)$(BINSUF) : $(OBJS) + $(GCC) $^ -o $@ $(LFLAGS) + @echo The $(BINDIR)/$(BINARY)$(BINSUF) is ready! + +$(OBJDIR)/%$(OBJSUF) : $(SRCDIR)/%$(SRCSUF) + $(GCC) $(CFLAGS) -c $< -o $@ + +.PHONY: clean and also clean the test +clean: cleantest + rm -rf $(BINDIR)/$(BINARY)$(BINSUF) $(OBJS) + +# Strip symbols and compress with upx packer (http://upx.sf.net/) +.PHONY: upx +upx: + strip --strip-all $(BINDIR)/$(BINARY)$(BINSUF) + upx --best $(BINDIR)/$(BINARY)$(BINSUF) + +# Install - Please add here your custom installation functions +.PHONY: install +install: + @echo Sorry, the install feature is not done yet. + +# Test if the system can compile +# Compile the program +# The test.run is to check if make run or not in DJGPP enviroment (ugly hack) +# seems that DOS don't set errorlevel if fails to execute a program +.PHONY: test +test: + $(GCC) $(TESTFILE)$(SRCSUF) -o $(TESTFILE)$(BINSUF) $(CFLAGS) $(LFLAGS) + @echo "don't edit me" > test.run + +# Cleans the test +.PHONY: cleantest +cleantest: + rm -rf $(TESTFILE)$(BINSUF) test.run \ No newline at end of file diff --git a/obj/djgpp/remove.me b/obj/djgpp/remove.me new file mode 100644 index 0000000..99e8d22 --- /dev/null +++ b/obj/djgpp/remove.me @@ -0,0 +1 @@ +This file can be safely removed \ No newline at end of file diff --git a/obj/linux/remove.me b/obj/linux/remove.me new file mode 100644 index 0000000..99e8d22 --- /dev/null +++ b/obj/linux/remove.me @@ -0,0 +1 @@ +This file can be safely removed \ No newline at end of file diff --git a/obj/mingw32/remove.me b/obj/mingw32/remove.me new file mode 100644 index 0000000..bbdbf99 --- /dev/null +++ b/obj/mingw32/remove.me @@ -0,0 +1 @@ +Remove me diff --git a/src/backgbmp.cpp b/src/backgbmp.cpp new file mode 100644 index 0000000..34d66fc --- /dev/null +++ b/src/backgbmp.cpp @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------ +// backgbmp.cpp +// System for handling level backgrounds +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#include "backgbmp.h" +#include "gerror.h" +#include "filehelp.h" + +CBackground::CBackground() +{ + bmp_in_ram = NULL; + bmp_default = NULL; + data_loaded = NULL; + free_memory(); +} + +CBackground::~CBackground() +{ + free_memory(); + + if (bmp_default != NULL) + destroy_bitmap(bmp_default); +} + +BITMAP *CBackground::get_background(char *filename, int index) +{ + char str[1024]; + DATAFILE *data = NULL; + char tmp_file_buf[2048]; + + where_is_the_filename(tmp_file_buf, filename); + + // use the cache if it is present + if (index == index_loaded) + { + if (ustrcmp(tmp_file_buf, file_loaded) == 0) + return bmp_in_ram; + } + + free_memory(); + + usprintf(str, "B%d_BMP", index); + data = load_datafile_object(tmp_file_buf, str); + + if (data != NULL) + { + usprintf(file_loaded, "%s", tmp_file_buf); + index_loaded = index; + + data_loaded = data; + bmp_in_ram = (BITMAP *)data->dat; + return bmp_in_ram; + } + + + return bmp_default; +} + +void CBackground::free_memory() +{ + if (data_loaded != NULL) + unload_datafile_object(data_loaded); + + data_loaded = NULL; + + bmp_in_ram = NULL; + + if (bmp_default != NULL) + destroy_bitmap(bmp_default); + + bmp_default = create_bitmap(16, 16); + + if (bmp_default == NULL) + raise_error("* FATAL ERROR * \nCBackgrounds::free_memory()\n\tCan't create default bitmap!"); + + clear(bmp_default); + + file_loaded[0] = '\0'; + + index_loaded = -1; +} + diff --git a/src/cball.cpp b/src/cball.cpp new file mode 100644 index 0000000..7d7087b --- /dev/null +++ b/src/cball.cpp @@ -0,0 +1,472 @@ +// ------------------------------------------------------------------ +// cball.cpp +// ------------------------------------------------------------------ +// This class is the ball of the player +// By Kronoman - In loving memory of my father +// Copyright (c) 2003,2004, Kronoman +// ------------------------------------------------------------------ + +#include "cball.h" +#include "gerror.h" +#include "sphermap.h" +#include "sound.h" + +// ------------------------------------------------------------------ +// Constructor +// ------------------------------------------------------------------ +CBall::CBall() +{ + x = y = z = dx = dy = dz = 0.0; + spr = spr_shadow = spr_cache = NULL; + ctmap = NULL; + particle_manager = NULL; + + lives = BALL_LIVES; + anglex = angley = 0.0; + score = 0; + + // DEBUG - we use all available controllers by default + control.set_use_mouse(true); + control.set_use_joystick(true); + control.set_use_keyboard(true); + +} + +// ------------------------------------------------------------------ +// Destructor +// ------------------------------------------------------------------ +CBall::~CBall() +{ + // release RAM + if (spr_cache != NULL) + { + destroy_bitmap(spr_cache); + spr_cache = NULL; + } +} + +// ------------------------------------------------------------------ +// Draw the ball, in bmp, displaced by sx,sy pixels +// This uses 3D Allegro polygon routines +// A *scene* must be initialized!! +// ------------------------------------------------------------------ +void CBall::draw(BITMAP *bmp, int sx, int sy) +{ + float s = 0; // scale (is added to the size, not multiplied!) + if (spr == NULL) + return ; // can't draw, set the sprite first (texture of sphere) + if (z < BALL_MIN_Z*0.95) + return ; // we are dead, the ball is gone, don't draw + + // create sprite cache for rendering sphere texture (this is the size of texture, not the radius of the ball) + if (spr_cache == NULL) + spr_cache = create_bitmap(spr->w, spr->h); + + if (spr_cache == NULL) + raise_error("ERROR: CBall:draw() -> can't create sprite texture render cache (spr_cache) (out of memory?)\n"); + + // if the ball is jumping or falling, we scale it acording to Z + if (z < -1.0) + { + s = - (z * BALL_RADIUS / BALL_MIN_Z); + } + + if (z > 1.0) + { + s = (z * BALL_RADIUS * 2 / BALL_MAX_Z); + } + + + // render sphere + MATRIX m; + clear_to_color(spr_cache, makecol(255, 0, 255)); // magic pink fill + + get_planet_rotation_matrix(&m, ftofix(anglex), ftofix(angley), 0); // rotate texture + mapped_sphere_ex(spr_cache, spr_cache->w / 2, spr_cache->h / 2, spr_cache->w / 2, spr, &m); // render texture + + // add shadow enviroment map + set_trans_blender(200, 200, 200, 200); // transparent blender + draw_trans_sprite(spr_cache, spr_shadow, 0, 0); // blit the shiny shadow + + + // do a quad for render of the ball + V3D_f *v_xyz_p[4]; // this is needed to pass the array pointer to software renderer + V3D_f pc_v[4]; // the 4 vertex of the quad are precalculated and stored here + + int poltype = POLYTYPE_ATEX_MASK; // type of polygon to render, usually POLYTYPE_ATEX_MASK + + pc_v[0].x = x - sx - s; + pc_v[0].y = y - sy - s; + + pc_v[1].x = x - sx + BALL_RADIUS * 2 + s; + pc_v[1].y = y - sy - s; + + pc_v[2].x = x - sx + BALL_RADIUS * 2 + s; + pc_v[2].y = y - sy + BALL_RADIUS * 2 + s; + + pc_v[3].x = x - sx - s; + pc_v[3].y = y - sy + BALL_RADIUS * 2 + s; + + pc_v[0].z = pc_v[1].z = pc_v[2].z = pc_v[3].z = (z < 0) ? 2 : 0.25; + + + v_xyz_p[0] = &pc_v[0]; + v_xyz_p[1] = &pc_v[1]; + v_xyz_p[2] = &pc_v[2]; + v_xyz_p[3] = &pc_v[3]; + + // texture map coordinates + pc_v[0].u = 0; + pc_v[0].v = 0; + pc_v[1].u = spr->w; + pc_v[1].v = 0; + pc_v[2].u = spr->w; + pc_v[2].v = spr->h; + pc_v[3].u = 0; + pc_v[3].v = spr->h; + + scene_polygon3d_f(poltype, spr_cache, 4, v_xyz_p); // render polygon to scene + +} + + +// ------------------------------------------------------------------ +// Update ball logic, returns true if ball is 'dead' +// Also, does sounds, pick up prizes, etc... :) +// ------------------------------------------------------------------ + +int CBall::update_logic() +{ + int ret ; // this ret is NOT used as return value of 'return' in this routine. + bool over_exit_floor = false; // we are over floor that is level's exit? + + if (ctmap == NULL) + return CBALL_IS_FINE; // sorry, I don't know the map where I move + + if (z < BALL_MIN_Z) + return CBALL_IS_DEAD; // we are dead (Z too low), we lose, we can't handle the ball, etc... :P // DEBUG : sound ? + + // rotate the thing + if ((int)dx != 0 || (int)dy != 0 || (int)dz != 0) + { + // DEBUG - check this, the rotation is not 100% correct in certain cases + if ((angley >= 0 && angley <= 64) || (angley >= 192)) + anglex -= dx; + else + anglex += dx; + + angley -= dy; + + if (anglex < 0) + anglex = 255; + if (anglex > 255) + anglex = 0; + if (angley < 0) + angley = 255; + if (angley > 255) + angley = 0; + } + + + // control input + ret = control.do_input_poll(); + + if (ret & KC_UP) + dy -= BALL_SPEED_Y; + if (ret & KC_DOWN) + dy += BALL_SPEED_Y; + if (ret & KC_LEFT) + dx -= BALL_SPEED_X; + if (ret & KC_RIGHT) + dx += BALL_SPEED_X; + + // apply current tile floor transformations (current floor on center of the ball) + ret = ctmap->get_tile_type(((int)x + (BALL_RADIUS)) / TMAPS_W, ((int)y + (BALL_RADIUS)) / TMAPS_H, 0); + if (0 < z && z < 1.0) // only if 0 < Z < 1.0 we are in the floor + { + if (ret > 0) // is over a valid tile + { + + if (ctmap->tile_set[ret].sound && (!ctmap->tile_set[ret].exit_level || (ctmap->tile_set[ret].exit_level && (ctmap->prize_map_indispensable<1) ) ) ) + soundw.play_sample(ctmap->tile_set[ret].sound, rand() % 50 + 180, 128, 1000 + rand() % 250 - 125, 0); // SOUND of the tile, DEBUG + + dx += ctmap->tile_set[ret].adx; + dy += ctmap->tile_set[ret].ady; + dz += ctmap->tile_set[ret].adz; + + dx *= ctmap->tile_set[ret].mdx; + dy *= ctmap->tile_set[ret].mdy; + dz *= ctmap->tile_set[ret].mdz; + + // if we are over a floor, we are safe + if (dz < 0) + dz = 0.0; // we aren't falling anymore =D + if (z < 1) + z = 1.0; // we are over floor =D + + // this floor is exit? + over_exit_floor = ctmap->tile_set[ret].exit_level; + } + else + { + // if we are outside map or in a empty floor, we must fall to death... :^O + dz -= BALL_SPEED_Z; // negative dz, means fall! + } + } + else + { + z -= N_BALL_SPEED_Z; // apply gravity + } + + // comprobations of max values + + if (dy < -BALL_MSPEED) + dy = -BALL_MSPEED; + + if (dy > BALL_MSPEED) + dy = BALL_MSPEED; + + if (dx < -BALL_MSPEED) + dx = -BALL_MSPEED; + + if (dx > BALL_MSPEED) + dx = BALL_MSPEED; + + if (dz < -BALL_MSPEED) + dz = -BALL_MSPEED; + + if (dz > BALL_MSPEED) + dz = BALL_MSPEED; + + // friction + if (dy > 0) + dy -= N_BALL_SPEED_Y; + + if (dy < 0) + dy += N_BALL_SPEED_Y; + + if (dx > 0) + dx -= N_BALL_SPEED_X; + + if (dx < 0) + dx += N_BALL_SPEED_X; + + // 'dead zone' of the ball + if (dy < N_BALL_SPEED_Y*1.1 && dy > 0) + dy = 0.0; + + if (dy > -N_BALL_SPEED_Y*1.1 && dy < 0) + dy = 0.0; + + if (dx < N_BALL_SPEED_X*1.1 && dx > 0) + dx = 0.0; + + if (dx > -N_BALL_SPEED_X*1.1 && dx < 0) + dx = 0.0; + + // gravity (if dz > 1.0, the ball is jumping) + if (dz > 0) + dz -= N_BALL_SPEED_Z; + + // Collision with walls -- DEBUG - DEBUG -- THIS NEEDS HEAVY CHECK!!! + // predict where the ball goes (for check collisions) + float xp, yp; + xp = x + dx; + yp = y + dy; + + /* + Atento: + formula para sacar el rebote + + r = u - 2*n*(n dot u) + + donde + r = vector que quiero + u = vector direccion bola + n = normal de la superficie (pared) + + dot = producto escalar + */ + + + if (z > 0) // only if z > 0 we are over the floor; we only collide with walls if we are over the floor. duh! + { + float n_dot_u = 0, yn = 0, xn = 0; // n dot u , n(x,y) + float part_x = 0, part_y = 0; // particle spark position + + for (int i = 0; i < 8; i ++) // check 8 points of ball's sprite + { + // NOTE: the size of the bounding box is smaller than the ball radius + + // BGAP: size to shrink the bounding box +#define BGAP 6 + switch (i) + { + case 0: // up middle + part_x = xp + BALL_RADIUS; + part_y = yp + BGAP; + + xn = 0; + yn = 1; + break; + + case 1: // down middle + part_x = xp + BALL_RADIUS; + part_y = yp + BALL_RADIUS * 2 - BGAP; + xn = 0; + yn = -1; + break; + + case 2: // left middle + part_x = xp + BGAP; + part_y = yp + BALL_RADIUS; + xn = 1; + yn = 0; + break; + + case 3: // right middle + part_x = xp + BALL_RADIUS * 2 - BGAP; + part_y = yp + BALL_RADIUS; + xn = -1; + yn = 0; + break; + +#define normalized_v 0.707106781 + // this are the four cornes + // the weird numbers on normal vectors are because they are normalized! + case 4: // up left + part_x = xp + BGAP; + part_y = yp + BGAP; + xn = normalized_v; + yn = normalized_v; + break; + + case 5: // down left + part_x = xp + BGAP; + part_y = yp + BALL_RADIUS * 2 - BGAP; + xn = normalized_v; + yn = -normalized_v; + break; + + case 6: // up right + part_x = xp + BALL_RADIUS * 2 - BGAP; + part_y = yp + BGAP; + xn = -normalized_v; + yn = normalized_v; + break; + + case 7: // down right + part_x = xp + BALL_RADIUS * 2 - BGAP; + part_y = yp + BALL_RADIUS * 2 - BGAP; + xn = -normalized_v; + yn = -normalized_v; + break; +#undef normalized_v + + } +#undef BGAP + ret = ctmap->get_tile_type((int)part_x / TMAPS_W, (int)part_y / TMAPS_H, 0); // up + + if (ret > 0) + { + if (ctmap->tile_set[ret].solid) + { + // DEBUG -- hack way to solve bug of corners!! + if (i < 4) // hack with corners - it WORKS, and that is what counts :P + { + // bounce! + n_dot_u = dx * xn + dy * yn; + // this is the real bounce, proper done with vectors (although most of the time it don't work :P) + dx = dx - 2 * xn * n_dot_u; + dy = dy - 2 * yn * n_dot_u; + } + else + { + dx *= -1; // hacky bounce for conners... I know, is wrong... I dont care... + dy *= -1; + } // end of hack + + // bounce factor of wall + dx *= ctmap->tile_set[ret].bounce_factor; + dy *= ctmap->tile_set[ret].bounce_factor; + + + // DEBUG: the particle spark NEEDS to be accurate + for (int i = 0; i < rand() % 10 + 5; i++) // particle spark + particle_manager->add_particle(new CBaseParticle(part_x + (rand() % 6) - 3, part_y + (rand() % 6) - 3, (float)(rand() % 300) / 100.0*(dx < 0 ? -1 : 1), (float)(rand() % 300) / 100.0*(dy < 0 ? -1 : 1), makecol(255, rand() % 55 + 200, 0), rand() % 15 + 5)); + + // bounce sound + if (ctmap->tile_set[ret].sound) + soundw.play_sample(ctmap->tile_set[ret].sound, rand() % 56 + 200, 128 + rand() % 64 - 32, 1000 + rand() % 300 - 150, 0); + + break; // end for (if not, the code will fail, will bounce many times, most of the time just rendering void the bounce) + } + } + } + } + + // move the ball + + + x += dx; + y += dy; + z += dz; + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + if (x > TMAP_SIZE * TMAPS_W) + x = TMAP_SIZE * TMAPS_W; + + if (y > TMAP_SIZE * TMAPS_H) + y = TMAP_SIZE * TMAPS_H; + + if (z > BALL_MAX_Z) // top jump, hit the 'roof' + { + z = BALL_MAX_Z; + + if (dz > 0) + dz = dz * 0.8; // decrease jump by 20 % also + } + + // finally, check the 'prize' layer (only if we are over the floor) + + ret = ctmap->get_tile_type(((int)x + (BALL_RADIUS)) / TMAPS_W, ((int)y + (BALL_RADIUS)) / TMAPS_H, 1); + + if ((ctmap->tile_set[ret].indispensable || ctmap->tile_set[ret].is_a_prize) && z >= 0 && ret > 0) + { + + score += ctmap->tile_set[ret].score; + + if (ctmap->tile_set[ret].indispensable) + ctmap->prize_map_indispensable--; + + ctmap->set_tile_type(((int)x + (BALL_RADIUS)) / TMAPS_W, ((int)y + (BALL_RADIUS)) / TMAPS_H, 1, 0); // clean the tile + + // SOUND + if (ctmap->tile_set[ret].sound) + soundw.play_sample(ctmap->tile_set[ret].sound, rand() % 26 + 230, 128 + rand() % 64 - 32, 1000 + rand() % 100 - 50, 0); + + // add score message + if (particle_manager != NULL) + { + char score_txt[256]; + usprintf(score_txt, "%c%d", (ctmap->tile_set[ret].score > 0 ? '+' : '-'), ctmap->tile_set[ret].score); + + particle_manager->add_particle(new CExplosiveTextParticle(x + BALL_RADIUS, y, 0.0, (float)(rand() % 100 + 25) / -100.0, makecol(255, rand() % 128 + 128, 0), rand() % 30 + 30, string(score_txt) ) ); + + for (int i = 0; i < rand() % 30 + 30; i++) + particle_manager->add_particle(new CSparkParticle(x + BALL_RADIUS, y + BALL_RADIUS, (float)((rand() % 800) - 400) / 100.0, (float)((rand() % 800) - 400) / 100.0, makecol(rand() % 128 + 128, 255, 0), rand() % 15 + 15, 3)); + } + } + + + // if I'm not dead, and I'm over level exit and I'm still, exit level + if (over_exit_floor) + return CBALL_EXIT_LEVEL; + + return CBALL_IS_FINE; // not dead yet ;) +} diff --git a/src/control.cpp b/src/control.cpp new file mode 100644 index 0000000..f4dba51 --- /dev/null +++ b/src/control.cpp @@ -0,0 +1,407 @@ +// ----------------------------------------------------------------------- +// Controller wrapper +// control.cpp +// ----------------------------------------------------------------------- +// This class handles a generic controller. +// Simulates a 8 way gamepad with 6 buttons using keyboard,joystick,mouse +// By Kronoman - In loving memory of my father +// Copyright (c) 2003 - This file released under the MIT license +// 08-NOV-2003 +// +// ** NOTE ** +// Modified 04-MAR-2004 to add customize control interface. +// This class is arcane, I have a better implementation. +// +// We will use this in this game, in future games, we will use the new implementation. +// +// **** NOTICE **** +// THIS PARTICULAR GAME, KBALL DOES NOT NEED BUTTONS, SO IS DISABLED IN INTERACTIVE CONFIGURATION! +// ----------------------------------------------------------------------- + +#include "control.h" + +int CController::controller_count = 0; + +CController::CController() +{ + // keyboard stuff + this->use_keyboard = TRUE; // want to use keyboard input? (default=true) + this->set_default_keyboard(); + + // joystick stuff + this->joy_num = 0; // wich joystick to use? 0..num_joysticks (default=0) + this->use_joystick = FALSE; + + // mouse stuff + this->use_mouse = TRUE; // use mouse input? (default=true) + this->mouse_sens = 10; // square of 'dead' until mouse movement is detected; (default 10) + + this->controller_id = CController::controller_count; + CController::controller_count++; +} + +CController::~CController() +{ + CController::controller_count--; +} + +// sets the default keyboard configuration +void CController::set_default_keyboard() +{ + // keys to input: 0..3= up,down,left,right | 4..7= reserved | 8..13= buttons | 14= reserved + key_val[0] = KEY_UP; + key_val[1] = KEY_DOWN; + key_val[2] = KEY_LEFT; + key_val[3] = KEY_RIGHT; + + key_val[8] = KEY_A; + key_val[9] = KEY_S; + key_val[10] = KEY_D; + key_val[11] = KEY_Z; + key_val[12] = KEY_X; + key_val[13] = KEY_C; +} + +// this function is used to configure the keyboard +void CController::set_keyboard_par(int value, int index) +{ + if ((index < 0) || (index > 14)) return; // error :P + + this->key_val[index] = value; +} + +void CController::set_use_keyboard(bool use) +{ + this->use_keyboard = use; +} + +void CController::set_mouse_sens(int s) +{ + this->mouse_sens = abs(s); +} + +void CController::set_use_mouse(bool use) +{ + this->use_mouse = use; +} + +void CController::set_joystick_number(int n) +{ + if (n > num_joysticks-1) return; // error, that joystick is not there! + + this->joy_num = n; +} + +void CController::set_use_joystick(bool use) +{ + this->use_joystick = use; +} + + +// This is the main function +// This actually does the input from hardware +// and returns the bitmask acording to action +int CController::do_input_poll() +{ + int ret = KC_NONE; // return value + static int old_mouse_z = -666; // mouse_z last call, special flag = -666, means dirty + + // keyboard + if (this->use_keyboard) + { + if (keyboard_needs_poll()) poll_keyboard(); + + if (key[key_val[0]]) ret |= KC_UP; + if (key[key_val[1]]) ret |= KC_DOWN; + if (key[key_val[2]]) ret |= KC_LEFT; + if (key[key_val[3]]) ret |= KC_RIGHT; + + if (key[key_val[8]]) ret |= KC_BTN1; + if (key[key_val[9]]) ret |= KC_BTN2; + if (key[key_val[10]]) ret |= KC_BTN3; + if (key[key_val[11]]) ret |= KC_BTN4; + if (key[key_val[12]]) ret |= KC_BTN5; + if (key[key_val[13]]) ret |= KC_BTN6; + } + + // mouse + if (this->use_mouse) + { + int mickeyx = 0; int mickeyy = 0; + + if (mouse_needs_poll()) poll_mouse(); + + get_mouse_mickeys(&mickeyx, &mickeyy); + + if (mickeyx < -mouse_sens) ret |= KC_LEFT; + if (mickeyx > mouse_sens) ret |= KC_RIGHT; + + if (mickeyy < -mouse_sens) ret |= KC_UP; + if (mickeyy > mouse_sens) ret |= KC_DOWN; + + if (mouse_b & 1) ret |= KC_BTN1; + if (mouse_b & 2) ret |= KC_BTN2; + if (mouse_b & 4) ret |= KC_BTN3; + + // also mouse_z is used, up = btn4, down = btn5, sadly I can't do yet button 6 + if (old_mouse_z != -666) + { + if (mouse_z < old_mouse_z) ret |= KC_BTN4; + if (mouse_z > old_mouse_z) ret |= KC_BTN5; + } + old_mouse_z = mouse_z; + } + + // joystick + if (use_joystick) + { + poll_joystick(); // needed + + // digital joystick input + if (joy[joy_num].stick[0].axis[0].d1) ret |= KC_LEFT; + if (joy[joy_num].stick[0].axis[0].d2) ret |= KC_RIGHT; + + if (joy[joy_num].stick[0].axis[1].d1) ret |= KC_UP; + if (joy[joy_num].stick[0].axis[1].d2) ret |= KC_DOWN; + + if (joy[joy_num].num_buttons > 0) + if (joy[joy_num].button[0].b) ret |= KC_BTN1; + + if (joy[joy_num].num_buttons > 1) + if (joy[joy_num].button[1].b) ret |= KC_BTN2; + + if (joy[joy_num].num_buttons > 2) + if (joy[joy_num].button[2].b) ret |= KC_BTN3; + + if (joy[joy_num].num_buttons > 3) + if (joy[joy_num].button[3].b) ret |= KC_BTN4; + + if (joy[joy_num].num_buttons > 4) + if (joy[joy_num].button[4].b) ret |= KC_BTN5; + + if (joy[joy_num].num_buttons > 5) + if (joy[joy_num].button[5].b) ret |= KC_BTN6; + } + +return ret; +} + + +int CController::get_keyboard_par(int value, int index) +{ + if ((index < 0) || (index > 14)) return -1; // error :P + + return this->key_val[index]; +} + + +void CController::save_configuration_of_controller(char *cfg_section) +{ +char str[256]; + + // how can I save a bool? is this the correct way? + set_config_int(cfg_section, "use_keyboard", (int)this->use_keyboard); + set_config_int(cfg_section, "use_joystick", (int)this->use_joystick); + set_config_int(cfg_section, "use_mouse", (int)this->use_mouse); + + // save keys + for (int i=0; i < 15; i++) + { + usprintf(str,"key_val_%d",i); + set_config_int(cfg_section, str, this->key_val[i]); + } + + // save joystick number + set_config_int(cfg_section, "joy_num", this->joy_num); + + set_config_int(cfg_section, "mouse_sens", this->mouse_sens); + +} + +void CController::load_configuration_of_controller(char *cfg_section) +{ +char str[256]; + + // is this the correct way? + this->use_keyboard = (bool)get_config_int(cfg_section, "use_keyboard", (int)this->use_keyboard); + this->use_joystick = (bool)get_config_int(cfg_section, "use_joystick", (int)this->use_joystick); + this->use_mouse = (bool)get_config_int(cfg_section, "use_mouse", (int)this->use_mouse); + + // get keys + for (int i=0; i < 15; i++) + { + usprintf(str,"key_val_%d",i); + this->key_val[i] = get_config_int(cfg_section, str, this->key_val[i]); + } + + this->joy_num = get_config_int(cfg_section, "joy_num", this->joy_num); + + this->mouse_sens = get_config_int(cfg_section, "mouse_sens", this->mouse_sens); +} + +// interactive configuration of controller (really LAME interface with user... improve it! :P) +void CController::interactive_configuration_keyboard(FONT *font, int color) +{ + int y = 100, h = text_height(font); + + clear_keybuf(); + rest(10); + while (keypressed()) readkey(); + + textout_ex(screen, font, "-- Keyboard configuration--", 0, y, color,-1); + + y+=h; + + textout_ex(screen, font, "Press key for 'UP'", 0, y+=h, color,-1); + key_val[0] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'DOWN' ", 0, y+=h, color,-1); + key_val[1] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'LEFT' ", 0, y+=h, color,-1); + key_val[2] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'RIGHT' ", 0, y+=h, color,-1); + key_val[3] = readkey() >> 8; + +// DEBUG -- DEBUG -- THIS PARTICULAR GAME, KBALL DOES NOT NEED BUTTONS, SO THIS IS DISABLED +#ifdef THIS_IS_DISABLED_FOR_THIS_GAME + + textout_ex(screen, font, "Press key for 'BUTTON 1' ", 0, y+=h, color,-1); + key_val[8] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'BUTTON 2' ", 0, y+=h, color,-1); + key_val[9] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'BUTTON 3' ", 0, y+=h, color,-1); + key_val[10] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'BUTTON 4' ", 0, y+=h, color,-1); + key_val[11] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'BUTTON 5' ", 0, y+=h, color,-1); + key_val[12] = readkey() >> 8; + + textout_ex(screen, font, "Press key for 'BUTTON 6' ", 0, y+=h, color,-1); + key_val[13] = readkey() >> 8; + +#endif + + clear_keybuf(); + rest(10); + + textout_ex(screen, font, "Done. Press any key... ", 0, y+=h*2, color,-1); + readkey(); + // done +} + +// interactive configuration of controller (really LAME interface with user... improve it! :P) +void CController::interactive_configuration_joystick(FONT *font, int color) +{ + int y = 100, h = text_height(font); + + clear_keybuf(); + while (keypressed()) readkey(); + + if (!num_joysticks) + { + textout_ex(screen, font, "-- Error: Joystick not found! --", 0, y, makecol(255,0,0),-1); + textout_ex(screen, font, "Press any key...", 0, y+=h, color,-1); + readkey(); + return; + } + + textout_ex(screen, font, "-- Joystick configuration--", 0, y, color,-1); + + y+=h; + + if (num_joysticks > 1) + { + textprintf_ex(screen, font, 0, y+=h, color,-1, "You have %d joysticks", num_joysticks); + textprintf_ex(screen, font, 0, y+=h, color,-1, "Wich one to use? (Press number 1..%d)", num_joysticks); + + char tmp = (readkey() & 0xff); // take ASCII code + + joy_num = 0; // default + + if (tmp == '1') joy_num = 0; + if (tmp == '2') joy_num = 1; + if (tmp == '3') joy_num = 2; + if (tmp == '4') joy_num = 3; + // although Allegro driver currently supports up to 4 controllers, we let room to grow in future + if (tmp == '5') joy_num = 4; + if (tmp == '6') joy_num = 5; + if (tmp == '7') joy_num = 6; + if (tmp == '8') joy_num = 7; + if (tmp == '9') joy_num = 8; + + if (joy_num > num_joysticks) joy_num = 0; // default + } + else + { + joy_num = 0; + } + + textprintf_ex(screen, font, 0, y+=h, color, -1,"We will use joystick number %d", joy_num+1); + + // calibrate joystick if needed, although not needed, because we use digital input + AL_CONST char *msg; + while (joy[joy_num].flags & JOYFLAG_CALIBRATE) + { + msg = calibrate_joystick_name(joy_num); + + textout_ex(screen, font, msg, 0, y+=h, color,-1); + textout_ex(screen, font, "And press any key.", 0, y+=h, color,-1); + + if ((readkey()&0xFF) == 27) return; + + if (calibrate_joystick(0) != 0) + { + textout_ex(screen, font, "Error calibrating joystick!", 0, y+=h, makecol(255,0,0),-1); + textout_ex(screen, font, "Press any key...", 0, y+=h, color,-1); + readkey(); + return; + } + } + + clear_keybuf(); + rest(10); + + textout_ex(screen, font, "Done. Press any key... ", 0, y+=h*2, color,-1); + readkey(); + // done +} + +void CController::interactive_configuration_mouse(FONT *font, int color) +{ + int y = 100, h = text_height(font); + + clear_keybuf(); + while (keypressed()) readkey(); + + textout_ex(screen, font, "--Mouse configuration--", 0, y, color,-1); + + y+=h; + + textout_ex(screen, font, "Press a key number from 1..9 to choose sensitiviness", 0, y+=h, color,-1); + textout_ex(screen, font, "1 = minimum to 9 = maximum", 0, y+=h, color,-1); + + textout_ex(screen, font, "Any other key to cancel.", 0, y+=h, color,-1); + char ret = (readkey() & 0xff); // take ASCII code + + if (ret == '1') mouse_sens = 40; + if (ret == '2') mouse_sens = 30; + if (ret == '3') mouse_sens = 25; + if (ret == '4') mouse_sens = 20; + if (ret == '5') mouse_sens = 15; + if (ret == '6') mouse_sens = 10; + if (ret == '7') mouse_sens = 5; + if (ret == '8') mouse_sens = 1; + if (ret == '9') mouse_sens = 0; + + textout_ex(screen, font, "Done. Press any key... ", 0, y+=h, color,-1); + clear_keybuf(); + rest(10); + readkey(); + // done +} diff --git a/src/cwdata.cpp b/src/cwdata.cpp new file mode 100644 index 0000000..62e8cca --- /dev/null +++ b/src/cwdata.cpp @@ -0,0 +1,205 @@ +// ------------------------------------------------------------------ +// cwdata.cpp +// ------------------------------------------------------------------ +// This is a wrapper over a datafile. +// Basically, it wraps a datafile in resources like bitmaps, +// sound and fonts in a way that can be requested +// and used by the program, just requesting them by name +// ------------------------------------------------------------------ +// By Kronoman +// In loving memory of my father +// Copyright (c) 2003-2004, Kronoman +// ------------------------------------------------------------------ +// Upgraded in January 2004, based on skin.cpp of my simple GUI manager +// ------------------------------------------------------------------ + +#include "cwdata.h" +#include "gerror.h" // error manager +#include "filehelp.h" // to find the datafile + +bool CWDatafile::die_on_failure = true; // default = die on failure + +// ------------------------------------------------------------------ +// Constructor +// ------------------------------------------------------------------ +CWDatafile::CWDatafile() +{ + datafile = NULL; + datafile_cache_map.clear(); +} + +// ------------------------------------------------------------------ +// This inits the datafile with a file +// ------------------------------------------------------------------ +CWDatafile::CWDatafile(const char *filename) +{ + CWDatafile::CWDatafile(); + + this->load_datafile(filename); +} + +// ------------------------------------------------------------------ +// Destructor, free the datafile RAM... +// ------------------------------------------------------------------ +CWDatafile::~CWDatafile() +{ + this->nuke_datafile(); +} + +// ------------------------------------------------------------------ +// Free the memory of the datafile +// ------------------------------------------------------------------ +void CWDatafile::nuke_datafile() +{ + if (datafile != NULL) + { + unload_datafile(this->datafile); + datafile = NULL; + } + + datafile_cache_map.clear(); +} + +// ------------------------------------------------------------------ +// Load a datafile from a disk datafile +// ------------------------------------------------------------------ +bool CWDatafile::load_datafile(const char *filename) +{ + char tmp_file_buf[2048]; + this->nuke_datafile(); // old datafile goes to hell + + datafile = ::load_datafile(where_is_the_filename(tmp_file_buf, filename)); // note: the '::' before load_datafile means 'call the Allegro's load_datafile, not this->load_datafile + + if (datafile == NULL) + { + if (die_on_failure) + raise_error("FATAL ERROR\nCWDatafile::load_datafile(\"%s\") failed\nFile not found or can't be loaded", filename); + + return true; // error d00d + } + else + { + // the data is loaded, cache it! + this->do_cache(); + } + + return false; +} + +// ------------------------------------------------------------------ +// This caches the data, is automatically done in data file load +// ------------------------------------------------------------------ +void CWDatafile::do_cache() +{ + + if (datafile == NULL) + return ; // no data to cache! + + datafile_cache_map.clear(); // cache start from zero + + // walk the datafile, and cache key->data pairs + for (int pos = 0; datafile[pos].type != DAT_END; pos++) + { + datafile_cache_map[get_datafile_property(datafile + pos, DAT_NAME)] = (DATAFILE *)datafile + pos; + } + + // cache ready =) God bless STL +} + +// ------------------------------------------------------------------ +// Overloaded function, just calls void *CWDatafile::get_resource_dat(string resource_name) +// ------------------------------------------------------------------ + +void *CWDatafile::get_resource_dat(const char *resource_name) +{ + return this->get_resource_dat(string(resource_name)); +} + +// ------------------------------------------------------------------ +// This returns a resource by name, or NULL on error +// Notice, the pointer returned IS THE DATA itself +// So is safe to do things like this: +// bmp = (BITMAP *)(this->datafile->get_resource_dat(string("WINDOW_BASE_BMP"))); +// if (bmp != NULL) blah blah +// ------------------------------------------------------------------ +void *CWDatafile::get_resource_dat(const string resource_name) +{ + DATAFILE *p; + + p = this->get_resource(resource_name); + + if (p == NULL) + { + if (die_on_failure) + raise_error("FATAL ERROR!\nCWDatafile::get_resource_dat(\"%s\")\nCan't find resource.", resource_name.c_str()); + + return NULL; + } + else + { + return p->dat; + } +} + +// ------------------------------------------------------------------ +// Overloaded, just calls DATAFILE *CWDatafile::get_resource(const string resource_name) +// ------------------------------------------------------------------ +DATAFILE *CWDatafile::get_resource(const char *resource_name) +{ + return this->get_resource(string(resource_name)); +} + +// ------------------------------------------------------------------ +// This returns a resource by name, or NULL on error +// Notice, the pointer returned is a DATAFILE * +// You should add ->dat to get the data itself +// Like this (for example): +// bmp = (BITMAP *)((this->datafile->get_resource(string("WINDOW_BASE_BMP"))->dat)); +// NOTICE: if you check bmp != NULL like this, you will get a seg fault, because ->dat != NULL +// ------------------------------------------------------------------ +DATAFILE *CWDatafile::get_resource(const string resource_name) +{ + DatafileCacheMap::iterator pos; + + pos = datafile_cache_map.find(resource_name); + + if (pos != datafile_cache_map.end() ) + { + return pos->second; // return the value (a *DATAFILE) + } + else + { + if (die_on_failure) + raise_error("FATAL ERROR!\nCWDatafile::get_resource(\"%s\")\nCan't find resource.", resource_name.c_str()); + + return NULL; // error + } + +} + +// ------------------------------------------------------------------ +// This returns a pointer to the whole loaded DATAFILE (in case that you need it for something) +// All coredumps using this are YOUR responsability :P +// ------------------------------------------------------------------ + +DATAFILE *CWDatafile::get_whole_datafile() +{ + return this->datafile; // all seg faults are YOUR responsability from here, don't screw my nice pointer! =^) +} + +// ------------------------------------------------------------------ +// This is a debug function, dumps the data loaded on the console +// ------------------------------------------------------------------ + +void CWDatafile::dump_debug_datafile_data() +{ + DatafileCacheMap::iterator pos; + + cout << "CWDatafile::dump_debug_datafile_data() called " << endl ; + + for (pos = datafile_cache_map.begin(); pos != datafile_cache_map.end(); ++pos) + { + cout << "resource name: '" << pos->first << "'\t" + << "pointer: " << pos->second << endl; + } +} diff --git a/src/filehelp.cpp b/src/filehelp.cpp new file mode 100644 index 0000000..0462026 --- /dev/null +++ b/src/filehelp.cpp @@ -0,0 +1,62 @@ +// -------------------------------------------------------- +// filehelp.cpp +// -------------------------------------------------------- +// Copyright (c) 2003, 2004, Kronoman +// In loving memory of my father +// -------------------------------------------------------- +// I use this for reach the datafile searching in common paths +// Is specially useful for datafiles in Windows, because most +// of the time Windows don't start the program in the executable path, +// and the program is unable to find the datafile. +// -------------------------------------------------------- + +#include +#include "filehelp.h" +//#include + +// -------------------------------------------------------- +// This checks for the filename in several places. +// Returns where the filename is located in buffer +// buffer should be a 2048 bytes char +// If the file is not found, return the filename... +// -------------------------------------------------------- +char *where_is_the_filename(char *buffer, const char *filename) +{ +char str[2048], str2[2048]; // buffer for path making + +// check in current executable path +get_executable_name(str, 2048); +replace_filename(str2, str, filename, 2048); + +if (! exists(filename) ) + { + if (exists(str2)) + { + usprintf(buffer,"%s", str2); + + //printf("--> %s\n", buffer); + + return buffer; + } + else + { + get_executable_name(str, 2048); + replace_filename(str, str, "", 2048); + if (! find_allegro_resource(str2, filename, get_extension(filename), NULL, NULL, NULL, str, 2048) ) + { + usprintf(buffer,"%s", str2); + + //printf("--> %s\n", buffer); + + return buffer; + } + } + } + +// default +usprintf(buffer,"%s", filename); + +//printf("--> %s\n", buffer); + +return buffer; +} diff --git a/src/gamemenu.cpp b/src/gamemenu.cpp new file mode 100644 index 0000000..6f6e6ab --- /dev/null +++ b/src/gamemenu.cpp @@ -0,0 +1,687 @@ +// ------------------------------------------------------------------ +// gamemenu.cpp +// ------------------------------------------------------------------ +// This has the menu of the Kball game +// ------------------------------------------------------------------ +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ------------------------------------------------------------------ + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// DEBUG -- THIS FILE IS A MESS, DO SOMETHING ABOUT IT (SPANK IT?) +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Dig up her bones!!! +#include "filehelp.h" +#include "gamemenu.h" // I need my prototypes +#include "sound.h" // I need the sound system + +// some globals needed by callback +CParticleManager menu_particle_manager; // our own particle managar :^D -- REMEMBER TO CALL NUKE PARTICLES BEFORE LEAVING! +BITMAP *bmp_real_background = NULL; // menu real background +CSoundWrapper *sound_menu = NULL; // sound manager of game menu object + +// ------------------------------------------------------------------ +// This callback function produces the background animation in the menus +// also, polls the music +// ------------------------------------------------------------------ +void menu_callback_animation(CQMenu &d, bool do_logic) +{ + BITMAP *bmp = NULL; + + if (sound_menu) // poll music + sound_menu->music_poll(); + + time_t t_now = time(NULL); // get current time, for displaying it, and also, for little surprise :O + + struct tm *time_now_local = localtime (&t_now); // get finally the time in appropiate format + + bmp = d.get_back_bitmap(); + + if (bmp == NULL) + return ; // can't draw :( + + // DEBUG -- improve this animation + if (do_logic) // update logic + { + menu_particle_manager.update_logic(); + // add particles at random + if (rand() % 100 > 95) + { + int c, x, y; // color, pos of explosion + + switch (rand() % 4) // pick color scheme + { + + case 0: + c = makecol(255, rand() % 256, 0); + break; + + case 1: + c = makecol(0, rand() % 256, 255); + break; + + case 2: + c = makecol(rand() % 256, 0, 255); + break; + + case 3: + c = makecol(rand() % 256, 255, 0); + break; + + default: + c = makecol(rand() % 256, rand() % 256, rand() % 256); + break; + } + + x = (rand() % (int)(SCREEN_W * 0.8)) + (int)(SCREEN_W * 0.1); // use 80% of screen width + y = (rand() % (int)(SCREEN_H * 0.8)) + (int)(SCREEN_H * 0.1); // use 80% of screen height + for (int i = 0; i < rand() % 50 + 100; i++) // add particles + { + if (rand() % 100 < 85) + menu_particle_manager.add_particle(new CCircleParticle(x, y, (float)((rand() % 800) - 400) / 100.0, (float)((rand() % 800) - 400) / 100.0, c, rand() % 60 + 5, rand() % 4 + 1)); + else + menu_particle_manager.add_particle(new CSparkParticle(x, y, (float)((rand() % 800) - 400) / 100.0, (float)((rand() % 800) - 400) / 100.0, c, rand() % 60 + 5, rand() % 3 + 1)); + } + + // a little text message + if (time_now_local->tm_mday == 30 && time_now_local->tm_mon == 3 && rand() % 100 < 75) + menu_particle_manager.add_particle(new CTextParticle(x, y, (float)((rand() % 800) - 400) / 100.0, (float)(rand() % 400 + 150) / -100.0, c, rand() % 60 + 35, (rand() % 1000 < 990 ? string("Kronoman Rulez!") : string("Send me lots of $$$! ;)")) ) ); + + if (time_now_local->tm_year + 1900 >= 2010 && rand() % 100 < 75) + { + char tmpstr[256]; + usprintf(tmpstr, "Yeah! My game still runs in %d", 1900 + time_now_local->tm_year); + + if (time_now_local->tm_year + 1900 > 2082 && rand() % 100 < 75) + menu_particle_manager.add_particle(new CTextParticle(x, y, (float)((rand() % 800) - 400) / 100.0, (float)(rand() % 400 + 150) / -100.0, c, rand() % 60 + 35, string("I must be over 100 years by now... send me dead flowers to my grave :'(") ) ); + else + menu_particle_manager.add_particle(new CTextParticle(x, y, (float)((rand() % 800) - 400) / 100.0, (float)(rand() % 400 + 150) / -100.0, c, rand() % 60 + 35, string(tmpstr) ) ); + } + + } + } + else + { + // draw particles + if (bmp_real_background != NULL) + blit (bmp_real_background, bmp, 0, 0, 0, 0, bmp_real_background->w, bmp_real_background->h); + else + clear(bmp); + + //set_trans_blender(128,128,128,128); + //drawing_mode(DRAW_MODE_TRANS, NULL,0,0); + menu_particle_manager.render(bmp, 0, 0); + + //solid_mode(); + + // show time of day + char tbufstr[256]; // time string buffer + + strftime(tbufstr, 255, "%b %d %Y %H:%M:%S", time_now_local); // This function formats the time data + + text_mode( -1); + + textout(bmp, font, tbufstr, 0, bmp->h - text_height(font), makecol(128, 0, 255)); + } +} + + +CGameMenu::CGameMenu() +{ + mtracer.add("CGameMenu::CGameMenu()"); + + menu_back = NULL; // doble buffer bitmap for menu +} + +CGameMenu::~CGameMenu() +{ + mtracer.add("CGameMenu::~CGameMenu()"); + // DEBUG! + // here we should release RAM used, etc (we should wrap init code and end code in a nice place... not the shit that is right now!) +} + +// ------------------------------------------------------------------ +// This shows the main menu to the user +// This is the menu that let the user choose the main options +// ------------------------------------------------------------------ +void CGameMenu::do_main_menu() +{ + CQMenu menu; // menu to use/show + char tmp_file_buf[2048]; + + mtracer.add("CGameMenu::do_main_menu() started"); + + // I set this so the callback 'knows' the sound manager + sound_menu = &this->soundw; + //soundw.set_enabled(true); + + int ret = 0; + + clear_bitmap(screen); + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64)); + + menu_datafile.load_datafile(where_is_the_filename(tmp_file_buf, "gui.dat")); // load datafile + + // set music + soundw.music_load((DUH *)menu_datafile.get_resource_dat("MENU_MUSIC_XM")); + + bmp_real_background = (BITMAP *)menu_datafile.get_resource_dat("MENU_BMP_BACKGROUND"); // get back bmp image + + // set some gravity to particles + menu_particle_manager.add_dy = 0.08; + + menu_back = create_bitmap(SCREEN_W, SCREEN_H); // DEBUG -- HERE I SHOULD LOAD THE BACKGROUND FROM DATAFILE! + if (menu_back == NULL) + raise_error("do_main_menu()\nERROR: can't create menu_back bitmap!"); + + clear(menu_back); + + menu.set_to_bitmap(screen); + + menu.set_back_bitmap(menu_back); + + menu.set_callback_drawer(menu_callback_animation); + + menu.set_font_s((FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG")); + + menu.set_fg_color_s(makecol(255, 255, 255)); + + menu.set_bg_color_s(makecol(0, 0, 200)); + + menu.set_font_ns((FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG")); + + menu.set_fg_color_ns(makecol(128, 128, 128)); + + menu.set_bg_color_ns( -1); + + menu.set_xywh(320, 100, SCREEN_W, SCREEN_H); + + menu.text_align = 2; // center align + + menu.item_y_separation = 25; + + // add menu items + menu.add_item_to_menu(string("Start new game")); // 0 + + menu.add_item_to_menu(string("Options")); // 1 + + menu.add_item_to_menu(string("Credits")); // 2 + + menu.add_item_to_menu(string("Map editor")); // 3 + + menu.add_item_to_menu(string("Quit")); // 4 + + while (ret != 4) // 4 = quit + { + set_config_file("kball.cfg"); + menu.control.load_configuration_of_controller("KBALL_CONTROLLER"); + + soundw.music_resume(); + + ret = menu.do_the_menu(); // show the menu + + soundw.music_pause(); + + switch (ret) + { + + case 0: // start + do_file_level_selector(); + break; + + case 1: // options + do_options_menu(); + break; + + case 2: // credits + do_about_stuff(); + break; + + case 3: // map editor (from hell they came...) + map_editor.start_map_editor(); + break; + + case 4: // quit + for (int yi = 0; yi < SCREEN_H; yi += 2) + hline(screen, 0, yi, SCREEN_W, makecol(0, 0, 20)); + + textout_centre(screen, (FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG"), "QUIT GAME? Y/N", SCREEN_W / 2 + 2, SCREEN_H / 2 + 2, makecol(0, 0, 0)); + + textout_centre(screen, (FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG"), "QUIT GAME? Y/N", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 255, 255)); + + clear_keybuf(); + + rest(25); + + clear_keybuf(); + + if ((readkey() >> 8) != KEY_Y) + { + ret = -1; // abort exit + } + + break; + } + + rest(100); + clear_keybuf(); + } + + // DEBUG -- REMEMBER HERE TO FREE THE MEMORY/OBJECTS USED! + destroy_bitmap(menu_back); + + menu_particle_manager.nuke_particle_list(); // destroy particles + + menu_datafile.nuke_datafile(); // free RAM of datafile + + bmp_real_background = NULL; +} + +// ------------------------------------------------------------------ +// This lets the user choose a level to start it, or a full campaign +// It WILL start a game +// For this, it list on a menu the files of the current directory +// This should *ONLY* be called from main menu, otherwise data needed +// for menu will be absent on RAM (have a nice SEG FAULT!) +// ------------------------------------------------------------------ +void CGameMenu::do_file_level_selector() +{ + // the level path is defined as [install dir]/levels + + al_ffblk dir_reader; + char path_str[2048]; // path to level to play + char level_path[2048]; // full path to levels in general (dir/kball/bin/levels, etc) + CQMenu menu; + int ret = 0; + bool full_campaign = false; // we are going to play full campaing, or single map? + + mtracer.add("CGameMenu::do_file_level_selector() started\n"); + + set_config_file("kball.cfg"); + menu.control.load_configuration_of_controller("KBALL_CONTROLLER"); + + menu.set_to_bitmap(screen); + menu.set_back_bitmap(menu_back); + menu.set_callback_drawer(menu_callback_animation); + menu.text_align = 2; + menu.item_y_separation = 25; + + // let choose single level, or campaign + menu.set_font_s((FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG")); + menu.set_fg_color_s(makecol(255, 255, 255)); + menu.set_bg_color_s(makecol(0, 0, 200)); + + menu.set_font_ns((FONT *)menu_datafile.get_resource_dat("MENU_FONT_BIG")); + menu.set_fg_color_ns(makecol(128, 128, 128)); + menu.set_bg_color_ns( -1); + menu.set_xywh(320, 100, SCREEN_W, SCREEN_H); + + menu.clear_menu_list(); + menu.add_item_to_menu("Full campaign"); + menu.add_item_to_menu("Single level"); + menu.add_item_to_menu("< Cancel and return >"); + + // get level + get_executable_name(level_path, 2048); + replace_filename(level_path, level_path, "", 2048); + append_filename(level_path, level_path, "levels", 2048); + + fix_filename_slashes(level_path); + mtracer.add("--> \t Level path == %s", level_path); + + soundw.music_resume(); + + switch (menu.do_the_menu()) // show the menu and act in consecuence) + { + + + case 0: + soundw.music_pause(); + usprintf(path_str, "%s/*.fmp", level_path); + full_campaign = true; + break; + + case 1: + soundw.music_pause(); + usprintf(path_str, "%s/*.map", level_path); + full_campaign = false; + break; + + default: + soundw.music_pause(); + return ; + break; + } + + fix_filename_slashes(path_str); + + + // --- Now, list files to use --- + menu.set_font_s((FONT *)menu_datafile.get_resource_dat("MENU_FONT_MEDIUM")); + menu.set_fg_color_s(makecol(255, 255, 255)); + menu.set_bg_color_s(makecol(0, 0, 200)); + + menu.set_font_ns((FONT *)menu_datafile.get_resource_dat("MENU_FONT_MEDIUM")); + menu.set_fg_color_ns(makecol(128, 128, 128)); + menu.set_bg_color_ns( -1); + + menu.set_xywh(320, 100, SCREEN_W, SCREEN_H); + menu.item_y_separation = 5; + + menu.clear_menu_list(); + + // read files and populate menu with them + ret = al_findfirst(path_str, &dir_reader, FA_ARCH); + + while (!ret) + { + menu.add_item_to_menu(dir_reader.name); + ret = al_findnext(&dir_reader); + } + + al_findclose(&dir_reader); + + // if we don't have items, we must return with a message + if (menu.get_menu_item_count() < 1) + { + menu.clear_menu_list(); + menu.add_item_to_menu("ERROR: NO FILE AVAILABLE!"); + + soundw.music_resume(); + + menu.do_the_menu(); + + soundw.music_pause(); + return ; // abort the mission :^P + } + + menu.add_item_to_menu("< Cancel and return >"); + + soundw.music_resume(); + + ret = menu.do_the_menu(); // show the menu + + soundw.music_pause(); + + if (ret == menu.get_menu_item_count() - 1) + return ; // last item == cancel, then, cancel + + // get the file name + usprintf(path_str, "%s/%s", level_path, menu.get_menu_item_text(ret).c_str()); + + fix_filename_slashes(path_str); + + // play the game - rock 'n roll! + set_config_file("kball.cfg"); + + game_kernel.player_ball.control.load_configuration_of_controller("KBALL_CONTROLLER"); + + text_mode(makecol(0, 0, 0)); + + if (full_campaign) + { + mtracer.add("CGameMenu::do_file_level_selector()\n\tStarting campaign game\n"); + + game_kernel.player_ball.lives = BALL_LIVES_CAMPAIGN; // ball of the player lives + game_kernel.player_ball.score = 0; + game_kernel.stats.reset(); + game_kernel.play_a_full_campaign(path_str); + + mtracer.add("CGameMenu::do_file_level_selector()\n\tEnd of campaign game\n"); + } + else + { + // start single level game play + mtracer.add("CGameMenu::do_file_level_selector()\n\tStarting single level game\n"); + + game_kernel.player_ball.lives = BALL_LIVES; // ball of the player lives + game_kernel.player_ball.score = 0; + game_kernel.stats.reset(); + game_kernel.play_a_single_level(path_str); + + mtracer.add("CGameMenu::do_file_level_selector()\n\tEnd of single level game\n"); + } + +} + +// ------------------------------------------------------------------ +// This shows the 'About' stuff +// ------------------------------------------------------------------ +void CGameMenu::do_about_stuff() +{ + mtracer.add("CGameMenu::do_about_stuff();"); + + BITMAP *back = (BITMAP *)menu_datafile.get_resource_dat("ABOUT_BACKGROUND"); // get back bmp image + BITMAP *dbuf = create_bitmap(SCREEN_W, SCREEN_H); + FONT *font_about = (FONT *)menu_datafile.get_resource_dat("MENU_FONT_SMALL"); + SAMPLE *snd_loop_sample = (SAMPLE *)menu_datafile.get_resource_dat("ABOUT_BACK_SOUND"); // back sound + if (dbuf == NULL) + { + mtracer.add("CGameMenu::do_about_stuff() -- ERROR!!! Can't create dbuf bitmap!"); + return ; + } + +#define LINES_SIZE 30 + string about_txt[LINES_SIZE]; // current lines of about text + + DATAFILE *datastream; // data stream for reading about text + + char *readstream; // stream for reading about text + + int xstream = 0; // where I'm reading the stream? + + datastream = menu_datafile.get_resource("ABOUT_TEXT"); // get about text + + readstream = (char *)datastream->dat; + + int text_shift_y = 0; // y displacement for smooth text movement + + + if (snd_loop_sample != NULL) + play_sample(snd_loop_sample, 255, 128, 1000, TRUE); + + clear_keybuf(); + + while (!keypressed()) + { + blit(back, dbuf, 0, 0, 0, 0, back->w, back->h); + + if (text_shift_y > text_height(font_about)) + { + text_shift_y = 0; + + // shift 1 up all text in string array + for (int i = 0; i < LINES_SIZE - 1; i ++) + { + about_txt[i] = about_txt[i + 1]; + } + + // take input from user + about_txt[LINES_SIZE - 1] = ""; + + while (xstream < datastream->size && readstream[xstream] > 13) + { + // read current line from stream + about_txt[LINES_SIZE - 1] = about_txt[LINES_SIZE - 1] + readstream[xstream]; + xstream++; + } + + while (xstream < datastream->size && readstream[xstream] <= 13) + xstream++; // skip bad chars (CR, LF, etc) + + if (xstream >= datastream->size) + xstream ++; // if we reached end, keep counting, so the thing starts again + + if (xstream > datastream->size + LINES_SIZE) + xstream = 0; // start again after a whole clear screen + } + + + // write text + int ytext = -5 - (text_height(font_about) / 2) - text_shift_y; // starting (a little above screen top, to make the effect OK) + + for (int i = 0; i < LINES_SIZE; i ++) + { + text_mode( -1); + // the text is rendered in a way so the text is outlined, thats why is rendered many times + + //textout_centre(dbuf, font_about, about_txt[i].c_str(), SCREEN_W/2+2, ytext-2, makecol(0,0,200)); + //textout_centre(dbuf, font_about, about_txt[i].c_str(), SCREEN_W/2+2, ytext+2, makecol(0,0,200)); + //textout_centre(dbuf, font_about, about_txt[i].c_str(), SCREEN_W/2-2, ytext-2, makecol(0,0,200)); + //textout_centre(dbuf, font_about, about_txt[i].c_str(),SCREEN_W/2-2, ytext+2, makecol(0,0,200)); + + textout_centre(dbuf, font_about, about_txt[i].c_str(), SCREEN_W / 2 + 3, ytext + 3, makecol(0, 0, 0)); + + textout_centre(dbuf, font_about, about_txt[i].c_str(), SCREEN_W / 2, ytext, makecol(255, 255, 255)); + ytext += text_height(font_about); + } + + text_shift_y++; // this makes the 'smooth' scrolling possible :^O (/me pats himself on the back... good job boy...) + + blit(dbuf, screen, 0, 0, 0, 0, back->w, back->h); + + rest(7); // do a little pause (time in ms) + } + + if (snd_loop_sample != NULL) + stop_sample(snd_loop_sample); + + clear_keybuf(); + +#undef LINES_SIZE +} + +// ------------------------------------------------------------------ +// Options menu +// ------------------------------------------------------------------ +void CGameMenu::do_options_menu() +{ + CQMenu menu; + int ret = 0; + mtracer.add("CGameMenu::do_options_menu() started\n"); + + set_config_file("kball.cfg"); + menu.control.load_configuration_of_controller("KBALL_CONTROLLER"); + game_kernel.player_ball.control.load_configuration_of_controller("KBALL_CONTROLLER"); + + menu.set_to_bitmap(screen); + menu.set_back_bitmap(menu_back); + menu.set_callback_drawer(menu_callback_animation); + menu.text_align = 2; + menu.set_xywh(SCREEN_W / 2, 100, SCREEN_W, SCREEN_H); + menu.item_y_separation = 5; + + menu.set_font_s((FONT *)menu_datafile.get_resource_dat("MENU_FONT_MEDIUM")); + menu.set_fg_color_s(makecol(255, 255, 255)); + menu.set_bg_color_s(makecol(0, 0, 200)); + + menu.set_font_ns((FONT *)menu_datafile.get_resource_dat("MENU_FONT_MEDIUM")); + menu.set_fg_color_ns(makecol(128, 128, 128)); + menu.set_bg_color_ns( -1); + + while (ret != 8) + { + menu.clear_menu_list(); + + if (game_kernel.player_ball.control.use_keyboard) + menu.add_item_to_menu("Keyboard is ON"); + else + menu.add_item_to_menu("Keyboard is OFF"); + + menu.add_item_to_menu("Configure Keyboard"); + + if (game_kernel.player_ball.control.use_mouse) + menu.add_item_to_menu("Mouse is ON"); + else + menu.add_item_to_menu("Mouse is OFF"); + + menu.add_item_to_menu("Configure Mouse"); + + if (game_kernel.player_ball.control.use_joystick) + menu.add_item_to_menu("Joystick is ON"); + else + menu.add_item_to_menu("Joystick is OFF"); + + menu.add_item_to_menu("Configure Joystick"); + + if (CSoundWrapper::global_is_enabled()) + menu.add_item_to_menu("Sound is ON"); + else + menu.add_item_to_menu("Sound is OFF"); + + // add sound volume + char tmpstrvol[256]; + usprintf(tmpstrvol, "Sound volume %3d%%", CSoundWrapper::global_get_volume()*100/255); + menu.add_item_to_menu(tmpstrvol); + + menu.add_item_to_menu("< Return >"); + + set_config_file("kball.cfg"); + + menu.control.load_configuration_of_controller("KBALL_CONTROLLER"); + + soundw.music_resume(); + + ret = menu.do_the_menu(ret); + + soundw.music_pause(); + + switch (ret) + { + + case 0: // toggle keyboard + game_kernel.player_ball.control.use_keyboard = !game_kernel.player_ball.control.use_keyboard; + break; + + case 1: // configure keyb + blit (bmp_real_background, screen, 0, 0, 0, 0, bmp_real_background->w, bmp_real_background->h); + game_kernel.player_ball.control.interactive_configuration_keyboard((FONT *)menu_datafile.get_resource_dat("MENU_FONT_SMALL"), makecol(0, 255, 255)); + break; + + case 2: // toggle mouse + game_kernel.player_ball.control.use_mouse = !game_kernel.player_ball.control.use_mouse; + break; + + case 3: // configure mouse + blit (bmp_real_background, screen, 0, 0, 0, 0, bmp_real_background->w, bmp_real_background->h); + game_kernel.player_ball.control.interactive_configuration_mouse((FONT *)menu_datafile.get_resource_dat("MENU_FONT_SMALL"), makecol(0, 255, 255)); + break; + + case 4: // toggle joystick + game_kernel.player_ball.control.use_joystick = !game_kernel.player_ball.control.use_joystick; + break; + + case 5: // configure joy + blit (bmp_real_background, screen, 0, 0, 0, 0, bmp_real_background->w, bmp_real_background->h); + game_kernel.player_ball.control.interactive_configuration_joystick((FONT *)menu_datafile.get_resource_dat("MENU_FONT_SMALL"), makecol(0, 255, 255)); + break; + + case 6: // sound + CSoundWrapper::global_set_enabled(!CSoundWrapper::global_is_enabled()); + break; + + case 7: // toggle sound volume + { + + int v = CSoundWrapper::global_get_volume(); + v += 15; + + if (v > 255) + v = 0; + + CSoundWrapper::global_set_volume(v); + + soundw.music_stop(); // DEBUG - MUSIC GLITCH TO AJUST MUSIC ; THIS IS ALMOST A BUG + soundw.music_start(); + } + break; + + } + + set_config_file("kball.cfg"); + game_kernel.player_ball.control.save_configuration_of_controller("KBALL_CONTROLLER"); + } + +} + diff --git a/src/gerror.cpp b/src/gerror.cpp new file mode 100644 index 0000000..114fb19 --- /dev/null +++ b/src/gerror.cpp @@ -0,0 +1,48 @@ +/* +----------------------------------------------- +Generic Allegro Project Template +By Kronoman - July 2003 +In loving memory of my father +----------------------------------------------- +gerror.c +----------------------------------------------- +Error messages +----------------------------------------------- +*/ + +#ifndef GERROR_C +#define GERROR_C + +#include "gerror.h" + +/* -------------------------------------------------------- + raise_error() + Goes back to text mode, shows the message + and ends the program + -------------------------------------------------------- */ +void raise_error(AL_CONST char *msg, ...) +{ +char *buf; + /* exits the graphics mode */ + set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); + + /* creates the buffer */ + buf = (char *)malloc(4096); + if (buf == NULL) + { + allegro_message("raise_error(): There is a error, and I'm out of virtual memory to show the error message. :^(\n"); } + else + { + /* parse the variable parameters */ + va_list ap; + va_start(ap, msg); + uvszprintf(buf, 4096, msg, ap); + va_end(ap); + + allegro_message("%s\n", buf); + free(buf); + } + exit(-1); /* abort the program */ +} + +#endif diff --git a/src/gkernel.cpp b/src/gkernel.cpp new file mode 100644 index 0000000..1b10bae --- /dev/null +++ b/src/gkernel.cpp @@ -0,0 +1,854 @@ +// ----------------------------------------------- +// KBall Game +// By Kronoman +// Copyright (c) 2003,2004,2005, Kronoman +// In loving memory of my father +// ----------------------------------------------- +// gkernel.h +// ----------------------------------------------- +// Game main loop and kernel +// ----------------------------------------------- + +#include "gkernel.h" // kernel header + +#include "gerror.h" // to show any error that may arise +#include "misc_def.h" // some definitions +#include "filehelp.h" +#include "savescrs.h" // for screenshoots +#include "ui_misc.h" + +// ----------------------------------------------- +// Global variables +// ----------------------------------------------- +// Counter for the main timer +static volatile int speed_counter = 0; + +// this 2 counters are for count the fps +static volatile int frame_count, fps; + +// ---- End of global variables declaration ---- + +// ----------------------------------------------- +// Timing stuff +// ----------------------------------------------- +// Timer callback for the speed counter +void increment_speed_counter() +{ + speed_counter++; +} + +END_OF_FUNCTION(increment_speed_counter); + +// Timer callback for measuring the frames per second +void fps_proc(void) +{ + fps = frame_count; + frame_count = 0; +} + +END_OF_FUNCTION(fps_proc); + +// ========================================================================== +// KERNEL CLASS ITSELF BELOW THIS +// ========================================================================== + +int CGameKernel::timer_installed_count = 0; // how many times we installed the timer; when this is 0, remove/install timers + +// ------------------------------------- +// Constructor +// ------------------------------------- +CGameKernel::CGameKernel() +{ + mtracer.add("CGameKernel::CGameKernel()"); + + game_over = false; + + dbuffer = NULL; // doble buffer + + backdropbmp = NULL; // background bitmap for current level + + current_level_file_name[0] = '\0'; + + stats.reset(); // reset stats +} + +// ------------------------------------- +// Destructor +// ------------------------------------- +CGameKernel::~CGameKernel() +{ + mtracer.add("CGameKernel::~CGameKernel()"); + + this->shutdown(); +} + +// ------------------------------------------------------------------- +// Initializes game (sets timers, loads data, etc) +// ------------------------------------------------------------------- +void CGameKernel::init() +{ + mtracer.add("CGameKernel::init()"); + + if (dbuffer == NULL) + dbuffer = create_bitmap(SCREEN_W, SCREEN_H); // create doble buffer bitmap + + if (dbuffer == NULL) + raise_error("CGameKernel::init()\nERROR: unable to create doble buffer bitmap!\n"); + + clear_bitmap(screen); + + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64)); + + // Load general data for the game + char tmp_file_buf[2048]; + + if (game_map.load_tile_set_from_file(where_is_the_filename(tmp_file_buf, TILESET_FILE)) ) + raise_error("CGameKernel::init()\nERROR!\nCan't load \"%s\" file!\n", TILESET_FILE); // load tileset + + if (main_data_file.load_datafile(where_is_the_filename(tmp_file_buf, SPRITES_FILE)) ) + raise_error("CGameKernel::init()\nERROR!\nUnable to load \"%s\" file!\n", SPRITES_FILE); + + mtracer.add("\tGeneral data loaded OK"); + + // find the ball's sprite texture and mask + player_ball.spr = (BITMAP *)main_data_file.get_resource_dat("BALL"); + + if (player_ball.spr == NULL) + raise_error("CGameKernel::init()\nERROR! Can't find BALL sprite!\n"); + + player_ball.spr_shadow = (BITMAP *)main_data_file.get_resource_dat("BALL_MASK_SHADOW"); + + if (player_ball.spr_shadow == NULL) + raise_error("CGameKernel::init()\nERROR! Can't find BALL_MASK_SHADOW sprite!\n"); + + mtracer.add("\tBall data loaded OK"); + + // find GUI fonts + game_time_font = (FONT *)main_data_file.get_resource_dat("GAME_TIME_FONT"); + + game_score_font = (FONT *)main_data_file.get_resource_dat("GAME_SCORE_FONT"); + + game_messages_font = (FONT *)main_data_file.get_resource_dat("GAME_MESSAGES_FONT"); + + // set some stuff + game_map.timer_tick_rate = GKERNEL_FPSQ; // set timer ratio for map + + // set the timers + if (timer_installed_count == 0) + { + LOCK_VARIABLE(speed_counter); + LOCK_FUNCTION((void *)increment_speed_counter); + + LOCK_VARIABLE(fps); + LOCK_VARIABLE(frame_count); + LOCK_FUNCTION((void *)fps_proc); + + // install the timer + if (install_int_ex(increment_speed_counter, BPS_TO_TIMER(GKERNEL_FPSQ))) + raise_error("CGameKernel::init() : Can't install timer at %d bps\n", GKERNEL_FPSQ); + + if (install_int(fps_proc, 1000)) + raise_error("CGameKernel::init() : Can't install timer to measure fps."); + + mtracer.add("\tTimer installed for first time"); + } + + timer_installed_count++; // just to make sure that nobody else will remove our precious timer until we are ready too + + mtracer.add("\ttimer_installed_count = %d", timer_installed_count); + + // here start all the variables and all stuff + game_over = false; + frame_count = fps = 0; + + current_level_file_name[0] = '\0'; +} + +// ------------------------------------------------------------------- +// shuts down game (unsets timers, unloads data, etc) +// ------------------------------------------------------------------- +void CGameKernel::shutdown() +{ + mtracer.add("CGameKernel::shutdown()"); + + // remove timer(s), only if active + if (timer_installed_count > 0) + { + timer_installed_count--; + + mtracer.add("\ttimer_installed_count = %d", timer_installed_count); + + if (timer_installed_count == 0) + { + remove_int(increment_speed_counter); + remove_int(fps_proc); + timer_installed_count = 0; + + mtracer.add("\tTimer totally removed"); + } + } + + // FREE THE RAM USED + game_map.free_memory(); + + main_data_file.nuke_datafile(); + + particle_manager.nuke_particle_list(); + + if (dbuffer != NULL) + { + destroy_bitmap(dbuffer); + dbuffer = NULL; + } + + if (backdropbmp != NULL) + { + destroy_bitmap(backdropbmp); + backdropbmp = NULL; + } + + current_level_file_name[0] = '\0'; + + music_loader.free_memory(); + + background_loader.free_memory(); +} + +// ------------------------------------------------------------------- +// This is the main game loop +// +// **NOTICE** +// YOU **MUST** CALL IN THIS ORDER: +// gkernel.init(); +// gkernel.load_level_file("level.map"); +// gkernel.game_loop(); +// gkernel.shutdown(); +// +// Will return CBALL_IS_DEAD, or CBALL_EXIT_LEVEL (dead, or won level) +// +// **NOTE** THE SPECIAL RETURN VALUE 'GKERNEL_USER_FINISHED_GAME' MEANS THAT THE PLAYER +// DON'T WANT TO PLAY (HE HIT ESC, SO ABORT THE GAME) +// ------------------------------------------------------------------- +int CGameKernel::game_loop() +{ + int ret = 0; + mtracer.add("CGameKernel::game_loop()"); + + // Main game loop + game_over = false; // we start just now :P + + speed_counter = 0; + + // play music + soundw.music_start(); + + while (game_over == false || ret == 0) // don't go out if this particular game is not over, and if ret don't has a valid value (!= 0) + { + while (speed_counter > 0) + { + ret = this->update_logic(); // update game logic + speed_counter--; + + if (key[KEY_ESC]) + { + game_over = true; + ret = GKERNEL_USER_FINISHED_GAME; + } // with ESC you end the game + + if (ret == CBALL_IS_DEAD || ret == CBALL_EXIT_LEVEL) + game_over = true; // end of this game session + } + + this->update_screen(); // update screen + + frame_count++; // count fps + + if (key[KEY_F12]) // with F12 you take a screenshoot + { + soundw.music_pause(); + + // watermark + textout_right_ex(screen, font, "(C) 2004, Kronoman", SCREEN_W, SCREEN_H - text_height(font), makecol(64, 64, 64),-1); + + save_screenshoot("kball", 0, "bmp"); // save + + // add confirmation message + + ui_misc_text_out_shadow(screen, font, "Screenshoot saved", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(255, 255, 255)); + clear_keybuf(); + rest(800); + clear_keybuf(); + this->update_screen(); + speed_counter = 0; + + soundw.music_resume(); + } + + if (key[KEY_ESC] || ret == GKERNEL_USER_FINISHED_GAME ) // with ESC you end the program + { + soundw.music_pause(); + + // add confirmation message + ui_misc_dark_bmp(screen); + ui_misc_text_out_shadow(screen, game_messages_font, "END GAME? Y/N", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255)); + clear_keybuf(); + rest(25); + clear_keybuf(); + + if ((readkey() >> 8) == KEY_Y) + { + game_over = true; + ret = GKERNEL_USER_FINISHED_GAME; + } + else + { + game_over = false; + ret = 0; // abort the exit -- DEBUG this, may be the misterious 0 value returned... :P + } + + this->update_screen(); + + speed_counter = 0; + + soundw.music_resume(); + } + + + } // end of main game loop + + + soundw.music_stop(); + + return ret; +} + +// ------------------------------------------------------------------- +// this updates 1 logic update of game ; also has code to PAUSE the game (KEY P or PAUSE) +// will return CBALL_IS_DEAD, CBALL_IS_FINE, or CBALL_EXIT_LEVEL +// ------------------------------------------------------------------- +int CGameKernel::update_logic() +{ + int ret = CBALL_IS_FINE; + + // statistics + + // para contar el tiempo + static int last_t_s = 0; + if (++last_t_s > GKERNEL_FPSQ) + { + stats.add_time(0,0,1); + last_t_s = 0; + } + + stats.score = player_ball.score; + // end statistics + + if (key[KEY_PAUSE] || key[KEY_P]) // - pause the game - + { + soundw.music_pause(); + + ui_misc_dark_bmp(screen); + + ui_misc_text_out_shadow(screen, game_messages_font, "- PAUSE -", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255)); + + ui_misc_wait_for_input(); + + this->update_screen(); + speed_counter = 0; + + soundw.music_resume(); + } + + game_map.update_logic(); + particle_manager.update_logic(); + + ret = player_ball.update_logic(); // return value + + if (game_map.time_m*60 + game_map.time_s <= 10) // we are running low in time!, put a warning count + { + static int old_time = 0; + + if (old_time != game_map.time_s) // only each second + { + old_time = game_map.time_s; + + char tmp_str[80]; + usprintf(tmp_str, "%d", game_map.time_s); + + particle_manager.add_particle(new CExplosiveTextParticle(player_ball.x + player_ball.spr->w / 2, player_ball.y, (float)(rand() % 100 - 50) / 100.0, (float)((rand() % 300) + 150) / -100.0, makecol(rand() % 55 + 200, 0, 0), rand() % 15 + 25, tmp_str, game_time_font)); + } + } + + if (game_map.time_m*60 + game_map.time_s <= 0) // time out, we are so DEAD! + { + ret = CBALL_IS_DEAD; + } + + if (ret == CBALL_EXIT_LEVEL) // end of level :^D + { + if (game_map.prize_map_indispensable > 0) // we can't exit if we have prizes left. + { + // the user can't exit now, so drop a visual text saying it + if (rand() % 100 < 40) + particle_manager.add_particle(new CTextParticle(player_ball.x + player_ball.spr->w / 2, player_ball.y + player_ball.spr->h / 2, (float)(rand() % 300 - 150) / 100.0, (float)(rand() % 300) / -100.0, makecol(rand() % 55 + 200, 0, 255), rand() % 25 + 15, string("CAN'T EXIT"), font)); + + ret = CBALL_IS_FINE; // abort level exit + } + + } + + soundw.music_poll(); + + return ret; +} + +// ------------------------------------------------------------------- +// This updates the screen +// ------------------------------------------------------------------- +void CGameKernel::update_screen() +{ + int xc, yc; // x y of camera + static int blink_counter = 0; // counter for blink text timer... :P + static bool show_fps = false; // show fps ? F11 turns it + + if (key[KEY_F11]) // show fps + show_fps = !show_fps; + + blink_counter++; + + if (blink_counter > GKERNEL_FPSQ) + blink_counter = 0; + + // camera position + xc = (int)player_ball.x - SCREEN_W / 2; + + yc = (int)player_ball.y - SCREEN_H / 2; + + if (xc < 0) + xc = 0; + + if (yc < 0) + yc = 0; + + if (xc > TMAP_SIZE*TMAPS_W - SCREEN_W) + xc = TMAP_SIZE * TMAPS_W - SCREEN_W; + + if (yc > TMAP_SIZE*TMAPS_H - SCREEN_H) + yc = TMAP_SIZE * TMAPS_H - SCREEN_H; + + + blit(backdropbmp, dbuffer, 0, 0, 0, 0, backdropbmp->w, backdropbmp->h); // blit background + + //text_mode( -1); // transparent background for text + + + if (show_fps) + { + textprintf_ex(dbuffer, font, 0, SCREEN_H - text_height(font), makecol(255, 255, 255), -1, "%4d fps", fps); + textout_ex(dbuffer, font, "Copyright (c) 2003, 2004, 2005, Kronoman", 0, SCREEN_H - text_height(font)*3, makecol(128, 128, 255),-1); + } + + // ---- 3D scene (this items are draw using the Allegro's 3D software polygon routines) + + clear_scene(dbuffer); // clear 3D scene zbuffer + + player_ball.draw(dbuffer, xc, yc); // draw ball + + game_map.draw_map(dbuffer, xc, yc, SCREEN_W, SCREEN_H, 0, false); // draw map + + game_map.draw_map(dbuffer, xc, yc, SCREEN_W, SCREEN_H, 1, false); // draw top layer (the one with prizes =D) + + render_scene(); // render the 3D scene to the bitmap + + + // ---- end of 3D scene + + // draw particles, they are 2D + particle_manager.render(dbuffer, xc, yc); + + // time left message + textprintf_centre_ex(dbuffer, game_time_font, dbuffer->w / 2 + 3, 3, (game_map.time_m*60 + game_map.time_s > 10) ? makecol(100, 0, 128) : makecol(128, 0, 0),-1,"%02d:%02d", game_map.time_m, game_map.time_s); + + textprintf_centre_ex(dbuffer, game_time_font, dbuffer->w / 2, 0, (game_map.time_m*60 + game_map.time_s > 10) ? makecol(200, 0, 255) : makecol(255, 0, 0),-1, "%02d:%02d", game_map.time_m, game_map.time_s); + + // score message + textprintf_ex(dbuffer, game_score_font, 3, 3, makecol(128, 100, 0), -1,"%010ld", player_ball.score); + + textprintf_ex(dbuffer, game_score_font, 0, 0, makecol(255, 200, 0), -1,"%010ld", player_ball.score); + + // prizes left message + if (game_map.prize_map_indispensable > 0) + { + textprintf_right_ex(dbuffer, game_score_font, dbuffer->w + 3, 3, makecol(0, 64, 128),-1, "%04d", game_map.prize_map_indispensable); + textprintf_right_ex(dbuffer, game_score_font, dbuffer->w, 0, makecol(0, 128, 255),-1, "%04d", game_map.prize_map_indispensable); + } + else + { + int cc1, cc2; // text color for blink text + // the EXIT blinks... :D + if (blink_counter < GKERNEL_FPSQ / 2) + { + cc1 = makecol(0, 100, 128); + cc2 = makecol(0, 200, 255); + } + else + { + cc1 = makecol(128, 100, 0); + cc2 = makecol(255, 200, 0); + + } + + textprintf_right_ex(dbuffer, game_score_font, dbuffer->w + 3, 3, cc1,-1, "EXIT"); + textprintf_right_ex(dbuffer, game_score_font, dbuffer->w, 0, cc2,-1, "EXIT"); + } + + // lives left message + textprintf_ex(dbuffer, game_score_font, 0 + 3, (int)(text_height(game_score_font)*1.05) + 3, makecol(128, 110, 0),-1, "Balls:%2d", player_ball.lives); + + textprintf_ex(dbuffer, game_score_font, 0, (int)(text_height(game_score_font)*1.05), makecol(255, 220, 0),-1, "Balls:%2d", player_ball.lives); + + // debug, remove this! + //textprintf(dbuffer, font,0,0,makecol(255,255,0), "x%3f,y%3f", player_ball.anglex,player_ball.angley); + + + //stats.print(dbuffer, 50, makecol(255,255,255), -1, font); // DEBUG -- REMOVE THIS OR MAKE IT TOGGEABLE WITH A KEY + + blit(dbuffer, screen, 0, 0, 0, 0, dbuffer->w, dbuffer->h); // dump the render to screen + + // yield_timeslice(); // play nice with multitask + //rest(0); // the way of playing nice with multitask since Allegro 4.1.15 WIP +} + +// ------------------------------------------------------------------- +// Helper function, takes a bitmap, and returns a new allocated bitmap +// with the 1st bitmap tiled in the size of the screen +// this serves for the background imagen +// ------------------------------------------------------------------- +BITMAP *CGameKernel::tile_bmp_to_screen_size(BITMAP *bmp) +{ + BITMAP *b = NULL; + + if (bmp == NULL) + raise_error("CGameKernel::tile_bmp_to_screen_size()\nERROR: bitmap null; can't tile it!\n"); + + b = create_bitmap(SCREEN_W, SCREEN_H); + + if (b == NULL) + raise_error("CGameKernel::tile_bmp_to_screen_size()\nERROR: unable to create bitmap!\n"); + + for (int y = 0; y < SCREEN_H + bmp->h; y += bmp->h) + for (int x = 0; x < SCREEN_W + bmp->w; x += bmp->w) + blit(bmp, b, 0, 0, x, y, bmp->w, bmp->h); + + return b; +} + +// ----------------------------------------------------------------------- +// Loads a level from a file, and sets the game ready to play on that level +// +// *MUST* BE CALLED BEFORE STARTING THE GAME LOOP! +// WILL LOAD THE MAP, BACKGROUND, etc +// ----------------------------------------------------------------------- +void CGameKernel::load_level_file(const char *file) +{ + mtracer.add("CGameKernel::load_level_file(\"%s\")", file); + + if (game_map.load_map_from_file(file)) + raise_error("CGameKernel::load_level_file()\nERROR!\nCan't load level '%s'!\n", file); + + usprintf(current_level_file_name, "%s", file); // save current file name + + // set the player position and properties + player_ball.x = (game_map.sxp * TMAPS_W) + (TMAPS_W - BALL_RADIUS * 2) / 2; + + player_ball.y = (game_map.syp * TMAPS_H) + (TMAPS_H - BALL_RADIUS * 2) / 2; + + player_ball.z = 1.0; + + player_ball.dx = player_ball.dy = player_ball.dz = 0.0; + + player_ball.ctmap = &game_map; + + player_ball.particle_manager = &particle_manager; + + particle_manager.nuke_particle_list(); // CLEAN THE PARTICLES FOR THE NEW LEVEL :P + + // DEBUG -- BACKGROUND! - DECIDIR QUE CORNO HAGO CON ESTO + // Load the background from info stored on the level map + if (backdropbmp != NULL) + { + destroy_bitmap(backdropbmp); + backdropbmp = NULL; + } + + backdropbmp = tile_bmp_to_screen_size( background_loader.get_background("backgr.dat", game_map.background_index) ); + + mtracer.add("\tLoaded background (%d) of level OK", game_map.background_index); + + // load music + soundw.music_load(music_loader.get_music("music_l.dat", game_map.music_index)); + mtracer.add("\tLoaded music (%d) of level OK", game_map.music_index); +} + + +// ----------------------------------------------------------------------- +// This will start a single level game +// Will return CBALL_IS_DEAD(totally DEAD, full game over, 0 lives) +// CBALL_EXIT_LEVEL (only useful for campaign), or GKERNEL_USER_FINISHED_GAME if player cancelled game +// ----------------------------------------------------------------------- +int CGameKernel::play_a_single_level(char *level_filename) +{ + int ret; + bool do_the_loop = true; // loop, for loading level many times, etc + int old_score = 0; // this is for restoring the score when the player loses a ball + + mtracer.add("\nCGameKernel::play_a_single_level('%s');\n", level_filename); + + this->init(); // init game kernel + + old_score = player_ball.score; + + while (do_the_loop) + { + mtracer.add("\tLoading and playing level."); + + this->load_level_file(level_filename); + + ret = this->game_loop(); + + mtracer.add("\t--> this->game_loop = %4d\n", ret); + + // according to ret value, show the player, if we won, we lose, or if we have lives to keep playing, etc + // the { } in the case : are there to force the scope, so don't remove them (I know what I'm doing...) + switch (ret) + { + + case CBALL_EXIT_LEVEL: + { + // the player won the level, OK, so we exit + // this value will be used by campaign function, if available + + this->update_screen(); + + ui_misc_dark_bmp(screen); + ui_misc_text_out_shadow(screen, game_messages_font, "YOU WON THE LEVEL!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255)); + + // GIVE BONUS + int bonus = 0; + //text_mode(makecol(0, 0, 64)); + + clear_keybuf(); + rest(550); + clear_keybuf(); + + rectfill(screen, 0, SCREEN_H / 2 + text_height(game_messages_font), SCREEN_W, SCREEN_H, makecol(0, 0, 64)); + + + textout_centre_ex(screen, game_messages_font, "TIME BONUS", SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font), makecol(0, 255, 255),makecol(0,0,64)); + + for (int i = 0; i < game_map.time_m*60 + game_map.time_s; i++) + { + textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*2, makecol(0, 255, 255),makecol(0,0,64), " %04d x 100 = %07d ", i, bonus); + bonus += 100; // 100 bonus points for each second + + // DEBUG -- falta SONIDO! + + if (keypressed() || mouse_b || joy[0].button[0].b) + break; // let the user cancel the count + + poll_joystick(); + + if (keyboard_needs_poll()) + poll_keyboard(); + + if (mouse_needs_poll()) + poll_mouse(); + + rest(10); + } + + // give real bonus + bonus = (game_map.time_m * 60 + game_map.time_s) * 100; + + player_ball.score += bonus; + stats.score = player_ball.score; + + textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*2, makecol(0, 255, 255), makecol(0,0,64)," %04d x 100 = %07d ", game_map.time_m*60 + game_map.time_s, bonus); + + textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*3, makecol(0, 255, 255), makecol(0,0,64),"Score = %010ld", player_ball.score); + + //text_mode( -1); + + textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 255),-1); + + ui_misc_wait_for_input(); + + do_the_loop = false; + } + + break; // CBALL_EXIT_LEVEL + + + case CBALL_IS_DEAD: + { + this->update_screen(); + + ui_misc_dark_bmp(screen); + + if (game_map.time_m*60 + game_map.time_s > 0) + ui_misc_text_out_shadow(screen, game_messages_font, "YOU LOST THE BALL!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255)); + else + ui_misc_text_out_shadow(screen, game_messages_font, "TIME UP!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255)); + + textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 255),-1); + + ui_misc_wait_for_input(); + + // the player died, rest one live + player_ball.lives--; + stats.blost++; // statistics + stats.score = player_ball.score; + + player_ball.anglex = player_ball.angley = 0.0; // reset ball rotations + + if (player_ball.lives < 0) // the player is dead. 0 lives. GAME OVER. :( + { + + BITMAP *bmp_game_over = (BITMAP *)main_data_file.get_resource_dat("GAME_OVER_BMP"); + + blit(bmp_game_over, screen, 0, 0, 0, 0, bmp_game_over->w, bmp_game_over->h); + + stats.score = player_ball.score; + stats.print(screen, 150, makecol(255,255,0), -1, game_score_font); // print stats + + // sound + SAMPLE *game_over_smp = (SAMPLE *)main_data_file.get_resource_dat("GAME_OVER_WAV"); + play_sample(game_over_smp,255,128,1000,0); + + textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(128, 0, 0),-1); + + ui_misc_wait_for_input(); + + do_the_loop = false; + + stats.reset(); // reset stats + } + else + { + // perdio una vida, pero le queda, jugar de nuevo el nivel + do_the_loop = true; // load the next level, damn + player_ball.score = old_score; // restore score + } + + } + + break; // end of CBALL_IS_DEAD + + case GKERNEL_USER_FINISHED_GAME: + do_the_loop = false; // the player hit , end of game :( + break; // GKERNEL_USER_FINISHED_GAME + + default: // it should NEVER come here + raise_error("ERROR at CGameKernel::play_a_single_level\nGuru meditation:\ngame_loop returned a invalid value (ret = %d)\n", ret); + break; + } + + } + + this->shutdown(); // shutdown this beauty... + + return ret; +} + + +// ----------------------------------------------------------------------- +// This will start a full campaign game +// ----------------------------------------------------------------------- +void CGameKernel::play_a_full_campaign(char *level_filename) +{ + char tmp_str[4096]; // buffer to handle level filenames + int current_level = 1; // current level number + bool finished = false; + int ret = 0; + + mtracer.add("\nCGameKernel::play_a_full_campaign('%s')\n", level_filename); + + while (!finished) + { + // load and play level + usprintf(tmp_str, "%s#%d_MAP", level_filename, current_level); + fix_filename_slashes(tmp_str); + + // check if the level exits before trying to play it -- DEBUG -- slow code here! do some better + PACKFILE *fp_test = pack_fopen(tmp_str, F_READ); + + if (fp_test == NULL) + { + // EL NIVEL NO EXISTE, LISTO, GANO + + // the player won the campaign + + // NOTA: dado que cuando llega aca, ya hizo el this->shutdown, los .DAT estan DESCARGADOS + // por eso, recargo el bitmap y font en RAM y el sonido... + + clear_bitmap(screen); + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64)); + + DATAFILE *dattmp = load_datafile_object(SPRITES_FILE, "WON_BMP"); + if (!dattmp) + raise_error("CGameKernel::play_a_full_campaign\nCan't load %s -> WON_BMP\n", SPRITES_FILE); + BITMAP *bmp_won = (BITMAP *)dattmp->dat; + + DATAFILE *dattmp2 = load_datafile_object(SPRITES_FILE, "GAME_SCORE_FONT"); + if (!dattmp2) + raise_error("CGameKernel::play_a_full_campaign\nCan't load %s -> GAME_SCORE_FONT\n", SPRITES_FILE); + FONT *fs = (FONT *)dattmp2->dat; + + + blit(bmp_won, screen, 0, 0, 0, 0, bmp_won->w, bmp_won->h); + + stats.print(screen, 150, makecol(0,0,255), -1, fs); + + // sound + DATAFILE *dattmp3 = load_datafile_object(SPRITES_FILE, "WON_WAV"); + if (dattmp3) + { + play_sample((SAMPLE *)dattmp3->dat,255,128,1000,0); + } + + textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 0),-1); + + ui_misc_wait_for_input(); + + unload_datafile_object(dattmp); // bye bye bitmap... :) + unload_datafile_object(dattmp2); // bye bye font... :) + if (dattmp3) + unload_datafile_object(dattmp3); // bye bye sample + + dattmp = NULL; + + stats.reset(); + + return ; // chau... finalizo! + } + + pack_fclose(fp_test); + + ret = this->play_a_single_level(tmp_str); + + mtracer.add("\t--> this->play_a_single_level('%s') = %4d\n", tmp_str, ret); + + if (ret == CBALL_EXIT_LEVEL) // exit level! + { + current_level++; + finished = false; + } + else + { + finished = true; // any other value means GAME OVER, sorry d00d, insert coin :P + } + + } + +} + diff --git a/src/intro.cpp b/src/intro.cpp new file mode 100644 index 0000000..3af144d --- /dev/null +++ b/src/intro.cpp @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------ +// intro.cpp +// ------------------------------------------------------------------ +// This is the intro and exit secuence for the game - everything hardcoded, sorry +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#include +#include "intro.h" +#include "cwdata.h" + +void kball_do_the_intro() +{ + CWDatafile data; + + clear_bitmap(screen); + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64)); + + data.load_datafile("intro.dat"); + + BITMAP *logo = (BITMAP *)data.get_resource_dat("LOGO_KRONOMAN_BMP"); + + SAMPLE *krono_wav = (SAMPLE *)data.get_resource_dat("KRONOMAN_WAV"); + + clear_bitmap(screen); + + blit(logo, screen,0,0,0,0,logo->w,logo->h); + + rest(1000); + + play_sample(krono_wav, 255,128,1000,0); + + rest(3000); + + clear_bitmap(screen); + data.nuke_datafile(); +} + +void kball_do_the_exit() +{ + char tmp_str[2048]; + BITMAP *face = NULL; + CWDatafile data; + DATAFILE *datastream; // data stream for reading about text + char *readstream; // stream for reading about text + int xstream = 0; // where I'm reading the stream? + + clear_bitmap(screen); + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64)); + + data.load_datafile("intro.dat"); + + datastream = data.get_resource("INTRO_TXT"); // get about text + + readstream = (char *)datastream->dat; + + usprintf(tmp_str, "%d_BMP", rand()%5+1); + face = (BITMAP *)data.get_resource_dat(tmp_str); + + clear_to_color(screen, makecol(255,255,255)); + blit(face, screen,0,0,(SCREEN_W - face->w) / 2, 5,face->w,face->h); + // poner texto + int y = face->h+5; + int x = SCREEN_W / 2; + int c = makecol(0,0,0); + int c2 = makecol(0,0,0); + char buf[1024]; + int xbuf = 0; + for (xstream =0; xstream < datastream->size; xstream++) + { + if (readstream[xstream] > '\n') + { + buf[xbuf] = readstream[xstream]; + xbuf++; + } + else + { + if (readstream[xstream] == '\n') + { + buf[xbuf] = '\0'; + //textout_centre_ex(screen, font, buf, x+1,y+1,c2,-1); + textout_centre_ex(screen, font, buf, x,y,c,-1); + + xbuf = 0; + x = SCREEN_W/2; + y += text_height(font); + } + } + } + buf[xbuf] = '\0'; + textout_centre_ex(screen, font, buf, x,y,c,-1); + + rest(5000); + clear_bitmap(screen); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5b7ec9f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,235 @@ +// ----------------------------------------------- +// KBall +// ----------------------------------------------- +// By Kronoman +// Copyright (c) 2003, 2004, Kronoman +// In loving memory of my father +// Made in Argentina +// ----------------------------------------------- +// main.c +// ----------------------------------------------- +// Start up code ; program entry point. +// ----------------------------------------------- + +#include // Allegro : http://alleg.sf.net/ +#include // DUMB : http://dumb.sf.net/ + +#include // for checking command line + +#include "gerror.h" // error reporting +#include "mapedit.h" // built in map editor -:^D +#include "gamemenu.h" // game menu +#include "intro.h" +#include "mytracer.h" + +class CMain +{ +public: + CMain() + { + mtracer.add("CMain()::CMain()"); + } + ~CMain() + { + mtracer.add("CMain()::~CMain()"); + } + + void start(bool want_map_editor); + +private: + CMyTracer mtracer; // debug tracer + CGameMenu game_menu; // game menu(s) + CMapEditor map_editor; // map editor +}; + +void CMain::start(bool want_map_editor) +{ + mtracer.add("CMain()::start()"); + + clear_bitmap(screen); + + if (want_map_editor) + { + mtracer.add("CMain::start()\n\tStarting map editor"); + map_editor.start_map_editor(); + } + else + { + // DEBUG -- here you can construct a more complex loop, for using different stages + // like presentation, game menu, and game start, or just a menu, etc + + mtracer.add("CMain::start()\n\tStarting main menu"); + game_menu.do_main_menu(); // main menu + } +} + + +// -------------------------------------------------------- +// main() +// Program entry point +// -------------------------------------------------------- + + +int main(int argc, char *argv[] ) +{ + int i; + int vid_m = GFX_AUTODETECT_FULLSCREEN; // screen desired graphic mode + int game_color_depth = -1; // default color depth, -1 = autodetect from desktop default + int vid_w = 640; // desired video resolution + int vid_h = 480; + bool want_sound = true; // want sound? + bool want_map_editor = false; // want to use the map editor? + int desk_bpp = 0; + + CMyTracer mtracer; // debug tracer + CMyTracer::DISABLE_TRACE = true; // by default, we not debug trace + + allegro_init(); // init allegro + atexit(&dumb_exit); // DUMB exit functions + + // check command line parameters + for (i=1; i < argc; i++) + { + if (stricmp(argv[i], "-trace") == 0) + CMyTracer::DISABLE_TRACE = false; // enable debug tracer + + if (stricmp(argv[i], "-wn") == 0) + vid_m = GFX_AUTODETECT_WINDOWED; + + if (stricmp(argv[i], "-nosound") == 0) + want_sound = false; + + if (stricmp(argv[i], "-w") == 0) + vid_m = GFX_AUTODETECT_WINDOWED; + + if (stricmp(argv[i], "-bpp16") == 0) + game_color_depth = 16; + + if (stricmp(argv[i], "-bpp15") == 0) + game_color_depth = 15; + + if (stricmp(argv[i], "-bpp32") == 0) + game_color_depth = 32; + + if (stricmp(argv[i], "-bpp24") == 0) + game_color_depth = 24; + + if (stricmp(argv[i], "-bpp8") == 0) + { + raise_error("main() : Sorry, this program don't support 8 bpp displays.\nThis program needs a true color display at %3d x %3d resolution.\nTip: Try removing the -bpp8 switch from the command line invocation.", vid_w, vid_h); + } + + // development options + if (stricmp(argv[i], "-mapeditor") == 0) + want_map_editor = true; // use the map editor... cool + } + + // dumb_register_packfiles(); // DUMB will read from packfiles - NO NECESARIO, no cargo nada del disco, solo a traves de datafiles + + // register DUMB music files -- DEBUG : registrar solo el formato que voy a usar! + dumb_register_dat_it(DUMB_DAT_IT); + dumb_register_dat_xm(DUMB_DAT_XM); + dumb_register_dat_s3m(DUMB_DAT_S3M); + dumb_register_dat_mod(DUMB_DAT_MOD); + + set_window_title("KBall"); // setear nombre de la ventana :o + + mtracer.add("\n--------------------------------------------------------\nmain() started\n"); + + desk_bpp = desktop_color_depth(); // using the same color depth as the host will make the game run faster + if (desk_bpp != 8 && desk_bpp != 0 && game_color_depth == -1) + { + // use the color depth of desktop + game_color_depth = desk_bpp; + } + + if (game_color_depth < 8) + game_color_depth = 16; // safe check + + srand(time(NULL)); // init random numbers + + if (install_timer() != 0) + raise_error("main() : can't install timer driver"); + + mtracer.add("\tTimer installed"); + + if (install_keyboard() != 0) + raise_error("main() : can't install keyboard driver"); + + install_mouse(); + + install_joystick(JOY_TYPE_AUTODETECT); + + mtracer.add("\tInput devices installed"); + + if (want_sound) + { + //if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL) ) raise_error("install_sound() failed.\n'%s'\nTry disabling the sound with -nosound parameter.\n", allegro_error); + + reserve_voices(8, 0); + set_volume_per_voice(2); // warning - this may cause distortion + + mtracer.add("\tInstall sound"); + if (!install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL) ) + mtracer.add("\t\tSound system OK!\n" ); + else + mtracer.add("\t\tSound system FAILED! : %s", allegro_error); + + set_volume(255,-1); + //set_hardware_volume(255,-1); + } + + + // set graphics mode + set_color_depth(game_color_depth); + + mtracer.add("\tTrying to run in %3dx%3d@%2d bpp",vid_w, vid_h, game_color_depth); + + if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) + { + set_color_depth(16); + if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) + { + set_color_depth(15); + if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) + { + set_color_depth(32); + if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) + { + set_color_depth(24); + if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) + { + raise_error("main() : I can't set the graphics mode (%3d x %3d @ %2d bpp)\nI also tried with 16 bpp, 15 bpp, 32 bpp and 24 bpp\n", vid_w, vid_h, game_color_depth); + } + } + } + } + } + + set_color_conversion(COLORCONV_TOTAL | COLORCONV_KEEP_TRANS); + // I need software 3D code for this game, so I init the 3D scene system of Allegro - DEBUG + mtracer.add("Starts 3D software rendered scene -> create_scene()"); + create_scene(4800,2000); // max n of edges and polygons to render - DEBUG - take care of this to see if the calculus is right + + mtracer.add("-- Allegro start up code done --\n\nStarting CMain class"); + + kball_do_the_intro(); // INTRO OF GAME + + textout_centre_ex(screen, font, "[ Please wait... loading... ]", SCREEN_W/2, SCREEN_H/2, makecol(255,255,255), makecol(0,0,64)); + + CMain *cmain; + cmain = new(CMain); + cmain->start(want_map_editor); + delete(cmain); + + kball_do_the_exit(); // exit of game + + mtracer.add("\nmain() finished"); + + // Release memory used by the 3D code -- DEBUG + mtracer.add("Stops 3D software rendered scene -> destroy_scene()"); + destroy_scene(); + + return 0; // normal end of the program +} +END_OF_MAIN(); diff --git a/src/mapedit.cpp b/src/mapedit.cpp new file mode 100644 index 0000000..86abe2b --- /dev/null +++ b/src/mapedit.cpp @@ -0,0 +1,753 @@ +// ----------------------------------------------- +// mapedit.cpp +// ----------------------------------------------- +// Built-in map editor (totally l33t!) +// ----------------------------------------------- +// By Kronoman - Copyright (c) 2003 +// In loving memory of my father +// ----------------------------------------------- + +#include "mapedit.h" +#include "filehelp.h" +#include "sound.h" +#include "musiclvl.h" + +CMapEditor::CMapEditor() +{ + mtracer.add("CMapEditor::CMapEditor()"); + + tx = ty = 0; + ts = 1; + tl = 0; + + dbuffer = NULL; + mcursor = NULL; + + ykprint = ckprint = 0; + + draw_grid = true; + +} + +CMapEditor::~CMapEditor() +{ + // debug, source need here? maybe... somebody call 911 + mtracer.add("CMapEditor::~CMapEditor()"); +} + +// ----------------------------------------------- +// Helper functions +// ----------------------------------------------- +void CMapEditor::kprint(char *msg) +{ + textout_ex(screen, font, msg, 0, ykprint, ckprint, -1); + ykprint += text_height(font); +} + +void CMapEditor::map_editor_help_message() +{ + ykprint = 0; + ckprint = makecol(255,255,0); + scare_mouse(); + clear_bitmap(screen); + kprint("Map Editor - Help"); + kprint("-----------------"); + kprint(""); + kprint(""); + + ckprint = makecol(128,128,255); + kprint("Copyright (c) 2003, 2004, Kronoman"); + kprint("In loving memory of my father"); + + kprint(""); + kprint(""); + + ckprint = makecol(255,255,255); + kprint("Reference:"); + kprint("----------"); + kprint(""); + kprint(""); + kprint("Arrow keys: scroll the map; press SHIFT+Arrow keys for faster scroll."); + kprint(""); + kprint("Left mouse button or ENTER: set pointed tile to current selected tile type."); + kprint(""); + kprint("Right mouse button or DELETE: delete tile on that area."); + kprint(""); + kprint("Middle mouse button or SPACE: set start position for player."); + kprint(""); + kprint("Z,X or mouse wheel: select tile type to use. (Mouse cursor will reflect changes)"); + kprint(""); + kprint("F: find player start position."); + kprint(""); + kprint("G: toggle drawing of grid."); + kprint(""); + kprint("C: clear current map."); + kprint(""); + kprint("P: set map properties (TIME, etc)"); + kprint(""); + kprint("S: save current map to a file."); + kprint(""); + kprint("L: load map from a file."); + kprint(""); + kprint("V: toggle 'show only current layer'"); + kprint(""); + kprint("D: dump tile set configuration to text file \"TILE_SET_CFG.TXT\""); + kprint(""); + kprint("I: show information about current selected tile"); + kprint(""); + kprint("B: select background image for this level."); + kprint(""); + kprint("M: select background music for this level."); + kprint(""); + kprint("PLUS (+) and MINUS (-): change current layer in edition"); + kprint(""); + kprint("ESC: exit map editor."); + kprint(""); + kprint("F1: show this help"); + kprint(""); + kprint(""); + ckprint = makecol(128,128,128); + kprint("-- Press any key to continue --"); + unscare_mouse(); + clear_keybuf(); + readkey(); +} + +// layer = layer to redraw, or -1 for full layers +// grid = true/false (draw or not the grid) +void CMapEditor::redraw_the_tile_map(int layer, bool grid) +{ + clear_bitmap(dbuffer); + + clear_scene(dbuffer); // clear 3D scene zbuffer + if (layer > -1) + { + game_map.draw_map(dbuffer, tx, ty, SCREEN_W, SCREEN_H, layer, grid); // draw it + } + else + { + for (int l=0; l < MAP_LAYERS; l++) + game_map.draw_map(dbuffer, tx, ty, SCREEN_W, SCREEN_H, l, grid); // draw it + } + render_scene(); // render the 3D scene to the bitmap + + scare_mouse(); + blit(dbuffer, screen,0,0,0,0,dbuffer->w,dbuffer->h); + unscare_mouse(); +} + +void CMapEditor::do_change_mouse_cursor() +{ + // set mouse cursor on the tile to put, and also, show it + if (game_map.tile_set[ts].spr != NULL) + { + stretch_blit(game_map.tile_set[ts].spr, mcursor, 0,0,game_map.tile_set[ts].spr->w, game_map.tile_set[ts].spr->h,0,0, mcursor->w,mcursor->h); + } + else + { + clear_to_color(mcursor, makecol(255,0,255)); + + if (ts == 0) + textout_ex(mcursor, font, "CLS", 0, 0, makecol(128,128,255), -1); + else + textprintf_ex(mcursor, font, 0,0,makecol(255,0,0),-1, "%d", ts); + } + set_mouse_sprite(mcursor); + show_mouse(screen); +} + +// ----------------------------------------------- +// Map edit entry point +// ----------------------------------------------- +void CMapEditor::start_map_editor() +{ + bool dirty = false; + bool finish = false; + bool socl = false; + int omx=0,omy=0,old_mouse_z; + char str[2048]; + str[0] = '\0'; + usprintf(str, "levels"); + + mtracer.add("CMapEditor::start_map_editor()"); + + dbuffer = create_bitmap(SCREEN_W, SCREEN_H); + if (dbuffer == NULL) + raise_error("ERROR: unable to create doble buffer bitmap!\n"); + + mcursor = create_bitmap(32,32); + if (mcursor == NULL) + raise_error("ERROR: unable to create mouse cursor!\n"); + + clear_bitmap(screen); + clear_bitmap(mcursor); + clear_bitmap(dbuffer); + + + + textout_centre_ex(screen, font, "[Map Editor]-[ Please wait... loading... ]", SCREEN_W/2, SCREEN_H/2, makecol(255,255,255), makecol(0,64,0)); + + char tmp_file_buf[2048]; + if (game_map.load_tile_set_from_file(where_is_the_filename(tmp_file_buf,TILESET_FILE)) ) + raise_error("ERROR! Can't load \"%s\" file!\n", TILESET_FILE); // load tileset + + game_map.empty_the_map(); // reset the map + tx = 0, ty = 0; // current scroll in the map + ts = 1; // current tile selected from the tile set (1..255) + tl = 0; // current layer of map selected 0..MAP_LAYERS-1 (MAP_LAYERS in tmap.h) + + redraw_the_tile_map(-1, draw_grid); + textout_centre_ex(screen, font, "[ Map editor - Press F1 for help ]", SCREEN_W/2, SCREEN_H/2, makecol(255,255,0),makecol(128,0,0)); + do_change_mouse_cursor(); + + // go to map start point + tx = (game_map.sxp-(SCREEN_W/2/TMAPS_W)) * TMAPS_W; + ty = (game_map.syp-(SCREEN_H/2/TMAPS_H)) * TMAPS_H; + + // main editor loop + while (!finish) + { + + if (dirty) + { + if (socl) + redraw_the_tile_map(tl,draw_grid); + else + redraw_the_tile_map(-1,draw_grid); + + dirty = false; + } + + // little 'HUD' + textprintf_ex(screen, font, 0,0,makecol(255,255,255), makecol(0,0,0), "[%03d,%03d]-[%3d]-[L:%2d(%c)]", tx/TMAPS_W,ty/TMAPS_H,ts,tl, socl ? 'X' : ' '); + + old_mouse_z = mouse_z; + while (!keypressed() && !mouse_b &&mouse_z == old_mouse_z) // wait until user action + { + if (keyboard_needs_poll()) + poll_keyboard(); + if (mouse_needs_poll()) + poll_mouse(); + //yield_timeslice(); // play nice with multitasking + + // sub hud + if (omx != mouse_x/10 || omy != mouse_y/10) + { + scare_mouse(); + textprintf_ex(screen, font, 0,text_height(font),makecol(128,255,128), makecol(0,0,0), "[%03d,%03d]", (tx+mouse_x)/TMAPS_W, (ty+mouse_y)/TMAPS_H); + unscare_mouse(); + // div by 10 to get few precission + omx = mouse_x/10; + omy = mouse_y/10; + } + } + + if (mouse_b & 1 || key[KEY_ENTER]) // click of left mouse == set the tile + { + // DEBUG : NOTICE : if the tile is a prize, it always go on tile 1, so the player can pickup it + game_map.set_tile_type((tx+mouse_x)/TMAPS_W, (ty+mouse_y)/TMAPS_H, (game_map.tile_set[ts].is_a_prize) ? 1 : tl, ts); + dirty = true; + while (mouse_b) + { + if (mouse_needs_poll()) + poll_mouse(); + } + ; // wait until mouse release + } + + if (mouse_b & 2 || key[KEY_DEL]) // click of right mouse == clear the tile + { + game_map.set_tile_type((tx+mouse_x)/TMAPS_W, (ty+mouse_y)/TMAPS_H,tl, 0); + dirty = true; + while (mouse_b) + { + if (mouse_needs_poll()) + poll_mouse(); + } + ; // wait until mouse release + } + + if (mouse_b & 4 || key[KEY_SPACE]) // change the start pos of the player + { + game_map.sxp = (tx+mouse_x)/TMAPS_W; + game_map.syp = (ty+mouse_y)/TMAPS_H; + if (game_map.sxp < 0) + game_map.sxp = 0; + if (game_map.sxp > 255) + game_map.sxp = 255; + if (game_map.syp < 0) + game_map.syp = 0; + if (game_map.syp > 255) + game_map.syp = 255; + dirty = true; + while (mouse_b) + { + if (mouse_needs_poll()) + poll_mouse(); + } + ; // wait until mouse release + } + + // with shift, you move faster + + if (key_shifts & KB_SHIFT_FLAG) + { + if (key[KEY_LEFT] ) + { + tx -= 64; + dirty = true; + } + if (key[KEY_RIGHT] ) + { + tx += 64; + dirty = true; + } + if (key[KEY_UP ] ) + { + ty -= 64; + dirty = true; + } + if (key[KEY_DOWN] ) + { + ty += 64; + dirty = true; + } + } + else + { + // without it, slower + if (key[KEY_LEFT] ) + { + tx -= 24; + dirty = true; + } + if (key[KEY_RIGHT] ) + { + tx += 24; + dirty = true; + } + if (key[KEY_UP ] ) + { + ty -= 24; + dirty = true; + } + if (key[KEY_DOWN] ) + { + ty += 24; + dirty = true; + } + } + + if (key[KEY_X] || mouse_z > old_mouse_z) // change tile selected + { + ts++; + if (ts > 255) + ts = 0; + do_change_mouse_cursor(); + } + + if (key[KEY_Z] || mouse_z < old_mouse_z) // change tile selected + { + ts--; + if (ts < 0) + ts = 255; + do_change_mouse_cursor(); + } + + if (key[KEY_ESC] ) // exit editor + { + set_mouse_sprite(NULL); + if (alert("Exit map editor", "Are you sure?", NULL, "Yes", "No", 'y', 'n') == 1) + finish = true; + do_change_mouse_cursor(); + } + + if (key[KEY_I]) // show info about current tile + { + set_mouse_sprite(NULL); + ykprint = 0; + ckprint = makecol(255,255,255); + scare_mouse(); + clear_bitmap(screen); + kprint("Info about tile"); + kprint(""); + kprint(""); + + if (game_map.tile_set[ts].spr != NULL) + { + draw_sprite(screen, game_map.tile_set[ts].spr, 0, ykprint ); + ykprint += (int)(game_map.tile_set[ts].spr->w*1.1); + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint,-1, "Sprite: %s", game_map.tile_set[ts].spr_name ); + } + else + kprint("This tile don't has sprite defined"); + + kprint(""); + + if (game_map.tile_set[ts].sound != NULL) + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint, -1, "Sound: %s", game_map.tile_set[ts].sound_name ); + + kprint(""); + + if (game_map.tile_set[ts].exit_level) + kprint("Is a level exit block"); + + kprint(""); + + if (game_map.tile_set[ts].solid) + { + kprint("Is a solid wall"); + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint,-1, "Bounce factor: %f", game_map.tile_set[ts].bounce_factor ); + } + + kprint(""); + + if (game_map.tile_set[ts].is_a_prize) + { + kprint("Is a prize"); + if (game_map.tile_set[ts].indispensable) + kprint("Is indispensable for grant level exit"); + + kprint(""); + + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint,-1, "Score: %d", game_map.tile_set[ts].score); + } + + kprint(""); + + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint,-1, "Add factor : x:%f, y:%f, z:%f", game_map.tile_set[ts].adx, game_map.tile_set[ts].ady, game_map.tile_set[ts].adz ); + + kprint(""); + + textprintf_ex(screen, font, 0, ykprint+=text_height(font), ckprint,-1, "Multiplier factor: x:%f, y:%f, z:%f", game_map.tile_set[ts].mdx, game_map.tile_set[ts].mdy, game_map.tile_set[ts].mdz ); + + kprint(""); + kprint(""); + kprint(""); + kprint("-- Press any key to continue --"); + + clear_keybuf(); + rest(100); + readkey(); + + unscare_mouse(); + do_change_mouse_cursor(); + dirty = true; + } + + if (key[KEY_B]) // select background + { + int t = 0; + + set_mouse_sprite(NULL); + ykprint = 0; + ckprint = makecol(255,255,0); + scare_mouse(); + + while (t != KEY_ENTER) + { + ykprint = 0; + clear_bitmap(screen); + kprint("Select background with arrow keys, and press ENTER"); + kprint(""); + textprintf_ex(screen, font, 0, 32, makecol(255,255,255),-1,"Current: %d", game_map.background_index); + + // show the current one + blit(background.get_background("backgr.dat", game_map.background_index), screen, 0,0,0,64,SCREEN_W, SCREEN_H); + + t = readkey() >> 8; + rest(100); + if (t == KEY_UP || t == KEY_LEFT) + game_map.background_index--; + if (t == KEY_DOWN || t == KEY_RIGHT) + game_map.background_index++; + if (t == KEY_PGUP ) + game_map.background_index-=10; + if (t == KEY_PGDN ) + game_map.background_index+=10; + if (game_map.background_index < 0) + game_map.background_index = 255; + if (game_map.background_index > 255) + game_map.background_index = 0; + } + + unscare_mouse(); + do_change_mouse_cursor(); + dirty = true; + } + + if (key[KEY_M]) // select music + { + int t = 0; + + set_mouse_sprite(NULL); + ykprint = 0; + ckprint = makecol(255,255,0); + scare_mouse(); + + while (t != KEY_ENTER) + { + ykprint = 0; + clear_bitmap(screen); + kprint("Select music with arrow keys, and press ENTER ; hit SPACE to play/stop the song"); + kprint(""); + textprintf_ex(screen, font, 0, 32, makecol(255,255,255),-1,"Current: %d", game_map.music_index); + + clear_keybuf(); + t = readkey() >> 8; + rest(100); + + if (t == KEY_UP || t == KEY_LEFT) + game_map.music_index--; + + if (t == KEY_DOWN || t == KEY_RIGHT) + game_map.music_index++; + + if (t == KEY_PGUP ) + game_map.music_index-=10; + + if (t == KEY_PGDN ) + game_map.music_index+=10; + + if (game_map.music_index < 0) + game_map.music_index = 255; + + if (game_map.music_index > 255) + game_map.music_index = 0; + + if (t == KEY_SPACE) + { + // play the tune... + CSoundWrapper soundw; // sound system + CMusicLvl music_loader; // loader system for music from datafile + DUH *mm = music_loader.get_music("music_l.dat", game_map.music_index); + if (!mm) + { + clear(screen); + kprint("Sorry, can't play that tune ; must be non existant"); + rest(100); + kprint("-- press any key --"); + readkey(); + rest(10); + } + else + { + clear(screen); + kprint("Now playing tune..."); + kprint("-- press any key to stop--"); + + soundw.music_load(mm); + + soundw.music_start(); + + clear_keybuf(); + + while (!keypressed()) + soundw.music_poll(); + + clear_keybuf(); + rest(100); + + soundw.music_stop(); + + music_loader.free_memory(); + } + } + } + + unscare_mouse(); + do_change_mouse_cursor(); + dirty = true; + } + + if (key[KEY_C] ) // clear map with C + { + set_mouse_sprite(NULL); + if (alert("Clear the map", "Are you sure?", NULL, "Yes", "No", 'y', 'n') == 1) + { + game_map.empty_the_map(); + dirty = true; + } + do_change_mouse_cursor(); + } + + if (key[KEY_S]) // save map + { + set_mouse_sprite(NULL); + if (file_select_ex("Save map to file", str, NULL, 2048, (int)(SCREEN_W*0.85), (int)(SCREEN_H*0.85))) + { + if (game_map.save_map_to_file(str)) + { + // failed :( + alert("ERROR", "File saving failed :(", NULL, "OK", NULL, 0,0); + } + else + { + // ok! + alert("SUCESS", "File saved! :^D", NULL, "Great!", NULL, 0,0); + } + } + do_change_mouse_cursor(); + } + + if (key[KEY_L]) // load map - THIS IS DESTRUCTIVE, IF FAILS, IT WILL CORRUPT CURRENT MAP!!! + { + set_mouse_sprite(NULL); + if (alert("Warning!", "If file load fails,", "your MAP will be reset to empty!", "Continue", "Cancel", 0,0) == 1) + { + if (file_select_ex("Load map from file", str, NULL, 2048, (int)(SCREEN_W*0.85), (int)(SCREEN_H*0.85))) + { + if (game_map.load_map_from_file(str)) + { + // failed :( + alert("ERROR", "File load failed :(", "Your MAP probably has garbage, clear it with 'C' key!", "Crap!", NULL, 0,0); + } + else + { + // ok! + alert("SUCESS", "File loaded! :^D", NULL, "Great!", NULL, 0,0); + // go to map entry point + tx = (game_map.sxp-(SCREEN_W/2/TMAPS_W)) * TMAPS_W; + ty = (game_map.syp-(SCREEN_H/2/TMAPS_H)) * TMAPS_H; + } + dirty = true; + } + } + do_change_mouse_cursor(); + } + + if (key[KEY_D]) // dump tile settings to file + { + set_mouse_sprite(NULL); + if (alert("Dump tile settings", "Dump tile set settings to file?","File will be TILE_SET_CFG.TXT", "Do it", "Cancel", 0,0) == 1) + { + scare_mouse(); + textout_centre_ex(screen, font, "Please wait... writing file...", SCREEN_W/2, SCREEN_H/2, makecol(255,255,255), makecol(128,0,0)); + unscare_mouse(); + game_map.save_tile_set_config_to_file("TILE_SET_CFG.TXT"); + alert("SUCESS", "File saved as TILE_SET_CFG.TXT", NULL, "OK", NULL, 0,0); + dirty = true; + } + + do_change_mouse_cursor(); + } + + if (key[KEY_G] ) // toggle grid + { + draw_grid = !draw_grid; + dirty = true; + } + + if (key[KEY_F] ) // goto player's start point + { + tx = (game_map.sxp-(SCREEN_W/2/TMAPS_W)) * TMAPS_W; + ty = (game_map.syp-(SCREEN_H/2/TMAPS_H)) * TMAPS_H; + dirty = true; + } + + if (key[KEY_F1]) + { + set_mouse_sprite(NULL); + map_editor_help_message(); + do_change_mouse_cursor(); + dirty = true; + } + + if (key[KEY_V]) // toggle 'show only current layer' + { + socl = !socl; + dirty = true; + } + + + if (key[KEY_PLUS_PAD] || key[KEY_EQUALS]) // keypad plus + = change edit layer + { + tl++; + dirty = true; + } + + if (key[KEY_MINUS_PAD] || key[KEY_MINUS]) // keypad plus - = change edit layer + { + tl--; + dirty = true; + } + + + if (key[KEY_P]) // set map properties (time, etc) + { + char st1[255], st2[255]; // d_edit_buffers + int it1 = 0, it2 = 0; + // dialog to set time + DIALOG set_time_dlg[] = + { + /* (proc) (x) (y) (w) (h) (fg) (bg) (key) (flags) (d1) (d2) (dp) (dp2) (dp3) */ + { d_box_proc, 0, 0, 216, 104, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, + { d_text_proc, 8, 8, 144, 8, 0, 0, 0, 0, 0, 0, (void *)"Set map properties", NULL, NULL }, + { d_text_proc, 8, 24, 40, 16, 0, 0, 0, 0, 0, 0, (void *)"Time to complete the map:", NULL, NULL }, + { d_text_proc, 8, 40, 56, 16, 0, 0, 0, 0, 0, 0, (void *)"Minutes:", NULL, NULL }, + { d_text_proc, 8, 56, 56, 16, 0, 0, 0, 0, 0, 0, (void *)"Seconds:", NULL, NULL }, + { d_edit_proc, 88, 40, 56, 16, 0, 0, 0, 0, 3, 0, (void *)"edit", NULL, NULL }, + { d_edit_proc, 88, 56, 56, 16, 0, 0, 0, 0, 2, 0, (void *)"edit", NULL, NULL }, + { d_button_proc, 8, 72, 200, 24, 0, 0, 0, D_EXIT, 0, 0, (void *)"OK", NULL, NULL }, + { NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL } + }; + + // set initial data + usprintf(st1, "%d", game_map.time_m); + usprintf(st2, "%d", game_map.time_s); + set_time_dlg[5].dp = st1; + set_time_dlg[6].dp = st2; + + // do input dialog + set_mouse_sprite(NULL); + set_dialog_color(set_time_dlg, makecol(0,0,0), makecol(255,255,255)); + centre_dialog(set_time_dlg); + popup_dialog(set_time_dlg, -1); + + // validate new data + it1 = atoi(st1); + it2 = atoi(st2); + if (it1 >= 0 && it2 >= 0 && it1 < 256 && it2 <= 60) + { + // set new data + game_map.time_m = it1; + game_map.time_s = it2; + usprintf(st1, "Time of map set to %d:%d", game_map.time_m, game_map.time_s); + alert("Done", st1, NULL, "OK", NULL, 0,0); + } + else + { + alert("ERROR", "Range of input invalid", "Valid: 0 <= minutes <= 255, and 0 <= seconds <= 60", "OK", NULL, 0,0); + } + + do_change_mouse_cursor(); + dirty = true; + } + + + if (tl < 0) + tl = MAP_LAYERS-1; + if (tl > MAP_LAYERS-1) + tl = 0; + + if (tx < 0) + tx = 0; + if (ty < 0) + ty = 0; + if (tx > TMAP_SIZE*TMAPS_W-SCREEN_W) + tx = TMAP_SIZE * TMAPS_W-SCREEN_W; + if (ty > TMAP_SIZE*TMAPS_H-SCREEN_H) + ty = TMAP_SIZE * TMAPS_H-SCREEN_H; + + clear_keybuf(); + } + + // free memory + destroy_bitmap(dbuffer); + destroy_bitmap(mcursor); + game_map.free_memory(); + set_mouse_sprite(NULL); + +} + + diff --git a/src/musiclvl.cpp b/src/musiclvl.cpp new file mode 100644 index 0000000..f4f13b8 --- /dev/null +++ b/src/musiclvl.cpp @@ -0,0 +1,76 @@ +// ------------------------------------------------------------------ +// musiclvl.cpp +// System for handling level's music +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#include +#include + +#include "musiclvl.h" +#include "gerror.h" +#include "filehelp.h" + +CMusicLvl::CMusicLvl() +{ + music_in_ram = NULL; + data_loaded = NULL; + free_memory(); +} + +CMusicLvl::~CMusicLvl() +{ + free_memory(); +} + + +void CMusicLvl::free_memory() +{ + if (data_loaded != NULL) + unload_datafile_object(data_loaded); + + data_loaded = NULL; + + music_in_ram = NULL; + + file_loaded[0] = '\0'; + + index_loaded = -1; +} + +DUH *CMusicLvl::get_music(char *filename, int index) +{ + char str[1024]; + DATAFILE *data = NULL; + char tmp_file_buf[2048]; + + where_is_the_filename(tmp_file_buf, filename); + + // use the cache if it is present + if (index == index_loaded) + { + if (ustrcmp(tmp_file_buf, file_loaded) == 0) + return music_in_ram; + } + + free_memory(); + + usprintf(str, "M%d_XM", index); + data = load_datafile_object(tmp_file_buf, str); + + if (data != NULL) + { + usprintf(file_loaded, "%s", tmp_file_buf); + index_loaded = index; + + data_loaded = data; + music_in_ram = (DUH *)data->dat; + + return music_in_ram; // found OK + } + + + return NULL; // not found +} diff --git a/src/mytracer.cpp b/src/mytracer.cpp new file mode 100644 index 0000000..66827d5 --- /dev/null +++ b/src/mytracer.cpp @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------ +// mytracer.cpp +// ------------------------------------------------------------------ +// This implements a tracer for debugging the game +// Basically, it records events on disk, so we can trace where the hell +// the bastard is crashing! +// ------------------------------------------------------------------ +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ------------------------------------------------------------------ + +#include "mytracer.h" + +bool CMyTracer::DISABLE_TRACE = false; + + +CMyTracer::CMyTracer() +{ + // nothing to do +} + +CMyTracer::~CMyTracer() +{ + // nothing to do +} + +void CMyTracer::add(string txt) +{ + if (DISABLE_TRACE) return; + + FILE *fp; + fp = fopen(TRACE_SAVE_IN_FILE, "a"); + if (fp == NULL) return; // crap! can't save + + fprintf(fp,"%s\n", txt.c_str()); + fclose(fp); +} + +void CMyTracer::add(const char *msg, ...) +{ + if (DISABLE_TRACE) return; + + char buf[4096]; + + /* parse the variable parameters */ + va_list ap; + va_start(ap, msg); + vsprintf(buf, msg, ap); // this is ANSI, POSIX, I hope... :O + va_end(ap); + + this->add(string(buf)); +} + +void CMyTracer::reset() +{ + if (DISABLE_TRACE) return; + + FILE *fp; + fp = fopen(TRACE_SAVE_IN_FILE, "w"); + if (fp == NULL) return; + + fclose(fp); +} diff --git a/src/particle.cpp b/src/particle.cpp new file mode 100644 index 0000000..431ef71 --- /dev/null +++ b/src/particle.cpp @@ -0,0 +1,95 @@ +// ----------------------------------------------- +// particle.cpp +// ----------------------------------------------- +// Many particle types, designed to be used with +// my particle manager. +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#include +#include "particle.h" + +// ----------------------------------------------- +// Spark particle render +// ----------------------------------------------- +void CSparkParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + line(bmp, (int)x - xd, (int)y - yd, (int)(x + dx*scale_spark - xd), (int)(y + dy*scale_spark - yd), color); +} + +// ----------------------------------------------- +// Circle particle render +// ----------------------------------------------- +void CCircleParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + circlefill(bmp, (int)x - xd, (int)y - yd, radius, color); +} + +// ----------------------------------------------- +// Rect particle render +// ----------------------------------------------- +void CRectParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + rectfill(bmp, (int)x - xd, (int)y - yd, (int)(x + size - xd), (int)(y + size - yd), color); +} + +// ----------------------------------------------- +// Text particle render +// ----------------------------------------------- +void CTextParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + text_mode(-1); // DEBUG -- TEXT ALWAYS TRANSPARENT :O + textout_centre(bmp, font_text, text_to_show.c_str(), (int)x - xd, (int)y - yd, color); +} + +// ----------------------------------------------- +// Explosive text particle +// ----------------------------------------------- +bool CExplosiveTextParticle::update_logic(CParticleManager &particle_manager) +{ + // when life == 1, spawn particles + if (life == 1) + { + int yt=0,xt=0; + + while ( (yt+=rand()%3+1) < text_height(font_text) ) + { + while ( (xt+=rand()%3+1) < text_length(font_text, text_to_show.c_str() ) ) + { + particle_manager.add_particle(new CCircleParticle((int)x+xt, (int)y+yt, (float)((rand()%800)-400)/100.0,(float)((rand()%800)-400)/100.0 , color, rand()%5+10, rand()%2+1)); + } + } + } + + return CTextParticle::update_logic(particle_manager); +} + +// ----------------------------------------------- +// Bitmap particle render +// ----------------------------------------------- +void CBitmapParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + if (spr == NULL) return; + draw_sprite(bmp, spr, (int)x - xd - spr->w/2, (int)y - yd - spr->h/2); +} + +// ----------------------------------------------- +// Rotating bitmap particle render & logic +// ----------------------------------------------- +bool CRotoBitmapParticle::update_logic(CParticleManager &particle_manager) +{ + angle += angle_speed; + if (angle < 0.0) angle = 255.0; + if (angle > 255.0) angle = 0.0; + + return CBaseParticle::update_logic(particle_manager); +} + +void CRotoBitmapParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + if (spr == NULL) return; + pivot_sprite(bmp, spr, (int)x - xd, (int)y - yd, spr->w/2, spr->h/2, ftofix(angle)); +} + diff --git a/src/partmang.cpp b/src/partmang.cpp new file mode 100644 index 0000000..e1b5ca5 --- /dev/null +++ b/src/partmang.cpp @@ -0,0 +1,145 @@ +// ----------------------------------------------- +// partmang.cpp +// ----------------------------------------------- +// Particle manager, to control particles in the game +// Also, implementation of base particle +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#include "partmang.h" +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// The base particle, a dot +// This particle must be used as base +// for making other types of particles, using +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +CBaseParticle::CBaseParticle() +{ + x = 0.0; + y = 0.0; + dx = 0.0; + dy = 0.0; + color = 0; + life = 0; +} + +CBaseParticle::CBaseParticle(float x1, float y1, float dx1, float dy1, int color1, int life1) +{ + x = x1; + y = y1; + dx = dx1; + dy = dy1; + color = color1; + life = life1; +} + +CBaseParticle::~CBaseParticle() +{ + // nothing to be done here +} +// ----------------------------------------------- +// updates particle's logic, +// must return TRUE if particle is dead +// (particle WILL be _deleted_ from memory +// by manager if dead) +// ----------------------------------------------- +bool CBaseParticle::update_logic(CParticleManager &particle_manager) +{ + // nothing to be done here, all that this particle needs is taken into account by manager :^D + return false; // I'm not dead (the manager will take care of disccount my life) +} + +// ----------------------------------------------- +// renders particle on bmp, displaced by x,y +// ----------------------------------------------- +void CBaseParticle::render_particle(BITMAP *bmp, int xd, int yd) +{ + putpixel(bmp, (int)x - xd, (int)y - yd, color); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// The particle manager +// This handles a bunch of particles +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +CParticleManager::CParticleManager() +{ + add_x = add_y = 0.0; + add_dx = add_dy = 0.0; +} + +CParticleManager::~CParticleManager() +{ + nuke_particle_list(); // our precious RAM needs freedom! Viva la revolucion de la RAM! +} + +// ----------------------------------------------- +// this will free all memory used by particles, +// and particles itself. +// ----------------------------------------------- +void CParticleManager::nuke_particle_list() +{ + list::iterator i = particle_list.begin(); // iterator for our linked list of particle pointers + + for (i = particle_list.begin(); i != particle_list.end(); i++) + delete (*i); // delete object + + // clear the list + particle_list.clear(); +} + +// ----------------------------------------------- +// adds a particle to manager (the particle +// WILL be nuked automatically when +// the manager object gets deleted) +// ----------------------------------------------- +void CParticleManager::add_particle(CBaseParticle *node) +{ + particle_list.push_back(node); +} + +// ----------------------------------------------- +// updates logic of all particles +// ----------------------------------------------- +void CParticleManager::update_logic() +{ + list::iterator i = particle_list.begin(); // iterator for our linked list of particle pointers + + while (i != particle_list.end()) + { + // apply wind, and gravity, and own movement of particle + (*i)->dx += add_dx; + (*i)->dy += add_dy; + (*i)->x += add_x + (*i)->dx; + (*i)->y += add_y + (*i)->dy; + + // decrease life of particle + (*i)->life--; + + if ((*i)->update_logic(*this) || ((*i)->life < 0)) + { + list::iterator i2 = i; + // particle is dead, erase it + i++; + delete (*i2); + particle_list.erase(i2); + } + else + { + i++; // next + } + } + +} + +// ----------------------------------------------- +// renders particles on bmp, displaced by x,y +// ----------------------------------------------- +void CParticleManager::render(BITMAP *bmp, int x, int y) +{ + list::iterator i = particle_list.begin(); // iterator for our linked list of particle pointers + + for (i = particle_list.begin(); i != particle_list.end(); i++) + (*i)->render_particle(bmp, x, y); // renders particle on bmp, displaced by x,y +} + diff --git a/src/qmenu.cpp b/src/qmenu.cpp new file mode 100644 index 0000000..8c11f64 --- /dev/null +++ b/src/qmenu.cpp @@ -0,0 +1,357 @@ +// ----------------------------------------------- +// qmenu.cpp +// ----------------------------------------------- +// Quick menu system for my games/etc +// ----------------------------------------------- +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +// Don't open this file until doomsday!! + +#include "qmenu.h" +#include "gerror.h" // error reporting +// ------------------------------------------------------------------- +// This are allegro timer routines, needed to keep the +// menu manager calling the events at constant rate +// ------------------------------------------------------------------- +#include // need this to know the max size of the counter... + +static volatile int time_counter = 0; // first timer counter, counts in range from 0..BPS_OF_MENU_MANAGER +static volatile unsigned long int big_timer_counter = 0; // second timer, counts in range from 0..maximun int +static volatile int speed_counter = 0; // this speed counter is to keep the rate of drawing at constant fps + +// how many times we have installed / erased the timer handler? +// really, the timer gets installed only once, this only counts how many calls are made +// when reaches <= 0, the timer is uninstalled and the 2 counter resets to zero +static int timers_installed = 0; + +// -------------------------------------------------------- +// The timer routine +// -------------------------------------------------------- +static void menu_manager_timer_handler() +{ + time_counter++; + big_timer_counter++; + speed_counter++; + if (time_counter > BPS_OF_MENU_MANAGER) time_counter = 0; + if (big_timer_counter > ULONG_MAX - 2 ) big_timer_counter = 0; +} +END_OF_FUNCTION(menu_manager_timer_handler); + +// -------------------------------------------------------- +// This starts the global timer +// -------------------------------------------------------- +static void menu_manager_start_global_timer() +{ + if (timers_installed == 0) + { + // Install timer + LOCK_VARIABLE(time_counter); + LOCK_VARIABLE(big_timer_counter); + LOCK_VARIABLE(speed_counter); + LOCK_FUNCTION((void *)menu_manager_timer_handler); + + // DEBUG - this SHOULD BE CHECKED FOR ERROR!!! + if (install_int_ex(menu_manager_timer_handler, BPS_TO_TIMER(BPS_OF_MENU_MANAGER))) + raise_error("menu_manager_start_global_timer():\nERROR: Can't install timer at %d bps\n", BPS_OF_MENU_MANAGER); + } + + timers_installed++; // this counts how many calls to this function were made +} + + +// -------------------------------------------------------- +// This ends the global timer +// -------------------------------------------------------- +static void menu_manager_stop_global_timer() +{ + if (timers_installed == 0) return; // no timer installed + + timers_installed--; + + if (timers_installed <= 0) + { + // remove and reset the timer + remove_int(menu_manager_timer_handler); + time_counter = 0; + speed_counter = 0; + big_timer_counter = 0; + timers_installed = 0; + } +} + +// =================================================================== +// =================================================================== +// From here is the menu class itself +// =================================================================== +// =================================================================== + +CQMenu::CQMenu() +{ + // set defaults + menu_font_s = menu_font_ns = font; + menu_fg_color_ns = makecol(128,128,128); + menu_bg_color_ns = -1; + + menu_fg_color_s = makecol(255,255,255); + menu_bg_color_s = makecol(128,0,0); + + to_bitmap = screen; + back_bitmap = NULL; + + mx = my = 0; + mw = 640; + mh = 480; + item_y_separation = 0; + text_align = 0; + callback = NULL; + + // DEBUG - we use all available controllers by default + control.set_use_mouse(true); + control.set_use_joystick(true); + control.set_use_keyboard(true); + + this->clear_menu_list(); +} + +CQMenu::~CQMenu() +{ + this->clear_menu_list(); +} + +// -------------------------------------------------------- +// empty the menu item list +// -------------------------------------------------------- +void CQMenu::clear_menu_list() +{ + menu_item_text.clear(); +} + +// -------------------------------------------------------- +// adds a selectable item to the menu +// returns the index of the item added, or -1 on error (menu full) +// -------------------------------------------------------- +int CQMenu::add_item_to_menu(string item) +{ + menu_item_text.push_back(item); + + return menu_item_text.size(); // done +} + +// -------------------------------------------------------- +// Starts the menu loop until the user selects one +// Return the index of the item selected by the user (starts at 0). +// selected is the item to let selected by default +// -------------------------------------------------------- +int CQMenu::do_the_menu(int selected) +{ + bool d_done = false; // dialog done? + int mis = 0; // mis == Menu Item Selected (current selected item by user) + int cret = 0; // controller return value + int mhi = 0; // menu item height (measured in items) + int msd = 0, med = 0, mdy = 0; // menu start drawing, menu end drawing, menu draw Y + int last_hit = 0; // timer to last hit of a key, to take a small delay; if not, the menu scrolls too fast :( + BITMAP *dbuf = NULL; // double buffer + + if (menu_item_text.size() < 1) raise_error("CQMenu::do_the_menu()\nERROR: no items in menu\n"); + + if (to_bitmap == NULL) raise_error("CQMenu::do_the_menu()\nERROR: no destination bitmap to render the menu\n"); + + menu_manager_start_global_timer(); // we need a timer running + + dbuf = create_bitmap(to_bitmap->w, to_bitmap->h); // double buffer + if (dbuf == NULL) raise_error("CQMenu::do_the_menu()\nERROR: unable to create doble buffer bitmap!\n"); + + mhi = mh / (text_height(menu_font_ns)+item_y_separation); // how many items can we fit vertically? + + // ------------- Main loop -------------- + clear_keybuf(); + rest(25); + clear_keybuf(); + + show_mouse(NULL); // go to hell, little mouse cursor :P + mis = selected; + if (mis < 0 || (unsigned)mis > menu_item_text.size()-1) mis = 0; + + while (!d_done) + { + while (speed_counter > 0) + { + // update menu logic here + + cret = control.do_input_poll(); + + // if the last hit was very close (1/6 of second), ignore input + if (abs((int)big_timer_counter - last_hit) < BPS_OF_MENU_MANAGER/10) + { + key[KEY_UP] = key[KEY_DOWN] = key[KEY_ENTER] = key[KEY_SPACE]=0; + clear_keybuf(); + cret = 0; + } + + if ((cret & KC_UP) || (cret & KC_LEFT) || key[KEY_UP]) + { + mis--; + // take a little delay to let the guy release the key (if any) + clear_keybuf(); + while (keypressed()) readkey(); + last_hit = big_timer_counter; + } + + if ((cret & KC_DOWN) || (cret & KC_RIGHT) || key[KEY_DOWN]) + { + mis++; + // take a little delay to let the guy release the key (if any) + clear_keybuf(); + while (keypressed()) readkey(); + last_hit = big_timer_counter; + } + + // don't let the selection go out of bounds + if (mis < 0) mis = menu_item_text.size()-1; + if ((unsigned)mis > menu_item_text.size()-1) mis = 0; + + if ((cret & KC_BTN1) || (cret & KC_BTN2) || (cret & KC_BTN3) || key[KEY_ENTER] || key[KEY_SPACE]) d_done = true; // done, item selected wow! + + if (callback != NULL) callback(*this, true); // call the callback if needed, to update logic + speed_counter--; + if (time_counter > BPS_OF_MENU_MANAGER - 5) yield_timeslice(); // we play nice with the OS multitask, when we can (when the timer is about to reach peak) + + //if (key[KEY_ESC]) d_done = true; // emergency key :^O + } + + // here update screen + if (back_bitmap) + { blit(back_bitmap, dbuf, 0,0,0,0,back_bitmap->w, back_bitmap->h);} + else + { clear(dbuf); } + + if (callback != NULL) callback(*this, false); // call the callback if needed, to update screen + + // Here we draw the menu + msd = mis - mhi/2 - 1; // menu start drawing from here + if (msd < 0) msd = 0; + med = msd + mhi; // menu drawing ends here + if ((unsigned)med > menu_item_text.size()) med = menu_item_text.size(); + mdy = my; + for (int i = msd; i < med; i ++) + { + if (i == mis) // is selected or unselected item? + { + text_mode(menu_bg_color_s); + switch (text_align) // wich text align to use? + { + case 1: + textout_right(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s); + break; + case 2: + textout_centre(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s); + break; + case 3: + textout_justify(dbuf, menu_font_s, menu_item_text[i].c_str(), mx,mx+mw, mdy, mw, menu_fg_color_s); + break; + default: + textout(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s); + break; + } + mdy += text_height(menu_font_s); + } + else + { + text_mode(menu_bg_color_ns); + switch (text_align) + { + case 1: + textout_right(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns); + break; + case 2: + textout_centre(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns); + break; + case 3: + textout_justify(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx,mx+mw, mdy, mw, menu_fg_color_ns); + break; + default: + textout(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns); + break; + } + mdy += text_height(menu_font_ns); + } + mdy += item_y_separation; + } + + // show it + blit(dbuf, to_bitmap, 0,0,0,0, dbuf->w, dbuf->h); + } // end of while !d_done + + // ------------- End of main loop ------- + menu_manager_stop_global_timer(); // we finish the timer + text_mode(-1); + + clear_keybuf(); + rest(25); + clear_keybuf(); + + return mis; // return menu item selected +} + +// -------------------------------------------------------- +// Set functions +// -------------------------------------------------------- + +void CQMenu::set_font_s(FONT *f) { menu_font_s = f; } +void CQMenu::set_font_ns(FONT *f) { menu_font_ns = f; } +void CQMenu::set_fg_color_ns(int fg) { menu_fg_color_ns = fg; } +void CQMenu::set_bg_color_ns(int bg) { menu_bg_color_ns = bg; } +void CQMenu::set_fg_color_s(int fg) { menu_fg_color_s = fg; } +void CQMenu::set_bg_color_s(int bg) { menu_bg_color_s = bg; } + +void CQMenu::set_xywh(int x, int y, int w, int h) { mx = x; my = y; mw = w; mh = h; } + +void CQMenu::set_to_bitmap(BITMAP *b) { to_bitmap = b; } +void CQMenu::set_back_bitmap(BITMAP *b) {back_bitmap = b; } + +void CQMenu::set_callback_drawer(void (*c)(CQMenu &d, bool do_logic)) +{ + callback = c; +} + +// -------------------------------------------------------- +// Get functions +// -------------------------------------------------------- +FONT *CQMenu::get_font_s() { return menu_font_s; } +FONT *CQMenu::get_font_ns(){ return menu_font_ns; } +int CQMenu::get_fg_color_ns() { return menu_fg_color_ns; } +int CQMenu::get_bg_color_ns() { return menu_bg_color_ns; } +int CQMenu::get_fg_color_s() { return menu_fg_color_s; } +int CQMenu::get_bg_color_s() { return menu_bg_color_s; } +int CQMenu::get_menu_item_count() { return menu_item_text.size(); } +int CQMenu::get_x() { return mx; } +int CQMenu::get_y() { return my; } +int CQMenu::get_w() { return mw; } +int CQMenu::get_h() { return mh; } +BITMAP *CQMenu::get_to_bitmap() { return to_bitmap; } +BITMAP *CQMenu::get_back_bitmap() { return back_bitmap; } + +string CQMenu::get_menu_item_text(int item_index) +{ + if (item_index < 0 || (unsigned)item_index > menu_item_text.size()-1) return menu_item_text[0]; + return menu_item_text[item_index]; +} + +// -------------------------------------------------------- +// Small timer, goes in range 0 to BPS_OF_MENU_MANAGER +// -------------------------------------------------------- +int CQMenu::get_time_counter() +{ + return time_counter; +} + +// -------------------------------------------------------- +// Big timer, goes in range 0 to ULONG_MAX +// -------------------------------------------------------- +unsigned long int CQMenu::get_big_timer_counter() +{ + return big_timer_counter; +} + diff --git a/src/savescrs.cpp b/src/savescrs.cpp new file mode 100644 index 0000000..8fe6609 --- /dev/null +++ b/src/savescrs.cpp @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------ +// savescrs.cpp +// ------------------------------------------------------------------ +// This saves screenshoots of the current screen +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2004, Kronoman +// ------------------------------------------------------------------ + +#include "savescrs.h" + +// ------------------------------------------------------------------ +// This captures the screenshoot, using the file format +// of file extension (can be PCX, BMP, TGA) +// +// Will use [name][hex number].[extension] for the name +// The hex number starts counting at hex_start +// Will *not* overwrite files +// ------------------------------------------------------------------ +void save_screenshoot(char *name, int hex_start, char *extension) +{ + char fname[2048]; + BITMAP *bmp; + PALETTE pal; + int i = 0; + + // will try at least 4096 numbers from hex_start before giving up trying to save + do + { + usprintf(fname, "%s%x.%s", name, hex_start+i, extension); + i++; + } + while (exists(fname) && i < 4096); + + get_palette(pal); + bmp = create_sub_bitmap(screen, 0, 0, SCREEN_W, SCREEN_H); + save_bitmap(fname, bmp, pal); + destroy_bitmap(bmp); +} diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 0000000..974362a --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,232 @@ +// ----------------------------------------------- +// Sound System +// By Kronoman +// Copyright (c) 2004, Kronoman +// In loving memory of my father +// ----------------------------------------------- +// sound.cpp +// ----------------------------------------------- +// This system wraps around Allegro and DUMB, +// so I can use the high level sound routines, +// global adjust volume, disable volume, etc +// ----------------------------------------------- +// The formulae for the sound volume is: +// +// 255 -- D (digital volume) +// V ---- X1 ==> X1 = (V * D) / 255 +// +// 255 -- G (global volume, final result) +// X1 --- X2 ==> X2 = (X1 * G) / 255 +// +// ==> X2 = (( (V * D) / 255) * G ) / 255 +// +// For the music: +// 255 -- 1.0f +// X2 --- X3 ==> X3 = (X2 * 1.0f) / 255.0f ==> X2 / 255.0f +// ----------------------------------------------- + +#include // Allegro : http://alleg.sf.net/ +#include // DUMB : http://dumb.sf.net/ +#include "sound.h" + +// ----------------------------------------------- +// static members and methods +// ----------------------------------------------- + +bool CSoundWrapper::global_enabled = true; // enables the sound system ? +int CSoundWrapper::global_volume = 255; + +void CSoundWrapper::global_set_volume(int v) +{ + if ( v < 0 || v > 255) + return; + + global_volume = v; +} + +int CSoundWrapper::global_get_volume() +{ + return global_volume; +} + +void CSoundWrapper::global_set_enabled(bool s) +{ + global_enabled = s; +} + +bool CSoundWrapper::global_is_enabled() +{ + return global_enabled; +} + +// ----------------------------------------------- +// class methods +// ----------------------------------------------- + + +CSoundWrapper::CSoundWrapper() +{ + duh = NULL; + dp = NULL; + volume_d = 255; + volume_m = 255; + enabled = true; +} + +CSoundWrapper::~CSoundWrapper() +{ + // nothing to do for now +} + +// ----------------------------------------------- +// digital sample playing +// ----------------------------------------------- +int CSoundWrapper::play_sample(const SAMPLE *spl, int vol, int pan, int freq, int loop) +{ + if (spl == NULL || volume_d < 1 || global_volume < 1 || !enabled || !global_enabled) + return -1; // error man :P + + int v = (((vol * volume_d) / 255) * global_volume ) / 255; + + return ::play_sample(spl, v, pan, freq, loop); // call real allegro function +} + +// ----------------------------------------------- +// volume digital +// ----------------------------------------------- +void CSoundWrapper::set_volume_d(int v) +{ + if (v < 0 || v > 255) + return ; + + this->volume_d = v; +} + +int CSoundWrapper::get_volume_d() +{ + return this->volume_d; +} + +// ----------------------------------------------- +// music +// ----------------------------------------------- + +// ----------------------------------------------- +// call this before playing, dat should be a pointer to a music object of a datafile +// dat should be something like: +// DATAFILE *dat = load_datafile("smurf.dat"); +// DUH *myduh = (DUH *)dat[GAME_MUSIC].dat; +// ----------------------------------------------- +void CSoundWrapper::music_load(DUH *dat) +{ + // DEBUG -- revisar esto, o algo + duh = dat; +} + +// ----------------------------------------------- +// start playing +// ----------------------------------------------- +void CSoundWrapper::music_start() +{ + if (duh == NULL) + return ; + + if (!enabled || !global_enabled) + return; + + dp = al_start_duh(duh, 2, 0, ((float)(volume_m / 255) * global_volume ) / 255.0 , 4096, 11025); +} + +// ----------------------------------------------- +// pause playback +// ----------------------------------------------- + +void CSoundWrapper::music_pause() +{ + if (duh == NULL || dp == NULL) + return ; + + al_pause_duh(dp); +} + +// ----------------------------------------------- +// resume playback +// ----------------------------------------------- + +void CSoundWrapper::music_resume() +{ + if (duh == NULL) // no hay musica + return ; + + if (dp == NULL) // no estaba tocando + { + music_start(); + return; + } + + al_resume_duh(dp); +} + +// ----------------------------------------------- +// poll music +// _must_ be called at regular intervals to hear the music +// ----------------------------------------------- +void CSoundWrapper::music_poll() +{ + if (duh == NULL) + return ; + + if (dp == NULL) + music_start(); + + if (enabled && global_enabled && global_volume > 0) + al_poll_duh(dp); + else + music_stop(); +} + +// ----------------------------------------------- +// stop playing +// ----------------------------------------------- +void CSoundWrapper::music_stop() +{ + if (duh == NULL || dp == NULL) + return ; + + al_stop_duh(dp); + + dp = NULL; +} + +// ----------------------------------------------- +// volume music +// ----------------------------------------------- +void CSoundWrapper::set_volume_m(int v) +{ + if ( v < 0 || v > 255) + return ; + + this->volume_m = v; + + if (dp != NULL) + al_duh_set_volume(dp, ((float)(volume_m / 255) * global_volume ) / 255.0 ); +} + +int CSoundWrapper::get_volume_m() +{ + return volume_m; +} + +// ----------------------------------------------- +// for all the object +// ----------------------------------------------- +void CSoundWrapper::set_enabled(bool s) +{ + this->enabled = s; +} + +bool CSoundWrapper::is_enabled() +{ + return this->enabled; +} + diff --git a/src/sphermap.cpp b/src/sphermap.cpp new file mode 100644 index 0000000..4d14e89 --- /dev/null +++ b/src/sphermap.cpp @@ -0,0 +1,96 @@ +// ----------------------------------------------------------------------- +// sphermap.cpp +// ----------------------------------------------------------------------- +// Code to render a sphere +// From Pixelate #11, article written by Martijn 'amarillion' van Iersel +// ----------------------------------------------------------------------- +// Modified by Kronoman to suit the needs of the game. +// ----------------------------------------------------------------------- + +#include + +#include "sphermap.h" + +/* +get_planet_rotation_matrix() is a little helper function to calculate a +rotation matrix for a planet. An ordinary rotation matrix doesn't work well +for planets. + +MATRIX *m: pointer to the resulting MATRIX struct +fixed rotation: rotation of the planet around its axis of rotation +fixed axisx, axisz: rotation around the x and z axes. +*/ +void get_planet_rotation_matrix (MATRIX *m, fixed rotation, fixed axisx, fixed axisz) +{ + MATRIX m1, m2; + // apply rotation around y axis first + get_y_rotate_matrix (&m1, rotation); + + // then apply rotation around x and z axes. + get_rotation_matrix (&m2, axisx, 0, axisz); + + matrix_mul (&m2, &m1, m); +} + +/* + get_mapped_sphere_ex() is similar to get_mapped_sphere(), but also takes + a rotation matrix as argument to draw a rotated sphere. + + BITMAP *target = bitmap to display onto + int cx, cy = center of the sphere + int r = radius of the sphere + BITMAP *map = bitmap to map onto the sphere + MATRIX *rotmat = rotation matrix +*/ +void mapped_sphere_ex (BITMAP *target, int cx, int cy, int r, BITMAP *map, MATRIX *rotmat) +{ + int x, y; // coordinates on the target bitmap + int p, q; // coordinates on the source bitmap + + for (y = -r; y < r; y++) + { + fixed q_cos = fixcos (- fixasin (itofix (y) / r)) * r; + for (x = - fixtoi (q_cos) + 1; x < fixtoi(q_cos) - 1; x++) + { + fixed newq_cos, temp_p, temp_q=0; // some temporary variables + fixed newx, newy, newz; // x, y and z after rotation + fixed z; // z before rotation. we don't have to calculate x and y + + // calculate z + // using pythagoras and the formula for a sphere: + // x^2 + y^2 + z^2 = r^2 + // we know x, y and r + z = fixsqrt (r * itofix (r) - x * itofix (x) - y * itofix (y)); + + // apply the rotation matrix to x, y, z. + // put the result in newx, newy, newz + apply_matrix (rotmat, itofix(x), itofix(y), z, &newx, &newy, &newz); + + //just as in sphere2.c, we need to check if q_cos is 0 + //however, q_cos depends on y, and we just calculated a new y + //thus we have to calculate q_cos again. + newq_cos = fixcos (temp_q) * r; + if (newq_cos != 0) + { + // it is possible to use temp_p + // temp_p = fixasin (fixdiv (itofix (x), q_cos)); + // however, I found I get less rounding errors if I use + // fixatan2 instead. The principle remains the same. + temp_p = fixatan2 (fixdiv (newx, newq_cos), fixdiv (newz, newq_cos)); + } + else + temp_p = 0; + + temp_p &= 0xFFFFFF; + + p = fixtoi (temp_p) * (map->w-1) / 256; + + // calculate q + temp_q = fixasin (newy / r); + q = fixtoi (temp_q + itofix (64)) * (map->h-1) / 128; + + putpixel (target, x + cx, y + cy, getpixel(map, p, q) ); + } + } +} + diff --git a/src/stats.cpp b/src/stats.cpp new file mode 100644 index 0000000..8a8cb5c --- /dev/null +++ b/src/stats.cpp @@ -0,0 +1,56 @@ +// ----------------------------------------------- +// stats.cpp +// ----------------------------------------------- +// Statistics of gameplay for KBall +// ----------------------------------------------- +// By Kronoman +// Copyright (c) 2004 +// In loving memory of my father +// ----------------------------------------------- + +#include "stats.h" + +CGameStats::CGameStats() +{ + reset(); +} + +CGameStats::~CGameStats() +{ + // nothing to do +} + +void CGameStats::reset() +{ + h = s = m = score = blost = 0; +} + +void CGameStats::print(BITMAP *bmp, int y, int color, int bg, FONT *f) +{ + //textprintf_ex(bmp, f, 0, y+=text_height(f)*2, color, bg, "Statistics"); + + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f), color, bg, "TOTAL TIME"); + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f), color, bg, "%4d:%02d:%02d", h,m,s); + + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f)*2, color, bg, "TOTAL SCORE"); + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f), color, bg, "%010ld", score); + + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f)*2, color, bg, "BALLS LOST"); + textprintf_centre_ex(bmp, f, bmp->w/2, y+=text_height(f), color, bg, "%3d", blost); +} + +void CGameStats::add_time(int h1, int m1, int s1) +{ + s += s1; + + m += s / 60; + s = s % 60; + + m += m1; + + h += m / 60; + m = m % 60; + + h += h1; +} + diff --git a/src/tmap.cpp b/src/tmap.cpp new file mode 100644 index 0000000..2508e87 --- /dev/null +++ b/src/tmap.cpp @@ -0,0 +1,726 @@ +// ------------------------------------------------------------------ +// tmap.cpp +// ------------------------------------------------------------------ +// This handles the tile map for the ball game +// ------------------------------------------------------------------ +// By Kronoman - In loving memory of my father +// Copyright (c) 2003, Kronoman +// ------------------------------------------------------------------ +// ** VERY IMPORTANT NOTICE ** +// +// SINCE THE WALLS ARE 3D RENDERED, ALL DRAW ROUTINES *NEED* +// A 3D Allegro's create_scene(int nedge, int npoly); BEFORE CALLING THEM! +// ------------------------------------------------------------------ +#include "tmap.h" + +// NOTE: instead of printf, strcmp, etc, I use the Allegro's UNICODE equivalents + +// ============================================================================ +// ============================================================================ +// CTileClass class +// ============================================================================ +// ============================================================================ + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- +CTileClass::CTileClass() +{ + adx = ady = adz = 0.0; + mdx = mdy = mdz = 1.0; + bounce_factor = 0.9; + exit_level = false; + solid = false; + spr = NULL; + sound = NULL; + + // relative to prize layer + score = 0; + indispensable = false; + is_a_prize = false; + + usprintf(spr_name,"NULL"); + usprintf(sound_name,"NULL"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- +CTileClass::~CTileClass() +{ + // nothing to be done here yet +} + +// ---------------------------------------------------------------------------- +// This saves parameters to a previously set config *FILE* (not memory pointer) +// nt is the tile number ID +// ---------------------------------------------------------------------------- + +void CTileClass::save_to_config(int nt) +{ + char str1[2048]; + + usprintf(str1,"TILE_%d", nt); // section to read + + set_config_float(str1, "bounce_factor", bounce_factor); + + set_config_float(str1, "adx", adx); + set_config_float(str1, "ady", ady); + set_config_float(str1, "adz", adz); + + set_config_float(str1, "mdx", mdx); + set_config_float(str1, "mdy", mdy); + set_config_float(str1, "mdz", mdz); + + set_config_int(str1, "exit_level", (int)exit_level); + set_config_int(str1, "solid", (int)solid); + + set_config_string(str1, "spr", spr_name); + set_config_string(str1, "sound", sound_name); + + set_config_int(str1, "score", score); + set_config_int(str1, "is_a_prize", is_a_prize); + set_config_int(str1, "indispensable", (int)indispensable); + +} + +// ---------------------------------------------------------------------------- +// This loads the tile 'nt' (1..255) from a *previously* set config +// is mean to be called ONLY by CTMap::load_tile_set_from_file() +// ---------------------------------------------------------------------------- +void CTileClass::load_from_config(int nt, CWDatafile *data) +{ + char str1[2048]; + + usprintf(str1,"TILE_%d", nt); // section to read + + // parameters + bounce_factor = get_config_float(str1, "bounce_factor", 0.9); + adx = get_config_float(str1, "adx", 0.0); + ady = get_config_float(str1, "ady", 0.0); + adz = get_config_float(str1, "adz", 0.0); + + mdx = get_config_float(str1, "mdx", 1.0); + mdy = get_config_float(str1, "mdy", 1.0); + mdz = get_config_float(str1, "mdz", 1.0); + + exit_level = (bool)get_config_int(str1, "exit_level", 0); + solid = (bool)get_config_int(str1, "solid", 0); + + indispensable = (bool)get_config_int(str1,"indispensable",0); + score = get_config_int(str1, "score", 0); + is_a_prize = (bool)get_config_int(str1, "is_a_prize", 0); + + if (indispensable) + is_a_prize = true; // if is indispensable, it must has the prize flag set + + // sprite + spr = NULL; // default value for sprite + + usprintf(spr_name, "%s", get_config_string(str1, "spr", "null")); // I'm interested in keep the sprite name for future saving of tile set to disk + + // if spr_name != "null", then the sprite is defined, so fint it or die trying :P + if (ustricmp(spr_name, "null") != 0) + { + spr = (BITMAP *)data->get_resource_dat(spr_name); + } + + // sound + sound = NULL; + + usprintf(sound_name, "%s", get_config_string(str1, "sound", "null")); // I'm interested in keep the sound name for future saving of tile set to disk + + if (ustricmp(sound_name, "null") != 0) + { + sound = (SAMPLE *)data->get_resource_dat(sound_name); + } + + // done +} + +// ---------------------------------------------------------------------------- +// Draws the tile on bmp at x,y (pixel coordinates) +// draw_grid is of use for the map editor, will signal bad sprites +// layer is used to determine highness of the thing. +// * Note: prizes aren't scaled * +// ---------------------------------------------------------------------------- + +void CTileClass::draw(BITMAP *bmp, int x, int y, bool draw_grid, int layer) +{ + V3D_f *v_xyz_p[4]; // this is needed to pass the array pointer to software renderer + V3D_f pc_v[8]; // the 8 vertex of the quad are precalculated and stored here + + int poltype = POLYTYPE_ATEX_MASK; // type of polygon to render, usually POLYTYPE_ATEX_MASK + + float dx, dy; // displacement to give '3D look' + float scale_top = 5; // how much to add to the size of top block (pixels) (this is the height of the walls also) + + if (is_a_prize) + scale_top = 0; // is a prize? don't scale + + + if (spr == NULL) + { + if (draw_grid) + { + line(bmp,x,y,x+TMAPS_W, y+TMAPS_H, makecol(255,0,0)); + line(bmp,x+TMAPS_W, y, x, y+TMAPS_H, makecol(255,0,0)); + } + return; + } + + // maximun displacement of top of wall's box +#define max_displacement 32.0 + dx = ((x+spr->w/2) - (bmp->w/2)) * (max_displacement) / (float)(bmp->w/2.0); + dy = ((y+spr->h/2) - (bmp->h/2)) * (max_displacement) / (float)(bmp->h/2.0); + +#undef max_displacement + + + // 3D rendered (by software render, no OpenGL) + + // precalculate the 8 vertex, 4 first are 'on ground' + // REMEMBER to set 'u' and 'v' parameters for texture for each face!!! + // 0-->1 + // 3<--2 + // + // 4 last are 'on roof' + // 4-->5 + // 7<--6 + pc_v[0].x=x; + pc_v[0].y=y; + + pc_v[1].x=x+spr->w; + pc_v[1].y=y; + + pc_v[2].x=x+spr->w; + pc_v[2].y=y+spr->h; + + pc_v[3].x=x; + pc_v[3].y=y+spr->h; + + pc_v[0].z=pc_v[1].z=pc_v[2].z=pc_v[3].z=1; + + pc_v[4].x=x + dx - scale_top; + pc_v[4].y=y + dy - scale_top; + + pc_v[5].x=x + dx + spr->w + scale_top; + pc_v[5].y=y + dy - scale_top; + + pc_v[6].x=x + spr->w + dx + scale_top; + pc_v[6].y=y + spr->h + dy + scale_top; + + pc_v[7].x=x + dx - scale_top ; + pc_v[7].y=y + dy + spr->h + scale_top; + + pc_v[4].z=pc_v[5].z=pc_v[6].z=pc_v[7].z=0.1; + + // is a floor, or wall? + if (solid) + { + + // wall box + + // top face (roof) + v_xyz_p[0] = &pc_v[4]; + v_xyz_p[1] = &pc_v[5]; + v_xyz_p[2] = &pc_v[6]; + v_xyz_p[3] = &pc_v[7]; + + pc_v[4].u = 0; + pc_v[4].v = 0; + pc_v[5].u = spr->w; + pc_v[5].v = 0; + pc_v[6].u = spr->w; + pc_v[6].v = spr->h; + pc_v[7].u = 0; + pc_v[7].v = spr->h; + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); // top polygon + + // upper face + v_xyz_p[0] = &pc_v[4]; + v_xyz_p[1] = &pc_v[5]; + v_xyz_p[2] = &pc_v[1]; + v_xyz_p[3] = &pc_v[0]; + + pc_v[4].u = 0; + pc_v[4].v = 0; + pc_v[5].u = spr->w; + pc_v[5].v = 0; + pc_v[1].u = spr->w; + pc_v[1].v = spr->h; + pc_v[0].u = 0; + pc_v[0].v = spr->h; + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); + + // bottom face + v_xyz_p[0] = &pc_v[3]; + v_xyz_p[1] = &pc_v[2]; + v_xyz_p[2] = &pc_v[6]; + v_xyz_p[3] = &pc_v[7]; + + pc_v[3].u = 0; + pc_v[3].v = 0; + pc_v[2].u = spr->w; + pc_v[2].v = 0; + pc_v[6].u = spr->w; + pc_v[6].v = spr->h; + pc_v[7].u = 0; + pc_v[7].v = spr->h; + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); + + // left face + v_xyz_p[0] = &pc_v[0]; + v_xyz_p[1] = &pc_v[4]; + v_xyz_p[2] = &pc_v[7]; + v_xyz_p[3] = &pc_v[3]; + + pc_v[0].u = 0; + pc_v[0].v = 0; + pc_v[4].u = spr->w; + pc_v[4].v = 0; + pc_v[7].u = spr->w; + pc_v[7].v = spr->h; + pc_v[3].u = 0; + pc_v[3].v = spr->h; + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); + + // right face + v_xyz_p[0] = &pc_v[1]; + v_xyz_p[1] = &pc_v[5]; + v_xyz_p[2] = &pc_v[6]; + v_xyz_p[3] = &pc_v[2]; + + pc_v[1].u = 0; + pc_v[1].v = 0; + pc_v[5].u = spr->w; + pc_v[5].v = 0; + pc_v[6].u = spr->w; + pc_v[6].v = spr->h; + pc_v[2].u = 0; + pc_v[2].v = spr->h; + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); + + + } + else + { + + // flat surface + if (layer == 0 || is_a_prize) + { + v_xyz_p[0] = &pc_v[0]; + v_xyz_p[1] = &pc_v[1]; + v_xyz_p[2] = &pc_v[2]; + v_xyz_p[3] = &pc_v[3]; + + pc_v[0].u = 0; + pc_v[0].v = 0; + pc_v[1].u = spr->w; + pc_v[1].v = 0; + pc_v[2].u = spr->w; + pc_v[2].v = spr->h; + pc_v[3].u = 0; + pc_v[3].v = spr->h; + + pc_v[0].z=pc_v[1].z=pc_v[2].z=pc_v[3].z= (is_a_prize) ? 0.15 : 1; + } + else + { + v_xyz_p[0] = &pc_v[4]; + v_xyz_p[1] = &pc_v[5]; + v_xyz_p[2] = &pc_v[6]; + v_xyz_p[3] = &pc_v[7]; + + pc_v[4].u = 0; + pc_v[4].v = 0; + pc_v[5].u = spr->w; + pc_v[5].v = 0; + pc_v[6].u = spr->w; + pc_v[6].v = spr->h; + pc_v[7].u = 0; + pc_v[7].v = spr->h; + + pc_v[4].z=pc_v[5].z=pc_v[6].z=pc_v[7].z=0.1; + } + + scene_polygon3d_f(poltype, spr, 4, v_xyz_p); // render polygon to scene + } +} + +// ============================================================================ +// ============================================================================ +// CTMap class +// ============================================================================ +// ============================================================================ + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +CTMap::CTMap() +{ + empty_the_map(); // reset the map + + timer_tick_rate = 30; // set this to measure timer tick rate, otherwise, the time will run wild (ex: if update logic is called 30 times by second, then set this to 30) +} + +// ---------------------------------------------------------------------------- +// Destructor - NUNCA DEBE SER LLAMADO AUTOMATICAMENTE POR C++ +// PORQUE GENERA SEG FAULT, YA QUE ALLEGRO MUERE *ANTES* QUE ESTO +// Y LA LIBERACION DE MEMORIA *NO* ANDA +// ---------------------------------------------------------------------------- + +CTMap::~CTMap() +{ + free_memory(); +} + +// ---------------------------------------------------------------------------- +// This free the memory used by the map and the tile set +// ---------------------------------------------------------------------------- +void CTMap::free_memory() +{ + tile_set_data.nuke_datafile(); +} + +// ---------------------------------------------------------------------------- +// Resets all map to 0s +// ---------------------------------------------------------------------------- +void CTMap::empty_the_map() +{ + for (int l =0; l < MAP_LAYERS; l++) + for (int x = 0; x < TMAP_SIZE; x++) + for (int y = 0; y < TMAP_SIZE; y++) + tile_map[x][y][l] = 0; // empty the map + + // reset start position too (centered) + sxp = TMAP_SIZE / 2; + syp = TMAP_SIZE / 2; + + // reset other stuff + prize_map_indispensable = 0; + time_m = 60; + time_s = 0; + background_index = 0; + music_index = 0; + + curr_tick = -1; // special flag, means 'start recounting' +} + +// ---------------------------------------------------------------------------- +// This returns if a tile is walkable only +// Is useful to see if the ball can move or not in some particular direction +// Is not useful for check if the ball must fall (for that purpose, +// use get_tile_type and check against < 1 (empty)) +// +// This serves for validation purposes (for the ball), will return true +// if the tile is walkable, false otherwise (outside map counts as non walkable) +// ---------------------------------------------------------------------------- + +bool CTMap::get_tile_walkable(int x, int y, int layer) +{ + if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1) + return false; // outside map! + + if (tile_set[tile_map[x][y][layer]].solid) + return false; // the place is solid + + // if the tile is empty, or solid = false; then he can walk + return true; +} + +// ---------------------------------------------------------------------------- +// This returns the tile type (1..255), or -1 if outside map, or 0 if empty +// if empty, or outside map, ball should fall free to death +// ---------------------------------------------------------------------------- +int CTMap::get_tile_type(int x, int y, int layer) +{ + if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1) + return -1; // outside map! + + return tile_map[x][y][layer]; +} + +// ---------------------------------------------------------------------------- +// this sets the tile type at x,y (0..255) x,y are coordinates of the matrix +// ---------------------------------------------------------------------------- +void CTMap::set_tile_type(int x, int y, int layer, int value) +{ + if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1) + return; // outside map! + tile_map[x][y][layer] = value; +} + +// ---------------------------------------------------------------------------- +// Loads a tile map from a file, return true if FAILS, false otherwise +// NOTICE: FOR THIS TO COUNT PROPERLY THE PRIZE, THE TILE SET SHOULD BE *ALREADY* LOADED +// NOTICE: the header of the file is '6''6''6''M''A''P' (6 chars = "666MAP" +// ---------------------------------------------------------------------------- +bool CTMap::load_map_from_file(const char *filename) +{ + prize_map_indispensable = 0; //reset prize count + + // All file I/O in maps is done using Allegro's packfiles, that way I use compression :D + PACKFILE *fp = NULL; + + fp = pack_fopen(filename, F_READ); + + if (fp == NULL) + return true; // error d00d! :( + + // check header + if (pack_getc(fp) != '6') + return true; // error + if (pack_getc(fp) != '6') + return true; // error + if (pack_getc(fp) != '6') + return true; // error + if (pack_getc(fp) != 'M') + return true; // error + if (pack_getc(fp) != 'A') + return true; // error + if (pack_getc(fp) != 'P') + return true; // error + + // start reading data + + // start pos of the player + sxp = pack_getc(fp); + syp = pack_getc(fp); + + // time of the map + time_m = pack_getc(fp); + time_s = pack_getc(fp); + + // now, get the rest of the map + for (int l = 0; l < MAP_LAYERS; l ++) + { + for (int x = 0; x < TMAP_SIZE; x++) + { + for (int y = 0; y < TMAP_SIZE; y++) + { + // if we reach eof before ending the loop, a error occurs, I need all the map ! + if (pack_feof(fp)) + { + pack_fclose(fp); + return true; // error! + } + tile_map[x][y][l] = pack_getc(fp); + + // check if it is a indispensable prize, count it ; + // DEBUG: this only counts the indispensable intems if the layer == 1, + // otherwise they are on the floor and can't be pick up + if (tile_set[tile_map[x][y][l]].indispensable && l == 1) + { + prize_map_indispensable++; + } + } + } + } + + // get background ID + background_index = pack_getc(fp); + + // get music index ID + music_index = pack_getc(fp); + + pack_fclose(fp); // ready + + return false; // all OK +} + +// ---------------------------------------------------------------------------- +// Save tile map to filename, return true if FAILS, false otherwise +// NOTICE: the header of the file is '6''6''6''M''A''P' (6 chars = "666MAP" +// ---------------------------------------------------------------------------- + +bool CTMap::save_map_to_file(const char *filename) +{ + PACKFILE *fp = NULL; + + fp = pack_fopen(filename, F_WRITE); // DEBUG - deberia ser PACKED, pero PIERDE 2 bytes (BUG de Allegro?) + + if (fp == NULL) + return true; // error d00d! :( + + // Put file's header '666MAP' + pack_putc('6', fp); + pack_putc('6', fp); + pack_putc('6', fp); + pack_putc('M', fp); + pack_putc('A', fp); + pack_putc('P', fp); + + + // put 2 bytes, start pos of the player + pack_putc((char)sxp, fp); + pack_putc((char)syp, fp); + + // time of the map + pack_putc((char)time_m, fp); + pack_putc((char)time_s, fp); + + // now, set the rest of the map + for (int l = 0; l < MAP_LAYERS; l ++) + for (int x = 0; x < TMAP_SIZE; x++) + for (int y = 0; y < TMAP_SIZE; y++) + pack_putc((char)tile_map[x][y][l], fp); + + // set background ID + pack_putc((char)background_index, fp); + + // set music ID + pack_putc((char)music_index, fp); + + // DEBUG:: pad with extra bytes, this was used to fix a _PACK bug, but is no more needed, anyways let it here + for (int pad=0; pad<916; pad++) + pack_putc(rand()%128+64, fp); + + pack_fclose(fp); // ready + return false; +} + +// ---------------------------------------------------------------------------- +// loads a tile set from a DATAFILE (filename is the name of the datafile), +// return true if FAILS, false otherwise +// ---------------------------------------------------------------------------- +bool CTMap::load_tile_set_from_file(char *filename) +{ + this->free_memory(); // free used memory first + + if (tile_set_data.load_datafile(filename)) + return true; // failed :P + + // now, cache all tile set sprites and all the stuff + + // first, I need the tile set configuration, text object named "TILE_SET_CFG_TXT" + if (tile_set_data.get_resource_dat("TILE_SET_CFG_TXT") == NULL) + return true; // error, can't load tile sets without the configuration thing :( + + push_config_state(); // to later restore config settings + // set the config to the data + set_config_data((char *)tile_set_data.get_resource_dat("TILE_SET_CFG_TXT"), tile_set_data.get_resource("TILE_SET_CFG_TXT")->size); + + // get all the tile set + for (int i = 1; i < 256; i++) + tile_set[i].load_from_config(i, &tile_set_data); + + pop_config_state(); // restore config settings + + return false; // done +} + +// ---------------------------------------------------------------------------- +// saves the tile set configuration to a text file (no saves the bitmaps!) +// return true if FAILS, false otherwise +// ---------------------------------------------------------------------------- + +bool CTMap::save_tile_set_config_to_file(char *filename) +{ + push_config_state(); + + // set file + set_config_file(filename); + + // save all data + for (int i = 1; i < 256; i++) + tile_set[i].save_to_config(i); + + pop_config_state(); + return false; // done OK +} + + +// ---------------------------------------------------------------------------- +// Draws a zone of a layer of the map, coordinates are in pixels +// +// ix, iy = top left coordinate of map area to draw +// iw, ih = width, height of map area to draw +// draw_grid = draws the grid on top, only useful for the mapeditor +// +// NOTE: this has 3D software code embedded from Allegro, it *needs* +// a create_scene(), clear_scene() call somewhere before using this, and a +// ---------------------------------------------------------------------------- + +void CTMap::draw_map(BITMAP *bmp, int ix, int iy, int iw, int ih, int layer, bool draw_grid) +{ + // I want the integer remainder, so I can scroll + // this returns the remainder of a/b +#define remainder(a,b) (a-((a/b)*b)) + + int ret,xof,yof; + + if (layer < 0 || layer > MAP_LAYERS-1) + return; + + xof = remainder(ix, TMAPS_W); + yof = remainder(iy, TMAPS_H); + + for (int y = iy; y < iy + ih + TMAPS_H; y += TMAPS_H) + { + for (int x = ix; x < ix + iw + TMAPS_W; x += TMAPS_W) + { + ret = get_tile_type( (x / TMAPS_W), (y / TMAPS_H), layer ); + if (ret > 0) + tile_set[ret].draw(bmp, x-ix-xof, y-iy-yof, draw_grid,layer); + + if (x > TMAP_SIZE*TMAPS_W) + break; // outside bounds, no need to do it + + // draw the 'start' of the player, and the grid (only useful for the map editor) + if (draw_grid) + { + rect(bmp, x-ix-xof, y-iy-yof, x-ix-xof+TMAPS_W,y-iy-yof+TMAPS_H, makecol(128,128,128)); + if (x / TMAPS_W == sxp && y / TMAPS_H == syp) + { + circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/2, makecol(0,0,255)); + circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/3, makecol(255,0,0)); + circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/4, makecol(0,255,0)); + } + } + } + if (y > TMAP_SIZE*TMAPS_H) + break; // outside bounds, no need to do it + } + + +} + + + +// ---------------------------------------------------------------------------- +// updates the logic of the map (animations, time remaining, etc) +// ---------------------------------------------------------------------------- + +void CTMap::update_logic() +{ + curr_tick--; + if (curr_tick < 0) + { + curr_tick = timer_tick_rate; + + time_s--; + if (time_s < 0) + { + time_m--; + if (time_m < 0) + { + time_m = 0; // your time is OVER d00d! HA HA HA HA die bastard die! + time_s = 0; + } + else + { + time_s = 59; + } + } + + } +} + + diff --git a/src/ui_misc.cpp b/src/ui_misc.cpp new file mode 100644 index 0000000..6acaebc --- /dev/null +++ b/src/ui_misc.cpp @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------ +// ui_misc.cpp +// ------------------------------------------------------------------ +// This are some miscellaneous helper functions, mainly used by the kernel UI interface +// ------------------------------------------------------------------ +// Developed By Kronoman - Copyright (c) 2004 +// In loving memory of my father +// ------------------------------------------------------------------ + +#include "ui_misc.h" + +// ------------------------------------------------------------------ +// does a cheap interlaced darkening of bmp +// ------------------------------------------------------------------ +void ui_misc_dark_bmp(BITMAP *bmp) +{ + for (int yi = 0; yi < SCREEN_H; yi+=2) + hline(bmp,0,yi,SCREEN_W,makecol(0,0,0)); +} + +// ------------------------------------------------------------------ +// text in color of ct with shadow in color of cs +// ------------------------------------------------------------------ +void ui_misc_text_out_shadow(BITMAP *bmp, FONT *f, const char *s, int x, int y, int cs, int ct) +{ + textout_centre_ex(bmp,f,s,x+3,y+3,cs,-1); + textout_centre_ex(bmp,f,s,x,y,ct,-1); +} + + +// ------------------------------------------------------------------ +// waits for input from user, like key/mouse/joystick +// this DON'T PUT the message, just waits! +// ------------------------------------------------------------------ +void ui_misc_wait_for_input() +{ + rest(250); + + clear_keybuf(); + + while ((!keypressed()) && (!mouse_b) && (!joy[0].button[0].b)) + { + poll_joystick(); + if (keyboard_needs_poll()) poll_keyboard(); + if (mouse_needs_poll()) poll_mouse(); + }; + + clear_keybuf(); + + rest(250); +} diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..8590f69 --- /dev/null +++ b/test.cpp @@ -0,0 +1,21 @@ +// --------------------------------------------------------------------- +// test.c +// --------------------------------------------------------------------- +// This is a test program +// The only purpose of this program is to check +// if you can compile Allegro programs. +// By Kronoman - In lovin memory of my father - October 2003 +// --------------------------------------------------------------------- + +#include // Allegro : http://alleg.sf.net/ +#include // DUMB : http://dumb.sf.net/ + +int main() +{ + allegro_init(); + + allegro_message("This is a test program to check if you can compile Allegro programs.\nYou can safely erase this program.\n"); + +return 0; +} +END_OF_MAIN();