diff --git a/ReadMe.amigaos4 b/ReadMe.amigaos4 index 64580b1b..093ad23d 100755 --- a/ReadMe.amigaos4 +++ b/ReadMe.amigaos4 @@ -18,6 +18,11 @@ AMIGAOS4 (see main/master doc/ChangeLog.txt file) replay.c screenshot.c seg000.c seg001.c seg002.c seg003.c seg006.c seg009.c -Removed amigaos4 code to load gamecontrollerdb.txt [seg009.c], implemented in master branch. +(2021.07.10) +-Updated to SDLPoP 1.22: common.h. config.h data.c lighting.c menu.c midi.c options.c + proto.h replay.c screenshot.c seg000.c seg002.c seg003.c seg004.c + seg005.c seg006.c seg007.c seg008.c seg009.c types.h +-Compiled with "old" stb_vorbis.c (v1.14) as latest (v1.20) makes audio crawl. Thanks to (in no special order): @@ -35,3 +40,11 @@ https://www.popot.org/custom_levels.php Just unpack inside mods/ drawer AND if it has a .EXE, remove it. Then in SDLPoP.ini change "levelset = original" with "levelset = _custom_level_drawername_" + + +AUDIO IS SLOW/AWFULL +******************** +Use ogg/flac music files from here: (38 MB) + https://www.popot.org/get_the_games/various/PoP1_DOS_music.zip +It's the last link here: https://www.popot.org/get_the_games.php?game=1 +Copy the OGG files to the data/music folder. diff --git a/SDLPoP.ini b/SDLPoP.ini old mode 100644 new mode 100755 index 7661a6df..31db7070 --- a/SDLPoP.ini +++ b/SDLPoP.ini @@ -74,6 +74,11 @@ joystick_only_horizontal = true ; Joystick 'dead zone' sensitivity threshold. Range: 0 to 32767 (default = 8000) joystick_threshold = 8000 +; You can get this file from +; https://github.com/gabomdq/SDL_GameControllerDB/blob/master/gamecontrollerdb.txt +; if you need it +;gamecontrollerdb_file = gamecontrollerdb.txt + ; You can choose which levels to play using the 'levelset' option: ; 'original' --> play the original levels (Default) ; 'Your Mod Name' --> play a custom levelset (the custom files must be in a directory "mods/Your Mod Name/") @@ -221,12 +226,21 @@ fix_hang_on_teleport = true ; You can enter closed exit doors after you met the shadow or Jaffar died, or after you opened one of multiple exits. fix_exit_door = true +; You cannot save game while floating in feather mode. +fix_quicksave_during_feather = true + +; If you are using the caped prince graphics, and crouch with your back towards a closed gate on the left edge on the room, then the prince will slide through the gate. +fix_caped_prince_sliding_through_gate = true + +; Guards become inactive if they are standing on a door top (with floor), or if the prince is standing on a door top. +fix_doortop_disabling_guard = true + [CustomGameplay] ; Turn on customization options. ; Below, you can change individual options. ; NB. If use_custom_options is set to 'false', the options below are not used. -use_custom_options = false +use_custom_options = true ; Starting minutes left. (default = 60) ; To disable the time limit completely, set this to -1. @@ -440,15 +454,29 @@ victory_stops_time_level = 13 win_level = 14 win_room = 5 +; Number of ticks to wait before a loose floor falls. (default = 11) +loose_floor_delay = 11 + +; Game speed when not fighting (delay between frames in 1/60 seconds). Smaller is faster. (default = 5) +base_speed = 5 + +; Game speed when fighting (delay between frames in 1/60 seconds). Smaller is faster. (default = 6) +fight_speed = 6 + +; Chomper speed (length of the animation cycle in frames). Smaller is faster. (default = 15) +chomper_speed = 15 + ; The following customization options can be used in all level sections: ; level_type = 0: dungeon, 1: palace ; level_color = 0: colors from VDUNGEON.DAT/VPALACE.DAT, >0: colors from PRINCE.DAT (You need a PRINCE.DAT from 1.3 or 1.4 for this.) -; guard_type = 0: guard, 1: fat, 2: skel, 3: vizier, 4: shadow +; guard_type = -1: none, 0: guard, 1: fat, 2: skel, 3: vizier, 4: shadow ; guard_hp = Base hitpoints for guards on this level. ; cutscene = 0: none, 2 or 6: standing, 4: lying down, 8: mouse leaves, 9: mouse returns, 12: standing or turn around +; The numbers correspond to the number of the level which had that cutscene in the original game. ; entry_pose = 0: turning, 1: falling, 2: running ; seamless_exit = -1: disabled, >0: room where the level can be seamlessly exited. +; The default values can be found in doc/default_level_options.txt . [Level 0] ; demo [Level 1] [Level 2] @@ -465,3 +493,26 @@ win_room = 5 [Level 13] ; Jaffar [Level 14] ; princess [Level 15] ; potions + + +; The following customization options can be used for guard skills: +; strikeprob = Probability of striking, from 0 to 255. +; restrikeprob = Probability of re-striking after block, from 0 to 255. +; blockprob = Probability of blocking, from 0 to 255. +; impblockprob = Probability of improper blocking, from 0 to 255. +; advprob = Probability of going into hit range, from 0 to 255. +; refractimer = Refractory period after pain, in frames. +; extrastrength = Extra hit points. +; The default values can be found in doc/default_skill_options.txt . +[Skill 0] +[Skill 1] +[Skill 2] +[Skill 3] +[Skill 4] +[Skill 5] +[Skill 6] +[Skill 7] +[Skill 8] +[Skill 9] +[Skill 10] ; a in apoplexy +[Skill 11] ; b in apoplexy diff --git a/doc/ChangeLog.txt b/doc/ChangeLog.txt index 6a073650..076c08c4 100644 --- a/doc/ChangeLog.txt +++ b/doc/ChangeLog.txt @@ -3,12 +3,12 @@ For recent changes, look at GitHub: https://github.com/NagyD/SDLPoP/commits/mast 2012 July 28 ============ -Released disassembly of PoP1 DOS: http://forum.princed.org/viewtopic.php?p=11347#p11347 +Released disassembly of PoP1 DOS: https://forum.princed.org/viewtopic.php?p=11347#p11347 2013 September 01 ================= I posted the room-drawing code (rewritten to C+SDL) in the forum. -http://forum.princed.org/viewtopic.php?f=68&t=3368 +https://forum.princed.org/viewtopic.php?f=68&t=3368 2013 December ============= @@ -16,7 +16,7 @@ Started rewriting the further parts of the disassembly to C. 2014 January 03 =============== -Released disassembly: http://forum.princed.org/viewtopic.php?p=14291#p14291 +Released disassembly: https://forum.princed.org/viewtopic.php?p=14291#p14291 2014 January ============ @@ -31,13 +31,13 @@ Made the code compatible with GNU/Linux. 2014 July 11 ============ First public mention of the port. -http://forum.princed.org/viewtopic.php?p=14975#p14975 +https://forum.princed.org/viewtopic.php?p=14975#p14975 2014 July 21 ============ (version 1.00) The first public release. -http://forum.princed.org/viewtopic.php?f=69&t=3512 +https://forum.princed.org/viewtopic.php?f=69&t=3512 2014 July 22 ============ @@ -191,10 +191,10 @@ Fixed some memory leaks. ================ (version 1.14) FIXED: Changed project type to GUI in the "release" project, too. -FIXED: Ctrl-S did not turn off mixer sounds. -FIXED: Ctrl-S did not stop currently playing sounds. -DONE: Toggle fullscreen with alt-enter. -FIXED: If the game is in full screen, and I switch away (alt-tab) and back, most of the screen will be black, until it is redrawn. +FIXED: Ctrl+S did not turn off mixer sounds. +FIXED: Ctrl+S did not stop currently playing sounds. +DONE: Toggle fullscreen with Alt+Enter. +FIXED: If the game is in full screen, and I switch away (Alt+Tab) and back, most of the screen will be black, until it is redrawn. 2015 August 3 ============= @@ -222,7 +222,7 @@ FIXED: Hardcoded font (hc_font) appeared as white rectangles. New game additions and bug fixes: DONE: Added quicksaving and quickloading (F6 to quicksave anywhere in a level, F9 to quickload). -- Also quicksave/quickload when shift is pressed +- Also quicksave/quickload when Shift is pressed - Do not even attempt to quicksave after the kid has died - Deny quicksave during the feather fall effect - Allow quickloading from the title screen @@ -516,7 +516,7 @@ FIXED: Fix looping "sword moving" sound if the player leaves a room exactly when FIXED: Support 8-bpp images in DAT files. For example the Pyramid mod contains some of these. FIXED: Improved map-making on levels with broken room links: If a room is mapped to an already used place, then put it to the bottom of the map. DONE: Load custom options from DOS PRINCE.EXE files. -DONE: Added a hotkey to display SDL versions. (Ctrl-C) +DONE: Added a hotkey to display SDL versions. (Ctrl+C) FIXED: Better support for high-DPI (Retina) displays. FIXED: Disable integer scaling menu item if SDL version is too old. DONE: Save screenshots and maps into a separate folder and add numbers to the filenames. @@ -525,8 +525,124 @@ DONE: Added support for PC speaker sounds. Use command-line parameter "stdsnd". DONE: Added support for colored torch flames. FIXED: Torches appearing in the leftmost column are now animated. (They are actually in the rightmost column of the left-side room.) -(no release date yet) +2019 October 13 ================= -(upcoming version) +(version 1.20) +FIXED: Fixed crash on Linux when the prince fell out of the level while a guard was active. FIXED: With start_in_blind_mode enabled, moving objects were not displayed until blind mode was toggled off+on. +FIXED: Fix upside-down screen when using PRINCE.EXE from v1.3 or v1.4. +FIXED: Tell Windows that SDLPoP is DPI aware to prevent unwanted stretching. +DONE: Added customization option for loose floor delay. (Used in Neon Persia.) +FIXED: Fix detection of "allow triggering any tile" hack. +DONE: Enable use_custom_options by default in the INI. +FIXED: Fix priorities of sword and spike sounds. (As in PoP 1.3.) + The "spiked" sound didn't interrupt the normal spikes sound when the prince ran into spikes. + With PoP 1.3 sounds, the "guard hurt" sound didn't play when you hit a guard directly after parrying. + +2020 August 20 +================= +(version 1.21) + +FIXED: Skeletons not on level 3 did not behave like skeletons. +FIXED: Don't crash if the intro music is interrupted by Tab in PC Speaker mode. +FIXED: Don't switch to PC Speaker mode if there is a mod name in the replay file. +DONE: Detect guard skill customizations in PRINCE.EXE. (Used in Illusions of Persia, for example.) + TODO: Should we add the guard skill options to the INI, the CFG, the replays, or the settings menu? +FIXED: Don't draw the right edge of loose floors on the left side of a potion or sword. +FIXED: A guard standing on a door top (with floor) should not become inactive. +FIXED: Left jump (top-left) didn't work on some gamepads. +FIXED: Replaying from the command line did not work if there were no replay files in the replay folder. +DONE: Added support for gamecontrollerdb.txt file. +DONE: Detect changes of the shadow's starting positions and automatic moves in PRINCE.EXE. +DONE: Added "Restart Game" to the pause menu, so now it's possible to restart the game using a controller. +DONE: Added fast forward. + TODO: Speed up music and sound effects during fast forwarding. +ADDED: You can now use quicksave and quickload while recording a replay. + TODO: Verify that the quickloaded state is from the currently running recording. + +2021 July 5 +================= +(version 1.22) + +FIXED: The prince can now grab a ledge at the bottom right corner of a room with no room below. + Details: https://forum.princed.org/viewtopic.php?p=30410#p30410 + Testcase: doc/replays-testcases/SNES-PC-set level 11.p1r + See FIX_CORNER_GRAB in config.h . +FIXED: Don't allow killing a skeleton in cheat mode. +FIXED: Ctrl+S didn't mute music (except death music). +FIXED: A falling tile on the left side of a pillar could cause the blue stripe to be drawn over the pillar. +FIXED: The top of moving gates became glitched on levels using non-default palettes. +FIXED: The right edge of pressed drop buttons was black if a big pillar was next to them. +FIXED: Guards appeared in the current room when they fell into spikes in an adjacent room. (Example: original level 11, room 22.) +FIXED: Show an error message if a data file is missing. +FIXED: "Remember guard hp" for non-standard guards. +FIXED: Colored torches were not restored on quickload if you (quit and) restarted the game after quicksave. +DONE: Added command-line parameter "mute": Start the game with sound off. +FIXED: Fixed compiling with some features #undefined. (USE_TEXT, USE_LIGHTING, USE_MENU) +DONE: Hide references to various features if the game was compiled without them. +DONE: A scrollbar appears in the pause menu if scrolling is possible. + It's not clickable yet, it just shows where you are in the list. +DONE: Added command-line parameter "playdemo": Make the demo level playable. +FIXED: Fixed the detection of Ctrl+L during the demo level. +FIXED: Create the screenshots directory in SDLPoP's directory, even if the current directory is something else. + This is to match how the replay folder works. +FIXED: On Windows, use Unicode/UTF-8 for mkdir(). + So the replay and screenshots directories can be created within non-ASCII paths. +FIXED: After quickload, show the room where the prince is, even if the player moved the view away from it (with the H,J,U,N keys). +FIXED: After quickload, don't draw guard HP if a previously viewed room (with the H,J,U,N keys) had a guard but the current room doesn't have one. +DONE: Speed up music, sounds, and transitions during fast forwarding. +FIXED: Prevent the modifier remapping from accessing out-of-range rooms. +FIXED: Don't show the mirror image if the prince is not in the currently shown room. +DONE: Added a new cheat key: Ctrl+B: Go back to the room where the prince is. (Undo H,J,U,N.) +FIXED: Fixed the length of feather fall in fast-forward mode. +FIXED: Prevent torches from being randomly colored when an older replay is loaded. +FIXED: If the prince is fighting a guard, and the player does a quickload to a state where the prince is near the mouse, the prince would draw the sword. +DONE: Added options for changing speeds. They are detected from PRINCE.EXE. (Used in Hurry up Prince, for example.) +DONE: In the settings menu, allow using Page Up, Page Down, Home, End. +FIXED: Fixed graphical glitches with an opening gate: + 1. with a loose floor above and a wall above-right. + 2. with the top half of a big pillar above-right. + See FIX_ABOVE_GATE in config.h . +FIXED: Validate mode crashed with the error "init_scaling: SDL_CreateTexture: Invalid renderer". +DONE: Rewrote all mentions of key combinations to the "Ctrl+A" style. +DONE: Marked which features of SDLPoP are not in the original game. (To prevent confusions like #197) +DONE: Allow Backspace and Ctrl+C during replay. +DONE: During playback, display the number of ticks since start, if the timer is shown (debug cheats: T). +FIXED: Fixed crashing when a MIDI music interrupted another one. +FIXED: Make it possible to go through a certain closed gate on level 11 of Demo by Suave Prince. + Details: https://forum.princed.org/viewtopic.php?p=32326#p32326 + Testcase: doc/replays-testcases/Demo by Suave Prince level 11.p1r + See FIX_COLL_FLAGS in config.h . +FIXED: When the prince jumps up at the bottom of a big pillar split between two rooms, a part near the top of the screen disappears. + Example: The top row in the first room of the original level 5. + Details: See FIX_BIGPILLAR_JUMP_UP in config.h . +DONE: Added a debug cheat to quickload but keep the currently loaded level. (Shift+F9) + Motivation: https://forum.princed.org/viewtopic.php?p=32556#p32556 +FIXED: Fixed spiked/chomped/bumped guards teleporting into the wrong room. (if fixes are enabled) + Details: https://github.com/NagyD/SDLPoP/pull/237 +FIXED: Quicksave during feather fall mode. (if fixes are enabled) + Details: https://github.com/NagyD/SDLPoP/pull/236 +FIXED: Prevent the prince from entering a glitched room when he falls into a wall or he is revived near a wall. + Details: doc/replays-testcases/Original level 2 falling into wall.txt + Testcase: doc/replays-testcases/Original level 2 falling into wall.p1r + See FIX_ENTERING_GLITCHED_ROOMS in config.h . +FIXED: Optimized OPL mixing (MIDI music). + Details: https://github.com/NagyD/SDLPoP/pull/238 + "More importantly: when run on a Raspberry Pi Zero, the music went from unusable to perfect," +FIXED: Fixed the prince sliding through closed gates when you are using the caped prince graphics. + Details: See FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE in config.h . +DONE: Added a compilation-time flag to disable all fixes for vanilla execution. + Details: https://github.com/NagyD/SDLPoP/pull/239 +FIXED: If the prince dies on level 14, don't return to the intro after the level was restarted. + Details: See FIX_LEVEL_14_RESTARTING in config.h . +DONE: Waste an RNG cycle in loose_shake() to match DOS PoP. +DONE: In new replays, use deprecation_number = 2. On playback, waste the RNG cycle only if deprecation_number >= 2. + Older replays have deprecation_number <= 1, and we don't waste the RNG cycle when playing them back. +DONE: When backing offscreen (to the left) from the first guard on level 7 (among others), simulate the glitch from DOS PoP, which causes the prince to fall through the floor. + Details: See https://github.com/NagyD/SDLPoP/issues/229 +FIXED: On Windows, use Unicode/UTF-8 for stat(). + So SDLPoP can load levels from mod folders when a replay file restarts the level or advances to the next level. +DONE: Added a more visible error message when a mod referenced by a replay file cannot be found in the mods folder. +DONE: Allow guard skill customizations in SDLPoP.ini. +DONE: Made FIX_DOORTOP_DISABLING_GUARD configurable. diff --git a/doc/README-SDL.txt b/doc/README-SDL.txt index 8d92955a..e3401edd 100644 --- a/doc/README-SDL.txt +++ b/doc/README-SDL.txt @@ -9,5 +9,5 @@ The Simple DirectMedia Layer library source code is available from: https://www.libsdl.org/ This library is distributed under the terms of the zlib license: -http://www.zlib.net/zlib_license.html +https://www.zlib.net/zlib_license.html diff --git a/doc/Readme.txt b/doc/Readme.txt index e02159c6..d7c26571 100644 --- a/doc/Readme.txt +++ b/doc/Readme.txt @@ -1,7 +1,21 @@ -Name of program: SDLPoP -(Earlier name: David's open-source port of PoP) +SDLPoP +====== + +An open-source port of Prince of Persia, based on the disassembly of the DOS version, extended with new features. + +Links +----- +Forum board: https://forum.princed.org/viewforum.php?f=126 + +GitHub: https://github.com/NagyD/SDLPoP + +Compiled versions: https://www.popot.org/get_the_games.php?game=SDLPoP + +Authors +------- Author: David from forum.princed.org (NagyD on GitHub) + Contributors: (Usernames refer to forum.princed.org or GitHub.) * Andrew (bug reports) @@ -33,29 +47,31 @@ Contributors: * usineur (faster music loading) * yaqxsw (icon) -Forum board: http://forum.princed.org/viewforum.php?f=126 -GitHub: https://github.com/NagyD/SDLPoP - GENERAL ======= -Q: What is this? -A: This is an open-source port/conversion of the DOS game Prince of Persia. +What is this? +------------- +This is an open-source port/conversion of the DOS game Prince of Persia. It is based on the disassembly of the original PoP1 for DOS. -Q: Where can I download that disassembly? -A: Here: http://forum.princed.org/viewtopic.php?f=68&t=3423 -Scroll down to the newest zip files. -The exact version is PoP 1.0, i.e. pop1_ida.zip . -(But I also added some features from later versions.) +Note, however, that SDLPoP has many new features not found in the original game. +These are marked as such in the command-line and the keys sections below. -Sources that helped in making the disassembly: -* Modifications to prince.exe (hex editing) topic in the PoPUW forum. - - That forum is down, you can find some saved posts here: http://forum.princed.org/viewtopic.php?f=73&t=661 - - HTamas posted the dungeon wall drawing algorithm in C-style pseudocode here, along with many hex-edit hacks. - - It was his work that prompted me to start the disassembly and later SDLPoP. Thank you! -* PoP1 Technical Information by Mechner: https://www.popot.org/documentation.php?doc=OldDocuments -* PoP1 Apple II source code by Mechner: https://github.com/jmechner/Prince-of-Persia-Apple-II +Where can I download that disassembly? +-------------------------------------- +* Here: https://forum.princed.org/viewtopic.php?f=68&t=3423 + * Scroll down to the newest zip files. + * The exact version is PoP 1.0, i.e. pop1_ida.zip . + (But I also added some features from later versions.) + +* Sources which helped in making the disassembly: + * Modifications to prince.exe (hex editing) topic in the PoPUW forum. + - That forum is down, you can find some saved posts here: https://forum.princed.org/viewtopic.php?f=73&t=661 + - HTamas posted the dungeon wall drawing algorithm in C-style pseudocode here, along with many hex-edit hacks. + - It was his work that prompted me to start the disassembly and later SDLPoP. Thank you! + * PoP1 Technical Information by Mechner: https://www.popot.org/documentation.php?doc=OldDocuments + * PoP1 Apple II source code by Mechner: https://github.com/jmechner/Prince-of-Persia-Apple-II LICENSE ======= @@ -65,36 +81,38 @@ This program is open source under the GNU General Public License terms, see gpl- USAGE ===== -Q: How do I run it? -A: -Windows: - Double-click on the prince.exe file. +How do I run it? +---------------- +* **Windows:** + Double-click on the `prince.exe` file. If you want to pass command line parameters, you need to open a command line. -GNU/Linux: - First you have to compile the game. (See the DEVELOPING section.) +* **GNU/Linux:** + First you have to compile the game. (See the COMPILING section.) Then you can start the game with the - ./prince + `./prince` command. (Or just double-click it in a file-manager.) -Mac OS X: - See the DEVELOPING section. +* **Mac OS X:** + See the COMPILING section. Thanks to StaticReturn and Poirot for this! -eComStation (OS/2): - Unofficial binaries were posted here: http://forum.princed.org/viewtopic.php?p=18431#p18431 - Alternate link: http://hobbes.nmsu.edu/h-search.php?key=sdlpop - Or you can compile for yourself using gcc, according to that post. - Thanks to digi@os2.snc.ru for the bugfixes! +* You can find compiled versions for these three platforms here: https://www.popot.org/get_the_games.php?game=SDLPoP + +* Unofficial ports for other systems can be found here: https://forum.princed.org/viewtopic.php?f=126&t=4728 + * These were not made by the authors of SDLPoP. -Q: What command-line options are there? -A: +What command-line options are there? +--------------------------------------- * megahit -- Enable cheats. -* a number from 0 to 15 -- Start the given level. (if cheats are enabled) +* a number from 0 to 15 -- Start the given level. (Works only together with `megahit` or `record`.) * draw -- Draw directly to the screen, skipping the offscreen buffer. -* full -- Run in full screen mode. * demo -- Run in demo mode: only the first two levels will be playable, and quotes from magazine reviews will be displayed. +* stdsnd -- Use PC speaker sounds. + +**The following don't exist in the original game:** +* full -- Run in full screen mode. * record -- Start recording immediately. (See the Replays section.) * replay or a *.P1R filename -- Start replaying immediately. (See the Replays section.) * validate "replays/replay.p1r" -- Print out information about a replay file and quit. (See the Replays section.) @@ -106,103 +124,127 @@ A: * --screenshot -- Must be used with megahit and a level number. When the level starts, a screenshot is saved to the screenshots folder and the game quits. * --screenshot-level -- Similar to the above, except the whole level is screenshotted, thus creating a level map. * --screenshot-level-extras -- Similar to the above, except lots of additional info is displayed on the picture. - You can find the meaning of each symbol in Map_Symbols.txt. -* stdsnd -- Use PC speaker sounds. + * You can find the meaning of each symbol in Map_Symbols.txt. +* mute -- Start the game with sound off. (You can still enable sound with Ctrl+S.) +* playdemo -- Make the demo level playable. + * You may want to use it together with options which start the demo level immediately, such as `megahit 0 playdemo` or `record 0 playdemo`. + +What keys can I use? +----------------------- +### Controlling the kid: +* Left: turn or run left +* Right: turn or run right +* Up: jump or climb up +* Down: crouch or climb down +* Shift: pick up things +* Shift+Left/Right: careful step +* Home or Up+Left: jump left +* Page Up or Up+Right: jump right +* Shift while falling: grab onto ledge -Q: What keys can I use? -A: -Controlling the kid: -* left: turn or run left -* right: turn or run right -* up: jump or climb up -* down: crouch or climb down -* shift: pick up things -* shift+left/right: careful step -* home or up+left: jump left -* page up or up+right: jump right You can also use the numeric keypad. -Gamepad equivalents: +### Gamepad equivalents: * D-Pad: arrows * Joystick: left/right (for all-directional joystick movement, set joystick_only_horizontal to false in SDLPoP.ini) * A: down * Y: up -* X or triggers: shift -* Start or Back: display in-game menu - -Controlling the game: -* Esc: pause game -* Space: show time left -* Ctrl-A: restart level -* Ctrl-G: save game (on levels 3..13) -* Ctrl-J: joystick/gamepad mode -* Ctrl-K: keyboard mode - The initial mode is joystick/gamepad if such a device is detected, otherwise keyboard mode. - The game will automatically change input mode when there is input from either device. -* Ctrl-R: return to intro -* Ctrl-S: sound on/off -* Ctrl-V: show version of SDLPoP -* Ctrl-C: show versions of SDL: - COMP: the SDL version SDLPoP was compiled against, i.e. the version of the SDL headers. - LINK: the SDL version SDLPoP was linked against, i.e. the version of SDL2.dll (or its equivalent on other platforms). -* Ctrl-Q: quit game -* Ctrl-L: load game (when in the intro) -* Alt-Enter: toggle fullscreen -* F6: quicksave -* F9: quickload +* X or triggers: Shift +* Start or Back: Display in-game menu. + +If SDLPoP does not work correctly with your gamepad, it might help if you download gamecontrollerdb.txt and configure SDLPoP to use it. +See SDLPoP.ini for details. + +### Controlling the game: +* Esc: Pause game. +* Space: Show how much time is left. +* Ctrl+A: Restart level. +* Ctrl+G: Save game (on levels 3..13). + * This saves only the level number, the remaining time, and the number of hit points. +* Ctrl+L: Load game (press in the intro). + * The game will continue from the beginning of the level where you saved. +* Ctrl+J: Joystick/gamepad mode. +* Ctrl+K: Keyboard mode. + * The initial mode is joystick/gamepad if such a device is detected, otherwise keyboard mode. + * Since version 1.18, SDLPoP automatically changes the input mode when there is input from either device. As a result, Ctrl+K and Ctrl+J are now redundant. +* Ctrl+R: Return to intro. +* Ctrl+S: Sound on/off. +* Ctrl+V: Show version of SDLPoP. +* Ctrl+Q: Quit game. + +**The following don't exist in the original game:** +* Ctrl+C: Show versions of SDL: + * COMP: the SDL version SDLPoP was compiled against, i.e. the version of the SDL headers. + * LINK: the SDL version SDLPoP was linked against, i.e. the version of SDL2.dll (or its equivalent on other platforms). +* Alt+Enter: Toggle full-screen mode. +* F6: Quicksave: Save the exact state of the game. +* F9: Quickload: Load what the last quicksave saved. * F12: Save a screenshot to the screenshots folder. -* Backspace: display in-game menu - -Viewing or recording replays: -* Ctrl+Tab (in game, or on title screen): start or stop recording -* Tab (on title screen): view/cycle through the saved replays in the SDLPoP directory -* F (while viewing a replay): skip forward to the next room -* Shift-F (while viewing a replay): skip forward to the next level - -Cheats: -* Shift-L: go to next level -* c: show numbers of current and adjacent rooms -* Shift-C: show numbers of diagonally adjacent rooms -* -: less remaining time -* +: more remaining time -* r: resurrect kid -* k: kill guard -* Shift-I: flip screen upside-down -* Shift-W: slow falling -* h: look at room to the left -* j: look at room to the right -* u: look at room above -* n: look at room below -* Shift-B: toggle hiding of non-animated objects -* Shift-S: Restore lost hit-point. (Like a small red potion.) -* Shift-T: Give more hit-points. (Like a big red potion.) +* Backspace: Display the in-game menu. (Esc will also display the menu by default, but you can turn that off.) +* `: Fast forward. (It's the key above Tab. It might have a different label depending on your keyboard layout.) + +### Viewing or recording replays: +**Replays don't exist in the original game.** +* Ctrl+Tab (in game, or on title screen): Start or stop recording. +* Tab (on title screen): View/cycle through the saved replays in the SDLPoP directory. +* F (while viewing a replay): Skip forward to the next room. +* Shift+F (while viewing a replay): Skip forward to the next level. + +### Cheats: +* Shift+L: Go to next level. +* C: Show numbers of current and adjacent rooms. +* Shift+C: Show numbers of diagonally adjacent rooms. +* -: Decrease remaining time by one minute. +* +: Increase remaining time by one minute. +* R: Resurrect kid. +* K: Kill guard. +* Shift+I: Flip the screen upside down. +* Shift+W: Slow falling. +* H: Look at the room to the left. +* J: Look at the room to the right. +* U: Look at the room above. +* N: Look at the room below. +* Shift+B: Toggle hiding of non-animated objects. (Also known as "blind mode".) +* Shift+S: Restore a lost hit-point. (Like a small red potion.) +* Shift+T: Give more hit-points. (Like a big red potion.) + +**The following don't exist in the original game:** +* Ctrl+B: Go back to the room where the prince is. (Undo H,J,U,N.) * Shift+F12: Save a screenshot of the whole level to the screenshots folder, thus creating a level map. * Ctrl+Shift+F12: Save a screenshot of the whole level with extras to the screenshots folder. - You can find the meaning of each symbol in Map_Symbols.txt. - -Debug cheats: -* [: shift kid 1 pixel to the left -* ]: shift kid 1 pixel to the right -* t: toggle timer - -Q: Where is the music? -A: -Since version 1.13, the game supports loading music from the data/music folder. + * You can find the meaning of each symbol in Map_Symbols.txt. + +### Debug cheats: +**These don't exist in the original game.** +* [: Shift kid 1 pixel to the left. +* ]: Shift kid 1 pixel to the right. +* T: Toggle display of timer (remaining minutes:seconds:ticks). Also shows the total elapsed ticks during playback. +* F: Toggle display of the remaining feather fall time. (Only if "Fix quick save in feather mode" is enabled.) +* Shift+F9: Quickload but keep the currently loaded level. + * Intended use: Suppose you made a quicksave after you got the prince or a guard into a specific position needed for a trick. + Then you try to do the trick, but you realize that you need to change the level slightly to make the trick work. So you edit the level. + But you can't use a (regular) quickload to get back to the saved position, because that would load the previous version of the level from the quicksave file. + In this situation, press Ctrl+A to load the new version of the level, then press Shift+F9 to load the quicksave onto this new level. + * Motivation: https://forum.princed.org/viewtopic.php?p=32556#p32556 + +Where is the music? +---------------------- +Since version 1.13, the game supports loading music from the data/music folder. Until 1.15, music was not included in releases because it is very big, and it does not change between SDLPoP versions. You need to get the music from here: (38 MB) https://www.popot.org/get_the_games/various/PoP1_DOS_music.zip It's the last link here: https://www.popot.org/get_the_games.php?game=1 Copy the OGG files to the data/music folder. -Since version 1.15, music is included. +Since version 1.15, music is included. Since version 1.18, SDLPoP can play music from the MIDISND*.DAT files and OGG files are not included. MODS ==== -Q: Can I play mods? -A: +Can I play mods? +------------------- Since version 1.02, the game supports LEVELS.DAT, and since version 1.03, the game can use all .DAT files. You can either copy the modified .DAT files to the folder of the game, or the game to the mod's folder. @@ -210,14 +252,16 @@ Since version 1.17, the game can also load from mod folders that have been place If you use this method, only the files different from the original V1.0 data are required in the mod's folder. To choose which mod from the "mods/" folder to play, do one of the following: * Open SDLPoP.ini and change the 'levelset' option to the name of the mod's folder. -* Use the command line option "mod", like so: prince.exe mod "Mod Name" +* Use the command line option "mod", like so: `prince mod "Mod Name"` + Hall-of-Fame and saved game files will also be placed in the mod's folder. Another way to play a mod is to start the game while the current directory is the mod's directory. You can do this from the command line, or with batch files / shell scripts. This is useful if you want to compare the behavior of this port and the original DOS version (to find bugs). Especially if you're editing the level and don't want to copy LEVELS.DAT from one place to the other. -/!\ Note that as of 1.03, the data/font folder and its contents must exist in the current directory! + +/!\ Note that as of 1.03, the data/font folder and its contents must exist in the current directory! Since 1.11, the data/font folder is no longer required. Since version 1.19, SDLPoP can recognize most changes made with CusPoP in a DOS mod's PRINCE.EXE. @@ -226,18 +270,18 @@ In addition, since version 1.17, mods in the "mods/" folder can use a custom con Options in this file can override (most of) the gameplay-related options in SDLPoP.ini. Beware, some mods (especially the harder ones) might rely on bugs that are fixed in SDLPoP. -You can choose whether gameplay quirks should be fixed or not in the file 'SDLPoP.ini': -- Set the option 'use_fixes_and_enhancements' to 'false' to get the exact behavior of the original game. -- Alternatively, set the option 'use_fixes_and_enhancements' to 'true'. You can then also enable or disable - individual fixes and enhancements, depending on your preference. -You can also enable or disable gameplay fixes through the in-game menu. -- In the settings menu, look for the option "Enhanced mode (allow bug fixes)" in the GAMEPLAY section. +* You can choose whether gameplay quirks should be fixed or not in the file 'SDLPoP.ini': + - Set the option 'use_fixes_and_enhancements' to 'false' to get the exact behavior of the original game. + - Alternatively, set the option 'use_fixes_and_enhancements' to 'true'. You can then also enable or disable + individual fixes and enhancements, depending on your preference. +* You can also enable or disable gameplay fixes through the in-game menu. + - In the settings menu, look for the option "Enhanced mode (allow bug fixes)" in the GAMEPLAY section. Furthermore, SDLPoP opens up new possibilities for mod making. For example: Falcury released a mod, called "Secrets of the Citadel" that "has been designed to be played using a modified version of SDLPoP". -Description and download: http://forum.princed.org/viewtopic.php?f=73&t=3664 - Alternate link: https://www.popot.org/custom_levels.php?mod=0000153 +* Description and download: https://forum.princed.org/viewtopic.php?f=73&t=3664 +* Alternate link: https://www.popot.org/custom_levels.php?mod=0000153 Since version 1.16, there is support for fake tiles, for example walls that the prince can go through. The Apoplexy level editor supports these additional tiles since v3.0: https://www.apoplexy.org/ @@ -245,21 +289,23 @@ The Apoplexy level editor supports these additional tiles since v3.0: https://ww REPLAYS ======= +**Replays don't exist in the original game.** -Q: How do replays work? -A: +How do replays work? +----------------------- Starting from version 1.16, you can capture or view replays in SDLPoP. To start recording, press Ctrl+Tab on the title screen or while in game. To stop recording, press Ctrl+Tab again. Your replays get saved in the "replays/" directory as files with a .P1R extension. You can change the location where replays are kept using the setting 'replays_folder' in SDLPoP.ini. -If you want to start recording on a specific level, you can use the command "prince.exe record ", +If you want to start recording on a specific level, you can use the command `prince record `, where is the level on which you want to start. To view a replay, you can press Tab while on the title screen. To cycle to the next replay (in reverse creation order), press Tab again. You can also double-click on a replay file (and tell the OS that the file needs to be opened with the SDLPoP executable). SDLPoP will then immediately play that replay. Dragging and dropping onto the executable also works. + While viewing a replay, you can press F to skip forward to the next room, or Shift+F to skip to the next level. Your settings specified in SDLPoP.ini (including whether you are playing with bugfixes on or off) are remembered in the replay. @@ -267,81 +313,104 @@ It shouldn't matter how SDLPoP.ini is set up when you are viewing the replay lat Note that any cheats you use do not get saved as part of the replay. To print out information about the replay from the command-line, you can use the 'validate' command-line parameter. -Example usage: prince validate "replays/replay.p1r" - -DEVELOPING -========== - -Q: How do I (re)compile it? -A: -Prerequisites for all platforms: - Make sure that you have the development versions of the "SDL2" and "SDL2_image" libraries installed. - -Windows: - If you are using Dev-C++: - I originally used Dev-C++ version 4.9.9.2 from here: https://sourceforge.net/projects/dev-cpp/files/Binaries/ - More recently, I'm using this version: https://sourceforge.net/projects/orwelldevcpp/ - For Dev-C++ you need the MinGW Development Libraries of SDL2: - https://libsdl.org/download-2.0.php - https://libsdl.org/projects/SDL_image/ - To install these, just extract the contents of the i686-w64-mingw32 folder from each archive to the Dev-Cpp folder. - To compile, open one of the .dev files and click the compile icon. - - Building with Visual Studio: - Run build.bat in the src/ directory. - For this to work, you first need to do two other things: - a) Run vsvarsall.bat from the command line, with either 'x86' or 'x64' as a parameter. - This batch file is included with all installations of MS Visual Studio, but its exact location may vary. - For VS2017, the command you should run might look like this: - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 - This step sets up various environment variables, to enable running the compiler from the command line. - b) Set up the environment variable 'SDL2' to point to the SDL2 development library files. - To do this, you can use a command like so: - set "SDL2=C:\libraries\SDL2-2.0.8" - You can get the SDL2 library files from here: (download the Visual C++ 32/64-bit development package) - https://www.libsdl.org/download-2.0.php - (You could create a small batch file to automate the above steps on your system.) - Alternatively, you can also build SDLPoP using MSVC with NMake (use the makefile src/NMakefile). - - You can also use CMake, in conjunction with the MinGW-w64 toolchain. - You could either invoke CMake from the command line yourself, or use an IDE that uses CMake internally. - As an example, CLion uses CMake as its project model. - If you are using CLion as your IDE, you can simply load the src/ directory as a project. - -GNU/Linux: - You can install the libraries with apt-get or a package manager. - sudo apt-get install libsdl2-image-dev - - Alternatively, you can compile SDL2 and the other libraries from source. - https://libsdl.org/download-2.0.php - https://libsdl.org/projects/SDL_image/ - I recommend this if your distro does not have the newest SDL version, because older SDL versions have some known bugs. - Namely, sound becomes garbled in SDL versions older than 2.0.4 if the sound output is not 8-bit. - - When you have the libraries, just type the command: - make all +Example usage: `prince validate "replays/replay.p1r"` + +Since version 1.21 you can re-record if you make a mistake: +While recording, make a quicksave to mark your place, and press quickload to return to that place. + +COMPILING +========= + +Prerequisites for all platforms +------------------------------- +* Make sure that you have the development versions of the "SDL2" and "SDL2_image" libraries installed. + * The platform-specific sections below detail how to install them. + +Windows +------- +* If you are using Dev-C++: + * I originally used Dev-C++ version 4.9.9.2 from here: https://sourceforge.net/projects/dev-cpp/files/Binaries/ + * More recently, I'm using this version: https://sourceforge.net/projects/orwelldevcpp/ + * For Dev-C++ you need the MinGW Development Libraries of SDL2: + * https://libsdl.org/download-2.0.php + * https://libsdl.org/projects/SDL_image/ + * Download the `*-mingw.tar.gz` files. + * To install these, just extract the contents of the `i686-w64-mingw32` folder from each archive to: + * on 64-bit Windows: `c:\Program Files (x86)\Dev-Cpp\MinGW64\`. + * on 32-bit Windows: `c:\Program Files\Dev-Cpp\MinGW64\`. + * You need to "merge" the contents of the `bin`, `include`, etc. folders in the archives into the existing folders with the same name in the `MinGW64` folder. + * To compile, open one of the .dev files and click the compile icon. + +* Building with Visual Studio: + * Run build.bat in the src/ directory. + * For this to work, you first need to do two other things: + 1. Run vsvarsall.bat from the command line, with either 'x86' or 'x64' as a parameter. + This batch file is included with all installations of MS Visual Studio, but its exact location may vary. + For VS2017, the command you should run might look like this: + ``` + call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 + ``` + This step sets up various environment variables, to enable running the compiler from the command line. + 2. Set up the environment variable 'SDL2' to point to the SDL2 development library files. + To do this, you can use a command like so: + ``` + set "SDL2=C:\libraries\SDL2-2.0.8" + ``` + You can get the SDL2 library files from here: + https://www.libsdl.org/download-2.0.php + (download the Visual C++ 32/64-bit development package) + * (You could create a small batch file to automate the above steps on your system.) + * Alternatively, you can also build SDLPoP using MSVC with NMake (use the makefile src/NMakefile). + +* You can also use CMake, in conjunction with the MinGW-w64 toolchain. + * You could either invoke CMake from the command line yourself, or use an IDE that uses CMake internally. + * As an example, CLion uses CMake as its project model. + * If you are using CLion as your IDE, you can simply load the src/ directory as a project. + +GNU/Linux +--------- +* You can install the libraries with apt-get or a package manager. + ``` + sudo apt-get install libsdl2-image-dev + ``` + +* Alternatively, you can compile SDL2 and the other libraries from source. + * https://libsdl.org/download-2.0.php + * https://libsdl.org/projects/SDL_image/ + * I recommend this if your distro does not have the newest SDL version, because older SDL versions have some known bugs. + * Namely, sound becomes garbled in SDL versions older than 2.0.4 if the sound output is not 8-bit. + +* When you have the libraries, just type the command: + ``` + make all + ``` and the game should compile. - You can create a desktop/menu icon with: - sudo make install +* You can create a desktop/menu icon with: + ``` + sudo make install + ``` and remove it with: - sudo make uninstall - -macOS: - Get SDL2 and dependencies - a) Install "port" from https://www.macports.org/ - b) sudo port install libsdl2 libsdl2_image - or - a) Install "homebrew" - b) brew install sdl2 sdl2_image - - Get development tools: - a) Install Xcode. - b) Install the "command line developer tools" by typing 'xcode-select --install' at the prompt. - c) Using terminal, in the '/src' directory of SDLPoP, type: make - - PLAY! - a) Type './prince' or './prince full'. - b) Hit Control-Q to quit. - - Tested on OSX 10.9.5, OSX 10.11.2 and macOS 10.13. + ``` + sudo make uninstall + ``` + +macOS +----- +* Get SDL2 and dependencies + 1. Install "port" from https://www.macports.org/ + 2. `sudo port install libsdl2 libsdl2_image` + * or + 1. Install "homebrew" + 2. `brew install sdl2 sdl2_image` + +* Get development tools: + 1. Install Xcode. + 2. Install the "command line developer tools" by typing `xcode-select --install` at the prompt. + 3. Using terminal, in the '/src' directory of SDLPoP, type: `make` + +* PLAY! + 1. In the project root directory. Type `./prince` or `./prince full`. + 2. Hit Ctrl+Q to quit. + +* Tested on OSX 10.9.5, OSX 10.11.2, macOS 10.13 and 10.14. diff --git a/doc/gpl-3.0.txt b/doc/gpl-3.0.txt index 94a9ed02..f288702d 100644 --- a/doc/gpl-3.0.txt +++ b/doc/gpl-3.0.txt @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/src/Makefile.amigaos4 b/src/Makefile.amigaos4 index 29738bc3..14bee132 100755 --- a/src/Makefile.amigaos4 +++ b/src/Makefile.amigaos4 @@ -15,14 +15,16 @@ OS := $(shell uname) ifeq ($(strip $(OS)),AmigaOS) AMIGADATE = $(shell c:date LFORMAT %d.%m.%Y) + CXXINC = -ISDK:Local/newlib/include/SDL2 else AMIGADATE = $(shell date +"%-d.%-m.%Y") + CXXINC = -I/usr/local/amiga/ppc-amigaos/SDK/local/common/include/SDL2 endif #LIBS = -use-dynld -lSDL2_image -lz -lm -ltiff -lwebp -lpng16 -ljpeg -lvorbisfile -lvorbis -lSDL2 -athread=native -lpthread LIBS = -lSDL2_image -ltiff -lwebp -lpng -ljpeg -lz -lm -lvorbisfile -lvorbis -lSDL2 -lpthread #INCS = -gstabs -ISDK:Local/newlib/include/SDL2 -D__AMIGADATE__=\"`c:date LFORMAT %d.%m.%Y`\" -D__USE_INLINE__ -INCS = -gstabs -ISDK:Local/newlib/include/SDL2 -D__AMIGADATE__=\"$(AMIGADATE)\" -D__USE_INLINE__ +INCS = -gstabs $(CXXINC) -D__AMIGADATE__=\"$(AMIGADATE)\" -D__USE_INLINE__ CFLAGS += $(INCS) -Wall -std=gnu99 diff --git a/src/common.h b/src/common.h index 1dd65e87..aac84a83 100644 --- a/src/common.h +++ b/src/common.h @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -68,7 +68,7 @@ extern "C" { #define snprintf_check(dst, size, ...) do { \ int __len; \ __len = snprintf(dst, size, __VA_ARGS__); \ - if (__len < 0 || __len >= size) { \ + if (__len < 0 || __len >= (int)size) { \ fprintf(stderr, "%s: buffer truncation detected!\n", __func__);\ quit(2); \ } \ diff --git a/src/config.h b/src/config.h index 9301739d..43789915 100644 --- a/src/config.h +++ b/src/config.h @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,7 +30,7 @@ The authors of this program may be contacted at https://forum.princed.org #define POP_MAX_PATH 256 #define POP_MAX_OPTIONS_SIZE 256 -#define SDLPOP_VERSION "1.21" +#define SDLPOP_VERSION "1.22" #define WINDOW_TITLE "Prince of Persia (SDLPoP) v" SDLPOP_VERSION // Enable or disable the SDL hardware accelerated renderer backend @@ -65,9 +65,13 @@ The authors of this program may be contacted at https://forum.princed.org // The one minute penalty will also be applied when quickloading from e.g. the title screen. #define USE_QUICKLOAD_PENALTY +#ifdef USE_QUICKSAVE // Replay relies on quicksave, because the replay file begins with a quicksave of the initial state. + // Enable recording/replay feature. #define USE_REPLAY +#endif + // Adds a way to crouch immediately after climbing up: press down and forward simultaneously. // In the original game, this could not be done (pressing down always causes the kid to climb down). #define ALLOW_CROUCH_AFTER_CLIMBING @@ -98,6 +102,9 @@ The authors of this program may be contacted at https://forum.princed.org // The mentioned tricks can be found here: https://www.popot.org/documentation.php?doc=Tricks +// A compilation-time option to disable all fixes. Useful for automated solving tools that require vanilla emulation. +#ifndef DISABLE_ALL_FIXES + // If a room is linked to itself on the left, the closing sounds of the gates in that room can't be heard. #define FIX_GATE_SOUNDS @@ -206,6 +213,50 @@ The authors of this program may be contacted at https://forum.princed.org // A guard standing on a door top (with floor) should not become inactive. #define FIX_DOORTOP_DISABLING_GUARD +// Fix graphical glitches with an opening gate: +// 1. with a loose floor above and a wall above-right. +// 2. with the top half of a big pillar above-right. +// Details: https://forum.princed.org/viewtopic.php?p=31884#p31884 +#define FIX_ABOVE_GATE + +// Disable this fix to make it possible to go through a certain closed gate on level 11 of Demo by Suave Prince. +// Details: https://forum.princed.org/viewtopic.php?p=32326#p32326 +// Testcase: doc/replays-testcases/Demo by Suave Prince level 11.p1r +//#define FIX_COLL_FLAGS + +// The prince can now grab a ledge at the bottom right corner of a room with no room below. +// Details: https://forum.princed.org/viewtopic.php?p=30410#p30410 +// Testcase: doc/replays-testcases/SNES-PC-set level 11.p1r +#define FIX_CORNER_GRAB + +// When the prince jumps up at the bottom of a big pillar split between two rooms, a part near the top of the screen disappears. +// Example: The top row in the first room of the original level 5. +// Videos: https://forum.princed.org/viewtopic.php?p=32227#p32227 +// Explanation: https://forum.princed.org/viewtopic.php?p=32414#p32414 +#define FIX_BIGPILLAR_JUMP_UP + +// When the prince dies behind a wall, and he is revived with R, he appears in a glitched room. +// (Example: The bottom right part of the bottom right room of level 3.) +// The same room can also be reached by falling into a wall. (Falling into the wall, itself, is a different glitch, though.) +// Testcase: doc/replays-testcases/Original level 2 falling into wall.p1r +// More info: https://forum.princed.org/viewtopic.php?f=68&t=4467 +#define FIX_ENTERING_GLITCHED_ROOMS + +// If you are using the caped prince graphics, and crouch with your back towards a closed gate on the left edge on the room, then the prince will slide through the gate. +// You can also try this with the original graphics if your use the debug cheat "[" to push the prince into the gate. +// This option fixes that. +// You can get the caped prince graphics here: https://www.popot.org/custom_levels.php?action=KID.DAT (it's the one by Veke) +// Video: https://www.popot.org/documentation.php?doc=TricksPage3#83 +// Explanation: https://forum.princed.org/viewtopic.php?p=32701#p32701 +// This also fixes the bug described at FIX_COLL_FLAGS. +#define FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE + +// If the prince dies on level 14, restarting the level will not stop the "Press Button to Continue" timer, and the game will return to the intro after a few seconds. +// How to reproduce: https://forum.princed.org/viewtopic.php?p=16926#p16926 +// Technical explanation: https://forum.princed.org/viewtopic.php?p=16408#p16408 (the second half of the post) +#define FIX_LEVEL_14_RESTARTING + +#endif // ifndef DISABLE_ALL_FIXES // Debug features: @@ -223,7 +274,7 @@ The authors of this program may be contacted at https://forum.princed.org -// Darken those parts of the screen that are not near a torch. +// Darken those parts of the screen which are not near a torch. #define USE_LIGHTING // Enable screenshot features. @@ -233,11 +284,30 @@ The authors of this program may be contacted at https://forum.princed.org // Useful if SDL detected a gamepad but there is none. #define USE_AUTO_INPUT_MODE +#ifdef USE_TEXT // The menu won't work without text. + // Display the in-game menu. #define USE_MENU +#endif + +// Enable colored torches. A torch can be colored by changing its modifier in a level editor. #define USE_COLORED_TORCHES +// Enable fast forwarding with the backtick key. +#define USE_FAST_FORWARD + +// Set how much should the fast forwarding speed up the game. +#define FAST_FORWARD_RATIO 10 + +// Speed up the sound during fast forward using resampling. +// If disabled, the sound is sped up by clipping out parts from it. +//#define FAST_FORWARD_RESAMPLE_SOUND + +// Mute the sound during fast forward. +//#define FAST_FORWARD_MUTE + + // Default SDL_Joystick button values #define SDL_JOYSTICK_BUTTON_Y 2 #define SDL_JOYSTICK_BUTTON_X 3 diff --git a/src/data.c b/src/data.c index b51dead9..069da985 100644 --- a/src/data.c +++ b/src/data.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/data.h b/src/data.h index 64d84fdc..636eab41 100644 --- a/src/data.h +++ b/src/data.h @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -161,6 +161,10 @@ extern word room_AL; // data:4F84 extern level_type level; +#ifdef USE_COLORED_TORCHES +extern byte torch_colors[24+1][30]; // indexed 1..24 +#endif + // data:42AA extern short table_counts[5]; @@ -674,8 +678,10 @@ extern word justblocked; // name from Apple II source extern word last_loose_sound; extern int last_key_scancode; +#ifdef USE_TEXT extern font_type hc_font INIT(= {0x01,0xFF, 7,2,1,1, NULL}); extern textstate_type textstate INIT(= {0,0,0,15,&hc_font}); +#endif extern int need_quick_save INIT(= 0); extern int need_quick_load INIT(= 0); @@ -715,7 +721,7 @@ extern byte enable_info_screen INIT(= 1); extern byte enable_controller_rumble INIT(= 0); extern byte joystick_only_horizontal INIT(= 0); extern int joystick_threshold INIT(= 8000); -extern char gamecontrollerdb_file[POP_MAX_PATH] INIT(= "gamecontrollerdb.txt"); +extern char gamecontrollerdb_file[POP_MAX_PATH] INIT(= ""); extern byte enable_quicksave INIT(= 1); extern byte enable_quicksave_penalty INIT(= 1); extern byte enable_replay INIT(= 1); @@ -832,6 +838,11 @@ extern custom_options_type custom_defaults INIT(= { // automatic moves .demo_moves = {{0x00, 0}, {0x01, 1}, {0x0D, 0}, {0x1E, 1}, {0x25, 5}, {0x2F, 0}, {0x30, 1}, {0x41, 0}, {0x49, 2}, {0x4B, 0}, {0x63, 2}, {0x64, 0}, {0x73, 5}, {0x80, 6}, {0x88, 3}, {0x9D, 7}, {0x9E, 0}, {0x9F, 1}, {0xAB, 4}, {0xB1, 0}, {0xB2, 1}, {0xBC, 0}, {0xC1, 1}, {0xCD, 0}, {0xE9,-1}}, .shad_drink_move = {{0x00, 0}, {0x01, 1}, {0x0E, 0}, {0x12, 6}, {0x1D, 7}, {0x2D, 2}, {0x31, 1}, {0xFF,-2}}, + + // speeds + .base_speed = 5, + .fight_speed = 6, + .chomper_speed = 15, }); extern custom_options_type* custom INIT(= &custom_defaults); @@ -876,6 +887,7 @@ extern word cheats_enabled INIT(= 0); #ifdef USE_DEBUG_CHEATS extern byte debug_cheats_enabled INIT(= 0); extern byte is_timer_displayed INIT(= 0); +extern byte is_feather_timer_displayed INIT(= 0); #endif #ifdef USE_MENU @@ -891,7 +903,13 @@ extern bool escape_key_suppressed; extern int menu_control_scroll_y; extern sbyte is_menu_shown; extern byte enable_pause_menu INIT(= 1); +#endif extern char mods_folder[POP_MAX_PATH] INIT(= "mods"); + +extern int play_demo_level; + +#ifdef USE_REPLAY +extern int g_deprecation_number; #endif #undef INIT diff --git a/src/lighting.c b/src/lighting.c index 2e3ca145..350b90bb 100644 --- a/src/lighting.c +++ b/src/lighting.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/menu.c b/src/menu.c index d170bfbc..b06d43fc 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -83,8 +83,10 @@ pause_menu_item_type pause_menu_items[] = { {.id = PAUSE_MENU_RESUME, .text = "RESUME"}, // TODO: Add a cheats menu, where you can choose a cheat from a list? /*{.id = PAUSE_MENU_CHEATS, .text = "CHEATS", .required = &cheats_enabled},*/ - {.id = PAUSE_MENU_SAVE_GAME, .text = "SAVE GAME"}, - {.id = PAUSE_MENU_LOAD_GAME, .text = "LOAD GAME"}, +#ifdef USE_QUICKSAVE // TODO: If quicksave is disabled, show regular save/load instead? + {.id = PAUSE_MENU_SAVE_GAME, .text = "QUICKSAVE"}, + {.id = PAUSE_MENU_LOAD_GAME, .text = "QUICKLOAD"}, +#endif {.id = PAUSE_MENU_RESTART_LEVEL, .text = "RESTART LEVEL"}, {.id = PAUSE_MENU_SETTINGS, .text = "SETTINGS"}, {.id = PAUSE_MENU_RESTART_GAME, .text = "RESTART GAME"}, @@ -194,6 +196,9 @@ enum setting_ids { SETTING_FIX_HIDDEN_FLOORS_DURING_FLASHING, SETTING_FIX_HANG_ON_TELEPORT, SETTING_FIX_EXIT_DOOR, + SETTING_FIX_QUICKSAVE_DURING_FEATHER, + SETTING_FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE, + SETTING_FIX_DOORTOP_DISABLING_GUARD, SETTING_USE_CUSTOM_OPTIONS, SETTING_START_MINUTES_LEFT, SETTING_START_TICKS_LEFT, @@ -265,6 +270,9 @@ enum setting_ids { SETTING_WIN_LEVEL, SETTING_WIN_ROOM, SETTING_LOOSE_FLOOR_DELAY, + SETTING_BASE_SPEED, + SETTING_FIGHT_SPEED, + SETTING_CHOMPER_SPEED, SETTING_LEVEL_SETTINGS, SETTING_LEVEL_TYPE, SETTING_LEVEL_COLOR, @@ -348,24 +356,33 @@ setting_type visuals_settings[] = { .explanation = "Sharp - Use nearest neighbour resampling.\n" "Fuzzy - First upscale to double size, then use smooth scaling.\n" "Blurry - Use smooth scaling."}, +#ifdef USE_FADE {.id = SETTING_ENABLE_FADE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_fade, .text = "Fading enabled", .explanation = "Turn fading on or off."}, +#endif +#ifdef USE_FLASH {.id = SETTING_ENABLE_FLASH, .style = SETTING_STYLE_TOGGLE, .linked = &enable_flash, .text = "Flashing enabled", .explanation = "Turn flashing on or off."}, +#endif +#ifdef USE_LIGHTING {.id = SETTING_ENABLE_LIGHTING, .style = SETTING_STYLE_TOGGLE, .linked = &enable_lighting, .text = "Torch shadows enabled", - .explanation = "Darken those parts of the screen that are not near a torch."}, + .explanation = "Darken those parts of the screen which are not near a torch."}, +#endif }; setting_type gameplay_settings[] = { {.id = SETTING_ENABLE_CHEATS, .style = SETTING_STYLE_TOGGLE, .linked = &cheats_enabled, .text = "Enable cheats", .explanation = "Turn cheats on or off."/*"\nAlso, display the CHEATS option on the pause menu."*/}, +#ifdef USE_COPYPROT {.id = SETTING_ENABLE_COPYPROT, .style = SETTING_STYLE_TOGGLE, .linked = &enable_copyprot, .text = "Enable copy protection level", .explanation = "Enable or disable the potions (copy protection) level."}, +#endif +#ifdef USE_QUICKSAVE {.id = SETTING_ENABLE_QUICKSAVE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_quicksave, .text = "Enable quicksave", .explanation = "Enable quicksave/load feature.\nPress F6 to quicksave, F9 to quickload."}, @@ -374,11 +391,14 @@ setting_type gameplay_settings[] = { .explanation = "Try to let time run out when quickloading (similar to dying).\n" "Actually, the 'remaining time' will still be restored, " "but a penalty (up to one minute) will be applied."}, +#endif +#ifdef USE_REPLAY {.id = SETTING_ENABLE_REPLAY, .style = SETTING_STYLE_TOGGLE, .linked = &enable_replay, .text = "Enable replays", .explanation = "Enable recording/replay feature.\n" "Press Ctrl+Tab in-game to start recording.\n" "To stop, press Ctrl+Tab again."}, +#endif {.id = SETTING_USE_FIXES_AND_ENHANCEMENTS, .style = SETTING_STYLE_TOGGLE, .linked = &use_fixes_and_enhancements, .text = "Enhanced mode (allow bug fixes)", .explanation = "Turn on game fixes and enhancements.\n" @@ -530,6 +550,18 @@ setting_type gameplay_settings[] = { .linked = &fixes_saved.fix_exit_door, .required = &use_fixes_and_enhancements, .text = "Fix exit doors", .explanation = "You can enter closed exit doors after you met the shadow or Jaffar died, or after you opened one of multiple exits."}, + {.id = SETTING_FIX_QUICKSAVE_DURING_FEATHER, .style = SETTING_STYLE_TOGGLE, + .linked = &fixes_saved.fix_quicksave_during_feather, .required = &use_fixes_and_enhancements, + .text = "Fix quick save in feather mode", + .explanation = "You cannot save game while floating in feather mode."}, + {.id = SETTING_FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE, .style = SETTING_STYLE_TOGGLE, + .linked = &fixes_saved.fix_caped_prince_sliding_through_gate, .required = &use_fixes_and_enhancements, + .text = "Fix sliding through closed gate", + .explanation = "If you are using the caped prince graphics, and crouch with your back towards a closed gate on the left edge on the room, then the prince will slide through the gate."}, + {.id = SETTING_FIX_DOORTOP_DISABLING_GUARD, .style = SETTING_STYLE_TOGGLE, + .linked = &fixes_saved.fix_doortop_disabling_guard, .required = &use_fixes_and_enhancements, + .text = "Fix door top disabling guard", + .explanation = "Guards become inactive if they are standing on a door top (with floor), or if the prince is standing on a door top."}, }; NAMES_LIST(tile_type_setting_names, { @@ -847,6 +879,18 @@ setting_type mods_settings[] = { .linked = &custom_saved.loose_floor_delay, .number_type = SETTING_BYTE, .min = 0, .max = 127, .text = "Loose floor delay", .explanation = "Number of seconds to wait before a loose floor falls.\n(default = 0.92)"}, + {.id = SETTING_BASE_SPEED, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options, + .linked = &custom_saved.base_speed, .number_type = SETTING_BYTE, .min = 1, .max = 127, + .text = "Base speed", + .explanation = "Game speed when not fighting (delay between frames in 1/60 seconds). Smaller is faster.\n(default = 5)"}, + {.id = SETTING_FIGHT_SPEED, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options, + .linked = &custom_saved.fight_speed, .number_type = SETTING_BYTE, .min = 1, .max = 127, + .text = "Fight speed", + .explanation = "Game speed when fighting (delay between frames in 1/60 seconds). Smaller is faster.\n(default = 6)"}, + {.id = SETTING_CHOMPER_SPEED, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options, + .linked = &custom_saved.chomper_speed, .number_type = SETTING_BYTE, .min = 0, .max = 127, + .text = "Chomper speed", + .explanation = "Chomper speed (length of the animation cycle in frames). Smaller is faster.\n(default = 15)"}, }; NAMES_LIST(level_type_setting_names, { "Dungeon", "Palace", }); @@ -1221,6 +1265,7 @@ void turn_setting_on_off(int setting_id, byte new_state, void* linked) { #endif } break; +#ifdef USE_LIGHTING case SETTING_ENABLE_LIGHTING: enable_lighting = new_state; if (new_state && lighting_mask == NULL) { @@ -1228,6 +1273,7 @@ void turn_setting_on_off(int setting_id, byte new_state, void* linked) { } need_full_redraw = 1; break; +#endif case SETTING_ENABLE_SOUND: turn_sound_on_off((new_state != 0) * 15); break; @@ -1401,7 +1447,7 @@ void draw_setting(setting_type* setting, rect_type* parent, int* y_offset, int i rect_to_sdlrect(&setting_box, &dest_rect); uint32_t rgb_color = SDL_MapRGBA(overlay_surface->format, 55, 55, 55, 255); if (SDL_FillRect(overlay_surface, &dest_rect, rgb_color) != 0) { - sdlperror("SDL_FillRect"); + sdlperror("draw_setting: SDL_FillRect"); quit(1); } rect_type left_side_of_setting_box = setting_box; @@ -1559,6 +1605,25 @@ void draw_settings_area(settings_area_type* settings_area) { if (scroll_position + num_drawn_settings < settings_area->setting_count) { draw_image_with_blending(arrowhead_down_image, 200, 151); } + + // Draw a scroll bar if needed. + // It's not clickable yet, it just shows where you are in the list. + if (num_drawn_settings < settings_area->setting_count) { + const int scrollbar_width = 2; + rect_type scrollbar_rect = { + .top = settings_area_rect.top - 5, .bottom = settings_area_rect.bottom, + .left = settings_area_rect.right + 10 - scrollbar_width, .right = settings_area_rect.right + 10 + }; + method_5_rect(&scrollbar_rect, blitters_0_no_transp, color_8_darkgray); + + int scrollbar_height = scrollbar_rect.bottom - scrollbar_rect.top; + rect_type scrollbar_slider_rect = { + .top = scrollbar_rect.top + scroll_position * scrollbar_height / settings_area->setting_count, + .bottom = scrollbar_rect.top + (scroll_position + num_drawn_settings) * scrollbar_height / settings_area->setting_count, + .left = scrollbar_rect.left, .right = scrollbar_rect.right + }; + method_5_rect(&scrollbar_slider_rect, blitters_0_no_transp, color_7_lightgray); + } } void draw_settings_menu() { @@ -1583,18 +1648,52 @@ void draw_settings_menu() { hovering_item_changed = true; } } else if (controlled_area == 1) { + // settings area int old_highlighted_setting_id = highlighted_setting_id; - if (menu_control_y == 1) { - highlighted_setting_id = next_setting_id; - if (at_scroll_down_boundary) { - menu_scroll(1); - } - } else if (menu_control_y == -1) { - highlighted_setting_id = previous_setting_id; - if (at_scroll_up_boundary) { - menu_scroll(-1); + + // Why does the global variable contain the ID instead of the index?... + // Find the index from the ID. + settings_area_type* current_settings_area = get_settings_area(active_settings_subsection); + int highlighted_setting_index = -1; + for (int i = 0; i < current_settings_area->setting_count; i++) { + if (highlighted_setting_id == current_settings_area->settings[i].id) { + highlighted_setting_index = i; + break; } } + + int last = current_settings_area->setting_count - 1; + int max_scroll = MAX(0, current_settings_area->setting_count - 9); + + if (menu_control_y > 0) { + // DOWN + highlighted_setting_index += menu_control_y; + if (highlighted_setting_index > last) highlighted_setting_index = last; + + // With Page Down, try to leave the selection in the same row visually. + if (menu_control_y > +1) scroll_position += menu_control_y; + + } else if (menu_control_y < 0) { + // UP + highlighted_setting_index += menu_control_y; + if (highlighted_setting_index < 0) highlighted_setting_index = 0; + + // With Page Up, try to leave the selection in the same row visually. + if (menu_control_y < -1) scroll_position += menu_control_y; + + } + + if (menu_control_y != 0) { + // We check both directions in both cases, to scroll the highlighted row back into sight even if the user scrolled it out of sight (with the mouse wheel). + if (highlighted_setting_index - 8 > scroll_position) scroll_position = highlighted_setting_index - 8; + if (highlighted_setting_index < scroll_position) scroll_position = highlighted_setting_index; + if (scroll_position > max_scroll) scroll_position = max_scroll; + if (scroll_position < 0) scroll_position = 0; + } + + // Find the ID from the index. + highlighted_setting_id = current_settings_area->settings[highlighted_setting_index].id; + if (old_highlighted_setting_id != highlighted_setting_id) { hovering_item_changed = true; } @@ -1626,7 +1725,9 @@ void confirmation_dialog_result(int which_dialog, int button) { were_settings_changed = true; set_options_to_default(); turn_setting_on_off(SETTING_USE_INTEGER_SCALING, use_integer_scaling, NULL); +#ifdef USE_LIGHTING turn_setting_on_off(SETTING_ENABLE_LIGHTING, enable_lighting, NULL); +#endif apply_aspect_ratio(); turn_sound_on_off((is_sound_on != 0) * 15); turn_music_on_off(enable_music); @@ -1950,6 +2051,18 @@ int key_test_paused_menu(int key) { case SDL_SCANCODE_DOWN: menu_control_y = 1; break; + case SDL_SCANCODE_PAGEUP: + menu_control_y = -9; + break; + case SDL_SCANCODE_PAGEDOWN: + menu_control_y = +9; + break; + case SDL_SCANCODE_HOME: + menu_control_y = -1000; + break; + case SDL_SCANCODE_END: + menu_control_y = +1000; + break; case SDL_SCANCODE_RIGHT: menu_control_x = 1; break; @@ -1997,7 +2110,9 @@ void process_ingame_settings_user_managed(SDL_RWops* rw, rw_process_func_type pr process(scaling_type); process(enable_fade); process(enable_flash); +#ifdef USE_LIGHTING process(enable_lighting); +#endif } void process_ingame_settings_mod_managed(SDL_RWops* rw, rw_process_func_type process_func) { diff --git a/src/midi.c b/src/midi.c index 68b98d1f..967e93d8 100644 --- a/src/midi.c +++ b/src/midi.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ extern int digi_unavailable; // seg009.c static opl3_chip opl_chip; static void* instruments_data; static instrument_type* instruments; -static dword num_instruments; +static int num_instruments; static byte voice_note[MAX_OPL_VOICES]; static int voice_instrument[MAX_OPL_VOICES]; static int voice_channel[MAX_OPL_VOICES]; @@ -56,7 +56,7 @@ static int last_used_voice; static int num_midi_tracks; static parsed_midi_type parsed_midi; static midi_track_type* midi_tracks; -static uint64_t midi_current_pos; // in MIDI ticks +static int64_t midi_current_pos; // in MIDI ticks static float midi_current_pos_fract_part; // partial ticks after the decimal point static int ticks_to_next_pause; // in MIDI ticks static dword us_per_beat; @@ -383,7 +383,7 @@ static void midi_note_off(midi_event_type* event) { } static instrument_type* get_instrument(int id) { - if (id < num_instruments) { + if (id >= 0 && id < num_instruments) { return &instruments[id]; } else { return &instruments[0]; @@ -474,6 +474,7 @@ static void process_midi_event(midi_event_type* event) { midi_semitones_higher = data[5]; // Make all notes higher by this amount. } } + break; case 0xFF: // Meta event switch(event->meta.type) { default: break; @@ -515,7 +516,7 @@ void midi_callback(void *userdata, Uint8 *stream, int len) { advance_us = advance_frames * ONE_SECOND_IN_US / mixing_freq; // recalculate, in case the rounding up increased this. short* temp_buffer = malloc(advance_frames * 4); OPL3_GenerateStream(&opl_chip, temp_buffer, advance_frames); - if (enable_music) { + if (is_sound_on && enable_music) { for (int sample = 0; sample < advance_frames * 2; ++sample) { ((short*)stream)[sample] += temp_buffer[sample]; } @@ -573,7 +574,7 @@ void midi_callback(void *userdata, Uint8 *stream, int len) { return; } else { // Need to delay (let the OPL chip do its work) until one of the tracks needs to process a MIDI event again. - uint64_t first_next_pause_tick = INT64_MAX; + int64_t first_next_pause_tick = INT64_MAX; for (int i = 0; i < num_midi_tracks; ++i) { midi_track_type* track = &midi_tracks[i]; if (track->event_index >= track->num_events || midi_current_pos >= track->next_pause_tick) continue; @@ -622,7 +623,7 @@ void init_midi() { printf("Missing MIDI instruments data (resource 1)\n"); } else { num_instruments = *(byte*)instruments_data; - if (size == 1 + num_instruments*sizeof(instrument_type)) { + if (size == 1 + num_instruments*(int)sizeof(instrument_type)) { instruments = (instrument_type*) ((byte*)instruments_data+1); } else { printf("MIDI instruments data (resource 1) is not the expected size\n"); @@ -633,6 +634,7 @@ void init_midi() { } void play_midi_sound(sound_buffer_type far *buffer) { + stop_midi(); if (buffer == NULL) return; init_digi(); if (digi_unavailable) return; diff --git a/src/options.c b/src/options.c index de552033..6587b60d 100644 --- a/src/options.c +++ b/src/options.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -83,7 +83,9 @@ int ini_load(const char *filename, } NAMES_LIST(level_type_names, {"dungeon", "palace"}); -NAMES_LIST(guard_type_names, {"guard", "fat", "skel", "vizier", "shadow"}); +//NAMES_LIST(guard_type_names, {"guard", "fat", "skel", "vizier", "shadow"}); +// NAMES_LIST must start from 0, so I need KEY_VALUE_LIST if I want to assign a name to -1. +KEY_VALUE_LIST(guard_type_names, {{"none", -1}, {"guard", 0}, {"fat", 1}, {"skel", 2}, {"vizier", 3}, {"shadow", 4}}); NAMES_LIST(tile_type_names, { "empty", "floor", "spike", "pillar", "gate", // 0..4 "stuck", "closer", "doortop_with_floor", "bigpillar_bottom", "bigpillar_top", // 5..9 @@ -174,13 +176,13 @@ static int global_ini_callback(const char *section, const char *name, const char if (check_ini_section("General")) { #ifdef USE_MENU process_boolean("enable_pause_menu", &enable_pause_menu); +#endif if (strcasecmp(name, "mods_folder") == 0) { if (value[0] != '\0' && strcasecmp(value, "default") != 0) { strcpy(mods_folder, locate_file(value)); } return 1; } -#endif process_boolean("enable_copyprot", &enable_copyprot); process_boolean("enable_music", &enable_music); process_boolean("enable_fade", &enable_fade); @@ -276,6 +278,9 @@ static int global_ini_callback(const char *section, const char *name, const char process_boolean("fix_hidden_floors_during_flashing", &fixes_saved.fix_hidden_floors_during_flashing); process_boolean("fix_hang_on_teleport", &fixes_saved.fix_hang_on_teleport); process_boolean("fix_exit_door", &fixes_saved.fix_exit_door); + process_boolean("fix_quicksave_during_feather", &fixes_saved.fix_quicksave_during_feather); + process_boolean("fix_caped_prince_sliding_through_gate", &fixes_saved.fix_caped_prince_sliding_through_gate); + process_boolean("fix_doortop_disabling_guard", &fixes_saved.fix_doortop_disabling_guard); } if (check_ini_section("CustomGameplay")) { @@ -380,6 +385,9 @@ static int global_ini_callback(const char *section, const char *name, const char process_word("win_level", &custom_saved.win_level, &never_is_16_list); process_byte("win_room", &custom_saved.win_room, NULL); process_byte("loose_floor_delay", &custom_saved.loose_floor_delay, NULL); + process_byte("base_speed", &custom_saved.base_speed, NULL); + process_byte("fight_speed", &custom_saved.fight_speed, NULL); + process_byte("chomper_speed", &custom_saved.chomper_speed, NULL); } // end of section [CustomGameplay] // [Level 1], etc. @@ -403,9 +411,26 @@ static int global_ini_callback(const char *section, const char *name, const char process_byte("entry_pose", &custom_saved.tbl_entry_pose[ini_level], &entry_pose_names_list); process_sbyte("seamless_exit", &custom_saved.tbl_seamless_exit[ini_level], NULL); } else { - // TODO: warning? + printf("Warning: Invalid section [Level %d] in the INI!\n", ini_level); } } + + // [Skill 0], etc. + int ini_skill = -1; + if (strncasecmp(section, "Skill ", 6) == 0 && sscanf(section+6, "%d", &ini_skill) == 1) { + if (ini_skill >= 0 && ini_skill < NUM_GUARD_SKILLS) { + process_word("strikeprob", &custom_saved.strikeprob [ini_skill], NULL); + process_word("restrikeprob", &custom_saved.restrikeprob [ini_skill], NULL); + process_word("blockprob", &custom_saved.blockprob [ini_skill], NULL); + process_word("impblockprob", &custom_saved.impblockprob [ini_skill], NULL); + process_word("advprob", &custom_saved.advprob [ini_skill], NULL); + process_word("refractimer", &custom_saved.refractimer [ini_skill], NULL); + process_word("extrastrength", &custom_saved.extrastrength[ini_skill], NULL); + } else { + printf("Warning: Invalid section [Skill %d] in the INI!\n", ini_skill); + } + } + return 0; } @@ -442,7 +467,9 @@ void set_options_to_default() { enable_quicksave = 1; enable_quicksave_penalty = 1; enable_replay = 1; +#ifdef USE_LIGHTING enable_lighting = 0; +#endif // By default, all the fixes are used, unless otherwise specified. // So, if one of these options is omitted from the INI file, they default to true. memset(&fixes_saved, 1, sizeof(fixes_saved)); @@ -689,6 +716,11 @@ void load_dos_exe_modifications(const char* folder_name) { process(&custom_saved.shad_drink_move, 8*4, { -1, 0x1D492, -1, 0x1D384, -1, 0x19D2E}); // in the packed versions, the four zero bytes at the start are compressed process(&custom_saved.demo_moves , 25*4, {0x1B8EE, 0x1D4B2, 0x1C70B, 0x1D3A4, 0x18ADD, 0x19D4E}); + // speeds + process(&custom_saved.base_speed , 1, { 0x4F01, 0x65B1, 0x5389, 0x5AC9, 0x4E45, 0x5F75 }); + process(&custom_saved.fight_speed , 1, { 0x4EF9, 0x65A9, 0x5381, 0x5AC1, 0x4E3D, 0x5F6D }); + process(&custom_saved.chomper_speed, 1, { 0x8BBD, 0xA26D, 0x906D, 0x97AD, 0x8B29, 0x9C59 }); + // The order of offsets is: dos_10_packed, dos_10_unpacked, dos_13_packed, dos_13_unpacked, dos_14_packed, dos_14_unpacked #undef process @@ -706,6 +738,7 @@ void load_mod_options() { char folder_name[POP_MAX_PATH]; snprintf_check(folder_name, sizeof(folder_name), "%s/%s", mods_folder, levelset_name); const char* located_folder_name = locate_file(folder_name); + //printf("located_folder_name = %s\n", located_folder_name); bool ok = false; struct stat info; if (stat(located_folder_name, &info) == 0) { @@ -728,6 +761,12 @@ void load_mod_options() { } } else { printf("Mod '%s' not found\n", levelset_name); + char message[256]; + snprintf_check(message, sizeof(message), "Cannot find the mod '%s' in the mods folder.", levelset_name); + show_dialog(message); +#ifdef USE_REPLAY + if (replaying) show_dialog("If the replay file restarts the level or advances to the next level, a wrong level will be loaded."); +#endif } if (!ok) { use_custom_levelset = 0; @@ -737,3 +776,14 @@ void load_mod_options() { turn_fixes_and_enhancements_on_off(use_fixes_and_enhancements); turn_custom_options_on_off(use_custom_options); } + +int process_rw_write(SDL_RWops* rw, void* data, size_t data_size) { + return SDL_RWwrite(rw, data, data_size, 1); +} + +int process_rw_read(SDL_RWops* rw, void* data, size_t data_size) { + return SDL_RWread(rw, data, data_size, 1); + // if this returns 0, most likely the end of the stream has been reached +} + + diff --git a/src/proto.h b/src/proto.h index e20207e0..51ebb996 100644 --- a/src/proto.h +++ b/src/proto.h @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,7 +59,6 @@ void __pascal far gen_palace_wall_colors(); void __pascal far show_title(); void __pascal far transition_ltr(); void __pascal far release_title_images(); -void __pascal far draw_image_2(int id,chtab_type* chtab_ptr,int xpos,int ypos,int blit); void __pascal far draw_full_image(enum full_image_id id); void __pascal far load_kid_sprite(); void __pascal far save_game(); @@ -514,17 +513,30 @@ void sdlperror(const char* header); bool file_exists(const char* filename); #define locate_file(filename) locate_file_(filename, alloca(POP_MAX_PATH), POP_MAX_PATH) const char* locate_file_(const char* filename, char* path_buffer, int buffer_size); + #ifdef _WIN32 + FILE* fopen_UTF8(const char* filename, const char* mode); #define fopen fopen_UTF8 + int chdir_UTF8(const char* path); #define chdir chdir_UTF8 + +int mkdir_UTF8(const char* path); +#define mkdir mkdir_UTF8 + int access_UTF8(const char* filename_UTF8, int mode); #ifdef access #undef access #endif #define access access_UTF8 + +int stat_UTF8(const char *filename_UTF8, struct stat *_Stat); +// We define a function-like macro, because `stat` is also the name of the type, and we don't want to redefine that. +#define stat(filename_UTF8, _Stat) stat_UTF8(filename_UTF8, _Stat) + #endif //_WIN32 + directory_listing_type* create_directory_listing_and_find_first_file(const char* directory, const char* extension); char* get_current_filename_from_directory_listing(directory_listing_type* data); bool find_next_file(directory_listing_type* data); @@ -596,6 +608,7 @@ const rect_type far * __pascal far method_5_rect(const rect_type far *rect,int b void draw_rect_with_alpha(const rect_type* rect, byte color, byte alpha); image_type far * __pascal far method_6_blit_img_to_scr(image_type far *image,int xpos,int ypos,int blit); void reset_timer(int timer_index); +double get_ticks_per_sec(int timer_index); void set_timer_length(int timer_index, int length); void __pascal start_timer(int timer_index, int length); int __pascal do_wait(int timer_index); @@ -664,7 +677,8 @@ void stop_recording(); void start_replay(); void end_replay(); void do_replay_move(); -int save_recorded_replay(); +int save_recorded_replay_dialog(); +int save_recorded_replay(const char* full_filename); void replay_cycle(); int load_replay(); void key_press_while_recording(int* key_ptr); diff --git a/src/replay.c b/src/replay.c index 2bab41a9..c79143bb 100644 --- a/src/replay.c +++ b/src/replay.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,7 +29,8 @@ const char* implementation_name = "SDLPoP v" SDLPOP_VERSION; #define REPLAY_FORMAT_CURR_VERSION 102 // current version number of the replay format #define REPLAY_FORMAT_MIN_VERSION 101 // SDLPoP will open replays with this version number and higher -#define REPLAY_FORMAT_DEPRECATION_NUMBER 1 // SDLPoP won't open replays with a higher deprecation number +#define REPLAY_FORMAT_DEPRECATION_NUMBER 2 // SDLPoP won't open replays with a higher deprecation number +// If deprecation_number >= 2: Waste an RNG cycle in loose_shake() to match DOS PoP. #define MAX_REPLAY_DURATION 345600 // 8 hours: 720 * 60 * 8 ticks byte moves[MAX_REPLAY_DURATION] = {0}; // static memory for now because it is easier (should this be dynamic?) @@ -157,6 +158,8 @@ int read_replay_header(replay_header_type* header, FILE* fp, char* error_message return 0; } + g_deprecation_number = deprecation_number; + if (is_validate_mode) { static byte is_replay_info_printed = 0; if (!is_replay_info_printed) { @@ -177,7 +180,7 @@ int read_replay_header(replay_header_type* header, FILE* fp, char* error_message } int num_replay_files = 0; // number of listed replays -size_t max_replay_files = 128; // initially, may grow if there are > 128 replay files found +int max_replay_files = 128; // initially, may grow if there are > 128 replay files found replay_info_type* replay_list = NULL; // Compare function -- for qsort() in list_replay_files() below @@ -238,6 +241,7 @@ void list_replay_files() { }; byte open_replay_file(const char *filename) { + printf("Opening replay file: %s\n", filename); if (replay_file_open) fclose(replay_fp); replay_fp = fopen(filename, "rb"); if (replay_fp != NULL) { @@ -306,15 +310,6 @@ void start_with_replay_file(const char *filename) { } } -int process_rw_write(SDL_RWops* rw, void* data, size_t data_size) { - return SDL_RWwrite(rw, data, data_size, 1); -} - -int process_rw_read(SDL_RWops* rw, void* data, size_t data_size) { - return SDL_RWread(rw, data, data_size, 1); - // if this returns 0, most likely the end of the stream has been reached -} - // The functions options_process_* below each process (read/write) a section of options variables (using SDL_RWops) // This is I/O for the *binary* representation of the relevant options - this gets saved as part of a replay. @@ -371,6 +366,9 @@ void options_process_fixes(SDL_RWops* rw, rw_process_func_type process_func) { process(fixes_options_replay.fix_hidden_floors_during_flashing); process(fixes_options_replay.fix_hang_on_teleport); process(fixes_options_replay.fix_exit_door); + process(fixes_options_replay.fix_quicksave_during_feather); + process(fixes_options_replay.fix_caped_prince_sliding_through_gate); + process(fixes_options_replay.fix_doortop_disabling_guard); } void options_process_custom_general(SDL_RWops* rw, rw_process_func_type process_func) { @@ -522,6 +520,9 @@ int process_to_buffer(void* data, size_t data_size) { } int process_load_from_buffer(void* data, size_t data_size) { + // Prevent torches from being randomly colored when an older replay is loaded. + if (savestate_offset >= savestate_size) return 0; + memcpy(data, savestate_buffer + savestate_offset, data_size); savestate_offset += data_size; return 1; @@ -560,6 +561,7 @@ void reload_resources() { int restore_savestate_from_buffer() { int ok = 0; savestate_offset = 0; + // This condition should be checked in process_load_from_buffer() instead of here. while (savestate_offset < savestate_size) { ok = quick_process(process_load_from_buffer); } @@ -605,7 +607,7 @@ void add_replay_move() { void stop_recording() { recording = 0; - if (save_recorded_replay()) { + if (save_recorded_replay_dialog()) { display_text_bottom("REPLAY SAVED"); } else { display_text_bottom("REPLAY CANCELED"); @@ -670,8 +672,9 @@ void start_replay() { //if (num_replay_files == 0) return; } if (!load_replay()) return; - apply_replay_options(); + // Set replaying before applying options, so the latter can display an appropriate error message if the referenced mod is missing. replaying = 1; + apply_replay_options(); curr_tick = 0; } @@ -732,7 +735,7 @@ void do_replay_move() { control_x = curr_move.x; control_y = curr_move.y; - // Ignore shift if the kid is dead: restart moves are hard-coded as a 'special move'. + // Ignore Shift if the kid is dead: restart moves are hard-coded as a 'special move'. if (rem_min != 0 && Kid.alive > 6) control_shift = 0; else @@ -753,7 +756,7 @@ void do_replay_move() { } } -int save_recorded_replay() { +int save_recorded_replay_dialog() { // prompt for replay filename rect_type rect; short bgcolor = color_8_darkgray; @@ -795,6 +798,11 @@ int save_recorded_replay() { // NOTE: We currently overwrite the replay file if it exists already. Maybe warn / ask for confirmation?? + return save_recorded_replay(full_filename); +} + +int save_recorded_replay(const char* full_filename) +{ replay_fp = fopen(full_filename, "wb"); if (replay_fp != NULL) { fwrite(replay_magic_number, COUNT(replay_magic_number), 1, replay_fp); // magic number "P1R" @@ -830,6 +838,7 @@ int save_recorded_replay() { fclose(replay_fp); replay_fp = NULL; } + return 1; } @@ -860,9 +869,9 @@ void replay_cycle() { start_game(); return; } - curr_tick = 0; apply_replay_options(); restore_savestate_from_buffer(); + curr_tick = 0; // Do this after restoring the savestate, in case the savestate contained a non-zero curr_tick. show_level(); } @@ -921,7 +930,7 @@ void key_press_while_recording(int* key_ptr) { special_move = MOVE_RESTART_LEVEL; break; case SDL_SCANCODE_R | WITH_CTRL: - save_recorded_replay(); + save_recorded_replay_dialog(); recording = 0; default: break; @@ -941,9 +950,11 @@ void key_press_while_replaying(int* key_ptr) { // ...but these are allowable actions: case SDL_SCANCODE_ESCAPE: // pause case SDL_SCANCODE_ESCAPE | WITH_SHIFT: + case SDL_SCANCODE_BACKSPACE: // menu case SDL_SCANCODE_SPACE: // time case SDL_SCANCODE_S | WITH_CTRL: // sound toggle case SDL_SCANCODE_V | WITH_CTRL: // version + case SDL_SCANCODE_C | WITH_CTRL: // SDL version case SDL_SCANCODE_C: // room numbers case SDL_SCANCODE_C | WITH_SHIFT: case SDL_SCANCODE_I | WITH_SHIFT: // invert diff --git a/src/screenshot.c b/src/screenshot.c index 3a607413..e073e31f 100644 --- a/src/screenshot.c +++ b/src/screenshot.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,12 +22,14 @@ The authors of this program may be contacted at https://forum.princed.org #ifdef USE_SCREENSHOT -const char screenshots_folder[] = "screenshots"; -char screenshot_filename[POP_MAX_PATH] = "screenshot.png"; +char screenshots_folder[POP_MAX_PATH] = "screenshots"; +char screenshot_filename[POP_MAX_PATH*2] = "screenshot.png"; int screenshot_index = 0; // Use incrementing numbers and a separate folder, like DOSBox. void make_screenshot_filename() { + // Create the screenshots directory in SDLPoP's directory, even if the current directory is something else. + strncpy(screenshots_folder, locate_file("screenshots"), sizeof(screenshots_folder)); // Create the folder if it doesn't exist yet: #if defined WIN32 || _WIN32 || WIN64 || _WIN64 mkdir (screenshots_folder); @@ -223,13 +225,13 @@ void draw_extras() { */ char events[256*4] = ""; // More than enough space to list all the numbers from 0 to 255. int events_pos = 0; - for (int event=first_event; event<=last_event && events_pos0 && events_pos0 && events_pos<(int)sizeof(events)) events[events_pos]='\0'; // trim trailing space rect_type buttonmod_rect = {y/*+50-3*/, x, y+60-3, x+32}; show_text_with_color(&buttonmod_rect, 0, 1, events, color_14_brightyellow); } @@ -239,15 +241,15 @@ void draw_extras() { // door events that point here char events[256*4] = ""; int events_pos = 0; - for (int event=0; event<256 && events_pos0 && events_pos0 && events_pos<(int)sizeof(events)) events[events_pos]='\0'; // trim trailing space if (*events) { //printf("room %d, tile %d, events: %s\n", drawn_room, tilepos, events); // debug rect_type events_rect = {y,x,y+63-3,x+32-7}; diff --git a/src/seg000.c b/src/seg000.c index fe8585ea..5b03f66f 100644 --- a/src/seg000.c +++ b/src/seg000.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -62,6 +62,7 @@ void far pop_main() { #ifdef USE_MENU load_ingame_settings(); #endif + if (check_param("mute")) is_sound_on = 0; turn_sound_on_off((is_sound_on != 0) * 15); // Turn off sound/music if those options were set. #ifdef USE_REPLAY @@ -96,7 +97,7 @@ void far pop_main() { /*video_mode =*/ parse_grmode(); - init_timer(60); + init_timer(BASE_FPS); parse_cmdline_sound(); set_hc_pal(); @@ -130,6 +131,9 @@ void far pop_main() { } } } + + play_demo_level = (check_param("playdemo") != NULL); + #ifdef USE_SCREENSHOT init_screenshot(); #endif @@ -250,7 +254,15 @@ int quick_process(process_func_type process_func) { int ok = 1; #define process(x) ok = ok && process_func(&(x), sizeof(x)) // level - process(level); +#ifdef USE_DEBUG_CHEATS + // Don't load the level if the user holds either Shift key while pressing F9. + if (debug_cheats_enabled && (key_states[SDL_SCANCODE_LSHIFT] || key_states[SDL_SCANCODE_RSHIFT])) { + fseek(quick_fp, sizeof(level), SEEK_CUR); + } else +#endif + { + process(level); + } process(checkpoint); process(upside_down); process(drawn_room); @@ -335,6 +347,9 @@ int quick_process(process_func_type process_func) { #ifdef USE_REPLAY process(curr_tick); #endif +#ifdef USE_COLORED_TORCHES + process(torch_colors); +#endif #undef process return ok; } @@ -374,9 +389,16 @@ void restore_room_after_quick_load() { curr_guard_color = temp1; next_level = temp2; + // feather fall can only get restored if the fix enabled + if (!fixes->fix_quicksave_during_feather && is_feather_fall > 0) { + is_feather_fall = 0; + stop_sounds(); + } + //need_full_redraw = 1; different_room = 1; - next_room = drawn_room; + // Show the room where the prince is, even if the player moved the view away from it (with the H,J,U,N keys). + next_room = drawn_room = Kid.room; load_room_links(); //draw_level_first(); //gen_palace_wall_colors(); @@ -385,6 +407,13 @@ void restore_room_after_quick_load() { //redraw_screen(1); // for room_L hitp_delta = guardhp_delta = 1; // force HP redraw + // Don't draw guard HP if a previously viewed room (with the H,J,U,N keys) had a guard but the current room doesn't have one. + if (Guard.room != drawn_room) { + // Like in clear_char(). + Guard.direction = dir_56_none; + guardhp_curr = 0; + } + draw_hp(); loadkid_and_opp(); // Get rid of "press button" message if kid was dead before quickload. @@ -450,7 +479,7 @@ int quick_load() { void check_quick_op() { if (!enable_quicksave) return; if (need_quick_save) { - if (!is_feather_fall && quick_save()) { + if ((!is_feather_fall || fixes->fix_quicksave_during_feather) && quick_save()) { display_text_bottom("QUICKSAVE"); } else { display_text_bottom("NO QUICKSAVE"); @@ -518,7 +547,7 @@ int __pascal far process_key() { start_recording(); } else #endif - if (key == (SDL_SCANCODE_L | WITH_CTRL)) { // ctrl-L + if (key == (SDL_SCANCODE_L | WITH_CTRL)) { // Ctrl+L if (!load_game()) return 0; } else { start_level = custom->first_level; // 1 @@ -533,9 +562,9 @@ int __pascal far process_key() { start_game(); } } - // If the Kid died, enter or shift will restart the level. + // If the Kid died, Enter or Shift will restart the level. if (rem_min != 0 && Kid.alive > 6 && (control_shift || key == SDL_SCANCODE_RETURN)) { - key = SDL_SCANCODE_A | WITH_CTRL; // ctrl-a + key = SDL_SCANCODE_A | WITH_CTRL; // Ctrl+A } #ifdef USE_REPLAY if (recording) key_press_while_recording(&key); @@ -545,7 +574,7 @@ int __pascal far process_key() { if (is_keyboard_mode) clear_kbd_buf(); switch(key) { - case SDL_SCANCODE_ESCAPE: // esc + case SDL_SCANCODE_ESCAPE: // Esc case SDL_SCANCODE_ESCAPE | WITH_SHIFT: // allow pause while grabbing is_paused = 1; #ifdef USE_MENU @@ -560,23 +589,23 @@ int __pascal far process_key() { } #endif break; - case SDL_SCANCODE_SPACE: // space + case SDL_SCANCODE_SPACE: // Space is_show_time = 1; break; - case SDL_SCANCODE_A | WITH_CTRL: // ctrl-a + case SDL_SCANCODE_A | WITH_CTRL: // Ctrl+A if (current_level != 15) { stop_sounds(); is_restart_level = 1; } break; - case SDL_SCANCODE_G | WITH_CTRL: // ctrl-g + case SDL_SCANCODE_G | WITH_CTRL: // Ctrl+G // CusPoP: first and last level where saving is allowed // if (current_level > 2 && current_level < 14) { // original if (current_level >= custom->saving_allowed_first_level && current_level <= custom->saving_allowed_last_level) { save_game(); } break; - case SDL_SCANCODE_J | WITH_CTRL: // ctrl-j + case SDL_SCANCODE_J | WITH_CTRL: // Ctrl+J if ((sound_flags & sfDigi) && sound_mode == smTandy) { answer_text = "JOYSTICK UNAVAILABLE"; } else { @@ -588,20 +617,20 @@ int __pascal far process_key() { } need_show_text = 1; break; - case SDL_SCANCODE_K | WITH_CTRL: // ctrl-k + case SDL_SCANCODE_K | WITH_CTRL: // Ctrl+K answer_text = "KEYBOARD MODE"; is_joyst_mode = 0; is_keyboard_mode = 1; need_show_text = 1; break; - case SDL_SCANCODE_R | WITH_CTRL: // ctrl-r + case SDL_SCANCODE_R | WITH_CTRL: // Ctrl+R start_level = -1; #ifdef USE_MENU if (is_menu_shown) menu_was_closed(); // Do necessary cleanup. #endif start_game(); break; - case SDL_SCANCODE_S | WITH_CTRL: // ctrl-s + case SDL_SCANCODE_S | WITH_CTRL: // Ctrl+S turn_sound_on_off((!is_sound_on) * 15); answer_text = "SOUND OFF"; if (is_sound_on) { @@ -610,13 +639,13 @@ int __pascal far process_key() { // need_show_text = 1; break; - case SDL_SCANCODE_V | WITH_CTRL: // ctrl-v + case SDL_SCANCODE_V | WITH_CTRL: // Ctrl+V //answer_text = "PRINCE OF PERSIA V1.0"; snprintf(sprintf_temp, sizeof(sprintf_temp), "SDLPoP v%s\n", SDLPOP_VERSION); answer_text = sprintf_temp; need_show_text = 1; break; - case SDL_SCANCODE_C | WITH_CTRL: // ctrl-c + case SDL_SCANCODE_C | WITH_CTRL: // Ctrl+C { SDL_version verc, verl; SDL_VERSION (&verc); @@ -629,16 +658,16 @@ int __pascal far process_key() { need_show_text = 1; } break; - case SDL_SCANCODE_L | WITH_SHIFT: // shift-l + case SDL_SCANCODE_L | WITH_SHIFT: // Shift+L if (current_level < custom->shift_L_allowed_until_level /* 4 */ || cheats_enabled) { - // if shift is not released within the delay, the cutscene is skipped + // if Shift is not released within the delay, the cutscene is skipped Uint32 delay = 250; key_states[SDL_SCANCODE_LSHIFT] = 0; key_states[SDL_SCANCODE_RSHIFT] = 0; SDL_TimerID timer; timer = SDL_AddTimer(delay, temp_shift_release_callback, NULL); if (timer == 0) { - sdlperror("SDL_AddTimer"); + sdlperror("process_key: SDL_AddTimer"); quit(1); } if (current_level == 14) { @@ -691,7 +720,7 @@ int __pascal far process_key() { answer_text = /*&*/sprintf_temp; need_show_text = 1; break; - case SDL_SCANCODE_C | WITH_SHIFT: // shift-c + case SDL_SCANCODE_C | WITH_SHIFT: // Shift+C snprintf(sprintf_temp, sizeof(sprintf_temp), "AL%d AR%d BL%d BR%d", room_AL, room_AR, room_BL, room_BR); answer_text = /*&*/sprintf_temp; need_show_text = 1; @@ -733,13 +762,15 @@ int __pascal far process_key() { } break; case SDL_SCANCODE_K: // K --> kill guard cheat - guardhp_delta = -guardhp_curr; - Guard.alive = 0; + if (Guard.charid != charid_4_skeleton) { + guardhp_delta = -guardhp_curr; + Guard.alive = 0; + } break; - case SDL_SCANCODE_I | WITH_SHIFT: // shift+I --> invert cheat + case SDL_SCANCODE_I | WITH_SHIFT: // Shift+I --> invert cheat toggle_upside(); break; - case SDL_SCANCODE_W | WITH_SHIFT: // shift+W --> feather fall cheat + case SDL_SCANCODE_W | WITH_SHIFT: // Shift+W --> feather fall cheat feather_fall(); break; case SDL_SCANCODE_H: // H --> view room to the left @@ -758,7 +789,11 @@ int __pascal far process_key() { draw_guard_hp(0, 10); next_room = room_B; break; - case SDL_SCANCODE_B | WITH_SHIFT: // shift-b + case SDL_SCANCODE_B | WITH_CTRL: // Ctrl+B --> Go back to the room where the prince is. (Undo H,J,U,N.) + draw_guard_hp(0, 10); + next_room = Kid.room; + break; + case SDL_SCANCODE_B | WITH_SHIFT: // Shift+B is_blind_mode = !is_blind_mode; if (is_blind_mode) { draw_rect(&rect_top, 0); @@ -766,7 +801,7 @@ int __pascal far process_key() { need_full_redraw = 1; } break; - case SDL_SCANCODE_S | WITH_SHIFT: // shift-s + case SDL_SCANCODE_S | WITH_SHIFT: // Shift+S if (hitp_curr != hitp_max) { play_sound(sound_33_small_potion); // small potion (cheat) hitp_delta = 1; @@ -774,7 +809,7 @@ int __pascal far process_key() { flash_time = 2; } break; - case SDL_SCANCODE_T | WITH_SHIFT: // shift-t + case SDL_SCANCODE_T | WITH_SHIFT: // Shift+T play_sound(sound_30_big_potion); // big potion (cheat) flash_color = 4; // red flash_time = 4; @@ -784,6 +819,13 @@ int __pascal far process_key() { case SDL_SCANCODE_T: is_timer_displayed = 1 - is_timer_displayed; // toggle break; + case SDL_SCANCODE_F: + if (fixes->fix_quicksave_during_feather) { + is_feather_timer_displayed = 1 - is_feather_timer_displayed; // toggle + } else { + is_feather_timer_displayed = 0; + } + break; #endif } } @@ -798,6 +840,10 @@ int __pascal far process_key() { // seg000:08EB void __pascal far play_frame() { + // play feather fall music if there is more than 1 second of feather fall left + if (fixes->fix_quicksave_during_feather && is_feather_fall >= 10 && !check_sound_playing()) { + play_sound(sound_39_low_weight); + } do_mobs(); process_trobs(); check_skel(); @@ -1104,6 +1150,9 @@ void reset_level_unused_fields(bool loading_clean_level) { memset(level.fill_2, 0, sizeof(level.fill_2)); memset(level.fill_3, 0, sizeof(level.fill_3)); + // level.used_rooms is 25 on some levels. Limit it to the actual number of rooms. + if (level.used_rooms > 24) level.used_rooms = 24; + // For these fields, only use the bits that are actually used, and set the rest to zero. // Good for repurposing the unused bits in the future. int i; @@ -1646,6 +1695,12 @@ int __pascal far do_paused() { is_paused = 0; // fix being able to pause the game during the ending sequence } if (is_paused) { + // feather fall gets interrupted by pause + if (fixes->fix_quicksave_during_feather && + is_feather_fall > 0 && + check_sound_playing()) { + stop_sounds(); + } display_text_bottom("GAME PAUSED"); #ifdef USE_MENU if (enable_pause_menu || is_menu_shown) { @@ -1723,7 +1778,13 @@ void __pascal far toggle_upside() { // seg000:15F8 void __pascal far feather_fall() { - is_feather_fall = 1; + //printf("slow fall started at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick); + if (fixes->fix_quicksave_during_feather) { + // feather fall is treated as a timer + is_feather_fall = FEATHER_FALL_LENGTH * get_ticks_per_sec(timer_1); + } else { + is_feather_fall = 1; + } flash_color = 2; // green flash_time = 3; stop_sounds(); @@ -1887,6 +1948,10 @@ void __pascal far transition_ltr() { // Estimated transition fps based on the speed of the transition on an Apple IIe. // See: https://www.youtube.com/watch?v=7m7j2VuWhQ0 int transition_fps = 120; +#ifdef USE_FAST_FORWARD + extern int audio_speed; + transition_fps *= audio_speed; +#endif Uint64 counters_per_frame = perf_frequency / transition_fps; last_transition_counter = SDL_GetPerformanceCounter(); int overshoot = 0; @@ -1928,31 +1993,6 @@ void __pascal far release_title_images() { } } -// seg000:1C3A -void __pascal far draw_image_2(int id, chtab_type* chtab_ptr, int xpos, int ypos, int blit) { - image_type* source; - image_type* decoded_image; - image_type* mask; - mask = NULL; - if (NULL == chtab_ptr) return; - source = chtab_ptr->images[id]; - decoded_image = source; - if (blit != blitters_0_no_transp && blit != blitters_10h_transp) { - method_3_blit_mono(decoded_image, xpos, ypos, blitters_0_no_transp, blit); - } else if (blit == blitters_10h_transp) { - if (graphics_mode == gmCga || graphics_mode == gmHgaHerc) { - //... - } else { - mask = decoded_image; - } - draw_image_transp(decoded_image, mask, xpos, ypos); - if (graphics_mode == gmCga || graphics_mode == gmHgaHerc) { - free_far(mask); - } - } else { - method_6_blit_img_to_scr(decoded_image, xpos, ypos, blit); - } -} // seg000:1C3A void __pascal far draw_full_image(enum full_image_id id) { image_type* decoded_image; @@ -2269,8 +2309,10 @@ const rect_type splash_text_2_rect = {50, 0, 200, 320}; const char* splash_text_1 = "SDLPoP " SDLPOP_VERSION; const char* splash_text_2 = +#ifdef USE_QUICKSAVE "To quick save/load, press F6/F9 in-game.\n" "\n" +#endif #ifdef USE_REPLAY "To record replays, press Ctrl+Tab in-game.\n" "To view replays, press Tab on the title screen.\n" @@ -2291,6 +2333,7 @@ void show_splash() { show_text_with_color(&splash_text_1_rect, 0, 0, splash_text_1, color_15_brightwhite); show_text_with_color(&splash_text_2_rect, 0, -1, splash_text_2, color_7_lightgray); +#ifdef USE_TEXT // Don't wait for a keypress if there is no text for the user to read. int key = 0; do { idle(); @@ -2311,6 +2354,8 @@ void show_splash() { extern int last_key_scancode; // defined in seg009.c last_key_scancode = key; // can immediately do Ctrl+L, etc from the splash screen } - key_states[SDL_SCANCODE_LSHIFT] = 0; // don't immediately start the game if shift was pressed! + key_states[SDL_SCANCODE_LSHIFT] = 0; // don't immediately start the game if Shift was pressed! key_states[SDL_SCANCODE_RSHIFT] = 0; +#endif } + diff --git a/src/seg002.c b/src/seg002.c index b2ef3b9b..f9028515 100644 --- a/src/seg002.c +++ b/src/seg002.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -113,7 +113,6 @@ void __pascal far enter_guard() { room_minus_1 = drawn_room - 1; frame = Char.frame; // hm? guard_tile = level.guards_tile[room_minus_1]; - #ifndef FIX_OFFSCREEN_GUARDS_DISAPPEARING if (guard_tile >= 30) return; #else @@ -121,8 +120,8 @@ void __pascal far enter_guard() { if (!fixes->fix_offscreen_guards_disappearing) return; // try to see if there are offscreen guards in the left and right rooms that might be visible from this room - word left_guard_tile = 31; - word right_guard_tile = 31; + short left_guard_tile = 31; + short right_guard_tile = 31; if (room_L > 0) left_guard_tile = level.guards_tile[room_L-1]; if (room_R > 0) right_guard_tile = level.guards_tile[room_R-1]; @@ -138,11 +137,18 @@ void __pascal far enter_guard() { if (other_guard_dir == dir_0_right) other_guard_x -= 9; // only retrieve a guard if they will be visible if (other_guard_dir == dir_FF_left) other_guard_x += 1; // getting these right was mostly trial and error // only retrieve offscreen guards - if (!(other_guard_x < 58 + 4)) return; + if (!(other_guard_x < 58 + 4)) { + // check the left offscreen guard + if (left_guard_tile >= 0 && left_guard_tile < 30) { + goto loc_left_guard_tile; + } + return; + } delta_x = 140; // guard leaves to the left guard_tile = right_guard_tile; } else if (left_guard_tile >= 0 && left_guard_tile < 30) { +loc_left_guard_tile: other_room_minus_1 = room_L - 1; other_guard_x = level.guards_x[other_room_minus_1]; other_guard_dir = level.guards_dir[other_room_minus_1]; @@ -183,7 +189,7 @@ void __pascal far enter_guard() { } #ifdef REMEMBER_GUARD_HP - int remembered_hp = (curr_guard_color & 0xF0) >> 4; + int remembered_hp = (level.guards_color[room_minus_1] & 0xF0) >> 4; #endif curr_guard_color &= 0x0F; // added; only least significant 4 bits are used for guard color @@ -303,8 +309,8 @@ void __pascal far follow_guard() { // seg002:03C7 void __pascal far exit_room() { - word leave; - word kid_room_m1; + short leave; + short kid_room_m1; leave = 0; if (exit_room_timer != 0) { --exit_room_timer; @@ -370,8 +376,15 @@ void __pascal far exit_room() { // seg002:0486 int __pascal far goto_other_room(short direction) { + //printf("goto_other_room: direction = %d, Char.room = %d\n", direction, Char.room); short opposite_dir; - Char.room = ((byte*)&level.roomlinks[Char.room - 1])[direction]; + byte other_room = ((byte*)&level.roomlinks[Char.room - 1])[direction]; +#ifdef FIX_ENTERING_GLITCHED_ROOMS + if (Char.room == 0) { + other_room = 0; + } +#endif + Char.room = other_room; if (direction == 0) { // left Char.x += 140; @@ -902,7 +915,25 @@ void __pascal far hurt_by_sword() { Char.direction < dir_0_right && // looking left (curr_tile2 == tiles_4_gate || get_tile_at_char() == tiles_4_gate) ) { - Char.x = x_bump[tile_col - (curr_tile2 != tiles_4_gate) + 5] + 7; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + // a guard can get teleported to the other side of kid's room + // when fighting between rooms and hitting a gate + if (fixes->fix_offscreen_guards_disappearing) { + short gate_col = tile_col; + if (curr_room != Char.room) { + if (curr_room == level.roomlinks[Char.room - 1].right) { + gate_col += 10; + } else if (curr_room == level.roomlinks[Char.room - 1].left) { + gate_col -= 10; + } + } + Char.x = x_bump[gate_col - (curr_tile2 != tiles_4_gate) + 5] + 7; + } else { + #endif + Char.x = x_bump[tile_col - (curr_tile2 != tiles_4_gate) + 5] + 7; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + } + #endif Char.x = char_dx_forward(10); } Char.y = y_land[Char.curr_row + 1]; diff --git a/src/seg003.c b/src/seg003.c index 8c0095c7..572965fe 100644 --- a/src/seg003.c +++ b/src/seg003.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -358,10 +358,10 @@ int __pascal far play_level_2() { #endif if (Kid.sword == sword_2_drawn) { // speed when fighting (smaller is faster) - set_timer_length(timer_1, 6); + set_timer_length(timer_1, /*6*/ custom->fight_speed); } else { // speed when not fighting (smaller is faster) - set_timer_length(timer_1, 5); + set_timer_length(timer_1, /*5*/ custom->base_speed); } guardhp_delta = 0; hitp_delta = 0; @@ -496,13 +496,36 @@ void __pascal far timers() { if (resurrect_time > 0) { --resurrect_time; } - if (is_feather_fall && !check_sound_playing()) { -#ifdef USE_REPLAY - if (recording) special_move = MOVE_EFFECT_END; - if (!replaying) // during replays, feather effect gets cancelled in do_replay_move() -#endif - is_feather_fall = 0; + + if (fixes->fix_quicksave_during_feather) { + if (is_feather_fall > 0) { + --is_feather_fall; + if (is_feather_fall == 0) { + if (check_sound_playing()) { + stop_sounds(); + } + + //printf("slow fall ended at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick); + //printf("length = %d ticks\n", is_feather_fall); + #ifdef USE_REPLAY + if (recording) special_move = MOVE_EFFECT_END; + #endif + } + } + } else { + if (is_feather_fall) is_feather_fall++; + + if (is_feather_fall && (!check_sound_playing() || is_feather_fall > 225)) { + //printf("slow fall ended at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick); + //printf("length = %d ticks\n", is_feather_fall); + #ifdef USE_REPLAY + if (recording) special_move = MOVE_EFFECT_END; + if (!replaying) // during replays, feather effect gets cancelled in do_replay_move() + #endif + is_feather_fall = 0; + } } + // Special event: mouse if (current_level == /*8*/ custom->mouse_level && Char.room == /*16*/ custom->mouse_room && leveldoor_open) { ++leveldoor_open; @@ -523,7 +546,7 @@ void __pascal far check_mirror() { loadkid(); load_frame(); check_mirror_image(); - if (distance_mirror >= 0 && custom->show_mirror_image) { + if (distance_mirror >= 0 && custom->show_mirror_image && Char.room == drawn_room) { load_frame_to_obj(); reset_obj_clip(); clip_top = y_clip[Char.curr_row + 1]; @@ -624,6 +647,9 @@ Possible results in can_guard_see_kid: short right_pos; kid_frame = Kid.frame; if (Guard.charid == charid_24_mouse) { + // If the prince is fighting a guard, and the player does a quickload to a state where the prince is near the mouse, the prince would draw the sword. + // The following line prevents this. + can_guard_see_kid = 0; return; } if ((Guard.charid != charid_1_shadow || current_level == 12) && @@ -633,6 +659,14 @@ Possible results in can_guard_see_kid: ) { can_guard_see_kid = 2; left_pos = x_bump[Kid.curr_col + 5] + 7; +#ifdef FIX_DOORTOP_DISABLING_GUARD + if (fixes->fix_doortop_disabling_guard) { + // When the kid is hanging on the right side of a doortop, Kid.curr_col points at the doortop tile and a guard on the left side will see the prince. + // This fixes that. + if (Kid.action == actions_2_hang_climb || Kid.action == actions_6_hang_straight) left_pos += 14; + } +#endif + //printf("Kid.curr_col = %d, Kid.action = %d\n", Kid.curr_col, Kid.action); right_pos = x_bump[Guard.curr_col + 5] + 7; if (left_pos > right_pos) { temp = left_pos; @@ -646,7 +680,7 @@ Possible results in can_guard_see_kid: // A gate is on the right side of a tile, so it doesn't count. if (get_tile_at_kid(right_pos) == tiles_4_gate #ifdef FIX_DOORTOP_DISABLING_GUARD - || get_tile_at_kid(right_pos) == tiles_7_doortop_with_floor || get_tile_at_kid(right_pos) == tiles_12_doortop + || (fixes->fix_doortop_disabling_guard && (get_tile_at_kid(right_pos) == tiles_7_doortop_with_floor || get_tile_at_kid(right_pos) == tiles_12_doortop)) #endif ) { right_pos -= 14; diff --git a/src/seg004.c b/src/seg004.c index 6b5e51d8..7f0bfad1 100644 --- a/src/seg004.c +++ b/src/seg004.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -100,10 +100,12 @@ void __pascal far move_coll_to_prev() { below_row_coll_room[column] = -1; above_row_coll_room[column] = -1; curr_row_coll_room[column] = -1; +#ifdef FIX_COLL_FLAGS // bugfix: curr_row_coll_flags[column] = 0; below_row_coll_flags[column] = 0; above_row_coll_flags[column] = 0; +#endif } } @@ -346,11 +348,13 @@ void __pascal far clear_coll_rooms() { memset_near(curr_row_coll_room, -1, sizeof(curr_row_coll_room)); memset_near(below_row_coll_room, -1, sizeof(below_row_coll_room)); memset_near(above_row_coll_room, -1, sizeof(above_row_coll_room)); +#ifdef FIX_COLL_FLAGS // workaround memset_near(prev_coll_flags, 0, sizeof(prev_coll_flags)); memset_near(curr_row_coll_flags, 0, sizeof(curr_row_coll_flags)); memset_near(below_row_coll_flags, 0, sizeof(below_row_coll_flags)); memset_near(above_row_coll_flags, 0, sizeof(above_row_coll_flags)); +#endif prev_collision_row = -1; } @@ -449,7 +453,25 @@ void __pascal far chomped() { #endif curr_room_modif[curr_tilepos] |= 0x80; // put blood if (Char.frame != frame_178_chomped && Char.room == curr_room) { - Char.x = x_bump[tile_col + 5] + 7; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + // a guard can get teleported to the other side of kid's room + // when hitting a chomper in another room + if (fixes->fix_offscreen_guards_disappearing) { + short chomper_col = tile_col; + if (curr_room != Char.room) { + if (curr_room == level.roomlinks[Char.room - 1].right) { + chomper_col += 10; + } else if (curr_room == level.roomlinks[Char.room - 1].left) { + chomper_col -= 10; + } + } + Char.x = x_bump[chomper_col + 5] + 7; + } else { + #endif + Char.x = x_bump[tile_col + 5] + 7; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + } + #endif Char.x = char_dx_forward(7 - !Char.direction); Char.y = y_land[Char.curr_row + 1]; take_hp(100); @@ -463,23 +485,35 @@ void __pascal far chomped() { void __pascal far check_gate_push() { // Closing gate pushes Kid short frame; - short var_4; + short orig_col; frame = Char.frame; if (Char.action == actions_7_turn || frame == frame_15_stand || // stand (frame >= frame_108_fall_land_2 && frame < 111) // crouch ) { get_tile_at_char(); - var_4 = tile_col; + orig_col = tile_col; + int orig_room = curr_room; if ((curr_tile2 == tiles_4_gate || get_tile(curr_room, --tile_col, tile_row) == tiles_4_gate) && (curr_row_coll_flags[tile_col] & prev_coll_flags[tile_col]) == 0xFF && can_bump_into_gate() ) { bumped_sound(); - // push Kid left if var_4 <= tile_col, gate at char's tile - // push Kid right if var_4 > tile_col, gate is left from char's tile - Char.x += 5 - (var_4 <= tile_col) * 10; +#ifdef FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE + if (fixes->fix_caped_prince_sliding_through_gate) { + // If get_tile() changed curr_room from orig_room to the left neighbor of orig_room (because tile_col was outside room orig_room), + // then change tile_col (and curr_room) so that orig_col and tile_col are meant in the same room. + if (curr_room == level.roomlinks[orig_room - 1].left) { + tile_col -= 10; + curr_room = orig_room; + } + } +#endif + //printf("check_gate_push: orig_col = %d, tile_col = %d, curr_room = %d, Char.room = %d, orig_room = %d\n", orig_col, tile_col, curr_room, Char.room, orig_room); + // push Kid left if orig_col <= tile_col, gate at char's tile + // push Kid right if orig_col > tile_col, gate is left from char's tile + Char.x += 5 - (orig_col <= tile_col) * 10; } } } diff --git a/src/seg005.c b/src/seg005.c index cd5cdada..aa8bc06a 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -214,7 +214,25 @@ void __pascal far spiked() { // If someone falls into spikes, those spikes become harmless (to others). curr_room_modif[curr_tilepos] = 0xFF; Char.y = y_land[Char.curr_row + 1]; - Char.x = x_bump[tile_col + 5] + 10; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + // a guard can get teleported to the other side of kid's room + // when landing on spikes in another room + if (fixes->fix_offscreen_guards_disappearing) { + short spike_col = tile_col; + if (curr_room != Char.room) { + if (curr_room == level.roomlinks[Char.room - 1].right) { + spike_col += 10; + } else if (curr_room == level.roomlinks[Char.room - 1].left) { + spike_col -= 10; + } + } + Char.x = x_bump[spike_col + 5] + 10; + } else { + #endif + Char.x = x_bump[tile_col + 5] + 10; + #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING + } + #endif Char.x = char_dx_forward(8); Char.fall_y = 0; play_sound(sound_48_spiked); // something spiked diff --git a/src/seg006.c b/src/seg006.c index e07a89ec..f056d032 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,6 +45,18 @@ int __pascal far get_tile(int room,int col,int row) { // seg006:005D int __pascal far find_room_of_tile() { again: +#ifdef FIX_CORNER_GRAB + // Check tile_row < 0 first, this way the prince can grab a ledge at the bottom right corner of a room with no room below. + // Details: https://forum.princed.org/viewtopic.php?p=30410#p30410 + if (tile_row < 0) { + tile_row += 3; + if (curr_room) { + curr_room = level.roomlinks[curr_room - 1].up; + } + //find_room_of_tile(); + goto again; + } +#endif if (tile_col < 0) { tile_col += 10; if (curr_room) { @@ -52,21 +64,27 @@ int __pascal far find_room_of_tile() { } //find_room_of_tile(); goto again; - } else if (tile_col >= 10) { + } + if (tile_col >= 10) { tile_col -= 10; if (curr_room) { curr_room = level.roomlinks[curr_room - 1].right; } //find_room_of_tile(); goto again; - } else if (tile_row < 0) { + } +#ifndef FIX_CORNER_GRAB + // if (tile_row < 0) was here originally + if (tile_row < 0) { tile_row += 3; if (curr_room) { curr_room = level.roomlinks[curr_room - 1].up; } //find_room_of_tile(); goto again; - } else if (tile_row >= 3) { + } +#endif + if (tile_row >= 3) { tile_row -= 3; if (curr_room) { curr_room = level.roomlinks[curr_room - 1].down; @@ -574,6 +592,7 @@ void __pascal far play_seq() { } // fallthrough! case SEQ_JMP: // jump + //Char.curr_seq = *(const word*)(SEQTBL_0 + Char.curr_seq); Char.curr_seq = SDL_SwapLE16(*(const word*)(SEQTBL_0 + Char.curr_seq)); break; case SEQ_UP: // up @@ -708,13 +727,13 @@ const byte tile_mod_tbl[256] = { // seg006:03F0 int __pascal far get_tile_div_mod(int xpos) { - // xpos might be negative if the kid is far off left. - // In this case, the array index overflows. -/* if (xpos < 0 || xpos >= 256) { - printf("get_tile_div_mod(): xpos = %d\n", xpos); - }*/ + // Determine tile column (xh) and the position within the tile (xl) from xpos. + +// DOS PoP does this: // obj_xl = tile_mod_tbl[xpos]; // return tile_div_tbl[xpos]; + + // xpos uses a coordinate system in which the left edge of the screen is 58, and each tile is 14 units wide. int x = xpos - 58; int xl = x % 14; int xh = x / 14; @@ -724,6 +743,38 @@ int __pascal far get_tile_div_mod(int xpos) { // Modulo returns a negative number if x is negative, but we want 0 <= xl < 14. xl += 14; } + + // For compatibility with the DOS version, we allow for overflow access to these tables + // Considering the case of negative overflow + if (xpos < 0) { + // In this case DOS PoP reads the bytes directly before tile_div_tbl[] and tile_mod_tbl[] in the memory. + // Here we simulate these reads. + // Before tile_mod_tbl[] is tile_div_tbl[], and before tile_div_tbl[] are the following bytes: + static const byte bogus[] = {0x02, 0x00, 0x41, 0x00, 0x80, 0x00, 0xBF, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0x01, 0xFF, 0xC4, 0xFF, 0x03, 0x00, 0x42, 0x00, 0x81, 0x00, 0xC0, 0x00, 0xF8, 0xFF, 0x37, 0x00, 0x76, 0x00, 0xB5, 0x00, 0xF4, 0x00}; + if (COUNT(bogus) + xpos >= 0) { + xh = bogus[COUNT(bogus) + xpos]; // simulating tile_div_tbl[xpos] + xl = tile_div_tbl[COUNT(tile_div_tbl) + xpos]; // simulating tile_mod_tbl[xpos] + } else { + printf("xpos = %d (< %d) out of range for simulation of index overflow!\n", xpos, -(int)COUNT(bogus)); + } + } + + // Considering the case of positive overflow + int tblSize = 256; + + if (xpos >= tblSize) { + // In this case DOS PoP reads the bytes directly after tile_div_tbl[], that is: and tile_mod_tbl[] + // Here we simulate these reads. + // After tile_mod_tbl[] there are the following bytes: + static const byte bogus[] = {0xF4, 0x02, 0x10, 0x1E, 0x2C, 0x3A, 0x48, 0x56, 0x64, 0x72, 0x80, 0x8E, 0x9C, 0xAA, 0xB8, 0xC6, 0xD4, 0xE2, 0xF0, 0xFE, 0x00, 0x0A, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0D, 0x00, 0x00, 0x00, 0x00}; + if (xpos-tblSize < COUNT(bogus)) { + xh = tile_mod_tbl[xpos-tblSize]; // simulating tile_div_tbl[xpos] + xl = bogus[xpos-tblSize]; // simulating tile_mod_tbl[xpos] + } else { + printf("xpos = %d (> %d) out of range for simulation of index overflow!\n", xpos, (int)COUNT(bogus)+tblSize); + } + } + obj_xl = xl; return xh; } @@ -1082,7 +1133,7 @@ void __pascal far check_grab() { #define MAX_GRAB_FALLING_SPEED 32 #endif - if (control_shift < 0 && // press shift to grab + if (control_shift < 0 && // press Shift to grab Char.fall_y < MAX_GRAB_FALLING_SPEED && // you can't grab if you're falling too fast ... Char.alive < 0 && // ... or dead (word)y_land[Char.curr_row + 1] <= (word)(Char.y + 25) @@ -1224,16 +1275,27 @@ void __pascal far control_kid() { word key; if (Char.alive < 0 && hitp_curr == 0) { Char.alive = 0; + // stop feather fall when kid dies + if (fixes->fix_quicksave_during_feather && is_feather_fall > 0) { + is_feather_fall = 0; + if (check_sound_playing()) { + stop_sounds(); + } + } } if (grab_timer != 0) { --grab_timer; } - if (current_level == 0) { +#ifdef USE_REPLAY + if (current_level == 0 && !play_demo_level && !replaying) { +#else + if (current_level == 0 && !play_demo_level) { +#endif do_demo(); control(); - // we can start the game or load a game while the demo + // The player can start a new game or load a saved game during the demo. key = key_test_quit(); - if (key == 0x0C) { // ctrl-L + if (key == (SDL_SCANCODE_L | WITH_CTRL)) { // Ctrl+L if (load_game()) { start_game(); } diff --git a/src/seg007.c b/src/seg007.c index 67076ed7..f89be26e 100644 --- a/src/seg007.c +++ b/src/seg007.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -292,7 +292,7 @@ void __pascal far animate_chomper() { if (trob.type >= 0) { blood = curr_modifier & 0x80; frame = (curr_modifier & 0x7F) + 1; - if (frame > 15) { + if (frame > /*15*/ custom->chomper_speed) { frame = 1; } curr_modifier = blood | frame; @@ -876,6 +876,16 @@ void __pascal far loose_shake(int arg_0) { // Sounds 20,21,22: loose floor shaking sound_id = prandom(2) + sound_20_loose_shake_1; } while(sound_id == last_loose_sound); + +#ifdef USE_REPLAY + // Skip this prandom call if we are replaying, and the replay file was made with an old version of SDLPoP (which didn't have this call). + if (!(replaying && g_deprecation_number < 2)) +#endif + { + prandom(2); // For vanilla pop compatibility, an RNG cycle is wasted here + // Note: In DOS PoP, it's wasted a few lines below. + } + if (sound_flags & sfDigi) { last_loose_sound = sound_id; // random sample rate (10500..11500) @@ -1132,7 +1142,7 @@ void __pascal far draw_mob() { if (curmob.room == drawn_room) { if (curmob.y >= 210) return; } else if (curmob.room == room_B) { - if (ABS(ypos) >= 18) return; + if (ABS((sbyte)ypos) >= 18) return; curmob.y += 192; ypos = curmob.y; } else if (curmob.room == room_A) { diff --git a/src/seg008.c b/src/seg008.c index 894c4fb5..f7083a0a 100644 --- a/src/seg008.c +++ b/src/seg008.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -72,10 +72,6 @@ byte tile_left; // data:4CCC byte modifier_left; -#ifdef USE_COLORED_TORCHES -byte torch_colors[24+1][30]; // indexed 1..24 -#endif - // seg008:0006 void __pascal far redraw_room() { free_peels(); @@ -194,6 +190,10 @@ void __pascal far redraw_needed(short tilepos) { draw_tile_anim_topright(); draw_tile_anim_right(); draw_tile_anim(); +#ifdef FIX_ABOVE_GATE + draw_tile_fore(); + draw_tile_bottom(0); +#endif } } if (redraw_frames2[tilepos]) { @@ -222,8 +222,13 @@ void __pascal far redraw_needed(short tilepos) { void __pascal far redraw_needed_above(int column) { if (redraw_frames_above[column] != 0) { --redraw_frames_above[column]; - draw_tile_wipe(3); - draw_tile_floorright(); +#ifdef FIX_BIGPILLAR_JUMP_UP + if (curr_tile != tiles_9_bigpillar_top) +#endif + { + draw_tile_wipe(3); + draw_tile_floorright(); + } draw_tile_anim_topright(); draw_tile_right(); draw_tile_bottom(1); @@ -453,7 +458,7 @@ void __pascal far draw_tile_right() { if (id) { if (tile_left == tiles_5_stuck) { blit = blitters_10h_transp; - if (curr_tile == tiles_0_empty || curr_tile == tiles_5_stuck) { + if (curr_tile == tiles_0_empty || curr_tile == tiles_5_stuck || !tile_is_floor(curr_tile)) { id = 42; /*floor B*/ } } else { @@ -932,7 +937,7 @@ SDL_Surface* hflip(SDL_Surface* input) { SDL_SetSurfacePalette(output, input->format->palette); // The copied image will be overwritten anyway. if (output == NULL) { - sdlperror("SDL_ConvertSurface"); + sdlperror("hflip: SDL_ConvertSurface"); quit(1); } @@ -946,7 +951,7 @@ SDL_Surface* hflip(SDL_Surface* input) { SDL_Rect srcrect = {source_x, 0, 1, height}; SDL_Rect dstrect = {target_x, 0, 1, height}; if (SDL_BlitSurface(input/*32*/, &srcrect, output, &dstrect) != 0) { - sdlperror("SDL_BlitSurface"); + sdlperror("hflip: SDL_BlitSurface"); quit(1); } } @@ -1138,6 +1143,14 @@ void __pascal far draw_gate_fore() { void __pascal far alter_mods_allrm() { word tilepos; word room; + +#ifdef USE_COLORED_TORCHES + memset(torch_colors, 0, sizeof(torch_colors)); +#endif + + // level.used_rooms is 25 on some levels. Limit it to the actual number of rooms. + if (level.used_rooms > 24) level.used_rooms = 24; + for (room = 1; room <= level.used_rooms; room++) { get_room_address(room); room_L = level.roomlinks[room-1].left; @@ -1419,6 +1432,7 @@ void __pascal far draw_leveldoor() { // seg008:1E0C void __pascal far get_room_address(int room) { + //if (room < 0 || room > 24) printf("Tried to access room %d, not in 0..24.\n", room); loaded_room = (word) room; if (room) { curr_room_tiles = &level.fg[(room-1)*30]; @@ -1562,6 +1576,7 @@ void __pascal far draw_objtable_item(int index) { if (obj_id == 0xFF) return; // the Kid blinks a bit after uniting with shadow if (united_with_shadow && (united_with_shadow % 2) == 0) goto shadow; + // fallthrough! case 2: // Guard case 3: // sword case 5: // hurt splash @@ -1807,6 +1822,9 @@ void __pascal far show_time() { // seg008:25A8 void __pascal far show_level() { +#ifdef FIX_LEVEL_14_RESTARTING + text_time_remaining = text_time_total = 0; +#endif byte disp_level; char sprintf_temp[32]; disp_level = current_level; @@ -1841,7 +1859,7 @@ void __pascal far display_text_bottom(const char near *text) { draw_rect(&rect_bottom_text, 0); show_text(&rect_bottom_text, 0, 1, text); #ifndef USE_TEXT - SDL_WM_SetCaption(text, NULL); + SDL_SetWindowTitle(window_, text); #endif } @@ -1853,7 +1871,7 @@ void __pascal far erase_bottom_text(int arg_0) { text_time_remaining = 0; } #ifndef USE_TEXT - SDL_WM_SetCaption("", NULL); + SDL_SetWindowTitle(window_, WINDOW_TITLE); #endif } diff --git a/src/seg009.c b/src/seg009.c index 54c660ed..50064d23 100644 --- a/src/seg009.c +++ b/src/seg009.c @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,7 +29,6 @@ The authors of this program may be contacted at https://forum.princed.org #include "dirent.h" #endif - // Most functions in this file are different from those in the original game. void sdlperror(const char* header) { @@ -47,16 +46,16 @@ bool found_exe_dir = false; void find_exe_dir() { if (found_exe_dir) return; - snprintf_check(exe_dir, sizeof(exe_dir), "%s", g_argv[0]); #ifdef __amigaos4__ - if(g_argc == 0) { + if(g_argc == 0) { // from Workbench struct WBStartup *WBenchMsg = (struct WBStartup *)g_argv; NameFromLock( WBenchMsg->sm_ArgList->wa_Lock, exe_dir, sizeof(exe_dir) ); } - else { + else { // from Shell/CLI NameFromLock( GetProgramDir(), exe_dir, sizeof(exe_dir) ); } #else + snprintf_check(exe_dir, sizeof(exe_dir), "%s", g_argv[0]); char* last_slash = NULL; char* pos = exe_dir; for (char c = *pos; c != '\0'; c = *(++pos)) { @@ -110,12 +109,28 @@ int chdir_UTF8(const char* path_UTF8) { return result; } +int mkdir_UTF8(const char* path_UTF8) { + WCHAR* path_UTF16 = WIN_UTF8ToString(path_UTF8); + int result = _wmkdir(path_UTF16); + SDL_free(path_UTF16); + return result; +} + int access_UTF8(const char* filename_UTF8, int mode) { WCHAR* filename_UTF16 = WIN_UTF8ToString(filename_UTF8); int result = _waccess(filename_UTF16, mode); SDL_free(filename_UTF16); return result; } + +int stat_UTF8(const char *filename_UTF8, struct stat *_Stat) { + WCHAR* filename_UTF16 = WIN_UTF8ToString(filename_UTF8); + // There is a _wstat() function as well, but it expects the second argument to be a different type than stat(). + int result = wstat(filename_UTF16, _Stat); + SDL_free(filename_UTF16); + return result; +} + #endif //_WIN32 // OS abstraction for listing directory contents @@ -272,10 +287,10 @@ void __pascal far restore_stuff() { int __pascal far key_test_quit() { word key; key = read_key(); - if (key == (SDL_SCANCODE_Q | WITH_CTRL)) { // ctrl-q + if (key == (SDL_SCANCODE_Q | WITH_CTRL)) { // Ctrl+Q #ifdef USE_REPLAY - if (recording) save_recorded_replay(); + if (recording) save_recorded_replay_dialog(); #endif #ifdef USE_MENU if (is_menu_shown) menu_was_closed(); @@ -359,6 +374,8 @@ static FILE* open_dat_from_root_or_data_dir(const char* filename) { return fp; } +int __pascal far showmessage(char far *text,int arg_4,void far *arg_0); + // seg009:0F58 dat_type *__pascal open_dat(const char *filename,int drive) { FILE* fp = NULL; @@ -387,13 +404,40 @@ dat_type *__pascal open_dat(const char *filename,int drive) { if (fp != NULL) { if (fread(&dat_header, 6, 1, fp) != 1) goto failed; + //dat_table = (dat_table_type*) malloc(dat_header.table_size); dat_table = (dat_table_type*) malloc(SDL_SwapLE16(dat_header.table_size)); if (dat_table == NULL || + //fseek(fp, dat_header.table_offset, SEEK_SET) || + //fread(dat_table, dat_header.table_size, 1, fp) != 1) fseek(fp, SDL_SwapLE32(dat_header.table_offset), SEEK_SET) || fread(dat_table, SDL_SwapLE16(dat_header.table_size), 1, fp) != 1) goto failed; pointer->handle = fp; pointer->dat_table = dat_table; + } else { + /* // showmessage will crash if we call if before certain things are initialized! + // There is no DAT file, verify whether the corresponding directory exists. + char filename_no_ext[POP_MAX_PATH]; + // strip the .DAT file extension from the filename (use folders simply named TITLE, KID, VPALACE, etc.) + strncpy(filename_no_ext, pointer->filename, sizeof(filename_no_ext)); + size_t len = strlen(filename_no_ext); + if (len >= 5 && filename_no_ext[len-4] == '.') { + filename_no_ext[len-4] = '\0'; // terminate, so ".DAT" is deleted from the filename + } + char filename[POP_MAX_PATH]; + snprintf_check(filename,sizeof(filename),"data/%s",filename_no_ext); + const char* data_path = locate_file(filename); + struct stat path_stat; + int result = stat(data_path, &path_stat); + if (result != 0 || !S_ISDIR(path_stat.st_mode)) { + char error_message[256]; + snprintf_check(error_message, sizeof(error_message), "Cannot find a required data file: %s\nPress any key to quit.", filename); + if (onscreen_surface_ != NULL && copyprot_dialog != NULL) { // otherwise showmessage will crash + showmessage(error_message, 1, &key_test_quit); + quit(1); + } + } + */ } out: // stub @@ -430,7 +474,14 @@ chtab_type* __pascal load_sprites_from_file(int resource,int palette_bits, int q dat_shpl_type* shpl = (dat_shpl_type*) load_from_opendats_alloc(resource, "pal", NULL, NULL); if (shpl == NULL) { printf("Can't load sprites from resource %d.\n", resource); - //if (quit_on_error) quit(1); + if (quit_on_error) { + char error_message[256]; + // Unfortunately we don't know at this point which data file is missing. So we use the name of the last opened DAT file. + // It's also possible that the DAT file exists and it just doesn't contain the needed resource. + snprintf_check(error_message, sizeof(error_message), "Cannot find a required data file: %s\nMake sure that the data/ folder exists.\nPress any key to quit.", dat_chain_ptr->filename); + showmessage(error_message, 1, &key_test_quit); + quit(1); + } return NULL; } @@ -461,13 +512,13 @@ chtab_type* __pascal load_sprites_from_file(int resource,int palette_bits, int q if (image != NULL) { if (SDL_SetSurfaceAlphaMod(image, 0) != 0) { - sdlperror("SDL_SetAlpha"); + sdlperror("load_sprites_from_file: SDL_SetAlpha"); quit(1); } /* if (SDL_SetColorKey(image, SDL_SRCCOLORKEY, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("load_sprites_from_file: SDL_SetColorKey"); quit(1); } */ @@ -653,18 +704,22 @@ void __pascal far decompr_img(byte far *dest,const image_data_type far *source,i decompress_rle_lr(dest, source->data, decomp_size); break; case 2: // RLE up-to-down + //decompress_rle_ud(dest, source->data, decomp_size, stride, source->height); decompress_rle_ud(dest, source->data, decomp_size, stride, SDL_SwapLE16(source->height)); break; case 3: // LZG left-to-right decompress_lzg_lr(dest, source->data, decomp_size); break; case 4: // LZG up-to-down + //decompress_lzg_ud(dest, source->data, decomp_size, stride, source->height); decompress_lzg_ud(dest, source->data, decomp_size, stride, SDL_SwapLE16(source->height)); break; } } int calc_stride(image_data_type* image_data) { + //int width = image_data->width; + //int flags = image_data->flags; int width = SDL_SwapLE16(image_data->width); int flags = SDL_SwapLE16(image_data->flags); int depth = ((flags >> 12) & 7) + 1; @@ -694,8 +749,11 @@ byte* conv_to_8bpp(byte* in_data, int width, int height, int stride, int depth) } image_type* decode_image(image_data_type* image_data, dat_pal_type* palette) { + //int height = image_data->height; int height = SDL_SwapLE16(image_data->height); if (height == 0) return NULL; + //int width = image_data->width; + //int flags = image_data->flags; int width = SDL_SwapLE16(image_data->width); int flags = SDL_SwapLE16(image_data->flags); int depth = ((flags >> 12) & 7) + 1; @@ -709,11 +767,11 @@ image_type* decode_image(image_data_type* image_data, dat_pal_type* palette) { free(dest); dest = NULL; image_type* image = SDL_CreateRGBSurface(0, width, height, 8, 0, 0, 0, 0); if (image == NULL) { - sdlperror("SDL_CreateRGBSurface"); + sdlperror("decode_image: SDL_CreateRGBSurface"); quit(1); } if (SDL_LockSurface(image) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("decode_image: SDL_LockSurface"); } int y; for (y = 0; y < height; ++y) { @@ -758,15 +816,15 @@ image_type* far __pascal far load_image(int resource_id, dat_pal_type* palette) case data_directory: { // directory SDL_RWops* rw = SDL_RWFromConstMem(image_data, size); if (rw == NULL) { - sdlperror("SDL_RWFromConstMem"); + sdlperror("load_image: SDL_RWFromConstMem"); return NULL; } image = IMG_Load_RW(rw, 0); if (image == NULL) { - printf("IMG_Load_RW: %s\n", IMG_GetError()); + printf("load_image: IMG_Load_RW: %s\n", IMG_GetError()); } if (SDL_RWclose(rw) != 0) { - sdlperror("SDL_RWclose"); + sdlperror("load_image: SDL_RWclose"); } } break; } @@ -777,17 +835,17 @@ image_type* far __pascal far load_image(int resource_id, dat_pal_type* palette) // should immediately start using the onscreen pixel format, so conversion will not be needed if (SDL_SetColorKey(image, SDL_TRUE, 0) != 0) { //sdl 1.2: SDL_SRCCOLORKEY - sdlperror("SDL_SetColorKey"); + sdlperror("load_image: SDL_SetColorKey"); quit(1); } // printf("bpp = %d\n", image->format->BitsPerPixel); if (SDL_SetSurfaceAlphaMod(image, 0) != 0) { //sdl 1.2: SDL_SetAlpha removed - sdlperror("SDL_SetAlpha"); + sdlperror("load_image: SDL_SetAlpha"); quit(1); } // image_type* colored_image = SDL_ConvertSurfaceFormat(image, SDL_PIXELFORMAT_ARGB8888, 0); // if (!colored_image) { -// sdlperror("SDL_ConvertSurfaceFormat"); +// sdlperror("load_image: SDL_ConvertSurfaceFormat"); // quit(1); // } // SDL_FreeSurface(image); @@ -847,8 +905,10 @@ surface_type far *__pascal make_offscreen_buffer(const rect_type far *rect) { // stub #ifndef USE_ALPHA // Bit order matches onscreen buffer, good for fading. + //return SDL_CreateRGBSurface(0, rect->right, rect->bottom, 24, 0xFF, 0xFF<<8, 0xFF<<16, 0); //RGB888 (little endian) return SDL_CreateRGBSurface(0, rect->right, rect->bottom, 24, Rmsk, Gmsk, Bmsk, 0); //RGB888 (little endian) #else + //return SDL_CreateRGBSurface(0, rect->right, rect->bottom, 32, 0xFF, 0xFF<<8, 0xFF<<16, 0xFF<<24); return SDL_CreateRGBSurface(0, rect->right, rect->bottom, 32, Rmsk, Gmsk, Bmsk, Amsk); #endif //return surface; @@ -899,7 +959,7 @@ void __pascal far flip_screen(surface_type far *surface) { // stub if (graphics_mode != gmEga) { if (SDL_LockSurface(surface) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("flip_screen: SDL_LockSurface"); quit(1); } flip_not_ega((byte*) surface->pixels, surface->h, surface->pitch); @@ -1034,8 +1094,10 @@ static void load_font_character_offsets(rawfont_type* data) { int n_chars = data->last_char - data->first_char + 1; byte* pos = (byte*) &data->offsets[n_chars]; for (int index = 0; index < n_chars; ++index) { + //data->offsets[index] = (word) (pos - (byte*) data); data->offsets[index] = SDL_SwapLE16(pos - (byte*) data); image_data_type* image_data = (image_data_type*) pos; + //int image_bytes = image_data->height * calc_stride(image_data); int image_bytes = SDL_SwapLE16(image_data->height) * calc_stride(image_data); pos = (byte*) &image_data->data + image_bytes; } @@ -1045,12 +1107,17 @@ font_type load_font_from_data(/*const*/ rawfont_type* data) { font_type font; font.first_char = data->first_char; font.last_char = data->last_char; + //font.height_above_baseline = data->height_above_baseline; + //font.height_below_baseline = data->height_below_baseline; + //font.space_between_lines = data->space_between_lines; + //font.space_between_chars = data->space_between_chars; font.height_above_baseline = SDL_SwapLE16(data->height_above_baseline); font.height_below_baseline = SDL_SwapLE16(data->height_below_baseline); font.space_between_lines = SDL_SwapLE16(data->space_between_lines); font.space_between_chars = SDL_SwapLE16(data->space_between_chars); int n_chars = font.last_char - font.first_char + 1; // Allow loading a font even if the offsets for each character image were not supplied in the raw data. + //if (data->offsets[0] == 0) { if (SDL_SwapLE16(data->offsets[0]) == 0) { load_font_character_offsets(data); } @@ -1061,13 +1128,14 @@ font_type load_font_from_data(/*const*/ rawfont_type* data) { memset(&dat_pal, 0, sizeof(dat_pal)); dat_pal.vga[1].r = dat_pal.vga[1].g = dat_pal.vga[1].b = 0x3F; // white for (index = 0, chr = data->first_char; chr <= data->last_char; ++index, ++chr) { + ///*const*/ image_data_type* image_data = (/*const*/ image_data_type*)((/*const*/ byte*)data + data->offsets[index]); /*const*/ image_data_type* image_data = (/*const*/ image_data_type*)((/*const*/ byte*)data + SDL_SwapLE16(data->offsets[index])); //image_data->flags=0; if (image_data->height == 0) image_data->height = 1; // HACK: decode_image() returns NULL if height==0. image_type* image; chtab->images[index] = image = decode_image(image_data, &dat_pal); if (SDL_SetColorKey(image, SDL_TRUE, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("load_font_from_data: SDL_SetColorKey"); quit(1); } } @@ -1325,6 +1393,7 @@ int __pascal far showmessage(char far *text,int arg_4,void far *arg_0) { //current_target_surface = onscreen_surface_; // In the disassembly there is some messing with the current_target_surface and font (?) // However, this does not seem to be strictly necessary + if (NULL == offscreen_surface) offscreen_surface = make_offscreen_buffer(&screen_rect); // In case we get an error before there is an offsceen buffer method_1_blit_rect(offscreen_surface, onscreen_surface_, ©prot_dialog->peel_rect, ©prot_dialog->peel_rect, 0); draw_dialog_frame(copyprot_dialog); //saved_font_ptr = textstate.ptr_font; @@ -1509,7 +1578,7 @@ int __pascal far input_str(const rect_type far *rect,char *buffer,int max_length draw_text_cursor(current_xpos, ypos, color); cursor_visible = !cursor_visible; } - if (key == SDL_SCANCODE_RETURN) { // enter + if (key == SDL_SCANCODE_RETURN) { // Enter buffer[length] = 0; return length; } else break; @@ -1520,13 +1589,13 @@ int __pascal far input_str(const rect_type far *rect,char *buffer,int max_length char entered_char = last_text_input <= 0x7E ? last_text_input : 0; clear_kbd_buf(); - if (key == SDL_SCANCODE_ESCAPE) { // esc + if (key == SDL_SCANCODE_ESCAPE) { // Esc draw_rect(rect, bgcolor); buffer[0] = 0; return -1; } if (length != 0 && (key == SDL_SCANCODE_BACKSPACE || - key == SDL_SCANCODE_DELETE)) { // backspace, delete + key == SDL_SCANCODE_DELETE)) { // Backspace, Delete --length; draw_text_cursor(current_xpos, ypos, bgcolor); current_xpos -= get_char_width(buffer[length]); @@ -1564,7 +1633,7 @@ void __pascal far show_text(const rect_type far *rect_ptr,int x_align,int y_alig } // seg009:04FF -void __pascal far show_text_with_color(const rect_type far *rect_ptr,int x_align,int y_align,char far *text,int color) { +void __pascal far show_text_with_color(const rect_type far *rect_ptr,int x_align,int y_align,const char far *text,int color) { //short saved_textcolor; //saved_textcolor = textstate.textcolor; //textstate.textcolor = color; @@ -1578,7 +1647,7 @@ void __pascal far set_curr_pos(int xpos,int ypos) { } // seg009:0C44 -void __pascal far show_dialog(char *text) { +void __pascal far show_dialog(const char *text) { // stub puts(text); } @@ -1590,6 +1659,28 @@ int __pascal far input_str(const rect_type far *rect,char *buffer,int max_length return strlen(buffer); } +int __pascal far showmessage(char far *text,int arg_4,void far *arg_0) { + // stub + puts(text); + return 0; +} + +void __pascal far init_copyprot_dialog() { + // stub +} + +void __pascal far draw_dialog_frame(dialog_type *dialog) { + // stub +} + +void __pascal far add_dialog_rect(dialog_type *dialog) { + // stub +} + +void __pascal far dialog_method_2_frame(dialog_type *dialog) { + // stub +} + #endif // USE_TEXT // seg009:37E8 @@ -1630,11 +1721,13 @@ peel_type* __pascal far read_peel_from_screen(const rect_type far *rect) { #ifndef USE_ALPHA SDL_Surface* peel_surface = SDL_CreateRGBSurface(0, rect->right - rect->left, rect->bottom - rect->top, 24, Rmsk, Gmsk, Bmsk, 0); + //24, 0xFF, 0xFF<<8, 0xFF<<16, 0); #else + //SDL_Surface* peel_surface = SDL_CreateRGBSurface(0, rect->right - rect->left, rect->bottom - rect->top, 32, 0xFF, 0xFF<<8, 0xFF<<16, 0xFF<<24); SDL_Surface* peel_surface = SDL_CreateRGBSurface(0, rect->right - rect->left, rect->bottom - rect->top, 32, Rmsk, Gmsk, Bmsk, Amsk); #endif if (peel_surface == NULL) { - sdlperror("SDL_CreateRGBSurface"); + sdlperror("read_peel_from_screen: SDL_CreateRGBSurface"); quit(1); } result->peel = peel_surface; @@ -1707,7 +1800,7 @@ byte* digi_buffer = NULL; // The current position in digi_buffer. byte* digi_remaining_pos = NULL; // The remaining length. -size_t digi_remaining_length = 0; +int digi_remaining_length = 0; // The properties of the audio device. SDL_AudioSpec* digi_audiospec = NULL; @@ -1792,11 +1885,13 @@ void speaker_callback(void *userdata, Uint8 *stream, int len) { int samples_requested = len / bytes_per_sample; if (current_speaker_sound == NULL) return; + //word tempo = current_speaker_sound->tempo; word tempo = SDL_SwapLE16(current_speaker_sound->tempo); int total_samples_left = samples_requested; while (total_samples_left > 0) { note_type* note = current_speaker_sound->notes + speaker_note_index; + //if (note->frequency == 0x12 /*end*/) { if (SDL_SwapLE16(note->frequency) == 0x12 /*end*/) { speaker_playing = 0; current_speaker_sound = NULL; @@ -1813,9 +1908,11 @@ void speaker_callback(void *userdata, Uint8 *stream, int len) { int note_samples_to_emit = MIN(note_length_in_samples - current_speaker_note_samples_already_emitted, total_samples_left); total_samples_left -= note_samples_to_emit; size_t copy_len = (size_t)note_samples_to_emit * bytes_per_sample; + //if (note->frequency <= 0x01 /*rest*/) { if (SDL_SwapLE16(note->frequency) <= 0x01 /*rest*/) { memset(stream, digi_audiospec->silence, copy_len); } else { + //generate_square_wave(stream, (float)note->frequency, note_samples_to_emit); generate_square_wave(stream, (float)SDL_SwapLE16(note->frequency), note_samples_to_emit); } stream += copy_len; @@ -1904,7 +2001,25 @@ void ogg_callback(void *userdata, Uint8 *stream, int len) { } } -void audio_callback(void* userdata, Uint8* stream, int len) { +#ifdef USE_FAST_FORWARD +int audio_speed = 1; // =1 normally, >1 during fast forwarding +#endif + +void audio_callback(void* userdata, Uint8* stream_orig, int len_orig) { + + Uint8* stream; + int len; +#ifdef USE_FAST_FORWARD + if (audio_speed > 1) { + len = len_orig * audio_speed; + stream = malloc(len); + } else +#endif + { + len = len_orig; + stream = stream_orig; + } + memset(stream, digi_audiospec->silence, len); if (digi_playing) { digi_callback(userdata, stream, len); @@ -1918,6 +2033,45 @@ void audio_callback(void* userdata, Uint8* stream, int len) { } else if (ogg_playing) { ogg_callback(userdata, stream, len); } + +#ifdef USE_FAST_FORWARD + if (audio_speed > 1) { + +#ifdef FAST_FORWARD_MUTE + memset(stream_orig, digi_audiospec->silence, len_orig); +#else +#ifdef FAST_FORWARD_RESAMPLE_SOUND + static SDL_AudioCVT cvt; + static bool cvt_initialized = false; + if (!cvt_initialized) { + SDL_BuildAudioCVT(&cvt, + digi_audiospec->format, digi_audiospec->channels, digi_audiospec->freq * audio_speed, + digi_audiospec->format, digi_audiospec->channels, digi_audiospec->freq); + cvt_initialized = true; + } + //realloc(stream, len * cvt.len_mult); + //cvt.buf = stream; + cvt.len = len; + cvt.buf = malloc(cvt.len * cvt.len_mult); + memcpy(cvt.buf, stream, cvt.len); + //printf("cvt.needed = %d\n", cvt.needed); + //printf("cvt.len_mult = %d\n", cvt.len_mult); + //printf("cvt.len_ratio = %lf\n", cvt.len_ratio); + SDL_ConvertAudio(&cvt); + + memcpy(stream_orig, cvt.buf, len_orig); + free(cvt.buf); + cvt.buf = NULL; +#else + // Hack: use the beginning of the buffer instead of resampling. + memcpy(stream_orig, stream, len_orig); +#endif +#endif + + free(stream); + } +#endif + } int digi_unavailable = 0; @@ -1951,7 +2105,7 @@ void init_digi() { desired->callback = audio_callback; desired->userdata = NULL; if (SDL_OpenAudio(desired, NULL) != 0) { - sdlperror("SDL_OpenAudio"); + sdlperror("init_digi: SDL_OpenAudio"); //quit(1); digi_unavailable = 1; return; @@ -2102,14 +2256,18 @@ bool determine_wave_version(sound_buffer_type *buffer, waveinfo_type* waveinfo) switch (version) { case 1: // 1.0 and 1.1 + //waveinfo->sample_rate = buffer->digi.sample_rate; waveinfo->sample_rate = SDL_SwapLE16(buffer->digi.sample_rate); waveinfo->sample_size = buffer->digi.sample_size; + //waveinfo->sample_count = buffer->digi.sample_count; waveinfo->sample_count = SDL_SwapLE16(buffer->digi.sample_count); waveinfo->samples = buffer->digi.samples; return true; case 2: // 1.3 and 1.4 (and PoP2) + //waveinfo->sample_rate = buffer->digi_new.sample_rate; waveinfo->sample_rate = SDL_SwapLE16(buffer->digi_new.sample_rate); waveinfo->sample_size = buffer->digi_new.sample_size; + //waveinfo->sample_count = buffer->digi_new.sample_count; waveinfo->sample_count = SDL_SwapLE16(buffer->digi_new.sample_count); waveinfo->samples = buffer->digi_new.samples; return true; @@ -2274,7 +2432,9 @@ void window_resized() { void init_overlay() { static bool initialized = false; if (!initialized) { + //overlay_surface = SDL_CreateRGBSurface(0, 320, 200, 32, 0xFF, 0xFF << 8, 0xFF << 16, 0xFF << 24) ; overlay_surface = SDL_CreateRGBSurface(0, 320, 200, 32, Rmsk, Gmsk, Bmsk, Amsk) ; + //merged_surface = SDL_CreateRGBSurface(0, 320, 200, 24, 0xFF, 0xFF << 8, 0xFF << 16, 0) ; merged_surface = SDL_CreateRGBSurface(0, 320, 200, 24, Rmsk, Gmsk, Bmsk, 0) ; initialized = true; } @@ -2283,11 +2443,15 @@ void init_overlay() { SDL_Surface* onscreen_surface_2x; void init_scaling() { + // Don't crash in validate mode. + if (renderer_ == NULL) return; + if (texture_sharp == NULL) { texture_sharp = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, 320, 200); } if (scaling_type == 1) { if (!is_renderer_targettexture_supported && onscreen_surface_2x == NULL) { + //onscreen_surface_2x = SDL_CreateRGBSurface(0, 320*2, 200*2, 24, 0xFF, 0xFF << 8, 0xFF << 16, 0) ; onscreen_surface_2x = SDL_CreateRGBSurface(0, 320*2, 200*2, 24, Rmsk, Gmsk, Bmsk, 0) ; } if (texture_fuzzy == NULL) { @@ -2308,7 +2472,7 @@ void init_scaling() { target_texture = texture_sharp; } if (target_texture == NULL) { - sdlperror("SDL_CreateTexture"); + sdlperror("init_scaling: SDL_CreateTexture"); quit(1); } } @@ -2320,15 +2484,10 @@ void __pascal far set_gr_mode(byte grmode) { #endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC ) != 0) { - sdlperror("SDL_Init"); + sdlperror("set_gr_mode: SDL_Init"); quit(1); } -/*#ifdef __amigaos4__ - //SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2"); //"software", "opengl", "opengles2" or "compositing" - //SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); // implemented in master branch -#endif*/ - //SDL_EnableUNICODE(1); //deprecated Uint32 flags = 0; if (!start_fullscreen) start_fullscreen = check_param("full") != NULL; @@ -2385,7 +2544,7 @@ void __pascal far set_gr_mode(byte grmode) { SDL_Surface* icon = IMG_Load(locate_file("data/icon.png")); if (icon == NULL) { - sdlperror("Could not load icon"); + sdlperror("set_gr_mode: Could not load icon"); } else { SDL_SetWindowIcon(window_, icon); } @@ -2399,9 +2558,10 @@ void __pascal far set_gr_mode(byte grmode) { * subsequently displayed. * The function handling the screen updates is update_screen() * */ + //onscreen_surface_ = SDL_CreateRGBSurface(0, 320, 200, 24, 0xFF, 0xFF << 8, 0xFF << 16, 0); onscreen_surface_ = SDL_CreateRGBSurface(0, 320, 200, 24, Rmsk, Gmsk, Bmsk, 0); if (onscreen_surface_ == NULL) { - sdlperror("SDL_CreateRGBSurface"); + sdlperror("set_gr_mode: SDL_CreateRGBSurface"); quit(1); } init_overlay(); @@ -2413,7 +2573,7 @@ void __pascal far set_gr_mode(byte grmode) { //SDL_WM_SetCaption(WINDOW_TITLE, NULL); // if (SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL) != 0) { //deprecated -// sdlperror("SDL_EnableKeyRepeat"); +// sdlperror("set_gr_mode: SDL_EnableKeyRepeat"); // quit(1); // } graphics_mode = gmMcgaVga; @@ -2435,6 +2595,12 @@ void draw_overlay() { is_overlay_displayed = false; #ifdef USE_DEBUG_CHEATS if (is_timer_displayed && start_level > 0) overlay = 1; // Timer overlay + else if (fixes->fix_quicksave_during_feather && + is_feather_timer_displayed && + start_level > 0 && + is_feather_fall > 0) { + overlay = 3; // Feather timer overlay + } #endif #ifdef USE_MENU // Menu overlay - not drawn here directly, only copied from the overlay surface. @@ -2456,13 +2622,49 @@ void draw_overlay() { rem_min - 1, rem_tick / 12, rem_tick % 12); } int expected_numeric_chars = 6; - int extra_numeric_chars = MAX(0, strnlen(timer_text, sizeof(timer_text)) - 8); + int extra_numeric_chars = MAX(0, (int)strnlen(timer_text, sizeof(timer_text)) - 8); int line_width = 5 + (expected_numeric_chars + extra_numeric_chars) * 9; rect_type timer_box_rect = {0, 0, 11, 2 + line_width}; rect_type timer_text_rect = {2, 2, 10, 100}; draw_rect_with_alpha(&timer_box_rect, color_0_black, 128); show_text(&timer_text_rect, -1, -1, timer_text); + +#ifdef USE_REPLAY + // During playback, display the number of ticks since start, if the timer is shown (debug cheats: T). + if (replaying) { + char ticks_text[12]; + snprintf(ticks_text, sizeof(ticks_text), "T: %d", curr_tick); + rect_type ticks_box_rect = timer_box_rect; + ticks_box_rect.top += 12; + ticks_box_rect.bottom += 12; + rect_type ticks_text_rect = timer_text_rect; + ticks_text_rect.top += 12; + ticks_text_rect.bottom += 12; + + draw_rect_with_alpha(&ticks_box_rect, color_0_black, 128); + show_text(&ticks_text_rect, -1, -1, ticks_text); + + timer_box_rect.bottom += 12; + } +#endif + + drawn_rect = timer_box_rect; // Only need to blit this bit to the merged_surface. +#endif + } else if (overlay == 3) { // Feather timer +#ifdef USE_DEBUG_CHEATS + char timer_text[32]; + int ticks_per_sec = get_ticks_per_sec(timer_1); + snprintf(timer_text, sizeof(timer_text), "%02d:%02d", is_feather_fall / ticks_per_sec, is_feather_fall % ticks_per_sec); + int expected_numeric_chars = 6; + int extra_numeric_chars = MAX(0, (int)strnlen(timer_text, sizeof(timer_text)) - 8); + int line_width = 5 + (expected_numeric_chars + extra_numeric_chars) * 9; + + rect_type timer_box_rect = {0, 0, 11, 2 + line_width}; + rect_type timer_text_rect = {2, 2, 10, 100}; + draw_rect_with_alpha(&timer_box_rect, color_0_black, 128); + show_text_with_color(&timer_text_rect, -1, -1, timer_text, color_10_brightgreen); + drawn_rect = timer_box_rect; // Only need to blit this bit to the merged_surface. #endif } else { @@ -2573,15 +2775,20 @@ void load_from_opendats_metadata(int resource_id, const char* extension, FILE** fp = pointer->handle; dat_table_type* dat_table = pointer->dat_table; int i; + //for (i = 0; i < dat_table->res_count; ++i) { for (i = 0; i < SDL_SwapLE16(dat_table->res_count); ++i) { + //if (dat_table->entries[i].id == resource_id) { if (SDL_SwapLE16(dat_table->entries[i].id) == resource_id) { break; } } + //if (i < dat_table->res_count) { if (i < SDL_SwapLE16(dat_table->res_count)) { // found *result = data_DAT; + //*size = dat_table->entries[i].size; *size = SDL_SwapLE16(dat_table->entries[i].size); + //if (fseek(fp, dat_table->entries[i].offset, SEEK_SET) || if (fseek(fp, SDL_SwapLE32(dat_table->entries[i].offset), SEEK_SET) || fread(checksum, 1, 1, fp) != 1) { perror(pointer->filename); @@ -2725,18 +2932,18 @@ void __pascal far method_1_blit_rect(surface_type near *target_surface,surface_t if (blit == blitters_0_no_transp) { // Disable transparency. if (SDL_SetColorKey(source_surface, 0, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("method_1_blit_rect: SDL_SetColorKey"); quit(1); } } else { // Enable transparency. if (SDL_SetColorKey(source_surface, SDL_TRUE, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("method_1_blit_rect: SDL_SetColorKey"); quit(1); } } if (SDL_BlitSurface(source_surface, &src_rect, target_surface, &dest_rect) != 0) { - sdlperror("SDL_BlitSurface"); + sdlperror("method_1_blit_rect: SDL_BlitSurface"); quit(1); } } @@ -2745,7 +2952,7 @@ image_type far * __pascal far method_3_blit_mono(image_type far *image,int xpos, int w = image->w; int h = image->h; if (SDL_SetColorKey(image, SDL_TRUE, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("method_3_blit_mono: SDL_SetColorKey"); quit(1); } SDL_Surface* colored_image = SDL_ConvertSurfaceFormat(image, SDL_PIXELFORMAT_ARGB8888, 0); @@ -2753,13 +2960,13 @@ image_type far * __pascal far method_3_blit_mono(image_type far *image,int xpos, SDL_SetSurfaceBlendMode(colored_image, SDL_BLENDMODE_NONE); /* Causes problems with SDL 2.0.5 (see #105) if (SDL_SetColorKey(colored_image, SDL_TRUE, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("method_3_blit_mono: SDL_SetColorKey"); quit(1); } */ if (SDL_LockSurface(colored_image) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("method_3_blit_mono: SDL_LockSurface"); quit(1); } @@ -2785,7 +2992,7 @@ image_type far * __pascal far method_3_blit_mono(image_type far *image,int xpos, SDL_SetSurfaceBlendMode(current_target_surface, SDL_BLENDMODE_BLEND); SDL_SetSurfaceAlphaMod(colored_image, 255); if (SDL_BlitSurface(colored_image, &src_rect, current_target_surface, &dest_rect) != 0) { - sdlperror("SDL_BlitSurface"); + sdlperror("method_3_blit_mono: SDL_BlitSurface"); quit(1); } SDL_FreeSurface(colored_image); @@ -2836,7 +3043,7 @@ const rect_type far * __pascal far method_5_rect(const rect_type far *rect,int b uint32_t rgb_color = SDL_MapRGBA(current_target_surface->format, palette_color.r<<2, palette_color.g<<2, palette_color.b<<2, color == 0 ? SDL_ALPHA_TRANSPARENT : SDL_ALPHA_OPAQUE); #endif if (safe_SDL_FillRect(current_target_surface, &dest_rect, rgb_color) != 0) { - sdlperror("SDL_FillRect"); + sdlperror("method_5_rect: SDL_FillRect"); quit(1); } return rect; @@ -2848,7 +3055,7 @@ void draw_rect_with_alpha(const rect_type* rect, byte color, byte alpha) { rgb_type palette_color = palette[color]; uint32_t rgb_color = SDL_MapRGBA(overlay_surface->format, palette_color.r<<2, palette_color.g<<2, palette_color.b<<2, alpha); if (safe_SDL_FillRect(current_target_surface, &dest_rect, rgb_color) != 0) { - sdlperror("SDL_FillRect"); + sdlperror("draw_rect_with_alpha: SDL_FillRect"); quit(1); } } @@ -2864,7 +3071,7 @@ void draw_rect_contours(const rect_type* rect, byte color) { rgb_type palette_color = palette[color]; uint32_t rgb_color = SDL_MapRGBA(overlay_surface->format, palette_color.r<<2, palette_color.g<<2, palette_color.b<<2, 0xFF); if (SDL_LockSurface(current_target_surface) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("draw_rect_contours: SDL_LockSurface"); quit(1); } int bytes_per_pixel = current_target_surface->format->BytesPerPixel; @@ -2897,29 +3104,30 @@ void blit_xor(SDL_Surface* target_surface, SDL_Rect* dest_rect, SDL_Surface* ima printf("blit_xor: dest_rect and src_rect have different sizes\n"); quit(1); } + //SDL_Surface* helper_surface = SDL_CreateRGBSurface(0, dest_rect->w, dest_rect->h, 24, 0xFF, 0xFF<<8, 0xFF<<16, 0); SDL_Surface* helper_surface = SDL_CreateRGBSurface(0, dest_rect->w, dest_rect->h, 24, Rmsk, Gmsk, Bmsk, 0); if (helper_surface == NULL) { - sdlperror("SDL_CreateRGBSurface"); + sdlperror("blit_xor: SDL_CreateRGBSurface"); quit(1); } SDL_Surface* image_24 = SDL_ConvertSurface(image, helper_surface->format, 0); //SDL_CreateRGBSurface(0, src_rect->w, src_rect->h, 24, 0xFF, 0xFF<<8, 0xFF<<16, 0); if (image_24 == NULL) { - sdlperror("SDL_CreateRGBSurface"); + sdlperror("blit_xor: SDL_CreateRGBSurface"); quit(1); } SDL_Rect dest_rect2 = *src_rect; // Read what is currently where we want to draw the new image. if (SDL_BlitSurface(target_surface, dest_rect, helper_surface, &dest_rect2) != 0) { - sdlperror("SDL_BlitSurface"); + sdlperror("blit_xor: SDL_BlitSurface"); quit(1); } if (SDL_LockSurface(image_24) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("blit_xor: SDL_LockSurface"); quit(1); } if (SDL_LockSurface(helper_surface) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("blit_xor: SDL_LockSurface"); quit(1); } int size = helper_surface->h * helper_surface->pitch; @@ -2936,7 +3144,7 @@ void blit_xor(SDL_Surface* target_surface, SDL_Rect* dest_rect, SDL_Surface* ima SDL_UnlockSurface(helper_surface); // Put the new area in place of the old one. if (SDL_BlitSurface(helper_surface, src_rect, target_surface, dest_rect) != 0) { - sdlperror("SDL_BlitSurface 2065"); + sdlperror("blit_xor: SDL_BlitSurface 2065"); quit(1); } SDL_FreeSurface(image_24); @@ -2946,7 +3154,7 @@ void blit_xor(SDL_Surface* target_surface, SDL_Rect* dest_rect, SDL_Surface* ima #ifdef USE_COLORED_TORCHES void draw_colored_torch(int color, SDL_Surface* image, int xpos, int ypos) { if (SDL_SetColorKey(image, SDL_TRUE, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("draw_colored_torch: SDL_SetColorKey"); quit(1); } @@ -2954,7 +3162,7 @@ void draw_colored_torch(int color, SDL_Surface* image, int xpos, int ypos) { SDL_SetSurfaceBlendMode(colored_image, SDL_BLENDMODE_NONE); if (SDL_LockSurface(colored_image) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("draw_colored_torch: SDL_LockSurface"); quit(1); } @@ -3021,12 +3229,12 @@ image_type far * __pascal far method_6_blit_img_to_scr(image_type far *image,int SDL_SetColorKey(image, SDL_TRUE, 0); } if (SDL_BlitSurface(image, &src_rect, current_target_surface, &dest_rect) != 0) { - sdlperror("SDL_BlitSurface 2247"); + sdlperror("method_6_blit_img_to_scr: SDL_BlitSurface 2247"); quit(1); } if (SDL_SetSurfaceAlphaMod(image, 0) != 0) { - sdlperror("SDL_SetAlpha"); + sdlperror("method_6_blit_img_to_scr: SDL_SetAlpha"); quit(1); } return image; @@ -3058,8 +3266,36 @@ void reset_timer(int timer_index) { #endif } +double get_ticks_per_sec(int timer_index) { + return (double) fps / wait_time[timer_index]; +} + +void recalculate_feather_fall_timer(double previous_ticks_per_second, double ticks_per_second) { + if (is_feather_fall <= MAX(previous_ticks_per_second, ticks_per_second) || + previous_ticks_per_second == ticks_per_second) { + return; + } + // there are more ticks per second in base mode vs fight mode so + // feather fall length needs to be recalculated + is_feather_fall = is_feather_fall / previous_ticks_per_second * ticks_per_second; +} + void set_timer_length(int timer_index, int length) { + if (!fixes->fix_quicksave_during_feather) { + wait_time[timer_index] = length; + return; + } + if (is_feather_fall == 0 || + wait_time[timer_index] < custom->base_speed || + wait_time[timer_index] > custom->fight_speed) { + wait_time[timer_index] = length; + return; + } + double previous_ticks_per_second, ticks_per_second; + previous_ticks_per_second = get_ticks_per_sec(timer_index); wait_time[timer_index] = length; + ticks_per_second = get_ticks_per_sec(timer_index); + recalculate_feather_fall_timer(previous_ticks_per_second, ticks_per_second); } void __pascal start_timer(int timer_index, int length) { @@ -3101,10 +3337,13 @@ void process_events() { int scancode = event.key.keysym.scancode; // Handle these separately, so they won't interrupt things that are usually interrupted by a keypress. (pause, cutscene) +#ifdef USE_FAST_FORWARD if (scancode == SDL_SCANCODE_GRAVE) { - init_timer(60 * 10); // fast-forward on + init_timer(BASE_FPS * FAST_FORWARD_RATIO); // fast-forward on + audio_speed = FAST_FORWARD_RATIO; break; } +#endif #ifdef USE_SCREENSHOT if (scancode == SDL_SCANCODE_F12) { if (modifier & KMOD_SHIFT) { @@ -3126,7 +3365,7 @@ void process_events() { { // Only if the Enter key was pressed down right now. if (key_states[scancode] == 0) { - // Alt-Enter: toggle fullscreen mode + // Alt+Enter: toggle fullscreen mode toggle_fullscreen(); key_states[scancode] = 1; } @@ -3197,10 +3436,13 @@ void process_events() { // If Alt was held down from Alt+Tab but now it's released: stop ignoring Tab. if (event.key.keysym.scancode == SDL_SCANCODE_TAB && ignore_tab) ignore_tab = false; +#ifdef USE_FAST_FORWARD if (event.key.keysym.scancode == SDL_SCANCODE_GRAVE) { - init_timer(60); // fast-forward off + init_timer(BASE_FPS); // fast-forward off + audio_speed = 1; break; } +#endif key_states[event.key.keysym.scancode] = 0; #ifdef USE_MENU @@ -3232,14 +3474,14 @@ void process_events() { switch (event.cbutton.button) { case SDL_CONTROLLER_BUTTON_DPAD_LEFT: joy_hat_states[0] = -1; break; // left - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: joy_hat_states[0] = 1; break; // right + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: joy_hat_states[0] = 1; break; // right case SDL_CONTROLLER_BUTTON_DPAD_UP: joy_hat_states[1] = -1; break; // up - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: joy_hat_states[1] = 1; break; // down + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: joy_hat_states[1] = 1; break; // down - case SDL_CONTROLLER_BUTTON_A: joy_AY_buttons_state = 1; break; /*** A (down) ***/ + case SDL_CONTROLLER_BUTTON_A: joy_AY_buttons_state = 1; break; /*** A (down) ***/ case SDL_CONTROLLER_BUTTON_Y: joy_AY_buttons_state = -1; break; /*** Y (up) ***/ - case SDL_CONTROLLER_BUTTON_X: joy_X_button_state = 1; break; /*** X (shift) ***/ - case SDL_CONTROLLER_BUTTON_B: joy_B_button_state = 1; break; /*** B (unused) ***/ + case SDL_CONTROLLER_BUTTON_X: joy_X_button_state = 1; break; /*** X (Shift) ***/ + case SDL_CONTROLLER_BUTTON_B: joy_B_button_state = 1; break; /*** B (unused) ***/ case SDL_CONTROLLER_BUTTON_START: case SDL_CONTROLLER_BUTTON_BACK: @@ -3263,8 +3505,8 @@ void process_events() { case SDL_CONTROLLER_BUTTON_A: joy_AY_buttons_state = 0; break; /*** A (down) ***/ case SDL_CONTROLLER_BUTTON_Y: joy_AY_buttons_state = 0; break; /*** Y (up) ***/ - case SDL_CONTROLLER_BUTTON_X: joy_X_button_state = 0; break; /*** X (shift) ***/ - case SDL_CONTROLLER_BUTTON_B: joy_B_button_state = 0; break; /*** B (unused) ***/ + case SDL_CONTROLLER_BUTTON_X: joy_X_button_state = 0; break; /*** X (Shift) ***/ + case SDL_CONTROLLER_BUTTON_B: joy_B_button_state = 0; break; /*** B (unused) ***/ default: break; } @@ -3299,11 +3541,11 @@ void process_events() { #endif if (event.type == SDL_JOYBUTTONDOWN) { if (event.jbutton.button == SDL_JOYSTICK_BUTTON_Y) joy_AY_buttons_state = -1; // Y (up) - else if (event.jbutton.button == SDL_JOYSTICK_BUTTON_X) joy_X_button_state = -1; // X (shift) + else if (event.jbutton.button == SDL_JOYSTICK_BUTTON_X) joy_X_button_state = -1; // X (Shift) } else if (event.type == SDL_JOYBUTTONUP) { if (event.jbutton.button == SDL_JOYSTICK_BUTTON_Y) joy_AY_buttons_state = 0; // Y (up) - else if (event.jbutton.button == SDL_JOYSTICK_BUTTON_X) joy_X_button_state = 0; // X (shift) + else if (event.jbutton.button == SDL_JOYSTICK_BUTTON_X) joy_X_button_state = 0; // X (Shift) } break; @@ -3320,7 +3562,7 @@ void process_events() { memset(key_states, 0, sizeof(key_states)); } // Note: event.active.state can contain multiple flags or'ed. - // If the game is in full screen, and I switch away (alt-tab) and back, most of the screen will be black, until it is redrawn. + // If the game is in full screen, and I switch away (Alt+Tab) and back, most of the screen will be black, until it is redrawn. if ((event.active.state & SDL_APPACTIVE) && event.active.gain == 1) { update_screen(); } @@ -3338,6 +3580,7 @@ void process_events() { #endif case SDL_WINDOWEVENT_SIZE_CHANGED: window_resized(); + // fallthrough! //case SDL_WINDOWEVENT_MOVED: //case SDL_WINDOWEVENT_RESTORED: case SDL_WINDOWEVENT_EXPOSED: @@ -3447,12 +3690,12 @@ void __pascal far init_timer(int frequency) { #else if (global_timer != 0) { if (!SDL_RemoveTimer(global_timer)) { - sdlperror("SDL_RemoveTimer"); + sdlperror("init_timer: SDL_RemoveTimer"); } } global_timer = SDL_AddTimer(1000/frequency, timer_callback, NULL); if (global_timer == 0) { - sdlperror("SDL_AddTimer"); + sdlperror("init_timer: SDL_AddTimer"); quit(1); } #endif @@ -3479,13 +3722,13 @@ void __pascal far set_bg_attr(int vga_pal_index,int hc_pal_index) { if (vga_pal_index == 0) { /* if (SDL_SetAlpha(offscreen_surface, SDL_SRCALPHA, 0) != 0) { - sdlperror("SDL_SetAlpha"); + sdlperror("set_bg_attr: SDL_SetAlpha"); quit(1); } */ // Make the black pixels transparent. if (SDL_SetColorKey(offscreen_surface, SDL_TRUE, 0) != 0) { // SDL_SRCCOLORKEY old - sdlperror("SDL_SetColorKey"); + sdlperror("set_bg_attr: SDL_SetColorKey"); quit(1); } SDL_Rect rect = {0,0,0,0}; @@ -3496,7 +3739,7 @@ void __pascal far set_bg_attr(int vga_pal_index,int hc_pal_index) { //SDL_UpdateRect(onscreen_surface_, 0, 0, 0, 0); // First clear the screen with the color of the flash. if (safe_SDL_FillRect(onscreen_surface_, &rect, rgb_color) != 0) { - sdlperror("SDL_FillRect"); + sdlperror("set_bg_attr: SDL_FillRect"); quit(1); } //SDL_UpdateRect(onscreen_surface_, 0, 0, 0, 0); @@ -3505,7 +3748,7 @@ void __pascal far set_bg_attr(int vga_pal_index,int hc_pal_index) { } // Then draw the offscreen image onto it. if (SDL_BlitSurface(offscreen_surface, &rect, onscreen_surface_, &rect) != 0) { - sdlperror("SDL_BlitSurface"); + sdlperror("set_bg_attr: SDL_BlitSurface"); quit(1); } #ifdef USE_LIGHTING @@ -3522,12 +3765,12 @@ void __pascal far set_bg_attr(int vga_pal_index,int hc_pal_index) { //SDL_Flip(onscreen_surface_); /* if (SDL_SetAlpha(offscreen_surface, 0, 0) != 0) { - sdlperror("SDL_SetAlpha"); + sdlperror("set_bg_attr: SDL_SetAlpha"); quit(1); } */ if (SDL_SetColorKey(offscreen_surface, 0, 0) != 0) { - sdlperror("SDL_SetColorKey"); + sdlperror("set_bg_attr: SDL_SetColorKey"); quit(1); } } @@ -3643,11 +3886,11 @@ int __pascal far fade_in_frame(palette_fade_type far *palette_buffer) { int h = offscreen_surface->h; if (SDL_LockSurface(onscreen_surface_) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("fade_in_frame: SDL_LockSurface"); quit(1); } if (SDL_LockSurface(offscreen_surface) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("fade_in_frame: SDL_LockSurface"); quit(1); } int y,x; @@ -3764,11 +4007,11 @@ int __pascal far fade_out_frame(palette_fade_type far *palette_buffer) { int h = offscreen_surface->h; if (SDL_LockSurface(onscreen_surface_) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("fade_out_frame: SDL_LockSurface"); quit(1); } if (SDL_LockSurface(offscreen_surface) != 0) { - sdlperror("SDL_LockSurface"); + sdlperror("fade_out_frame: SDL_LockSurface"); quit(1); } int y,x; @@ -3826,6 +4069,7 @@ void set_chtab_palette(chtab_type* chtab, byte* colors, int n_colors) { // Color 0 of the palette data is not used, it is replaced by the background color. // Needed for correct alternate colors (v1.3) of level 8. scolors[0].r = scolors[0].g = scolors[0].b = 0; + scolors[0].a = SDL_ALPHA_TRANSPARENT; //printf("setcolors\n",i); for (i = 0; i < chtab->n_images; ++i) { @@ -3841,7 +4085,7 @@ void set_chtab_palette(chtab_type* chtab, byte* colors, int n_colors) { if (current_palette->ncolors < n_colors_to_be_set) n_colors_to_be_set = current_palette->ncolors; if (SDL_SetPaletteColors(current_palette, scolors, 0, n_colors_to_be_set) != 0) { - sdlperror("SDL_SetPaletteColors"); + sdlperror("set_chtab_palette: SDL_SetPaletteColors"); quit(1); } } diff --git a/src/types.h b/src/types.h index 24ba11a5..452e541c 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* SDLPoP, a port/conversion of the DOS game Prince of Persia. -Copyright (C) 2013-2020 Dávid Nagy +Copyright (C) 2013-2021 Dávid Nagy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,13 +24,16 @@ The authors of this program may be contacted at https://forum.princed.org #define STB_VORBIS_HEADER_ONLY #include "stb_vorbis.c" -#if !defined(_MSC_VER) -# include -# include -#else +//#if !defined(_MSC_VER) +//# include +//# include +//#else +// These headers for SDL seem to be the pkgconfig/meson standard as per the +// latest versions. If the old ones should be used, the ifdef must be used +// to compare versions. # include # include -#endif +//#endif #if SDL_BYTEORDER != SDL_LIL_ENDIAN //#error This program is not (yet) prepared for big endian CPUs, please contact the author. @@ -261,7 +264,7 @@ typedef struct chtab_type { word chtab_palette_bits; word has_palette_bits; // This is a variable-size array, with n_images elements. - image_type* far images[0]; + image_type* far images[]; } chtab_type; typedef struct full_image_type { @@ -427,7 +430,7 @@ SDL_COMPILE_TIME_ASSERT(dat_res_size, sizeof(dat_res_type) == 8); typedef struct dat_table_type { Uint16 res_count; - dat_res_type entries[0]; + dat_res_type entries[]; } dat_table_type; SDL_COMPILE_TIME_ASSERT(dat_table_size, sizeof(dat_table_type) == 2); @@ -435,7 +438,7 @@ typedef struct image_data_type { Uint16 height; Uint16 width; Uint16 flags; - byte data[0]; + byte data[]; } image_data_type; SDL_COMPILE_TIME_ASSERT(image_data_size, sizeof(image_data_type) == 6); #pragma pack(pop) @@ -493,7 +496,7 @@ typedef struct rawfont_type { short height_below_baseline; short space_between_lines; short space_between_chars; - word offsets[0]; + word offsets[]; } rawfont_type; SDL_COMPILE_TIME_ASSERT(rawfont_type, sizeof(rawfont_type) == 10); #pragma pack(pop) @@ -523,7 +526,7 @@ typedef struct note_type { SDL_COMPILE_TIME_ASSERT(note_type, sizeof(note_type) == 3); typedef struct speaker_type { // IBM word tempo; - note_type notes[0]; + note_type notes[]; } speaker_type; SDL_COMPILE_TIME_ASSERT(speaker_type, sizeof(speaker_type) == 2); @@ -532,7 +535,7 @@ typedef struct digi_type { // wave in 1.0 and 1.1 word sample_count; word unknown; byte sample_size; // =8 - byte samples[0]; + byte samples[]; } digi_type; SDL_COMPILE_TIME_ASSERT(digi_type, sizeof(digi_type) == 7); @@ -542,7 +545,7 @@ typedef struct digi_new_type { // wave in 1.3 and 1.4 (and PoP2) word sample_count; word unknown; word unknown2; - byte samples[0]; + byte samples[]; } digi_new_type; SDL_COMPILE_TIME_ASSERT(digi_new_type, sizeof(digi_new_type) == 9); @@ -570,7 +573,7 @@ typedef struct ogg_type { typedef struct converted_audio_type { int length; - short samples[0]; + short samples[]; } converted_audio_type; typedef struct sound_buffer_type { @@ -1139,7 +1142,7 @@ enum replay_seek_targets { }; #endif -#define COUNT(array) (sizeof(array)/sizeof(array[0])) +#define COUNT(array) ((int) (sizeof(array)/sizeof(array[0])) ) // These are or'ed with SDL_SCANCODE_* constants in last_key_scancode. enum key_modifiers { @@ -1212,6 +1215,9 @@ typedef struct fixes_options_type { byte fix_hidden_floors_during_flashing; byte fix_hang_on_teleport; byte fix_exit_door; + byte fix_quicksave_during_feather; + byte fix_caped_prince_sliding_through_gate; + byte fix_doortop_disabling_guard; } fixes_options_type; #define NUM_GUARD_SKILLS 12 @@ -1313,9 +1319,18 @@ typedef struct custom_options_type { auto_move_type demo_moves[25]; // prince on demo level auto_move_type shad_drink_move[8]; // shadow on level 5 + // speeds + byte base_speed; + byte fight_speed; + byte chomper_speed; + } custom_options_type; #pragma pack(pop) typedef struct directory_listing_type directory_listing_type; +#define BASE_FPS 60 + +#define FEATHER_FALL_LENGTH 18.75 + #endif