aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorVicent Marti2008-08-01 08:55:21 +0000
committerVicent Marti2008-08-01 08:55:21 +0000
commit7d797c878dd8f880a9e0045a93a3a7c44cb14c04 (patch)
treea5bcb451c87daf2934d132f161fff59e953c3283 /engines
parent169c4442d1264c0e807ae7bdccde928fb9e2e9b5 (diff)
parente89a01dc633ccdc821751fc076ae2d83c9315b33 (diff)
downloadscummvm-rg350-7d797c878dd8f880a9e0045a93a3a7c44cb14c04.tar.gz
scummvm-rg350-7d797c878dd8f880a9e0045a93a3a7c44cb14c04.tar.bz2
scummvm-rg350-7d797c878dd8f880a9e0045a93a3a7c44cb14c04.zip
Merged revisions 32701,32705,32727-32728,32730-32733,32737-32738,32742,32744-32745,32747,32750-32759,32762-32764,32769,32777,32783,32785-32786,32789-32791,32798-32799,32801-32807,32809-32812,32816-32817,32819-32821,32823-32830,32832-32836,32838-32844,32846-32850,32852-32854,32858-32859,32865-32868,32873-32874,32879,32883,32895,32899,32902-32904,32910-32912,32923-32924,32930-32931,32938,32940,32948-32949,32951,32960-32964,32966-32970,32972-32974,32976,32978,32983,32986-32990,32992,32994,33002-33004,33006-33007,33009-33010,33014,33017,33021-33023,33030,33033,33052-33053,33056-33058,33061-33064,33068,33070,33072,33075,33078-33079,33083,33086-33087,33089,33094-33096,33098-33099,33104,33108-33109,33114-33117,33120,33135-33146,33160,33162,33165,33167-33169,33188-33189,33191-33193,33196,33198,33202-33203,33206,33210,33212,33218-33220,33222,33224-33226,33229-33243,33246,33248-33250,33252,33258-33261,33263,33266,33270,33272-33283,33285,33287-33290,33295-33298,33321,33325-33330,33332-33335,33337-33340,33342,33345,33347,33349-33350,33352-33357,33359-33367,33369-33371,33373,33375-33377,33379-33380,33383-33385,33387-33389,33392-33394,33400-33402,33404-33405,33407-33410,33412-33416,33418-33419,33425-33427,33432,33436-33438,33444,33446,33452-33453,33455-33459,33463-33464,33466-33471,33473-33474,33478 via svnmerge from
https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk ................ r32701 | Tanoku | 2008-06-14 16:44:06 +0200 (Sat, 14 Jun 2008) | 1 line Updated MS Visual Studio project files for MusicPlugin. ................ r32705 | sev | 2008-06-15 09:15:58 +0200 (Sun, 15 Jun 2008) | 2 lines Make null plugin compilable ................ r32727 | thebluegr | 2008-06-17 20:27:03 +0200 (Tue, 17 Jun 2008) | 1 line Removed duplicate code ................ r32728 | thebluegr | 2008-06-17 23:52:58 +0200 (Tue, 17 Jun 2008) | 1 line Possible fix for bug #1979086 - "DRASCULA: Wrong language detection(?) and crash" ................ r32730 | buddha_ | 2008-06-18 05:31:13 +0200 (Wed, 18 Jun 2008) | 1 line Fixed a small discrepancy in Delphine unpacker's command 00b's documentation (Parameter range is 1..8, not 1..9). ................ r32731 | john_doe | 2008-06-18 13:01:51 +0200 (Wed, 18 Jun 2008) | 4 lines - Fixed sprite drawing in Rodney's Funscreen - Handle mouse button up events and event number fixes in MadeEngine::handleEvents() - Use milliseconds -> game ticks calculation based on Windows version of the original engine - "Rodney's Fun Screen" -> "Rodney's Funscreen" ................ r32732 | fingolfin | 2008-06-18 21:46:50 +0200 (Wed, 18 Jun 2008) | 1 line Setting svn:ignore for backends/fs/wii ................ r32733 | fingolfin | 2008-06-18 23:02:52 +0200 (Wed, 18 Jun 2008) | 1 line Renamed M4Surface::empty() to clear() (two reason: empty is not a verb, and in class String it is used for a bool property) ................ r32737 | drmccoy | 2008-06-19 18:27:49 +0200 (Thu, 19 Jun 2008) | 2 lines Only try playing object videos when the game version supports that (only Woodruff, for now) ................ r32738 | drmccoy | 2008-06-20 00:54:17 +0200 (Fri, 20 Jun 2008) | 2 lines Fixed a palette issue in Lost in Time ................ r32742 | cpage88 | 2008-06-20 22:20:46 +0200 (Fri, 20 Jun 2008) | 117 lines Merged revisions 31992,32088,32094,32129,32203,32208,32219,32236,32329,32332-32333,32357,32504,32519,32525,32566,32578,32641-32642,32673,32675,32677,32679-32680,32718-32719,32721,32739 via svnmerge from https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/branches/gsoc2008-rtl ........ r31992 | cpage88 | 2008-05-10 18:44:46 -0500 (Sat, 10 May 2008) | 1 line Modified game loop to return to launcher, codeblocks layouts update 1.5->1.6 ........ r32088 | cpage88 | 2008-05-13 11:09:57 -0500 (Tue, 13 May 2008) | 1 line used memmove() instead of memcpy() to fix memory overlap error ........ r32094 | cpage88 | 2008-05-13 17:59:19 -0500 (Tue, 13 May 2008) | 1 line Fixed memory leak by deallocating memory used by window->iconPtr ........ r32129 | cpage88 | 2008-05-14 18:26:32 -0500 (Wed, 14 May 2008) | 1 line Fixed memory leak when returning to launcher in AGOS engine by properly creating and deleting midi driver pointer ........ r32203 | cpage88 | 2008-05-20 16:40:53 -0500 (Tue, 20 May 2008) | 1 line AGOS Engine: Began implementation for a new quit event which will cleanly return to the launcher. This replaces the old shutdown() method within delay() ........ r32208 | cpage88 | 2008-05-21 13:52:27 -0500 (Wed, 21 May 2008) | 1 line AGOS: Fixed two memory leaks when returning to the launcher (_mouseData and _zoneBuffers) ........ r32219 | cpage88 | 2008-05-22 18:40:36 -0500 (Thu, 22 May 2008) | 1 line AGOS: Fixed a memory leak from earlier in a better way ........ r32236 | cpage88 | 2008-05-23 19:08:13 -0500 (Fri, 23 May 2008) | 1 line AGI: Fixed two memory leaks when returning to launcher from AGI Engine ........ r32329 | cpage88 | 2008-05-27 15:15:36 -0500 (Tue, 27 May 2008) | 1 line AGI: Modified AGI Engine to shutdown without using system->quit ........ r32332 | cpage88 | 2008-05-27 18:26:48 -0500 (Tue, 27 May 2008) | 1 line CINE: Fixed two memory leaks when shutting down the CINE engine ........ r32333 | cpage88 | 2008-05-27 19:52:45 -0500 (Tue, 27 May 2008) | 1 line CINE: Initialize exitEngine to 0 in mainLoop() to allow replay after returning to the launcher ........ r32357 | cpage88 | 2008-05-28 18:28:11 -0500 (Wed, 28 May 2008) | 1 line LURE: Fixed some memory leaks when returning to the launcher from the LURE engine ........ r32504 | cpage88 | 2008-06-02 16:08:49 -0500 (Mon, 02 Jun 2008) | 1 line PARA: Got rid of calls to system->quit() so that the Parallaction engine can return to the launcher. Also fixed a couple of memory leaks. ........ r32519 | cpage88 | 2008-06-03 13:24:54 -0500 (Tue, 03 Jun 2008) | 1 line PARA: Fixed some memory leaks ........ r32525 | cpage88 | 2008-06-04 00:21:47 -0500 (Wed, 04 Jun 2008) | 1 line QUEEN: Modified engine to use a quit flag instead of system->quit() in order to return to the launcher ........ r32566 | cpage88 | 2008-06-05 19:34:47 -0500 (Thu, 05 Jun 2008) | 1 line TOUCHE: Fixed a memory leak ........ r32578 | cpage88 | 2008-06-06 11:40:39 -0500 (Fri, 06 Jun 2008) | 1 line SKY: Fixed a memory leak in SKY engine ........ r32641 | cpage88 | 2008-06-09 17:26:05 -0500 (Mon, 09 Jun 2008) | 1 line TOUCHE: Reverted some comments that were used for testing purposes ........ r32642 | cpage88 | 2008-06-09 18:15:17 -0500 (Mon, 09 Jun 2008) | 1 line Implemented popAllCursors() in CursorManager to ensure that all unnecessary cursors are removed from the cursor stack when returning to the launcher ........ r32673 | cpage88 | 2008-06-12 11:58:02 -0500 (Thu, 12 Jun 2008) | 1 line SAGA: Fixed memory leaks in the SAGA engine ........ r32675 | cpage88 | 2008-06-12 13:11:09 -0500 (Thu, 12 Jun 2008) | 1 line LURE: Fixed memory leaks in the LURE engine ........ r32677 | cpage88 | 2008-06-12 13:52:43 -0500 (Thu, 12 Jun 2008) | 1 line CINE: Fixed memory leaks in the CINE engine ........ r32679 | cpage88 | 2008-06-12 14:34:32 -0500 (Thu, 12 Jun 2008) | 1 line AGOS: Fixed a memory leak in the AGOS engine ........ r32680 | cpage88 | 2008-06-12 14:43:54 -0500 (Thu, 12 Jun 2008) | 1 line SCUMM: Fixed a memory leak in the SCUMM engine ........ r32718 | cpage88 | 2008-06-16 12:34:58 -0500 (Mon, 16 Jun 2008) | 1 line AGOS: Found a system->quit(), changed to _quit=true ........ r32719 | cpage88 | 2008-06-16 13:47:32 -0500 (Mon, 16 Jun 2008) | 1 line Fixed a problem where the function that I previously implemented, popAllCursors(), was causing the mouse cursor to disapear ........ r32721 | cpage88 | 2008-06-16 14:55:59 -0500 (Mon, 16 Jun 2008) | 1 line Cleaned up some unnecessary comments ........ r32739 | cpage88 | 2008-06-20 14:38:38 -0500 (Fri, 20 Jun 2008) | 1 line Reverting changes to codeblocks layouts made in revision 31992 ........ ................ r32744 | peres001 | 2008-06-22 07:42:22 +0200 (Sun, 22 Jun 2008) | 1 line Circular references between Zone/Animation and Command are now manually removed, to allow the objects - which are stored into SharedPtr's - to be deallocated. ................ r32745 | fingolfin | 2008-06-22 12:26:18 +0200 (Sun, 22 Jun 2008) | 1 line Disabled return to launcher on trunk again ................ r32747 | thebluegr | 2008-06-22 12:29:36 +0200 (Sun, 22 Jun 2008) | 1 line Updated NEWS about Drascula support ................ r32750 | athrxx | 2008-06-22 14:31:05 +0200 (Sun, 22 Jun 2008) | 1 line this should fix bug #1997149: KYRA2: no text in spellbook ................ r32751 | lordhoto | 2008-06-22 14:36:38 +0200 (Sun, 22 Jun 2008) | 2 lines Cleanup. ................ r32752 | lordhoto | 2008-06-22 14:39:40 +0200 (Sun, 22 Jun 2008) | 2 lines Update comment. ................ r32753 | athrxx | 2008-06-22 14:41:46 +0200 (Sun, 22 Jun 2008) | 1 line missed this in last commit (bug fix for #1997149) ................ r32754 | lordhoto | 2008-06-22 14:43:32 +0200 (Sun, 22 Jun 2008) | 2 lines Cleanup. ................ r32755 | lordhoto | 2008-06-22 15:26:22 +0200 (Sun, 22 Jun 2008) | 2 lines Fixed getTotalPlayTime implementation for MP3InputStream. ................ r32756 | peres001 | 2008-06-22 16:31:45 +0200 (Sun, 22 Jun 2008) | 1 line Fixed leak when loading sounds for Amiga version of Nippon Safes. ................ r32757 | peres001 | 2008-06-22 16:46:08 +0200 (Sun, 22 Jun 2008) | 1 line Added constructor and destructor to Dialogue, thus fixing a long standing leak. ................ r32758 | thebluegr | 2008-06-22 19:36:14 +0200 (Sun, 22 Jun 2008) | 1 line Fixed 2 MSVC warnings (potentially undefined behavior and possibly uninitialized variable used) ................ r32759 | thebluegr | 2008-06-22 19:57:06 +0200 (Sun, 22 Jun 2008) | 1 line Turned off overzealous warning 4800 - "forcing value to bool 'true' or 'false' (performance warning)" ................ r32762 | peres001 | 2008-06-24 15:21:22 +0200 (Tue, 24 Jun 2008) | 1 line Fix for bug #2001193. Character confirmation screen didn't appear and game crashed because too many strings were added to the draw list. ................ r32763 | lordhoto | 2008-06-24 15:59:48 +0200 (Tue, 24 Jun 2008) | 2 lines Added const to some static data. ................ r32764 | jvprat | 2008-06-24 16:23:26 +0200 (Tue, 24 Jun 2008) | 2 lines Fixed mktemp usage in BSD systems (as noted in bug #2000931) ................ r32769 | buddha_ | 2008-06-24 22:44:37 +0200 (Tue, 24 Jun 2008) | 7 lines Fixed opcodes: - 0xA0: o2_addGfxElementType20 (Was o2_addGfxElementA0) Implemented opcodes: - 0xA1: o2_removeGfxElementType20 (Was o2_removeGfxElementA0) - 0xA2: o2_addGfxElementType21 (Was o2_opA2) - 0xA3: o2_removeGfxElementType21 (Was o2_opA3) NOTE: Drawing of type 21 overlay elements isn't coded yet. ................ r32777 | thebluegr | 2008-06-25 10:36:07 +0200 (Wed, 25 Jun 2008) | 1 line Possible fix for (for the drascula engine) for bug #2001583 - "WINCE: CRUISE and DRASCULA engines can not be compiled" ................ r32783 | thebluegr | 2008-06-25 14:02:34 +0200 (Wed, 25 Jun 2008) | 1 line Added patch from bug report #2001189 - "DRASCULA: Wrong intro music in Spanish version" ................ r32785 | buddha_ | 2008-06-25 17:09:24 +0200 (Wed, 25 Jun 2008) | 5 lines Implemented opcode: - 0x8D: o2_op8D (Didn't come up with a descriptive name yet) Compares ranges of x, y and mask parameters between two objects. Possibly some kind of an intersection testing function? ................ r32786 | buddha_ | 2008-06-25 19:14:44 +0200 (Wed, 25 Jun 2008) | 4 lines Implemented opcode: - 0x82: o2_modifySeqListElement (Was o2_op82) Seeks a matching element from the seqList and modifies its values. ................ r32789 | buddha_ | 2008-06-25 20:51:44 +0200 (Wed, 25 Jun 2008) | 1 line Added FIXME about the broken implementation of opcode 0x9A (o2_wasZoneChecked). ................ r32790 | buddha_ | 2008-06-25 23:57:08 +0200 (Wed, 25 Jun 2008) | 5 lines Implemented support for zoneQuery (Operation Stealth specific). Fixed opcodes (related to zoneQuery): - 0x08: o1_checkCollision - 0x9A: o2_wasZoneChecked NOTE: Savegame support for the zoneQuery data is broken ................ r32791 | buddha_ | 2008-06-26 00:13:18 +0200 (Thu, 26 Jun 2008) | 1 line Fix for GCC warning (Warned about testing x >= 0 when x is unsigned and therefore the test is always true). ................ r32798 | cyx | 2008-06-26 12:12:12 +0200 (Thu, 26 Jun 2008) | 1 line moved midi driver object creation to MidiPlayer class (to match delete call) ................ r32799 | cyx | 2008-06-26 12:12:47 +0200 (Thu, 26 Jun 2008) | 1 line fix possible oob access ................ r32801 | buddha_ | 2008-06-26 17:16:15 +0200 (Thu, 26 Jun 2008) | 1 line Comments update. ................ r32802 | buddha_ | 2008-06-26 17:44:26 +0200 (Thu, 26 Jun 2008) | 1 line Comments update. ................ r32803 | anotherguest | 2008-06-26 18:51:02 +0200 (Thu, 26 Jun 2008) | 1 line Fixed Symbian buildsystem for new defines. Fixed ARM asm syntax for Symbian build. ................ r32804 | buddha_ | 2008-06-26 19:29:21 +0200 (Thu, 26 Jun 2008) | 4 lines Fixed opcode: - 0x83: o2_isSeqRunning (Should it be named o2_isSeqNotRunning?) -- Added previously missing test part -- Negated the result (It was backwards before!) ................ r32805 | anotherguest | 2008-06-26 20:31:33 +0200 (Thu, 26 Jun 2008) | 1 line Changed default paths ................ r32806 | anotherguest | 2008-06-26 20:45:46 +0200 (Thu, 26 Jun 2008) | 1 line Remove inclusion of .o in mmp.in ................ r32807 | athrxx | 2008-06-26 21:42:59 +0200 (Thu, 26 Jun 2008) | 3 lines - improved hof music support for fm-towns (driver for *.twn tracks) (still needs quite some work) - some PC-98 music support since it uses a very similar driver, but this can't be considered working yet) - Kyra 1 PC-98 music doen't work at all since I haven't figured out yet how to turn track numbers into the corresponding music file names (might require a hard coded track map) ................ r32809 | drmccoy | 2008-06-26 21:56:18 +0200 (Thu, 26 Jun 2008) | 2 lines Fixing compilation for me. There are still lots of "cast casts away constness" warnings, though ................ r32810 | athrxx | 2008-06-26 22:13:04 +0200 (Thu, 26 Jun 2008) | 1 line cleanup ................ r32811 | athrxx | 2008-06-26 22:30:43 +0200 (Thu, 26 Jun 2008) | 1 line more cleanup ................ r32812 | athrxx | 2008-06-26 22:43:23 +0200 (Thu, 26 Jun 2008) | 1 line fix bad const casts ................ r32816 | buddha_ | 2008-06-27 01:30:45 +0200 (Fri, 27 Jun 2008) | 1 line Implemented Operation Stealth's version of addOverlay(objectIndex, overlayType). ................ r32817 | john_doe | 2008-06-27 11:57:38 +0200 (Fri, 27 Jun 2008) | 2 lines - Fixed umlauts in printText - Don't exit when a pmv video couldn't be found ................ r32819 | athrxx | 2008-06-28 15:13:37 +0200 (Sat, 28 Jun 2008) | 2 lines - HOF: bug fix for music driver - KYRA1 PC98: fix music file selection ................ r32820 | peres001 | 2008-06-28 15:31:58 +0200 (Sat, 28 Jun 2008) | 1 line Added comment for fix for bug #2001193. ................ r32821 | athrxx | 2008-06-28 15:40:03 +0200 (Sat, 28 Jun 2008) | 1 line hof: remove debug code ................ r32823 | joostp | 2008-06-28 15:53:39 +0200 (Sat, 28 Jun 2008) | 2 lines add getFilesystemFactory() method to null backend ................ r32824 | fingolfin | 2008-06-28 16:14:16 +0200 (Sat, 28 Jun 2008) | 1 line Removed OSystem::getFilesystemFactory() default implentation, as announced ................ r32825 | fingolfin | 2008-06-28 17:00:27 +0200 (Sat, 28 Jun 2008) | 1 line Removed dead X11 backend ................ r32826 | fingolfin | 2008-06-28 17:13:54 +0200 (Sat, 28 Jun 2008) | 1 line Removed obsolete ::clearSoundCallback() code ................ r32827 | fingolfin | 2008-06-28 17:27:40 +0200 (Sat, 28 Jun 2008) | 1 line Doxygenified a comment ................ r32828 | fingolfin | 2008-06-28 17:28:29 +0200 (Sat, 28 Jun 2008) | 1 line Patch ##1956946 (Audio::Mixer internal API revision) with some tweaks ................ r32829 | athrxx | 2008-06-28 17:36:50 +0200 (Sat, 28 Jun 2008) | 1 line - implement music fading for Hof FM-Towns ................ r32830 | eriktorbjorn | 2008-06-28 18:00:04 +0200 (Sat, 28 Jun 2008) | 2 lines Fixed warning. (Hopefully without breaking anything.) ................ r32832 | marcus_c | 2008-06-29 00:16:51 +0200 (Sun, 29 Jun 2008) | 1 line Set $(DEPDIR). ................ r32833 | peres001 | 2008-06-29 11:30:32 +0200 (Sun, 29 Jun 2008) | 1 line Changed all remaining code to use the GfxObj class to keep frames data. This allows for more uniform processing during rendering, and also fixes the display of dialogue faces for BRA. ................ r32834 | peres001 | 2008-06-29 11:56:44 +0200 (Sun, 29 Jun 2008) | 1 line Merged the three render lists (for animations, doors and objects) into a single one. ................ r32835 | djwillis | 2008-06-29 12:16:20 +0200 (Sun, 29 Jun 2008) | 1 line Small GP2X tidy (mostly svn:executable on scripts) and fixes needed to reflect "Patch ##1956946 (Audio::Mixer internal API revision)" ................ r32836 | marcus_c | 2008-06-29 13:51:47 +0200 (Sun, 29 Jun 2008) | 1 line New Mixer API. ................ r32838 | marcus_c | 2008-06-29 14:10:38 +0200 (Sun, 29 Jun 2008) | 1 line Updated to use new EngineMan.detectGames() API. ................ r32839 | knakos | 2008-06-29 16:40:41 +0200 (Sun, 29 Jun 2008) | 1 line finish up new mixer changes ................ r32840 | knakos | 2008-06-29 16:41:44 +0200 (Sun, 29 Jun 2008) | 1 line fix some quirks of the newer build system ................ r32841 | athrxx | 2008-06-29 17:25:45 +0200 (Sun, 29 Jun 2008) | 2 lines - some more work on the Hof FM-Towns/PC98 music driver - move channels to a separate class ................ r32842 | athrxx | 2008-06-29 17:59:35 +0200 (Sun, 29 Jun 2008) | 1 line cleanup ................ r32843 | athrxx | 2008-06-29 18:07:29 +0200 (Sun, 29 Jun 2008) | 1 line fix warning ................ r32844 | knakos | 2008-06-29 18:58:27 +0200 (Sun, 29 Jun 2008) | 1 line adding a fixme ................ r32846 | joostp | 2008-06-30 01:36:44 +0200 (Mon, 30 Jun 2008) | 2 lines changes required for new mixer API + implement getMillis() and delayMillis() using gettimeofday() and usleep() resp. ................ r32847 | peres001 | 2008-06-30 03:36:50 +0200 (Mon, 30 Jun 2008) | 1 line Small cleanup/shuffling of Gfx code. ................ r32848 | buddha_ | 2008-06-30 05:33:08 +0200 (Mon, 30 Jun 2008) | 1 line Implemented resetGfxEntityEntry and made it used where appropriate (The function wasn't very easy to reverse engineer so it may have flaws still, but let's hope it doesn't ;-)). ................ r32849 | thebluegr | 2008-06-30 10:46:20 +0200 (Mon, 30 Jun 2008) | 1 line Updated MSVC project files for commit #32828 ................ r32850 | buddha_ | 2008-06-30 19:24:23 +0200 (Mon, 30 Jun 2008) | 1 line Fixed addAni (A test before using resetGfxEntityEntry was incorrect). Also added comments and checked that most 8-bit values used in this function are used as signed integers. ................ r32852 | buddha_ | 2008-06-30 20:15:34 +0200 (Mon, 30 Jun 2008) | 1 line Removed TODO from checkCollision: Updating zoneQuery each time checkCollision is called seems to be fine. ................ r32853 | anotherguest | 2008-06-30 21:10:32 +0200 (Mon, 30 Jun 2008) | 1 line del instead of rm command ................ r32854 | athrxx | 2008-06-30 23:55:08 +0200 (Mon, 30 Jun 2008) | 2 lines - this fixes Hof PC98 music initialization - music sounds exactly like FM-Towns for now ................ r32858 | lordhoto | 2008-07-01 01:39:56 +0200 (Tue, 01 Jul 2008) | 2 lines Added support for Spanish fan translation of kyra3 (See fr #1994040 "KYRA3: Add support for Spanish fan translation"). ................ r32859 | lordhoto | 2008-07-01 01:44:33 +0200 (Tue, 01 Jul 2008) | 2 lines Fix game flags for detection entries of installed kyra3 versions. ................ r32865 | fingolfin | 2008-07-01 12:33:25 +0200 (Tue, 01 Jul 2008) | 1 line Fixed unitialized variables ................ r32866 | dreammaster | 2008-07-01 13:46:29 +0200 (Tue, 01 Jul 2008) | 1 line Made corrections to the Italian strings ................ r32867 | dreammaster | 2008-07-01 13:48:06 +0200 (Tue, 01 Jul 2008) | 1 line New lure.dat with corrections to the Italian strings ................ r32868 | fingolfin | 2008-07-01 16:51:44 +0200 (Tue, 01 Jul 2008) | 1 line Reverted accidental commit of Tinsel changes in engines.mk ................ r32873 | peres001 | 2008-07-02 03:41:08 +0200 (Wed, 02 Jul 2008) | 2 lines - Changed labels to be GfxObj's, thus removing the Label object altogether. - Changed Item's to be almost GfxObj's, since ownership and destruction of underlying resource is an issue here (got to think some more about it). ................ r32874 | buddha_ | 2008-07-02 06:31:50 +0200 (Wed, 02 Jul 2008) | 5 lines Fully implemented processSeqListElement - Added parts that were missing and fixed a couple of errors -- One test was backwards and a global variable was written to when it shouldn't have been Added global variable inputVar0 that's used in processSeqListElement NOTE: inputVar0 isn't updated anywhere yet, so that's a TODO ................ r32879 | sev | 2008-07-03 10:44:29 +0200 (Thu, 03 Jul 2008) | 1 line Fix for bug #2008054: Parallaction engine doesn't compile under MSVC9 ................ r32883 | peres001 | 2008-07-03 12:31:25 +0200 (Thu, 03 Jul 2008) | 1 line Changed balloons to use GfxObj as well. Next step is to integrate balloons for BRA. ................ r32895 | drmccoy | 2008-07-03 18:25:59 +0200 (Thu, 03 Jul 2008) | 2 lines Fixing a crash when loading a save made within the cult/bargon building (bug #2005965) ................ r32899 | athrxx | 2008-07-03 23:09:07 +0200 (Thu, 03 Jul 2008) | 1 line minor fix for Towns/PC98 music ................ r32902 | peres001 | 2008-07-04 02:29:21 +0200 (Fri, 04 Jul 2008) | 2 lines - Moved dialogue balloon management code from Gfx to its own class - Added a class to draw balloons in BRA (still without text and with wrong placement) ................ r32903 | Kirben | 2008-07-04 02:35:39 +0200 (Fri, 04 Jul 2008) | 1 line Correct typo. ................ r32904 | buddha_ | 2008-07-04 11:38:03 +0200 (Fri, 04 Jul 2008) | 1 line Updated MSVC project files (Added Parallaction's balloons.cpp). ................ r32910 | sev | 2008-07-05 06:28:17 +0200 (Sat, 05 Jul 2008) | 2 lines Mention merge of Chris Page's memory leak plugging code. ................ r32911 | lordhoto | 2008-07-05 09:47:27 +0200 (Sat, 05 Jul 2008) | 2 lines Added spanish menu strings for spanish fan translation of Kyrandia 3. ................ r32912 | lordhoto | 2008-07-05 10:20:10 +0200 (Sat, 05 Jul 2008) | 2 lines Added support for Italian fan translation of Kyrandia 3. (see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3") ................ r32923 | marcus_c | 2008-07-06 00:53:17 +0200 (Sun, 06 Jul 2008) | 1 line Enable all engines. ................ r32924 | dhewg | 2008-07-06 14:04:19 +0200 (Sun, 06 Jul 2008) | 1 line adjustments to the recent mixer api changes ................ r32930 | eriktorbjorn | 2008-07-06 20:37:52 +0200 (Sun, 06 Jul 2008) | 4 lines Implemented Good Enough(TM) XMIDI looping. This is used by Kyrandia 2 (the loop hack is no longer needed, and has been removed), and will be used by Discworld. ................ r32931 | eriktorbjorn | 2008-07-06 21:25:32 +0200 (Sun, 06 Jul 2008) | 4 lines Fixed silly error. The NEXT_BREAK event should, of course, jump to the event *after* the FOR_LOOP event. Apart from simplifying things, this should allow limited number of repeats to work. ................ r32938 | drmccoy | 2008-07-07 15:01:54 +0200 (Mon, 07 Jul 2008) | 2 lines Don't let listSavefiles() search subdirectories. Files with the same name (in different directories) would cause duplicate entries for the same file. ................ r32940 | peres001 | 2008-07-07 16:51:27 +0200 (Mon, 07 Jul 2008) | 1 line Fixed regression after label code refactoring. ................ r32948 | thebluegr | 2008-07-07 21:40:43 +0200 (Mon, 07 Jul 2008) | 1 line Some fixes for warnings under GCC 2.95 ................ r32949 | lordhoto | 2008-07-07 22:40:35 +0200 (Mon, 07 Jul 2008) | 1 line Fix for bug #2012293 "KYRA: Bad Spanish String in menu". ................ r32951 | buddha_ | 2008-07-08 00:02:01 +0200 (Tue, 08 Jul 2008) | 1 line Fix for bathroom door opening crash in Operation Stealth's start. ................ r32960 | fingolfin | 2008-07-08 12:29:35 +0200 (Tue, 08 Jul 2008) | 1 line Remove reference to kPlainSoundType ................ r32961 | sev | 2008-07-08 13:18:44 +0200 (Tue, 08 Jul 2008) | 1 line Shut couple of MSVC warnings ................ r32962 | drmccoy | 2008-07-08 13:35:09 +0200 (Tue, 08 Jul 2008) | 2 lines Modified the EGA story image delay to work for the Mac versions as well ................ r32963 | drmccoy | 2008-07-08 13:36:52 +0200 (Tue, 08 Jul 2008) | 3 lines Added a Gob1 version supplied by raina in the forums. It does look like the Mac version with a DOS executable and stripped music to me, so let's hope the Mac level image workaround works there as well. ................ r32964 | sev | 2008-07-08 13:48:16 +0200 (Tue, 08 Jul 2008) | 1 line One more MSVC warning ................ r32966 | eriktorbjorn | 2008-07-08 18:25:39 +0200 (Tue, 08 Jul 2008) | 4 lines On reading some more about XMIDI, I believe the NEXT and BREAK variants of the controller are mutually exclusive cases, i.e. a BREAK simply means forget about the innermost loop, and continue as if nothing had happened. ................ r32967 | joostp | 2008-07-08 19:20:26 +0200 (Tue, 08 Jul 2008) | 2 lines changes for new Mixer API ................ r32968 | joostp | 2008-07-08 19:41:09 +0200 (Tue, 08 Jul 2008) | 2 lines Enable static engines -- time to start thinking about prx/plugin support! ................ r32969 | joostp | 2008-07-08 19:46:26 +0200 (Tue, 08 Jul 2008) | 2 lines set DEPDIR ................ r32970 | tramboi | 2008-07-09 04:19:57 +0200 (Wed, 09 Jul 2008) | 3 lines New configure flag --enable-profiling to compile and link with -pg (for gprof) ................ r32972 | peres001 | 2008-07-09 04:49:20 +0200 (Wed, 09 Jul 2008) | 1 line Added a couple of NULLity checks. ................ r32973 | fingolfin | 2008-07-09 12:42:47 +0200 (Wed, 09 Jul 2008) | 1 line cleanup / code formatting ................ r32974 | peres001 | 2008-07-09 12:52:46 +0200 (Wed, 09 Jul 2008) | 1 line Fixed regression introduced with GfxObj: the character sprite was sometimes removed from the rendering list. ................ r32976 | peres001 | 2008-07-09 15:27:09 +0200 (Wed, 09 Jul 2008) | 1 line Fixed leaks in NS and BRA. ................ r32978 | agent-q | 2008-07-09 18:50:23 +0200 (Wed, 09 Jul 2008) | 1 line DS: Backend changes for new mixer code ................ r32983 | peres001 | 2008-07-10 04:00:54 +0200 (Thu, 10 Jul 2008) | 1 line Fixed destruction of sprites in BRA. ................ r32986 | lordhoto | 2008-07-10 13:25:43 +0200 (Thu, 10 Jul 2008) | 2 lines Workaround for gcc 2.95 compiler bug. ................ r32987 | lordhoto | 2008-07-10 13:28:51 +0200 (Thu, 10 Jul 2008) | 2 lines Fixed mem leak in MIDI related code. ................ r32988 | lordhoto | 2008-07-10 14:05:38 +0200 (Thu, 10 Jul 2008) | 2 lines Added filename to unknown opcode/command warnings of EMC scripts. ................ r32989 | lordhoto | 2008-07-10 14:12:42 +0200 (Thu, 10 Jul 2008) | 2 lines Added filename to unkown command/opcode warnings for TIM scripts. ................ r32990 | lordhoto | 2008-07-10 14:14:00 +0200 (Thu, 10 Jul 2008) | 2 lines Typo. ................ r32992 | eriktorbjorn | 2008-07-10 18:19:17 +0200 (Thu, 10 Jul 2008) | 2 lines Fixed Kyra 3 detection regression. ................ r32994 | tramboi | 2008-07-10 20:01:54 +0200 (Thu, 10 Jul 2008) | 1 line Fixed a few warnings ................ r33002 | peres001 | 2008-07-11 14:55:08 +0200 (Fri, 11 Jul 2008) | 2 lines Fixed leak in sound code by explicitly deleting the midi driver. ................ r33003 | peres001 | 2008-07-11 15:06:28 +0200 (Fri, 11 Jul 2008) | 3 lines Moved program and command execution code out of the engine, into their own brand new classes. ................ r33004 | buddha_ | 2008-07-11 15:13:28 +0200 (Fri, 11 Jul 2008) | 1 line Update MSVC project files. ................ r33006 | peres001 | 2008-07-11 15:36:22 +0200 (Fri, 11 Jul 2008) | 1 line Cleanup. ................ r33007 | peres001 | 2008-07-11 17:07:13 +0200 (Fri, 11 Jul 2008) | 1 line Added a script (courtesy of salty-horse) to create/set properties for source files under version control by Subversion. Specifically, the mime-type, eol-style and keywords properties are handled. ................ r33009 | fingolfin | 2008-07-11 22:28:14 +0200 (Fri, 11 Jul 2008) | 1 line Don't use kPlainSoundType if you don't have to ................ r33010 | fingolfin | 2008-07-11 22:28:50 +0200 (Fri, 11 Jul 2008) | 1 line cleanup ................ r33014 | drmccoy | 2008-07-12 17:21:38 +0200 (Sat, 12 Jul 2008) | 3 lines Changed tricky variable access from pointers to a new class that minds endianess. This should fix a few regressions with BE games on LE systems and vice versa that I introduced when I changed how variables are stored (which was necessary to get Woodruff work on BE systems). ................ r33017 | tramboi | 2008-07-12 22:35:44 +0200 (Sat, 12 Jul 2008) | 2 lines Minor constness fix to help with aliasing ................ r33021 | peres001 | 2008-07-13 05:30:14 +0200 (Sun, 13 Jul 2008) | 1 line Properly implemented the OFF command. The new rendering order for graphics let this mistake finally surface. ................ r33022 | peres001 | 2008-07-13 05:39:42 +0200 (Sun, 13 Jul 2008) | 1 line Cleanup and improved debugging output for CommandExec::run() ................ r33023 | peres001 | 2008-07-13 08:27:31 +0200 (Sun, 13 Jul 2008) | 1 line Cleanup of walk code. ................ r33030 | athrxx | 2008-07-13 14:20:24 +0200 (Sun, 13 Jul 2008) | 1 line - fix for bug #2016965: KYRA: does not compile in MSVC71 ................ r33033 | peres001 | 2008-07-13 15:04:36 +0200 (Sun, 13 Jul 2008) | 1 line More refactoring of walk code. ................ r33052 | peres001 | 2008-07-14 02:13:31 +0200 (Mon, 14 Jul 2008) | 1 line Made sure characters are not removed from the rendering list during switches. ................ r33053 | peres001 | 2008-07-14 02:21:05 +0200 (Mon, 14 Jul 2008) | 1 line Fixed regression in walk code. Now standing frames are correctly selected when the character encounters an unexpected blocking object in his/her path. ................ r33056 | fingolfin | 2008-07-14 09:54:18 +0200 (Mon, 14 Jul 2008) | 1 line Fixed code formatting ................ r33057 | dreammaster | 2008-07-14 12:33:57 +0200 (Mon, 14 Jul 2008) | 1 line Fix for missing data in the savegame format that could result in not being able to talk to Goewin in the apothecary after restoring a savegame ................ r33058 | peres001 | 2008-07-14 15:35:43 +0200 (Mon, 14 Jul 2008) | 1 line Removed all labels from the rendering list to avoid random crashes after introduction is over. ................ r33061 | fingolfin | 2008-07-14 21:14:26 +0200 (Mon, 14 Jul 2008) | 1 line cleanup (and test for Marwan's branch.... ;) ................ r33062 | tramboi | 2008-07-14 22:34:31 +0200 (Mon, 14 Jul 2008) | 1 line Register spilling avoided in AGOS background drawing (and 2x unrolling) ................ r33063 | wjpalenstijn | 2008-07-14 23:00:39 +0200 (Mon, 14 Jul 2008) | 1 line Don't draw scumm saveload dialog while reflowing layout, as that would use uninitialized values ................ r33064 | wjpalenstijn | 2008-07-14 23:04:42 +0200 (Mon, 14 Jul 2008) | 1 line remove accidentally committed debugging code; fix shadowing warning ................ r33068 | buddha_ | 2008-07-15 01:10:51 +0200 (Tue, 15 Jul 2008) | 8 lines Fix for bug #2016647 (FW: crash with italian amiga version). - Consists of a workaround for a script bug that used local variable 251 when it should've used global variable 251. - Also added a fix for a crash when failing copy protection in Amiga or Atari ST versions of Future Wars. NOTE: That any of the Amiga or Atari ST versions of Future Wars haven't crashed right in the beginning before seems like plain luck because accessing local variable 251 is out of bounds! ................ r33070 | fingolfin | 2008-07-15 12:47:24 +0200 (Tue, 15 Jul 2008) | 1 line config.log and tmp files should be put into the configure (= current) dir, not the source dir ................ r33072 | peres001 | 2008-07-15 12:59:58 +0200 (Tue, 15 Jul 2008) | 1 line Made frame unpacking buffer dynamic (this frees some BSS space). ................ r33075 | fingolfin | 2008-07-15 19:13:06 +0200 (Tue, 15 Jul 2008) | 1 line Implemented audio double buffering (for now OSX only) ................ r33078 | cyx | 2008-07-15 22:26:12 +0200 (Tue, 15 Jul 2008) | 1 line fix bug #1995042: stop previous sfx playback when starting a new sfx (matches original dos code). Also removed the "sound skipping" hack in final bam scene. ................ r33079 | cyx | 2008-07-15 22:31:11 +0200 (Tue, 15 Jul 2008) | 2 lines fix bug #1876741: changed .SB playback rate to 11840Hz (matches dos game) ................ r33083 | tramboi | 2008-07-16 11:08:44 +0200 (Wed, 16 Jul 2008) | 3 lines Split the drawVertImage function in the agos engine to make it clearer and easier to profile ................ r33086 | peres001 | 2008-07-17 02:38:11 +0200 (Thu, 17 Jul 2008) | 1 line Fixed regression bug in dialogue, which de-facto allowed user to skip in-game protection. ................ r33087 | buddha_ | 2008-07-17 09:13:41 +0200 (Thu, 17 Jul 2008) | 1 line Patch #2019455: Patch for reducing the BSS size of Cine engine. ................ r33089 | fingolfin | 2008-07-17 17:56:24 +0200 (Thu, 17 Jul 2008) | 1 line Committing PS2 changes on behalf of the other Max ;) ................ r33094 | eriktorbjorn | 2008-07-18 06:16:00 +0200 (Fri, 18 Jul 2008) | 4 lines Don't crash if you try to use music file #2 as music file #1. When the music wasn't found, it would close the file even if something else was already playing from it. (Some music is in both files.) ................ r33095 | dreammaster | 2008-07-18 11:36:49 +0200 (Fri, 18 Jul 2008) | 2 lines In OSystem_SDL::closeMixer moved the call to SDL_CloseAudio to before the deletion of the _mixer variable in to fix an assert that was being generated in OSystem_SDL::mixCallback ................ r33096 | buddha_ | 2008-07-18 16:01:53 +0200 (Fri, 18 Jul 2008) | 1 line Added savefile position comments to savegame loading routine (Helpful for debugging the formats). ................ r33098 | anotherguest | 2008-07-18 21:02:40 +0200 (Fri, 18 Jul 2008) | 1 line Symbian soundsmixer update. (Compile fix) ................ r33099 | anotherguest | 2008-07-18 22:40:48 +0200 (Fri, 18 Jul 2008) | 1 line Introduced cache for filereading to fix slowness in AGOS among others. ................ r33104 | anotherguest | 2008-07-19 00:07:52 +0200 (Sat, 19 Jul 2008) | 1 line Fixed typo in filehandling ................ r33108 | anotherguest | 2008-07-19 09:08:37 +0200 (Sat, 19 Jul 2008) | 1 line eof was not working properly with caching ................ r33109 | anotherguest | 2008-07-19 09:44:12 +0200 (Sat, 19 Jul 2008) | 1 line Enable support to turn off ALL variations. all variation is most often the one used ................ r33114 | fingolfin | 2008-07-19 23:42:31 +0200 (Sat, 19 Jul 2008) | 1 line Removed -Wundef from the default list of compiler flags, and changed PLUGIN_ENABLED_DYNAMIC to not use 'defined()', thus avoiding compiler problems on e.g. BeOS ................ r33115 | sunmax | 2008-07-20 03:30:47 +0200 (Sun, 20 Jul 2008) | 9 lines 1. Re-added "rpckbd" as in 0.11.0 2. Committed only its source, you will need to do a make inside its folder before being able to compile our beloved PlayStation2 backend. Enjoy, -max ................ r33116 | sunmax | 2008-07-20 03:40:08 +0200 (Sun, 20 Jul 2008) | 4 lines Added the empty "elf" folder for the PS2 scummvm binary, so that developers won't have to mkdir it by hand. ................ r33117 | sunmax | 2008-07-20 03:44:40 +0200 (Sun, 20 Jul 2008) | 5 lines Added "DEPDIR := .deps" in Makefile.ps2 Thanks Max [the other one] for pointing it out! ................ r33120 | knakos | 2008-07-20 13:15:29 +0200 (Sun, 20 Jul 2008) | 1 line workaround for bogus findfirstfile. kyra now starts up correctly ................ r33135 | fingolfin | 2008-07-20 18:27:12 +0200 (Sun, 20 Jul 2008) | 1 line cleanup ................ r33136 | fingolfin | 2008-07-20 18:28:06 +0200 (Sun, 20 Jul 2008) | 1 line Fixed nasty bug in findPlainGameDescriptor -- contrary to is documentation, it would not return 0 upon failure to find a match, but rather a (0,0) record. ................ r33137 | fingolfin | 2008-07-20 18:42:56 +0200 (Sun, 20 Jul 2008) | 1 line Fixed potential issue in Common::String when asserting a substring of a string X back to X (memcpy -> memmove); also added some other sanity checks, and merged some duplicate code into a new method String::initWithCStr ................ r33138 | fingolfin | 2008-07-20 18:47:34 +0200 (Sun, 20 Jul 2008) | 1 line Two new TODO/FIXME comments for class File ................ r33139 | fingolfin | 2008-07-20 18:47:52 +0200 (Sun, 20 Jul 2008) | 1 line New SeekableReadStream::readLine_NEW() method, closely modelled after fgets, w/o the line length limitations of the old eekableReadStream::readLine() (which it will replace, after the feature freeze has been lifted) ................ r33140 | fingolfin | 2008-07-20 18:52:25 +0200 (Sun, 20 Jul 2008) | 1 line Fix for bug #1971499: ALL: config manager crashes when reading too long lines ................ r33141 | knakos | 2008-07-20 18:55:51 +0200 (Sun, 20 Jul 2008) | 1 line modified patch #1882942 - optimize and kill code for really old platforms ................ r33142 | knakos | 2008-07-20 19:04:27 +0200 (Sun, 20 Jul 2008) | 1 line a little more cleanup ................ r33143 | marcus_c | 2008-07-20 19:56:43 +0200 (Sun, 20 Jul 2008) | 1 line Solaris tr does not like character classes in some locales. Use "C" locale. ................ r33144 | marcus_c | 2008-07-20 19:58:14 +0200 (Sun, 20 Jul 2008) | 1 line Deps now go in the .deps directory. ................ r33145 | athrxx | 2008-07-20 20:00:00 +0200 (Sun, 20 Jul 2008) | 1 line KYRA: disable incomplete PC-98 audio support for 0.12.0 release (use towns audio instead) ................ r33146 | eriktorbjorn | 2008-07-20 21:25:16 +0200 (Sun, 20 Jul 2008) | 2 lines Commented out some more PC-98 audio stuff, to avoid warnings. ................ r33160 | sev | 2008-07-21 07:09:29 +0200 (Mon, 21 Jul 2008) | 2 lines This is 0.13.0svn now ................ r33162 | peres001 | 2008-07-21 08:08:30 +0200 (Mon, 21 Jul 2008) | 1 line Massive refactoring of dialogue code, which is now implemented as a finite state machine. Related code in other files has been updated has well. ................ r33165 | peres001 | 2008-07-21 11:25:40 +0200 (Mon, 21 Jul 2008) | 1 line Some refactoring for tracking of floating labels. ................ r33167 | tramboi | 2008-07-21 12:13:44 +0200 (Mon, 21 Jul 2008) | 1 line Avoid branching in the inner loop of AGOS drawVertImageCompressed ................ r33168 | Kirben | 2008-07-21 12:32:20 +0200 (Mon, 21 Jul 2008) | 1 line Spacing. ................ r33169 | buddha_ | 2008-07-21 13:33:30 +0200 (Mon, 21 Jul 2008) | 1 line Silence a warning which complains about using an uninitialized variable. ................ r33188 | peres001 | 2008-07-22 11:00:39 +0200 (Tue, 22 Jul 2008) | 1 line Changed comment display code so that input polling is integrated into the main loop, instead of being performed in a blocking way from a separate routine. ................ r33189 | peres001 | 2008-07-22 11:12:10 +0200 (Tue, 22 Jul 2008) | 1 line Removed unneeded input code. ................ r33191 | peres001 | 2008-07-22 12:12:20 +0200 (Tue, 22 Jul 2008) | 1 line Fixed regression in dialogue code: certain commands weren't executed anymore after dialogue ended. ................ r33192 | buddha_ | 2008-07-22 12:15:58 +0200 (Tue, 22 Jul 2008) | 14 lines Fix for bug #2019355 (FW: broken compatibility with 0.11.1 saves): - Changed savegame loading related functions to use SeekableReadStream rather than InSaveFile so MemoryReadStream can be used transparently. - Fixed loadResourcesFromSave to load multiframe animations correctly and to load 0.11.0/0.11.1 Future Wars savegames which used a slightly different format. - Added a savegame format detector that tries to detect between the old Future Wars savegame format, the new one and a broken revision of the new one. - Changed makeLoad to first load the savegame fully into memory and only then handle it (If the savegame's packed then it's unpacked first). If the packed savegame can't tell its unpacked size (i.e. it's using zlib format) then we'll try to load up to 256kB of the savegame data. Thanks to wjp for his help with nailing this release critical bug. ................ r33193 | peres001 | 2008-07-22 12:17:19 +0200 (Tue, 22 Jul 2008) | 1 line Fix build. ................ r33196 | buddha_ | 2008-07-22 14:17:44 +0200 (Tue, 22 Jul 2008) | 1 line Fix CineSaveGameFormat enumeration's include order (Caused problems at least with GCC). ................ r33198 | peres001 | 2008-07-22 14:35:46 +0200 (Tue, 22 Jul 2008) | 1 line Merged inventory input code from different files. ................ r33202 | fingolfin | 2008-07-22 16:38:54 +0200 (Tue, 22 Jul 2008) | 1 line Fix warnings in CINE ................ r33203 | fingolfin | 2008-07-22 16:39:26 +0200 (Tue, 22 Jul 2008) | 1 line Added String::trim() method ................ r33206 | aquadran | 2008-07-22 17:24:39 +0200 (Tue, 22 Jul 2008) | 1 line shutup valgrind warning ................ r33210 | anotherguest | 2008-07-22 20:52:13 +0200 (Tue, 22 Jul 2008) | 1 line Fixed seek problem when cached data is used. ................ r33212 | anotherguest | 2008-07-22 21:09:10 +0200 (Tue, 22 Jul 2008) | 1 line Disable hashmemory pool for Symbian OS ................ r33218 | anotherguest | 2008-07-22 22:13:57 +0200 (Tue, 22 Jul 2008) | 2 lines AddedAdded MAD as default feature ................ r33219 | peres001 | 2008-07-23 03:07:39 +0200 (Wed, 23 Jul 2008) | 1 line More merging of input code. ................ r33220 | peres001 | 2008-07-23 04:01:15 +0200 (Wed, 23 Jul 2008) | 1 line Fixed mouse cursor when closing inventory. ................ r33222 | drmccoy | 2008-07-23 04:41:02 +0200 (Wed, 23 Jul 2008) | 2 lines More savegame-endianness fixes :/ ................ r33224 | peres001 | 2008-07-23 04:45:09 +0200 (Wed, 23 Jul 2008) | 1 line More decoupling of inventory code. ................ r33225 | peres001 | 2008-07-23 09:31:35 +0200 (Wed, 23 Jul 2008) | 1 line Removed useless event management code and made readInput() more general. ................ r33226 | peres001 | 2008-07-23 09:52:43 +0200 (Wed, 23 Jul 2008) | 1 line Removed the historical waitUntilLeftClick function and adapted code to use the more general readInput and waitForButtonEvent. ................ r33229 | Kirben | 2008-07-23 11:01:33 +0200 (Wed, 23 Jul 2008) | 1 line Update NEWS. ................ r33230 | fingolfin | 2008-07-23 11:02:47 +0200 (Wed, 23 Jul 2008) | 1 line Added Tinsel engine to main repos (no news item for it ON PURPOSE) ................ r33231 | dreammaster | 2008-07-23 11:28:23 +0200 (Wed, 23 Jul 2008) | 1 line Replaced the out of date file list in the MSVC8 project with the proper file list ................ r33232 | dreammaster | 2008-07-23 11:37:15 +0200 (Wed, 23 Jul 2008) | 1 line Added the Tinsel engine to the Scummvm MSVC8 Solution ................ r33233 | buddha_ | 2008-07-23 11:45:44 +0200 (Wed, 23 Jul 2008) | 1 line Updated rest of the MSVC project and solution files for Tinsel. ................ r33234 | fingolfin | 2008-07-23 11:53:29 +0200 (Wed, 23 Jul 2008) | 1 line Fix String::trim to work right for shared strings; augemented test cases to cover this ................ r33235 | fingolfin | 2008-07-23 12:27:24 +0200 (Wed, 23 Jul 2008) | 1 line Got rid of some typedefs ................ r33236 | fingolfin | 2008-07-23 12:29:37 +0200 (Wed, 23 Jul 2008) | 1 line Removed some dead code ................ r33237 | fingolfin | 2008-07-23 12:33:36 +0200 (Wed, 23 Jul 2008) | 1 line cleanup; removed const bNoScroll variable ................ r33238 | dreammaster | 2008-07-23 12:54:59 +0200 (Wed, 23 Jul 2008) | 1 line Added extra defines and include for Tinsel project ................ r33239 | buddha_ | 2008-07-23 14:15:02 +0200 (Wed, 23 Jul 2008) | 1 line Enable Tinsel also for MSVC 7 & 7.1 & 9 in addition to MSVC 8. ................ r33240 | buddha_ | 2008-07-23 16:19:31 +0200 (Wed, 23 Jul 2008) | 3 lines Renamed opcodes 0x49 and 0x68: - Opcode 0x49: setDefaultMenuColor2 -> setDefaultMenuBgColor - Opcode 0x68: setDefaultMenuColor -> setPlayerCommandPosY ................ r33241 | fingolfin | 2008-07-23 16:42:27 +0200 (Wed, 23 Jul 2008) | 1 line Moved POLYGON struct into polygon.cpp; got rid of some more typedefs ................ r33242 | fingolfin | 2008-07-23 16:43:41 +0200 (Wed, 23 Jul 2008) | 1 line Added svn:ignore attribute ................ r33243 | fingolfin | 2008-07-23 16:44:33 +0200 (Wed, 23 Jul 2008) | 1 line Added Tinsel to credits & NEWS ................ r33246 | fingolfin | 2008-07-23 18:33:53 +0200 (Wed, 23 Jul 2008) | 1 line Added convenience method String::makeUnique(); simplified String::operator=(char c); extended String unit tests ................ r33248 | fingolfin | 2008-07-23 18:49:45 +0200 (Wed, 23 Jul 2008) | 1 line Reorder stuff a little bit, moving private String methods together: cleanup ................ r33249 | fingolfin | 2008-07-23 18:55:52 +0200 (Wed, 23 Jul 2008) | 1 line TINSEL: Renamed CoroutineInstall back to ProcessCreate; got rid of yet another typedef; more cleanup ................ r33250 | fingolfin | 2008-07-23 19:01:42 +0200 (Wed, 23 Jul 2008) | 1 line cleanup ................ r33252 | sev | 2008-07-23 21:50:57 +0200 (Wed, 23 Jul 2008) | 2 lines Fix bug which was triggered by file named 'a' in current directory. ................ r33258 | peres001 | 2008-07-24 10:04:17 +0200 (Thu, 24 Jul 2008) | 1 line Can't test a SharedPtr for nullity! ................ r33259 | fingolfin | 2008-07-24 10:59:17 +0200 (Thu, 24 Jul 2008) | 1 line Moved scheduler / process managment code into a new class Scheduler ................ r33260 | peres001 | 2008-07-24 11:24:32 +0200 (Thu, 24 Jul 2008) | 3 lines * Moved end intro and end game sequences code to gui. * Rewrote all gui code to be run inside the main loop * Added code to avoid crashes when a scene with no standard background is drawn ................ r33261 | peres001 | 2008-07-24 11:42:44 +0200 (Thu, 24 Jul 2008) | 1 line Fixed leak in new gui code. ................ r33263 | fingolfin | 2008-07-24 12:31:37 +0200 (Thu, 24 Jul 2008) | 1 line cleanup ................ r33266 | eriktorbjorn | 2008-07-25 00:12:48 +0200 (Fri, 25 Jul 2008) | 2 lines Make sure _musicVolume and _sfxVolume are clipped to fit in a byte. ................ r33270 | peres001 | 2008-07-25 04:37:55 +0200 (Fri, 25 Jul 2008) | 3 lines * Merged old input management flags into a single mouse status variable. * Mouse is now displayed when it is needed, and hidden when it is not ;) ................ r33272 | peres001 | 2008-07-25 08:35:02 +0200 (Fri, 25 Jul 2008) | 1 line Converted BRA to work with the new menu approach. It is not yet well plugged-in as in NS, but it suffices for the moment. ................ r33273 | peres001 | 2008-07-25 10:27:44 +0200 (Fri, 25 Jul 2008) | 1 line Made character visible in BRA. ................ r33274 | fingolfin | 2008-07-25 11:05:04 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: Updating the palette should only require a call to OSystem::updateScreen and not a blit ................ r33275 | fingolfin | 2008-07-25 11:12:03 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: Get rid of Graphics::Surface class ................ r33276 | fingolfin | 2008-07-25 11:13:08 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: ActorTag & PolyTag abused a SCNHANDLE and some global enums to keep a trinary state -- fixed that by introducing a new enum HotSpotTag ................ r33277 | fingolfin | 2008-07-25 11:15:03 +0200 (Fri, 25 Jul 2008) | 1 line cleanup ................ r33278 | fingolfin | 2008-07-25 11:15:32 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: Got rid of NO_TAG (not used) ................ r33279 | fingolfin | 2008-07-25 11:16:33 +0200 (Fri, 25 Jul 2008) | 1 line Added Common::Rect::isEmpty() method ................ r33280 | fingolfin | 2008-07-25 11:17:47 +0200 (Fri, 25 Jul 2008) | 1 line More tinsel cleanup ................ r33281 | fingolfin | 2008-07-25 11:18:39 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: Fixed forgotten ClearScreen(0) call ................ r33282 | fingolfin | 2008-07-25 11:19:06 +0200 (Fri, 25 Jul 2008) | 1 line TINSEL: Got rid of PIMAGE, PINT_CONTEXT, PINV_OBJECT, PINV_DEF, PCONFBOX, PCONFINIT ................ r33283 | dreammaster | 2008-07-25 11:36:18 +0200 (Fri, 25 Jul 2008) | 1 line Fix to prevent attempt to delete a non-initialised object during game exit ................ r33285 | joostp | 2008-07-25 12:20:05 +0200 (Fri, 25 Jul 2008) | 2 lines change PPINIT struct to use ScummVM datatypes, so sizeof(PPINIT) is 28 on ppc/OSX as well. ................ r33287 | buddha_ | 2008-07-25 13:39:58 +0200 (Fri, 25 Jul 2008) | 1 line Update MSVC project files for Parallaction. ................ r33288 | drmccoy | 2008-07-25 14:59:46 +0200 (Fri, 25 Jul 2008) | 2 lines Fixed the inventory bug that's been reported in the forums ................ r33289 | peres001 | 2008-07-25 18:01:25 +0200 (Fri, 25 Jul 2008) | 2 lines * Changed walk code to use Common::Point instead of the clumsy WalkNode. * Changed walk code to use object copy instead of managing pointers. ................ r33290 | peres001 | 2008-07-25 18:08:10 +0200 (Fri, 25 Jul 2008) | 1 line Changed the remaining references to Nodes into Points. ................ r33295 | peres001 | 2008-07-26 04:09:50 +0200 (Sat, 26 Jul 2008) | 1 line BRA now parses path data from the scripts. ................ r33296 | peres001 | 2008-07-26 06:01:11 +0200 (Sat, 26 Jul 2008) | 2 lines * Added walk calculations to BRA (doesn't walk yet, though). * Adapted Character and Animation to handle both versions of the engine. ................ r33297 | peres001 | 2008-07-26 07:37:52 +0200 (Sat, 26 Jul 2008) | 1 line Cleanup. ................ r33298 | peres001 | 2008-07-26 07:56:39 +0200 (Sat, 26 Jul 2008) | 1 line More cleanup. ................ r33321 | Kirben | 2008-07-27 03:54:40 +0200 (Sun, 27 Jul 2008) | 1 line Fix buffer overflow in error message. ................ r33325 | peres001 | 2008-07-27 10:35:00 +0200 (Sun, 27 Jul 2008) | 1 line Doug from BRA can now walk in his hotel room. He still stops in bizarre poses, though. ................ r33326 | vinterstum | 2008-07-27 12:15:57 +0200 (Sun, 27 Jul 2008) | 1 line The iPhone backend is now (mostly) up to speed again, and works for firmware 2.0 ................ r33327 | Kirben | 2008-07-27 12:36:26 +0200 (Sun, 27 Jul 2008) | 1 line Search common directory, when loading frames and talks in Amiga verison of BRA. ................ r33328 | peres001 | 2008-07-27 12:37:54 +0200 (Sun, 27 Jul 2008) | 1 line Added rudimental support for location changes when walking through doors. The best part of this commit is that Doug now stops in a normal position. ................ r33329 | Kirben | 2008-07-27 12:43:15 +0200 (Sun, 27 Jul 2008) | 1 line Add music/sound loading in Amiga version of BRA. ................ r33330 | lordhoto | 2008-07-27 13:07:38 +0200 (Sun, 27 Jul 2008) | 1 line Fixed win32 plugin provider. ................ r33332 | lordhoto | 2008-07-27 14:05:40 +0200 (Sun, 27 Jul 2008) | 2 lines Fixed macro. ................ r33333 | lordhoto | 2008-07-27 14:09:10 +0200 (Sun, 27 Jul 2008) | 2 lines Fixed typo. ................ r33334 | lordhoto | 2008-07-27 14:12:40 +0200 (Sun, 27 Jul 2008) | 3 lines - Fixed fadePalette for HoF and Kyra3 - Fixed bug in wsaFrameAnimationStep ................ r33335 | peres001 | 2008-07-27 15:43:40 +0200 (Sun, 27 Jul 2008) | 1 line Inventory icons are now loaded correctly (not yet displayed). BRA doesn't crash anymore when pressing the right button. :) ................ r33337 | peres001 | 2008-07-27 16:21:16 +0200 (Sun, 27 Jul 2008) | 1 line Moved inventory cursor drawing code to InventoryRenderer. ................ r33338 | buddha_ | 2008-07-27 16:33:37 +0200 (Sun, 27 Jul 2008) | 1 line Cut savegame loading into smaller functional parts (resetEngine, loadPlainSave etc). ................ r33339 | buddha_ | 2008-07-27 16:36:53 +0200 (Sun, 27 Jul 2008) | 1 line Clear the confusing usage of NUM_MAX_VAR (It's 255 actually, not 256). ................ r33340 | anotherguest | 2008-07-27 20:22:23 +0200 (Sun, 27 Jul 2008) | 1 line Fix for ftell error when caching. HOF now starts properly ................ r33342 | sev | 2008-07-27 23:14:31 +0200 (Sun, 27 Jul 2008) | 2 lines Patch from bugreport #2020561: "MMNES : Incorrect detection (US/GB)" ................ r33345 | anotherguest | 2008-07-27 23:35:39 +0200 (Sun, 27 Jul 2008) | 1 line Fixed the Symbian default savepath, but adding the needed \ at the end. ................ r33347 | anotherguest | 2008-07-27 23:37:47 +0200 (Sun, 27 Jul 2008) | 1 line Fixed the Symbian default savepath, but adding the needed \ at the end and now as a string. ................ r33349 | buddha_ | 2008-07-28 00:50:36 +0200 (Mon, 28 Jul 2008) | 3 lines Added a preliminary saving routine for Operation Stealth (Disabled by default, needs more work still. WIP!). Added backgrounds' name saving (8 names in Operation Stealth instead of just 1 like in Future Wars). Added 256 color palette saving and restoring (One of the palettes isn't properly handled yet though). ................ r33350 | peres001 | 2008-07-28 04:56:17 +0200 (Mon, 28 Jul 2008) | 1 line Fixed constructor for WindowsFilesystemNode. On Windows, trailing slashes can be added only to directory names. ................ r33352 | peres001 | 2008-07-28 07:18:23 +0200 (Mon, 28 Jul 2008) | 3 lines * Changed Disk code in BRA to use FilesystemNode (duplication has become truly visible!). * Fixed Inventory items loading. ................ r33353 | peres001 | 2008-07-28 07:21:11 +0200 (Mon, 28 Jul 2008) | 1 line Some unneeded references slipped in with the last commit. ................ r33354 | Kirben | 2008-07-28 07:38:24 +0200 (Mon, 28 Jul 2008) | 1 line Fix compile. ................ r33355 | peres001 | 2008-07-28 08:06:35 +0200 (Mon, 28 Jul 2008) | 1 line Inventory is now properly rendered. Item selection is not yet working. ................ r33356 | Kirben | 2008-07-28 08:18:39 +0200 (Mon, 28 Jul 2008) | 1 line There is no mask or path directories for part0 of BRA (Amiga), so always check whether they exist. ................ r33357 | Kirben | 2008-07-28 09:20:55 +0200 (Mon, 28 Jul 2008) | 1 line Add basic support for running Amiga and PC demos of BRA. ................ r33359 | peres001 | 2008-07-28 10:25:06 +0200 (Mon, 28 Jul 2008) | 1 line Some instrumentation for script debugging. ................ r33360 | peres001 | 2008-07-28 10:25:52 +0200 (Mon, 28 Jul 2008) | 1 line Added support for text in BRA DOS demo. ................ r33361 | Kirben | 2008-07-28 10:44:14 +0200 (Mon, 28 Jul 2008) | 1 line Correct character name set by character location parser. ................ r33362 | buddha_ | 2008-07-28 10:44:49 +0200 (Mon, 28 Jul 2008) | 3 lines Made the savegame loading routine choose between loading a Future Wars or an Operation Stealth savegame format. Added a stub for loading the Operation Stealth's temporary savegame format (Not yet implemented). Made mouse cursor change to a disk icon when loading a savegame and back to normal after its done. ................ r33363 | peres001 | 2008-07-28 10:56:37 +0200 (Mon, 28 Jul 2008) | 1 line Added a post processing step to runScripts, so that Animation can be validated after buggy scripts have been executed. ................ r33364 | peres001 | 2008-07-28 11:00:00 +0200 (Mon, 28 Jul 2008) | 1 line Preliminary code for traps. ................ r33365 | buddha_ | 2008-07-28 12:09:00 +0200 (Mon, 28 Jul 2008) | 1 line Now detects temporary Operation Stealth savegame format and saves it. No loading yet. ................ r33366 | buddha_ | 2008-07-28 12:44:54 +0200 (Mon, 28 Jul 2008) | 1 line Cut Future Wars savegame loading routine into parts that can be reused when loading the Operation Stealth savegame format. ................ r33367 | buddha_ | 2008-07-28 12:54:53 +0200 (Mon, 28 Jul 2008) | 1 line Added remaining load functions needed for the Operation Stealth savegame format loading (loadSeqList and loadZoneQuery). Not used yet. ................ r33369 | peres001 | 2008-07-28 13:47:03 +0200 (Mon, 28 Jul 2008) | 1 line Tiny readability aid for parser code. ................ r33370 | peres001 | 2008-07-28 13:48:04 +0200 (Mon, 28 Jul 2008) | 1 line Implemented opcodes for picking up/dropping/opening/closing items. ................ r33371 | peres001 | 2008-07-28 13:50:36 +0200 (Mon, 28 Jul 2008) | 1 line Pick up/drop/open/close actions are now available in game. ................ r33373 | thebluegr | 2008-07-28 14:46:30 +0200 (Mon, 28 Jul 2008) | 1 line Cleanup ................ r33375 | peres001 | 2008-07-28 16:02:46 +0200 (Mon, 28 Jul 2008) | 1 line Fixed loading of static items. ................ r33376 | peres001 | 2008-07-28 16:22:44 +0200 (Mon, 28 Jul 2008) | 1 line Moved validation step from revision 33363, so that it is executed for all animations. ................ r33377 | peres001 | 2008-07-28 16:23:49 +0200 (Mon, 28 Jul 2008) | 1 line Yet another hack to deal with labels... Must rethink this crap from scratch. ................ r33379 | buddha_ | 2008-07-28 18:02:40 +0200 (Mon, 28 Jul 2008) | 6 lines Added loading of temporary Operation Stealth savegames. Needs testing! - Music related settings and adBgVar0 & adBgVar1 aren't loaded currently. Modified resetEngine to also reset more of the Operation Stealth specific variables. Added getter for background scrolling value. Changed additional background indices 1 & 2 from byte to uint16. Made savegame loading functions return !in.ioFailed() as return value instead of true as previously. ................ r33380 | buddha_ | 2008-07-28 18:46:20 +0200 (Mon, 28 Jul 2008) | 1 line Fixed crash when running Operation Stealth introduced in r33339 (There are actually 256 global variables although only 255 of them are saved and loaded from savegames. The last one is VAR_BYPASS_PROTECTION and it is written to in the mainLoop so that's why there was a crash). ................ r33383 | fingolfin | 2008-07-29 00:21:11 +0200 (Tue, 29 Jul 2008) | 1 line Fixing 'warning: comparison of unsigned expression < 0 is always false' ................ r33384 | peres001 | 2008-07-29 01:21:03 +0200 (Tue, 29 Jul 2008) | 1 line Enforcing use of nullZonePtr only for nulling out pointers, as it is useless for comparisons. ................ r33385 | fingolfin | 2008-07-29 02:02:06 +0200 (Tue, 29 Jul 2008) | 1 line SDL: Properly init vars related to mixer double buffering ................ r33387 | fingolfin | 2008-07-29 02:49:44 +0200 (Tue, 29 Jul 2008) | 1 line Changed output of --test-detector: multiple hits with same gameid now only are a warning, not a failure ................ r33388 | fingolfin | 2008-07-29 02:50:12 +0200 (Tue, 29 Jul 2008) | 1 line Changed advanced detector to *always* use the FSNode API for detection (i.e. killed second code path which used File::open trial&error directory 'scanning') ................ r33389 | fingolfin | 2008-07-29 02:54:28 +0200 (Tue, 29 Jul 2008) | 1 line BASE: in runGame, do not set addDefaultDirectory() the game path before invoking createInstance() -- detectors must use FSNode for detection, not rely on File::open ................ r33392 | peres001 | 2008-07-29 05:14:35 +0200 (Tue, 29 Jul 2008) | 2 lines * Fixed positioning of balloons and faces in BRA (dos, at least). * Adapted loading of faces. ................ r33393 | Kirben | 2008-07-29 06:00:07 +0200 (Tue, 29 Jul 2008) | 1 line Add check common directories, in loadScenery() for Amiga version of BRA. ................ r33394 | Kirben | 2008-07-29 06:06:10 +0200 (Tue, 29 Jul 2008) | 1 line Mask files don't always exist in Amiga version of BRA, in paricular NULL.msk. ................ r33400 | lordhoto | 2008-07-29 11:16:53 +0200 (Tue, 29 Jul 2008) | 2 lines Added a reset method to SharedPtr, which allows NULLifying it. ................ r33401 | lordhoto | 2008-07-29 11:23:54 +0200 (Tue, 29 Jul 2008) | 2 lines Formatting. ................ r33402 | peres001 | 2008-07-29 11:44:05 +0200 (Tue, 29 Jul 2008) | 1 line Added dialogue text rendering for BRA. ................ r33404 | buddha_ | 2008-07-29 12:13:53 +0200 (Tue, 29 Jul 2008) | 6 lines Rearranged parts of the Operation Stealth savegame loading routine. - Emulating the Future Wars savegame loading routine and hoping for the best. - Fixes an array out of bounds access when loading the global scripts. Now the loading crashes in the mainloop in processSeqList! But at least we got a bit farther this time. More fixing to come... ................ r33405 | peres001 | 2008-07-29 12:22:50 +0200 (Tue, 29 Jul 2008) | 2 lines * Added flexible verb configuration for both NS and BRA. * Objects can now be really opened and closed in BRA. ................ r33407 | buddha_ | 2008-07-29 14:56:32 +0200 (Tue, 29 Jul 2008) | 1 line Added a debug message to loadTempSaveOS's to check whether we loaded the whole savefile. Made objectStruct's clearing also clear x and y member variables in resetEngine. ................ r33408 | peres001 | 2008-07-29 14:59:55 +0200 (Tue, 29 Jul 2008) | 2 lines * Implemented pause/resume of command execution * Implemented command opcode MOVE (not the script instruction). ................ r33409 | buddha_ | 2008-07-29 15:44:14 +0200 (Tue, 29 Jul 2008) | 7 lines Added purgeSeqList function (Used in mainloop now). Let's see if this helps any... Renamed functions: * addScriptToList0 -> addScriptToGlobalScripts * executeList0 -> executeGlobalScripts * executeList1 -> executeObjectScripts * purgeList1 -> purgeObjectScripts (Also added a clarifying TODO to this function) * purgeList0 -> purgeGlobalScripts (Also added a clarifying TODO to this function) ................ r33410 | buddha_ | 2008-07-29 15:46:42 +0200 (Tue, 29 Jul 2008) | 1 line Make sure processSeqList and purgeSeqList are only called in the main loop when running Operation Stealth. Mostly a precaution as the seqList should be totally empty when running Future Wars as it doesn't use it. ................ r33412 | fingolfin | 2008-07-29 18:09:10 +0200 (Tue, 29 Jul 2008) | 1 line Changed class File (and derived classes) to only support read-only access; added a new class DumpFile for writing ................ r33413 | fingolfin | 2008-07-29 18:12:42 +0200 (Tue, 29 Jul 2008) | 1 line CONFIGMAN: Store domains in the order they were added ................ r33414 | fingolfin | 2008-07-29 18:16:15 +0200 (Tue, 29 Jul 2008) | 1 line Added convenience accessor method GameDescriptor::preferredtarget ................ r33415 | fingolfin | 2008-07-29 18:29:28 +0200 (Tue, 29 Jul 2008) | 1 line Mass detector: sort all newly detected games by target name before adding them to the config manager ................ r33416 | lordhoto | 2008-07-29 19:00:15 +0200 (Tue, 29 Jul 2008) | 2 lines Added documentation for the functions in algorithm.h. ................ r33418 | fingolfin | 2008-07-29 19:38:07 +0200 (Tue, 29 Jul 2008) | 1 line Set svn:ignore for tools/create_drascula ................ r33419 | fingolfin | 2008-07-29 19:42:19 +0200 (Tue, 29 Jul 2008) | 1 line Added two new classes, BufferedReadStream & BufferedSeekableReadStream, as proposed on scummvm-devel ................ r33425 | lordhoto | 2008-07-29 22:09:30 +0200 (Tue, 29 Jul 2008) | 2 lines Documentation for func.h. ................ r33426 | lordhoto | 2008-07-29 22:15:29 +0200 (Tue, 29 Jul 2008) | 2 lines Little fix for documentation. ................ r33427 | lordhoto | 2008-07-29 22:21:54 +0200 (Tue, 29 Jul 2008) | 3 lines - Formatting - Improved Functor#Mem::isValid implementations. ................ r33432 | peres001 | 2008-07-30 08:25:17 +0200 (Wed, 30 Jul 2008) | 1 line Reordered initialization lists to silence warning. ................ r33436 | fingolfin | 2008-07-30 09:39:41 +0200 (Wed, 30 Jul 2008) | 1 line Changed BufferedReadStream to not permanently decrease its buffer size at the end of a stream (this would fail when using BufferedSeekableReadStream and then seeking back from the end); this also fixes a bug which let you seek back beyond the start of a stream (not that we currently support that in other streams) ................ r33437 | peres001 | 2008-07-30 09:58:25 +0200 (Wed, 30 Jul 2008) | 2 lines * Unified implementation of flow control opcodes in NS and BRA * Simplified script execution loop and context ................ r33438 | thebluegr | 2008-07-30 10:23:04 +0200 (Wed, 30 Jul 2008) | 1 line Make sure that save game descriptions are 0-terminated ................ r33444 | buddha_ | 2008-07-30 13:03:52 +0200 (Wed, 30 Jul 2008) | 1 line Added some debug aids related to addAni and the processSeqList crashing. ................ r33446 | buddha_ | 2008-07-30 13:36:14 +0200 (Wed, 30 Jul 2008) | 1 line Debug printing a couple more relevant variables in addAni. ................ r33452 | peres001 | 2008-07-30 17:01:15 +0200 (Wed, 30 Jul 2008) | 1 line Reordered initialization order to kill a ton of warnings. ................ r33453 | fingolfin | 2008-07-30 17:16:57 +0200 (Wed, 30 Jul 2008) | 1 line Advanced detector: split out part of detectGame into a new function detectGameFilebased; some cleanup ................ r33455 | fingolfin | 2008-07-30 17:38:42 +0200 (Wed, 30 Jul 2008) | 1 line Simplified advanced detector file sys scanning code ................ r33456 | fingolfin | 2008-07-30 17:44:34 +0200 (Wed, 30 Jul 2008) | 1 line Revert my accidental commit of the OSystem changes (oops) ................ r33457 | fingolfin | 2008-07-30 17:48:16 +0200 (Wed, 30 Jul 2008) | 1 line Simplify/optimize/cleanup detectGameFilebased further ................ r33458 | peres001 | 2008-07-30 18:06:46 +0200 (Wed, 30 Jul 2008) | 1 line Fixed Win32 build, after Fingolfin's commits (probably because of the revert in revision 33456). ................ r33459 | fingolfin | 2008-07-30 18:26:38 +0200 (Wed, 30 Jul 2008) | 1 line This time properly reverted my accidental commits of the osystem&configman patch (I didn't mean to commit it in the first place, still waiting for any replies to my corresponding scummvm-devel mail). Sorry for messing up so badly ................ r33463 | lordhoto | 2008-07-31 12:47:15 +0200 (Thu, 31 Jul 2008) | 2 lines Committed slightly modified patch #2029395 "KYRA: Lands of Lore Intro + Character selection". ................ r33464 | lordhoto | 2008-07-31 12:52:29 +0200 (Thu, 31 Jul 2008) | 2 lines Removed debugging leftover. ................ r33466 | buddha_ | 2008-07-31 13:16:48 +0200 (Thu, 31 Jul 2008) | 1 line Update MSVC project files for Kyra Lands of Lore additions. ................ r33467 | peres001 | 2008-07-31 13:29:37 +0200 (Thu, 31 Jul 2008) | 1 line Set correct font for dialogues in BRA Amiga. ................ r33468 | peres001 | 2008-07-31 14:26:12 +0200 (Thu, 31 Jul 2008) | 1 line Changed Gfx::_backgroundInfo to be a pointer. This temporarily kills all z-buffering. ................ r33469 | peres001 | 2008-07-31 14:50:43 +0200 (Thu, 31 Jul 2008) | 1 line Made changing of background more flexible, in that the engine can now configure its BackgroundInfo before passing it to Gfx. ................ r33470 | lordhoto | 2008-07-31 15:36:13 +0200 (Thu, 31 Jul 2008) | 3 lines - Added Common::mem_fun_ref for object references instead of pointers. - Added simple tests for a little bit functionallity from common/func.h ................ r33471 | eriktorbjorn | 2008-07-31 15:45:58 +0200 (Thu, 31 Jul 2008) | 4 lines Applied my patch #2030058 ("Workaround for incorrectly compressed FotAQ"), and made a mention in NEWS that speech is played correctly now. Of course, we should still provide a correctly compressed version at some point. ................ r33473 | peres001 | 2008-07-31 16:20:51 +0200 (Thu, 31 Jul 2008) | 1 line Disabled masks in BRA Amiga, because the decoding is not known yet. ................ r33474 | peres001 | 2008-07-31 17:15:42 +0200 (Thu, 31 Jul 2008) | 2 lines * Removed references to the current _backgroundInfo from parser code. * Re-enabled masks (in BRA DOS). ................ r33478 | anotherguest | 2008-07-31 19:33:48 +0200 (Thu, 31 Jul 2008) | 1 line Enabled the correct features for standard builds ................ svn-id: r33486
Diffstat (limited to 'engines')
-rw-r--r--engines/agi/agi.cpp7
-rw-r--r--engines/agos/agos.cpp32
-rw-r--r--engines/agos/agos.h4
-rw-r--r--engines/agos/animation.cpp2
-rw-r--r--engines/agos/debug.cpp4
-rw-r--r--engines/agos/draw.cpp2
-rw-r--r--engines/agos/event.cpp6
-rw-r--r--engines/agos/gfx.cpp131
-rw-r--r--engines/agos/input.cpp4
-rw-r--r--engines/agos/intern.h1
-rw-r--r--engines/agos/saveload.cpp8
-rw-r--r--engines/agos/script.cpp9
-rw-r--r--engines/agos/script_e1.cpp4
-rw-r--r--engines/agos/script_s1.cpp4
-rw-r--r--engines/agos/subroutine.cpp4
-rw-r--r--engines/cine/anim.cpp74
-rw-r--r--engines/cine/anim.h57
-rw-r--r--engines/cine/bg.cpp2
-rw-r--r--engines/cine/bg.h3
-rw-r--r--engines/cine/bg_list.cpp4
-rw-r--r--engines/cine/bg_list.h2
-rw-r--r--engines/cine/cine.cpp5
-rw-r--r--engines/cine/cine.h8
-rw-r--r--engines/cine/gfx.cpp81
-rw-r--r--engines/cine/gfx.h12
-rw-r--r--engines/cine/main_loop.cpp39
-rw-r--r--engines/cine/object.cpp75
-rw-r--r--engines/cine/object.h10
-rw-r--r--engines/cine/pal.h2
-rw-r--r--engines/cine/part.cpp4
-rw-r--r--engines/cine/prc.cpp9
-rw-r--r--engines/cine/prc.h2
-rw-r--r--engines/cine/script.h30
-rw-r--r--engines/cine/script_fw.cpp88
-rw-r--r--engines/cine/script_os.cpp87
-rw-r--r--engines/cine/sound.cpp1
-rw-r--r--engines/cine/texte.cpp10
-rw-r--r--engines/cine/texte.h5
-rw-r--r--engines/cine/unpack.cpp2
-rw-r--r--engines/cine/various.cpp1385
-rw-r--r--engines/cine/various.h8
-rw-r--r--engines/cruise/cruise_main.h2
-rw-r--r--engines/cruise/volume.cpp4
-rw-r--r--engines/drascula/animation.cpp8
-rw-r--r--engines/drascula/drascula.h6
-rw-r--r--engines/drascula/graphics.cpp31
-rw-r--r--engines/drascula/palette.cpp8
-rw-r--r--engines/drascula/rooms.cpp16
-rw-r--r--engines/engines.mk5
-rw-r--r--engines/gob/dataio.cpp11
-rw-r--r--engines/gob/dataio.h6
-rw-r--r--engines/gob/detection.cpp13
-rw-r--r--engines/gob/driver_vga.cpp2
-rw-r--r--engines/gob/gob.cpp9
-rw-r--r--engines/gob/gob.h6
-rw-r--r--engines/gob/goblin.cpp330
-rw-r--r--engines/gob/goblin.h103
-rw-r--r--engines/gob/goblin_v2.cpp2
-rw-r--r--engines/gob/inter.cpp24
-rw-r--r--engines/gob/inter.h2
-rw-r--r--engines/gob/inter_v1.cpp173
-rw-r--r--engines/gob/inter_v2.cpp12
-rw-r--r--engines/gob/mult.cpp26
-rw-r--r--engines/gob/mult.h9
-rw-r--r--engines/gob/mult_v1.cpp33
-rw-r--r--engines/gob/mult_v2.cpp26
-rw-r--r--engines/gob/saveload.cpp49
-rw-r--r--engines/gob/saveload.h29
-rw-r--r--engines/gob/saveload_v2.cpp20
-rw-r--r--engines/gob/saveload_v3.cpp34
-rw-r--r--engines/gob/saveload_v4.cpp24
-rw-r--r--engines/gob/scenery.cpp4
-rw-r--r--engines/gob/sound/sound.h2
-rw-r--r--engines/gob/sound/soundmixer.h2
-rw-r--r--engines/gob/variables.cpp58
-rw-r--r--engines/gob/variables.h26
-rw-r--r--engines/gob/videoplayer.cpp14
-rw-r--r--engines/igor/igor.cpp2
-rw-r--r--engines/kyra/animator_lok.cpp4
-rw-r--r--engines/kyra/detection.cpp229
-rw-r--r--engines/kyra/gui_hof.cpp22
-rw-r--r--engines/kyra/gui_lok.cpp10
-rw-r--r--engines/kyra/gui_lok.h2
-rw-r--r--engines/kyra/items_lok.cpp2
-rw-r--r--engines/kyra/kyra_hof.cpp10
-rw-r--r--engines/kyra/kyra_mr.cpp11
-rw-r--r--engines/kyra/kyra_mr.h5
-rw-r--r--engines/kyra/kyra_v1.cpp50
-rw-r--r--engines/kyra/kyra_v1.h9
-rw-r--r--engines/kyra/kyra_v2.cpp32
-rw-r--r--engines/kyra/kyra_v2.h3
-rw-r--r--engines/kyra/lol.cpp806
-rw-r--r--engines/kyra/lol.h155
-rw-r--r--engines/kyra/module.mk2
-rw-r--r--engines/kyra/resource.cpp16
-rw-r--r--engines/kyra/scene_hof.cpp4
-rw-r--r--engines/kyra/scene_lok.cpp4
-rw-r--r--engines/kyra/screen.cpp125
-rw-r--r--engines/kyra/screen.h6
-rw-r--r--engines/kyra/screen_lol.cpp69
-rw-r--r--engines/kyra/screen_lol.h53
-rw-r--r--engines/kyra/screen_v2.cpp48
-rw-r--r--engines/kyra/screen_v2.h4
-rw-r--r--engines/kyra/script.cpp8
-rw-r--r--engines/kyra/script.h2
-rw-r--r--engines/kyra/script_hof.cpp12
-rw-r--r--engines/kyra/script_tim.cpp419
-rw-r--r--engines/kyra/script_tim.h73
-rw-r--r--engines/kyra/seqplayer.cpp4
-rw-r--r--engines/kyra/sequences_lok.cpp2
-rw-r--r--engines/kyra/sound.cpp23
-rw-r--r--engines/kyra/sound.h57
-rw-r--r--engines/kyra/sound_adlib.cpp9
-rw-r--r--engines/kyra/sound_lok.cpp16
-rw-r--r--engines/kyra/sound_towns.cpp2101
-rw-r--r--engines/kyra/staticres.cpp169
-rw-r--r--engines/kyra/wsamovie.h2
-rw-r--r--engines/lure/lure.cpp5
-rw-r--r--engines/lure/lure.h1
-rw-r--r--engines/lure/luredefs.h2
-rw-r--r--engines/lure/menu.cpp5
-rw-r--r--engines/lure/menu.h1
-rw-r--r--engines/lure/palette.cpp6
-rw-r--r--engines/lure/palette.h1
-rw-r--r--engines/lure/res.cpp1
-rw-r--r--engines/lure/res_struct.cpp6
-rw-r--r--engines/lure/sound.cpp6
-rw-r--r--engines/m4/assets.cpp1
-rw-r--r--engines/m4/converse.cpp2
-rw-r--r--engines/m4/globals.cpp10
-rw-r--r--engines/m4/globals.h2
-rw-r--r--engines/m4/graphics.cpp6
-rw-r--r--engines/m4/graphics.h2
-rw-r--r--engines/m4/m4_views.cpp2
-rw-r--r--engines/m4/mads_anim.cpp16
-rw-r--r--engines/m4/viewmgr.cpp2
-rw-r--r--engines/made/database.cpp13
-rw-r--r--engines/made/detection.cpp4
-rw-r--r--engines/made/made.cpp26
-rw-r--r--engines/made/made.h1
-rw-r--r--engines/made/pmvplayer.cpp5
-rw-r--r--engines/made/screen.cpp4
-rw-r--r--engines/made/scriptfuncs.cpp10
-rw-r--r--engines/parallaction/balloons.cpp728
-rw-r--r--engines/parallaction/callables_ns.cpp130
-rw-r--r--engines/parallaction/debug.cpp14
-rw-r--r--engines/parallaction/detection.cpp36
-rw-r--r--engines/parallaction/dialogue.cpp430
-rw-r--r--engines/parallaction/disk.h87
-rw-r--r--engines/parallaction/disk_br.cpp591
-rw-r--r--engines/parallaction/disk_ns.cpp38
-rw-r--r--engines/parallaction/exec.h255
-rw-r--r--engines/parallaction/exec_br.cpp180
-rw-r--r--engines/parallaction/exec_ns.cpp494
-rw-r--r--engines/parallaction/font.cpp140
-rw-r--r--engines/parallaction/gfxbase.cpp234
-rw-r--r--engines/parallaction/graphics.cpp784
-rw-r--r--engines/parallaction/graphics.h132
-rw-r--r--engines/parallaction/gui.cpp92
-rw-r--r--engines/parallaction/gui.h93
-rw-r--r--engines/parallaction/gui_br.cpp338
-rw-r--r--engines/parallaction/gui_ns.cpp941
-rw-r--r--engines/parallaction/input.cpp272
-rw-r--r--engines/parallaction/input.h54
-rw-r--r--engines/parallaction/inventory.cpp142
-rw-r--r--engines/parallaction/inventory.h27
-rw-r--r--engines/parallaction/module.mk2
-rw-r--r--engines/parallaction/objects.cpp24
-rw-r--r--engines/parallaction/objects.h29
-rw-r--r--engines/parallaction/parallaction.cpp261
-rw-r--r--engines/parallaction/parallaction.h246
-rw-r--r--engines/parallaction/parallaction_br.cpp154
-rw-r--r--engines/parallaction/parallaction_ns.cpp88
-rw-r--r--engines/parallaction/parser.cpp3
-rw-r--r--engines/parallaction/parser.h18
-rw-r--r--engines/parallaction/parser_br.cpp82
-rw-r--r--engines/parallaction/parser_ns.cpp23
-rw-r--r--engines/parallaction/sound.cpp4
-rw-r--r--engines/parallaction/walk.cpp568
-rw-r--r--engines/parallaction/walk.h84
-rw-r--r--engines/queen/graphics.cpp11
-rw-r--r--engines/queen/graphics.h4
-rw-r--r--engines/queen/input.cpp9
-rw-r--r--engines/queen/input.h4
-rw-r--r--engines/queen/journal.cpp4
-rw-r--r--engines/queen/queen.cpp2
-rw-r--r--engines/queen/resource.cpp2
-rw-r--r--engines/queen/sound.cpp60
-rw-r--r--engines/queen/sound.h1
-rw-r--r--engines/saga/animation.cpp1
-rw-r--r--engines/saga/font.cpp15
-rw-r--r--engines/saga/font.h3
-rw-r--r--engines/saga/font_map.cpp131
-rw-r--r--engines/saga/interface.cpp14
-rw-r--r--engines/saga/introproc_ihnm.cpp2
-rw-r--r--engines/saga/rscfile.cpp2
-rw-r--r--engines/saga/saga.cpp8
-rw-r--r--engines/saga/saga.h2
-rw-r--r--engines/saga/script.cpp1
-rw-r--r--engines/saga/sprite.cpp3
-rw-r--r--engines/scumm/charset.cpp2
-rw-r--r--engines/scumm/debugger.cpp2
-rw-r--r--engines/scumm/detection.cpp4
-rw-r--r--engines/scumm/dialogs.cpp27
-rw-r--r--engines/scumm/dialogs.h2
-rw-r--r--engines/scumm/file.cpp16
-rw-r--r--engines/scumm/file.h9
-rw-r--r--engines/scumm/file_nes.cpp15
-rw-r--r--engines/scumm/file_nes.h3
-rw-r--r--engines/scumm/gfxARM.s2
-rw-r--r--engines/scumm/he/resource_he.cpp5
-rw-r--r--engines/scumm/he/script_v60he.cpp2
-rw-r--r--engines/scumm/he/script_v72he.cpp2
-rw-r--r--engines/scumm/he/script_v80he.cpp2
-rw-r--r--engines/scumm/he/wiz_he.cpp6
-rw-r--r--engines/scumm/imuse_digi/dimuse.cpp2
-rw-r--r--engines/scumm/imuse_digi/dimuse_sndmgr.cpp6
-rw-r--r--engines/scumm/imuse_digi/dimuse_track.h4
-rw-r--r--engines/scumm/resource.cpp4
-rw-r--r--engines/scumm/saveload.cpp6
-rw-r--r--engines/scumm/scumm-md5.h10
-rw-r--r--engines/scumm/smush/codec47ARM.s8
-rw-r--r--engines/scumm/sound.cpp1
-rw-r--r--engines/sky/disk.cpp4
-rw-r--r--engines/sky/music/adlibmusic.cpp1
-rw-r--r--engines/sword1/resman.cpp4
-rw-r--r--engines/sword2/music.cpp21
-rw-r--r--engines/sword2/resman.cpp5
-rw-r--r--engines/sword2/sound.h2
-rw-r--r--engines/tinsel/actors.cpp897
-rw-r--r--engines/tinsel/actors.h125
-rw-r--r--engines/tinsel/anim.cpp404
-rw-r--r--engines/tinsel/anim.h71
-rw-r--r--engines/tinsel/background.cpp232
-rw-r--r--engines/tinsel/background.h107
-rw-r--r--engines/tinsel/bg.cpp189
-rw-r--r--engines/tinsel/cliprect.cpp313
-rw-r--r--engines/tinsel/cliprect.h76
-rw-r--r--engines/tinsel/config.cpp125
-rw-r--r--engines/tinsel/config.h72
-rw-r--r--engines/tinsel/coroutine.h147
-rw-r--r--engines/tinsel/cursor.cpp647
-rw-r--r--engines/tinsel/cursor.h56
-rw-r--r--engines/tinsel/debugger.cpp162
-rw-r--r--engines/tinsel/debugger.h49
-rw-r--r--engines/tinsel/detection.cpp278
-rw-r--r--engines/tinsel/dw.h119
-rw-r--r--engines/tinsel/effect.cpp134
-rw-r--r--engines/tinsel/events.cpp439
-rw-r--r--engines/tinsel/events.h84
-rw-r--r--engines/tinsel/faders.cpp175
-rw-r--r--engines/tinsel/faders.h55
-rw-r--r--engines/tinsel/film.h50
-rw-r--r--engines/tinsel/font.cpp96
-rw-r--r--engines/tinsel/font.h48
-rw-r--r--engines/tinsel/graphics.cpp440
-rw-r--r--engines/tinsel/graphics.h78
-rw-r--r--engines/tinsel/handle.cpp366
-rw-r--r--engines/tinsel/handle.h53
-rw-r--r--engines/tinsel/heapmem.cpp594
-rw-r--r--engines/tinsel/heapmem.h109
-rw-r--r--engines/tinsel/inventory.cpp4535
-rw-r--r--engines/tinsel/inventory.h142
-rw-r--r--engines/tinsel/mareels.cpp132
-rw-r--r--engines/tinsel/module.mk52
-rw-r--r--engines/tinsel/move.cpp1618
-rw-r--r--engines/tinsel/move.h43
-rw-r--r--engines/tinsel/multiobj.cpp533
-rw-r--r--engines/tinsel/multiobj.h124
-rw-r--r--engines/tinsel/music.cpp551
-rw-r--r--engines/tinsel/music.h118
-rw-r--r--engines/tinsel/object.cpp530
-rw-r--r--engines/tinsel/object.h206
-rw-r--r--engines/tinsel/palette.cpp440
-rw-r--r--engines/tinsel/palette.h144
-rw-r--r--engines/tinsel/pcode.cpp593
-rw-r--r--engines/tinsel/pcode.h155
-rw-r--r--engines/tinsel/pdisplay.cpp652
-rw-r--r--engines/tinsel/pid.h72
-rw-r--r--engines/tinsel/play.cpp507
-rw-r--r--engines/tinsel/polygons.cpp1862
-rw-r--r--engines/tinsel/polygons.h125
-rw-r--r--engines/tinsel/rince.cpp709
-rw-r--r--engines/tinsel/rince.h209
-rw-r--r--engines/tinsel/saveload.cpp475
-rw-r--r--engines/tinsel/savescn.cpp336
-rw-r--r--engines/tinsel/savescn.h103
-rw-r--r--engines/tinsel/scene.cpp306
-rw-r--r--engines/tinsel/scene.h73
-rw-r--r--engines/tinsel/sched.cpp345
-rw-r--r--engines/tinsel/sched.h110
-rw-r--r--engines/tinsel/scn.cpp80
-rw-r--r--engines/tinsel/scn.h68
-rw-r--r--engines/tinsel/scroll.cpp432
-rw-r--r--engines/tinsel/scroll.h77
-rw-r--r--engines/tinsel/serializer.h131
-rw-r--r--engines/tinsel/sound.cpp211
-rw-r--r--engines/tinsel/sound.h80
-rw-r--r--engines/tinsel/strres.cpp209
-rw-r--r--engines/tinsel/strres.h69
-rw-r--r--engines/tinsel/text.cpp279
-rw-r--r--engines/tinsel/text.h101
-rw-r--r--engines/tinsel/timers.cpp192
-rw-r--r--engines/tinsel/timers.h53
-rw-r--r--engines/tinsel/tinlib.cpp2980
-rw-r--r--engines/tinsel/tinlib.h41
-rw-r--r--engines/tinsel/tinsel.cpp999
-rw-r--r--engines/tinsel/tinsel.h143
-rw-r--r--engines/tinsel/token.cpp129
-rw-r--r--engines/tinsel/token.h57
-rw-r--r--engines/touche/midi.cpp10
-rw-r--r--engines/touche/midi.h2
-rw-r--r--engines/touche/saveload.cpp3
-rw-r--r--engines/touche/touche.cpp5
314 files changed, 40770 insertions, 4521 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 81aec3e351..9d88dd73ef 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -62,9 +62,7 @@ void AgiEngine::processEvents() {
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
- _gfx->deinitVideo();
- _gfx->deinitMachine();
- _system->quit();
+ _game.quitProgNow = true;
break;
case Common::EVENT_PREDICTIVE_DIALOG:
if (_predictiveDialogRunning)
@@ -766,12 +764,15 @@ AgiEngine::~AgiEngine() {
}
agiDeinit();
+ delete _loader;
_sound->deinitSound();
delete _sound;
_gfx->deinitVideo();
delete _sprites;
+ delete _picture;
free(_game.sbufOrig);
_gfx->deinitMachine();
+ delete _gfx;
delete _rnd;
delete _console;
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 9b22240f83..a9fd204d73 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -37,6 +37,7 @@
#include "sound/mididrv.h"
#include "sound/mods/protracker.h"
+#include "sound/audiocd.h"
using Common::File;
@@ -96,6 +97,8 @@ AGOSEngine::AGOSEngine(OSystem *syst)
_vc_get_out_of_code = 0;
_gameOffsetsPtr = 0;
+ _quit = false;
+
_debugger = 0;
_gameFile = 0;
@@ -556,14 +559,17 @@ int AGOSEngine::init() {
// Setup midi driver
int midiDriver = MidiDriver::detectMusicDriver(MDT_ADLIB | MDT_MIDI);
_nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+
+ _driver = MidiDriver::createMidi(midiDriver);
+
if (_nativeMT32) {
- driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
}
_midi.mapMT32toGM (getGameType() != GType_SIMON2 && !_nativeMT32);
- _midi.setDriver(driver);
+ _midi.setDriver(_driver);
+
int ret = _midi.open();
if (ret)
warning("MIDI Player init failed: \"%s\"", _midi.getErrorName (ret));
@@ -572,6 +578,8 @@ int AGOSEngine::init() {
_midiEnabled = true;
+ } else {
+ _driver = NULL;
}
// allocate buffers
@@ -875,6 +883,10 @@ AGOSEngine::~AGOSEngine() {
delete _gameFile;
_midi.close();
+ if (_driver)
+ delete _driver;
+
+ AudioCD.destroy();
for (uint i = 0; i < _itemHeap.size(); i++) {
delete[] _itemHeap[i];
@@ -883,6 +895,8 @@ AGOSEngine::~AGOSEngine() {
free(_tablesHeapPtr - _tablesHeapCurPos);
+ free(_mouseData);
+
free(_gameOffsetsPtr);
free(_iconFilePtr);
free(_itemArrayPtr);
@@ -894,6 +908,7 @@ AGOSEngine::~AGOSEngine() {
free(_backGroundBuf);
free(_backBuf);
free(_scaleBuf);
+ free(_zoneBuffers);
free(_window4BackScn);
free(_window6BackScn);
@@ -937,7 +952,7 @@ void AGOSEngine::pauseEngineIntern(bool pauseIt) {
void AGOSEngine::pause() {
pauseEngine(true);
- while (_pause) {
+ while (_pause && !_quit) {
delay(1);
if (_keyPressed.keycode == Common::KEYCODE_p)
pauseEngine(false);
@@ -974,7 +989,7 @@ int AGOSEngine::go() {
(getFeatures() & GF_DEMO)) {
int i;
- while (1) {
+ while (!_quit) {
for (i = 0; i < 4; i++) {
setWindowImage(3, 9902 + i);
debug(0, "Displaying image %d", 9902 + i);
@@ -1003,7 +1018,7 @@ int AGOSEngine::go() {
runSubroutine101();
permitInput();
- while (1) {
+ while (!_quit) {
waitForInput();
handleVerbClicked(_verbHitArea);
delay(100);
@@ -1012,6 +1027,9 @@ int AGOSEngine::go() {
return 0;
}
+
+/* I do not think that this will be used
+ *
void AGOSEngine::shutdown() {
// Sync with AGOSEngine::~AGOSEngine()
// In Simon 2, this gets deleted along with _sound further down
@@ -1019,6 +1037,7 @@ void AGOSEngine::shutdown() {
delete _gameFile;
_midi.close();
+ delete _driver;
for (uint i = 0; i < _itemHeap.size(); i++) {
delete[] _itemHeap[i];
@@ -1058,6 +1077,7 @@ void AGOSEngine::shutdown() {
_system->quit();
}
+*/
uint32 AGOSEngine::getTime() const {
// FIXME: calling time() is not portable, use OSystem::getMillis instead
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 448d26a9d0..8ad5487b35 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -269,6 +269,7 @@ protected:
uint16 _marks;
+ bool _quit;
bool _scriptVar2;
bool _runScriptReturn1;
bool _runScriptCondition[40];
@@ -523,6 +524,7 @@ protected:
byte _lettersToPrintBuf[80];
MidiPlayer _midi;
+ MidiDriver *_driver;
bool _midiEnabled;
bool _nativeMT32;
@@ -1073,6 +1075,8 @@ protected:
virtual void drawImage(VC10_state *state);
void drawBackGroundImage(VC10_state *state);
void drawVertImage(VC10_state *state);
+ void drawVertImageCompressed(VC10_state *state);
+ void drawVertImageUncompressed(VC10_state *state);
void setMoveRect(uint16 x, uint16 y, uint16 width, uint16 height);
diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp
index fd78c65002..c92f834a3b 100644
--- a/engines/agos/animation.cpp
+++ b/engines/agos/animation.cpp
@@ -280,7 +280,7 @@ void MoviePlayer::handleNextFrame() {
_rightButtonDown = false;
break;
case Common::EVENT_QUIT:
- _vm->_system->quit();
+ _vm->_quit = true;
break;
default:
break;
diff --git a/engines/agos/debug.cpp b/engines/agos/debug.cpp
index 76a0b8e76f..2cf285d56a 100644
--- a/engines/agos/debug.cpp
+++ b/engines/agos/debug.cpp
@@ -393,11 +393,11 @@ static const byte bmp_hdr[] = {
};
void dumpBMP(const char *filename, int w, int h, const byte *bytes, const uint32 *palette) {
- Common::File out;
+ Common::DumpFile out;
byte my_hdr[sizeof(bmp_hdr)];
int i;
- out.open(filename, Common::File::kFileWriteMode);
+ out.open(filename);
if (!out.isOpen())
return;
diff --git a/engines/agos/draw.cpp b/engines/agos/draw.cpp
index 737f5317af..d38a5ad33b 100644
--- a/engines/agos/draw.cpp
+++ b/engines/agos/draw.cpp
@@ -473,7 +473,7 @@ void AGOSEngine::restoreBackGround() {
animTable = animTableTmp = _screenAnim1;
while (animTable->srcPtr) {
if (!(animTable->windowNum & 0x8000)) {
- memcpy(animTableTmp, animTable, sizeof(AnimTable));
+ memmove(animTableTmp, animTable, sizeof(AnimTable));
animTableTmp++;
}
animTable++;
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index 250ff2fcfd..010b331cf8 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -142,7 +142,7 @@ bool AGOSEngine::kickoffTimeEvents() {
cur_time = getTime() - _gameStoppedClock;
- while ((te = _firstTimeStruct) != NULL && te->time <= cur_time) {
+ while ((te = _firstTimeStruct) != NULL && te->time <= cur_time && !_quit) {
result = true;
_pendingDeleteTimeEvent = te;
invokeTimeEvent(te);
@@ -521,7 +521,7 @@ void AGOSEngine::delay(uint amount) {
_rightButtonDown++;
break;
case Common::EVENT_QUIT:
- shutdown();
+ _quit = true;
return;
default:
break;
@@ -544,7 +544,7 @@ void AGOSEngine::delay(uint amount) {
_system->delayMillis(this_delay);
cur = _system->getMillis();
- } while (cur < start + amount);
+ } while (cur < start + amount && !_quit);
}
void AGOSEngine::timer_callback() {
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 193b7347d6..9a3962ea21 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -744,10 +744,6 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {
}
void AGOSEngine::drawBackGroundImage(VC10_state *state) {
- const byte *src;
- byte *dst;
- uint h, i;
-
state->width = _screenWidth;
if (_window3Flag == 1) {
state->width = 0;
@@ -755,15 +751,19 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {
state->y_skip = 0;
}
- src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8);
- dst = state->surf_addr;
+ const byte* src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8);
+ byte* dst = state->surf_addr;
state->draw_width *= 2;
- h = state->draw_height;
+ uint h = state->draw_height;
+ const uint w = state->draw_width;
+ const byte paletteMod = state->paletteMod;
do {
- for (i = 0; i != state->draw_width; i++)
- dst[i] = src[i] + state->paletteMod;
+ for (uint i = 0; i != w; i+=2) {
+ dst[i] = src[i] + paletteMod;
+ dst[i+1] = src[i+1] + paletteMod;
+ }
dst += state->surf_pitch;
src += state->width;
} while (--h);
@@ -771,63 +771,86 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {
void AGOSEngine::drawVertImage(VC10_state *state) {
if (state->flags & kDFCompressed) {
- uint w, h;
- byte *src, *dst, *dstPtr;
+ drawVertImageCompressed(state);
+ } else {
+ drawVertImageUncompressed(state);
+ }
+}
- state->x_skip *= 4; /* reached */
+void AGOSEngine::drawVertImageUncompressed(VC10_state *state) {
+ assert ((state->flags & kDFCompressed) == 0) ;
- state->dl = state->width;
- state->dh = state->height;
+ const byte *src;
+ byte *dst;
+ uint count;
- vc10_skip_cols(state);
+ src = state->srcPtr + (state->width * state->y_skip) * 8;
+ dst = state->surf_addr;
+ state->x_skip *= 4;
- dstPtr = state->surf_addr;
- if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */
- dstPtr += vcReadVar(252);
- }
- w = 0;
- do {
+ do {
+ for (count = 0; count != state->draw_width; count++) {
byte color;
+ color = (src[count + state->x_skip] / 16) + state->paletteMod;
+ if ((state->flags & kDFNonTrans) || color)
+ dst[count * 2] = color | state->palette;
+ color = (src[count + state->x_skip] & 15) + state->paletteMod;
+ if ((state->flags & kDFNonTrans) || color)
+ dst[count * 2 + 1] = color | state->palette;
+ }
+ dst += state->surf_pitch;
+ src += state->width * 8;
+ } while (--state->draw_height);
+}
- src = vc10_depackColumn(state);
- dst = dstPtr;
+void AGOSEngine::drawVertImageCompressed(VC10_state *state) {
+ assert (state->flags & kDFCompressed) ;
+ uint w, h;
+
+ state->x_skip *= 4; /* reached */
- h = 0;
+ state->dl = state->width;
+ state->dh = state->height;
+
+ vc10_skip_cols(state);
+
+ byte *dstPtr = state->surf_addr;
+ if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */
+ dstPtr += vcReadVar(252);
+ }
+ w = 0;
+ do {
+ byte color;
+
+ const byte *src = vc10_depackColumn(state);
+ byte *dst = dstPtr;
+
+ h = 0;
+ if (state->flags & kDFNonTrans) {
+ do {
+ byte colors = *src;
+ color = (colors / 16);
+ dst[0] = color | state->palette;
+ color = (colors & 15);
+ dst[1] = color | state->palette;
+ dst += state->surf_pitch;
+ src++;
+ } while (++h != state->draw_height);
+ } else {
do {
- color = (*src / 16);
- if ((state->flags & kDFNonTrans) || color != 0)
+ byte colors = *src;
+ color = (colors / 16);
+ if (color != 0)
dst[0] = color | state->palette;
- color = (*src & 15);
- if ((state->flags & kDFNonTrans) || color != 0)
+ color = (colors & 15);
+ if (color != 0)
dst[1] = color | state->palette;
dst += state->surf_pitch;
src++;
} while (++h != state->draw_height);
- dstPtr += 2;
- } while (++w != state->draw_width);
- } else {
- const byte *src;
- byte *dst;
- uint count;
-
- src = state->srcPtr + (state->width * state->y_skip) * 8;
- dst = state->surf_addr;
- state->x_skip *= 4;
-
- do {
- for (count = 0; count != state->draw_width; count++) {
- byte color;
- color = (src[count + state->x_skip] / 16) + state->paletteMod;
- if ((state->flags & kDFNonTrans) || color)
- dst[count * 2] = color | state->palette;
- color = (src[count + state->x_skip] & 15) + state->paletteMod;
- if ((state->flags & kDFNonTrans) || color)
- dst[count * 2 + 1] = color | state->palette;
- }
- dst += state->surf_pitch;
- src += state->width * 8;
- } while (--state->draw_height);
- }
+ }
+ dstPtr += 2;
+ } while (++w != state->draw_width);
}
void AGOSEngine::drawImage(VC10_state *state) {
@@ -1263,7 +1286,7 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vga_res) {
if (getGameType() == GType_WW && (mode == 6 || mode == 8 || mode == 9)) {
setWindowImage(mode, vga_res);
} else {
- while (_copyScnFlag)
+ while (_copyScnFlag && !_quit)
delay(1);
setWindowImage(mode, vga_res);
diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp
index add7eb96f0..d36549f187 100644
--- a/engines/agos/input.cpp
+++ b/engines/agos/input.cpp
@@ -189,12 +189,12 @@ void AGOSEngine::waitForInput() {
resetVerbs();
}
- for (;;) {
+ while (!_quit) {
_lastHitArea = NULL;
_lastHitArea3 = NULL;
_dragAccept = 1;
- for (;;) {
+ while (!_quit) {
if ((getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) &&
_keyPressed.keycode == Common::KEYCODE_F10)
displayBoxStars();
diff --git a/engines/agos/intern.h b/engines/agos/intern.h
index 54cf4dba16..4479e2851e 100644
--- a/engines/agos/intern.h
+++ b/engines/agos/intern.h
@@ -161,6 +161,7 @@ struct WindowBlock {
uint8 fill_color, text_color;
IconBlock *iconPtr;
WindowBlock() { memset(this, 0, sizeof(*this)); }
+ ~WindowBlock() { free (iconPtr); }
};
// note on text offset:
// the actual x-coordinate is: textColumn * 8 + textColumnOffset
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 34e5f2cfeb..4a5c43e706 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -75,7 +75,7 @@ int AGOSEngine::countSaveGames() {
}
char *AGOSEngine::genSaveName(int slot) {
- static char buf[15];
+ static char buf[20];
if (getGameId() == GID_DIMP) {
sprintf(buf, "dimp.sav");
@@ -111,7 +111,7 @@ void AGOSEngine::quickLoadOrSave() {
}
bool success;
- char buf[50];
+ char buf[60];
char *filename = genSaveName(_saveLoadSlot);
if (_saveLoadType == 2) {
@@ -978,7 +978,7 @@ bool AGOSEngine::loadGame(const char *filename, bool restartMode) {
if (restartMode) {
// Load restart state
Common::File *file = new Common::File();
- file->open(filename, Common::File::kFileReadMode);
+ file->open(filename);
f = file;
} else {
f = _saveFileMan->openForLoading(filename);
@@ -1154,7 +1154,7 @@ bool AGOSEngine_Elvira2::loadGame(const char *filename, bool restartMode) {
if (restartMode) {
// Load restart state
Common::File *file = new Common::File();
- file->open(filename, Common::File::kFileReadMode);
+ file->open(filename);
f = file;
} else {
f = _saveFileMan->openForLoading(filename);
diff --git a/engines/agos/script.cpp b/engines/agos/script.cpp
index 44fbb4e9e0..6758aec511 100644
--- a/engines/agos/script.cpp
+++ b/engines/agos/script.cpp
@@ -410,7 +410,7 @@ void AGOSEngine::o_msg() {
void AGOSEngine::o_end() {
// 68: exit interpreter
- shutdown();
+ _quit = true;
}
void AGOSEngine::o_done() {
@@ -965,6 +965,9 @@ void AGOSEngine::writeVariable(uint16 variable, uint16 contents) {
int AGOSEngine::runScript() {
bool flag;
+ if (_quit)
+ return 1;
+
do {
if (_continousMainScript)
dumpOpcode(_codePtr);
@@ -1007,7 +1010,7 @@ int AGOSEngine::runScript() {
error("Invalid opcode '%d' encountered", _opcode);
executeOpcode(_opcode);
- } while (getScriptCondition() != flag && !getScriptReturn());
+ } while (getScriptCondition() != flag && !getScriptReturn() && !_quit);
return getScriptReturn();
}
@@ -1063,7 +1066,7 @@ void AGOSEngine::waitForSync(uint a) {
_exitCutscene = false;
_rightButtonDown = false;
- while (_vgaWaitFor != 0) {
+ while (_vgaWaitFor != 0 && !_quit) {
if (_rightButtonDown) {
if (_vgaWaitFor == 200 && (getGameType() == GType_FF || !getBitFlag(14))) {
skipSpeech();
diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp
index 94df21979c..c7e1d6736e 100644
--- a/engines/agos/script_e1.cpp
+++ b/engines/agos/script_e1.cpp
@@ -565,7 +565,7 @@ void AGOSEngine_Elvira1::oe1_look() {
lobjFunc(l, "You can see "); /* Show objects */
}
if (r && (r->flags & 4) && levelOf(i) < 10000) {
- shutdown();
+ _quit = true;
}
}
@@ -944,7 +944,7 @@ restart:
windowPutChar(window, *message2);
if (confirmYesOrNo(120, 62) == 0x7FFF) {
- shutdown();
+ _quit = true;
} else {
goto restart;
}
diff --git a/engines/agos/script_s1.cpp b/engines/agos/script_s1.cpp
index a1308b951d..51918b9515 100644
--- a/engines/agos/script_s1.cpp
+++ b/engines/agos/script_s1.cpp
@@ -345,14 +345,14 @@ void AGOSEngine_Simon1::os1_pauseGame() {
if (isSmartphone()) {
if (_keyPressed.keycode) {
if (_keyPressed.keycode == Common::KEYCODE_RETURN)
- shutdown();
+ _quit = true;
else
break;
}
}
#endif
if (_keyPressed.keycode == keyYes)
- shutdown();
+ _quit = true;
else if (_keyPressed.keycode == keyNo)
break;
}
diff --git a/engines/agos/subroutine.cpp b/engines/agos/subroutine.cpp
index 44ada82585..cb71ed7efa 100644
--- a/engines/agos/subroutine.cpp
+++ b/engines/agos/subroutine.cpp
@@ -554,6 +554,10 @@ int AGOSEngine::startSubroutine(Subroutine *sub) {
_currentTable = sub;
restart:
+
+ if (_quit)
+ return result;
+
while ((byte *)sl != (byte *)sub) {
_currentLine = sl;
if (checkIfToRunSubroutineLine(sl, sub)) {
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 055eb733c3..8dbccebedf 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -769,19 +769,18 @@ void loadAbs(const char *resourceName, uint16 idx) {
/*! \brief Load animDataTable from save
* \param fHandle Savefile open for reading
- * \param broken Broken/correct file format switch
+ * \param saveGameFormat The used savegame format
* \todo Add Operation Stealth savefile support
*
* Unlike the old code, this one actually rebuilds the table one frame
* at a time.
*/
-void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
+void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) {
int16 currentAnim, foundFileIdx;
int8 isMask = 0, isSpl = 0;
byte *dataPtr, *ptr;
char *animName, part[256];
byte transparentColor = 0;
- AnimData *currentPtr;
AnimHeaderStruct animHeader;
uint16 width, height, bpp, var1;
@@ -791,30 +790,46 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
strcpy(part, currentPartName);
- for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim++) {
- currentPtr = &animDataTable[currentAnim];
+ // We only support these variations of the savegame format at the moment.
+ assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT);
+ const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30);
+ const int fileStartPos = fHandle.pos();
+ for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) {
+ // Initialize the number of frames variable to a sane number.
+ // This is needed when using continue later in this function.
+ animHeader.numFrames = 1;
+
+ // Seek to the start of the current animation's entry
+ fHandle.seek(fileStartPos + currentAnim * entrySize);
+ // Read in the current animation entry
width = fHandle.readUint16BE();
var1 = fHandle.readUint16BE();
bpp = fHandle.readUint16BE();
height = fHandle.readUint16BE();
- if (!broken) {
- if (!fHandle.readUint32BE()) {
- fHandle.skip(18);
- continue;
- }
- fHandle.readUint32BE();
+ bool validPtr = false;
+ // Handle variables only present in animation entries of size 30
+ if (entrySize == 30) {
+ validPtr = (fHandle.readUint32BE() != 0); // Read data pointer
+ fHandle.readUint32BE(); // Discard mask pointer
}
foundFileIdx = fHandle.readSint16BE();
frame = fHandle.readSint16BE();
fHandle.read(name, 10);
- if (foundFileIdx < 0 || (broken && !fHandle.readByte())) {
+ // Handle variables only present in animation entries of size 23
+ if (entrySize == 23) {
+ validPtr = (fHandle.readByte() != 0);
+ }
+
+ // Don't try to load invalid entries.
+ if (foundFileIdx < 0 || !validPtr) {
continue;
}
+ // Alright, the animation entry looks to be valid so let's start handling it...
if (strcmp(currentPartName, name)) {
closePart();
loadPart(name);
@@ -823,13 +838,14 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
animName = partBuffer[foundFileIdx].partName;
ptr = dataPtr = readBundleFile(foundFileIdx);
+ // isSpl and isMask are mutually exclusive cases
isSpl = (strstr(animName, ".SPL")) ? 1 : 0;
isMask = (strstr(animName, ".MSK")) ? 1 : 0;
if (isSpl) {
width = (uint16) partBuffer[foundFileIdx].unpackedSize;
height = 1;
- frame = 0;
+ animHeader.numFrames = 1;
type = ANIM_RAW;
} else {
Common::MemoryReadStream readS(ptr, 0x16);
@@ -843,25 +859,35 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
type = ANIM_MASK;
} else {
type = ANIM_MASKSPRITE;
+ }
+ }
- loadRelatedPalette(animName);
- transparentColor = getAnimTransparentColor(animName);
-
- // special case transparency handling
- if (!strcmp(animName, "L2202.ANI")) {
- transparentColor = (frame < 2) ? 0 : 7;
- } else if (!strcmp(animName, "L4601.ANI")) {
- transparentColor = (frame < 1) ? 0xE : 0;
- }
+ loadRelatedPalette(animName);
+ transparentColor = getAnimTransparentColor(animName);
+ // Make sure we load at least one frame and also that we
+ // don't overflow the animDataTable by writing beyond its end.
+ animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim);
+
+ // Load the frames
+ for (frame = 0; frame < animHeader.numFrames; frame++) {
+ // special case transparency handling
+ if (!strcmp(animName, "L2202.ANI")) {
+ transparentColor = (frame < 2) ? 0 : 7;
+ } else if (!strcmp(animName, "L4601.ANI")) {
+ transparentColor = (frame < 1) ? 0xE : 0;
}
+
+ // Load a single frame
+ animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor);
}
- ptr += frame * width * height;
- currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor);
free(dataPtr);
}
loadPart(part);
+
+ // Make sure we jump over all the animation entries
+ fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize);
}
} // End of namespace Cine
diff --git a/engines/cine/anim.h b/engines/cine/anim.h
index d63033e670..b0ce55f7ee 100644
--- a/engines/cine/anim.h
+++ b/engines/cine/anim.h
@@ -26,8 +26,63 @@
#ifndef CINE_ANIM_H
#define CINE_ANIM_H
+#include "common/endian.h"
+
namespace Cine {
+/**
+ * Cine engine's save game formats.
+ * Enumeration entries (Excluding the one used as an error)
+ * are sorted according to age (i.e. top one is oldest, last one newest etc).
+ *
+ * ANIMSIZE_UNKNOWN:
+ * - Animation data entry size is unknown (Used as an error).
+ *
+ * ANIMSIZE_23:
+ * - Animation data entry size is 23 bytes.
+ * - Used at least by 0.11.0 and 0.11.1 releases of ScummVM.
+ * - Introduced in revision 21772, stopped using in revision 31444.
+ *
+ * ANIMSIZE_30_PTRS_BROKEN:
+ * - Animation data entry size is 30 bytes.
+ * - Data and mask pointers in the saved structs are always NULL.
+ * - Introduced in revision 31453, stopped using in revision 32073.
+ *
+ * ANIMSIZE_30_PTRS_INTACT:
+ * - Animation data entry size is 30 bytes.
+ * - Data and mask pointers in the saved structs are intact,
+ * so you can test them for equality or inequality with NULL
+ * but don't try using them for anything else, it won't work.
+ * - Introduced in revision 31444, got broken in revision 31453,
+ * got fixed in revision 32073 and used after that.
+ *
+ * TEMP_OS_FORMAT:
+ * - Temporary Operation Stealth savegame format.
+ * - NOT backward compatible and NOT to be supported in the future.
+ * This format should ONLY be used during development and abandoned
+ * later in favor of a better format!
+ */
+enum CineSaveGameFormat {
+ ANIMSIZE_UNKNOWN,
+ ANIMSIZE_23,
+ ANIMSIZE_30_PTRS_BROKEN,
+ ANIMSIZE_30_PTRS_INTACT,
+ TEMP_OS_FORMAT
+};
+
+/** Identifier for the temporary Operation Stealth savegame format. */
+static const uint32 TEMP_OS_FORMAT_ID = MKID_BE('TEMP');
+
+/** The current version number of Operation Stealth's savegame format. */
+static const uint32 CURRENT_OS_SAVE_VER = 0;
+
+/** Chunk header used by the temporary Operation Stealth savegame format. */
+struct ChunkHeader {
+ uint32 id; ///< Identifier (e.g. MKID_BE('TEMP'))
+ uint32 version; ///< Version number
+ uint32 size; ///< Size of the chunk after this header in bytes
+};
+
struct AnimHeaderStruct {
byte field_0;
byte field_1;
@@ -101,7 +156,7 @@ void freeAnimDataTable(void);
void freeAnimDataRange(byte startIdx, byte numIdx);
void loadResource(const char *resourceName);
void loadAbs(const char *resourceName, uint16 idx);
-void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken);
+void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat);
void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency);
} // End of namespace Cine
diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp
index c5b7fb4e3d..2a4e7f0ab1 100644
--- a/engines/cine/bg.cpp
+++ b/engines/cine/bg.cpp
@@ -35,7 +35,7 @@ namespace Cine {
uint16 bgVar0;
byte *additionalBgTable[9];
-byte currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
+int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
byte loadCtFW(const char *ctName) {
uint16 header[32];
diff --git a/engines/cine/bg.h b/engines/cine/bg.h
index 5fa8209131..9f97bc467d 100644
--- a/engines/cine/bg.h
+++ b/engines/cine/bg.h
@@ -35,6 +35,9 @@ void addBackground(const char *bgName, uint16 bgIdx);
extern uint16 bgVar0;
+extern int16 currentAdditionalBgIdx;
+extern int16 currentAdditionalBgIdx2;
+
} // End of namespace Cine
#endif
diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp
index cf25f1d355..fddca078e5 100644
--- a/engines/cine/bg_list.cpp
+++ b/engines/cine/bg_list.cpp
@@ -63,6 +63,7 @@ void addSpriteFilledToBGList(int16 objIdx) {
void createBgIncrustListElement(int16 objIdx, int16 param) {
BGIncrust tmp;
+ tmp.unkPtr = 0;
tmp.objIdx = objIdx;
tmp.param = param;
tmp.x = objectTable[objIdx].x;
@@ -82,7 +83,7 @@ void resetBgIncrustList(void) {
/*! \brief Restore incrust list from savefile
* \param fHandle Savefile open for reading
*/
-void loadBgIncrustFromSave(Common::InSaveFile &fHandle) {
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
BGIncrust tmp;
int size = fHandle.readSint16BE();
@@ -90,6 +91,7 @@ void loadBgIncrustFromSave(Common::InSaveFile &fHandle) {
fHandle.readUint32BE();
fHandle.readUint32BE();
+ tmp.unkPtr = 0;
tmp.objIdx = fHandle.readUint16BE();
tmp.param = fHandle.readUint16BE();
tmp.x = fHandle.readUint16BE();
diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h
index 1849d6ec3d..9a402baee8 100644
--- a/engines/cine/bg_list.h
+++ b/engines/cine/bg_list.h
@@ -51,7 +51,7 @@ void addSpriteFilledToBGList(int16 idx);
void createBgIncrustListElement(int16 objIdx, int16 param);
void resetBgIncrustList(void);
-void loadBgIncrustFromSave(Common::InSaveFile &fHandle);
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle);
} // End of namespace Cine
diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp
index efc33fadaf..f6778b6457 100644
--- a/engines/cine/cine.cpp
+++ b/engines/cine/cine.cpp
@@ -42,7 +42,6 @@
#include "cine/sound.h"
#include "cine/various.h"
-
namespace Cine {
Sound *g_sound;
@@ -70,6 +69,10 @@ CineEngine::~CineEngine() {
freeErrmessDat();
}
Common::clearAllSpecialDebugLevels();
+
+ free(palPtr);
+ free(partBuffer);
+ free(textDataPtr);
}
int CineEngine::init() {
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index 710840c17e..eaae555812 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -94,10 +94,17 @@ public:
Common::StringList _volumeResourceFiles;
StringPtrHashMap _volumeEntriesMap;
+ TextHandler _textHandler;
private:
void initialize(void);
+ void resetEngine();
+ bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat);
+ bool loadTempSaveOS(Common::SeekableReadStream &in);
bool makeLoad(char *saveName);
+ void makeSaveFW(Common::OutSaveFile &out);
+ void makeSaveOS(Common::OutSaveFile &out);
+ void makeSave(char *saveFileName);
void mainLoop(int bootScriptIdx);
void readVolCnf();
@@ -107,6 +114,7 @@ private:
extern CineEngine *g_cine;
#define BOOT_PRC_NAME "AUTO00.PRC"
+#define COPY_PROT_FAIL_PRC_NAME "L201.ANI"
enum {
VAR_MOUSE_X_MODE = 253,
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index 47446f2410..cbddf0fc59 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -337,7 +337,7 @@ int FWRenderer::drawChar(char character, int x, int y) {
x += 5;
} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw(textTable[idx][0], textTable[idx][1], 16, 8, _backBuffer, x, y);
+ drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y);
x += width + 1;
}
@@ -601,20 +601,26 @@ void FWRenderer::setScroll(unsigned int shift) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
+/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero.
+ */
+uint FWRenderer::getScroll() const {
+ return 0;
+}
+
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::removeBg(unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
-void FWRenderer::saveBg(Common::OutSaveFile &fHandle) {
+void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
fHandle.write(_bgName, 13);
}
/*! \brief Restore active and backup palette from save
* \param fHandle Savefile open for reading
*/
-void FWRenderer::restorePalette(Common::InSaveFile &fHandle) {
+void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
int i;
if (!_palette) {
@@ -655,6 +661,49 @@ void FWRenderer::savePalette(Common::OutSaveFile &fHandle) {
}
}
+/*! \brief Write active and backup palette to save
+ * \param fHandle Savefile open for writing
+ */
+void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
+ int i;
+
+ assert(_activeHiPal);
+
+ // Write the active 256 color palette.
+ for (i = 0; i < _hiPalSize; i++) {
+ fHandle.writeByte(_activeHiPal[i]);
+ }
+
+ // Write the active 256 color palette a second time.
+ // FIXME: The backup 256 color palette should be saved here instead of the active one.
+ for (i = 0; i < _hiPalSize; i++) {
+ fHandle.writeByte(_activeHiPal[i]);
+ }
+}
+
+/*! \brief Restore active and backup palette from save
+ * \param fHandle Savefile open for reading
+ */
+void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
+ int i;
+
+ if (!_activeHiPal) {
+ _activeHiPal = new byte[_hiPalSize];
+ }
+
+ assert(_activeHiPal);
+
+ for (i = 0; i < _hiPalSize; i++) {
+ _activeHiPal[i] = fHandle.readByte();
+ }
+
+ // Jump over the backup 256 color palette.
+ // FIXME: Load the backup 256 color palette and use it properly.
+ fHandle.seek(_hiPalSize, SEEK_CUR);
+
+ _changePal = 1;
+}
+
/*! \brief Rotate active palette
* \param a First color to rotate
* \param b Last color to rotate
@@ -938,7 +987,7 @@ int OSRenderer::drawChar(char character, int x, int y) {
x += 5;
} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw2(textTable[idx][0], 0, 16, 8, _backBuffer, x, y);
+ drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y);
x += width + 1;
}
@@ -969,6 +1018,7 @@ void OSRenderer::drawBackground() {
/*! \brief Draw one overlay
* \param it Overlay info
+ * \todo Add handling of type 22 overlays
*/
void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
int len;
@@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
switch (it->type) {
// color sprite
case 0:
+ if (objectTable[it->objIdx].frame < 0) {
+ break;
+ }
sprite = animDataTable + objectTable[it->objIdx].frame;
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
@@ -988,6 +1041,13 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
delete[] mask;
break;
+ // bitmap
+ case 4:
+ if (objectTable[it->objIdx].frame >= 0) {
+ FWRenderer::renderOverlay(it);
+ }
+ break;
+
// masked background
case 20:
assert(it->objIdx < NUM_MAX_OBJECT);
@@ -1236,6 +1296,13 @@ void OSRenderer::setScroll(unsigned int shift) {
_bgShift = shift;
}
+/*! \brief Get background scroll
+ * \return Background scroll in pixels
+ */
+uint OSRenderer::getScroll() const {
+ return _bgShift;
+}
+
/*! \brief Unload background from renderer
* \param idx Background to unload
*/
@@ -1259,6 +1326,12 @@ void OSRenderer::removeBg(unsigned int idx) {
memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name));
}
+void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
+ for (int i = 0; i < 8; i++) {
+ fHandle.write(_bgTable[i].name, 13);
+ }
+}
+
/*! \brief Fade to black
* \bug Operation Stealth sometimes seems to fade to black using
* transformPalette resulting in double fadeout
diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h
index c63c79ac82..6a3aa1ef89 100644
--- a/engines/cine/gfx.h
+++ b/engines/cine/gfx.h
@@ -108,13 +108,14 @@ public:
virtual void selectBg(unsigned int idx);
virtual void selectScrollBg(unsigned int idx);
virtual void setScroll(unsigned int shift);
+ virtual uint getScroll() const;
virtual void removeBg(unsigned int idx);
- void saveBg(Common::OutSaveFile &fHandle);
+ virtual void saveBgNames(Common::OutSaveFile &fHandle);
virtual void refreshPalette();
virtual void reloadPalette();
- void restorePalette(Common::InSaveFile &fHandle);
- void savePalette(Common::OutSaveFile &fHandle);
+ virtual void restorePalette(Common::SeekableReadStream &fHandle);
+ virtual void savePalette(Common::OutSaveFile &fHandle);
virtual void rotatePalette(int a, int b, int c);
virtual void transformPalette(int first, int last, int r, int g, int b);
@@ -128,6 +129,7 @@ public:
*/
class OSRenderer : public FWRenderer {
private:
+ // FIXME: Background table's size is probably 8 instead of 9. Check to make sure and correct if necessary.
palBg _bgTable[9]; ///< Table of backgrounds loaded into renderer
byte *_activeHiPal; ///< Active 256 color palette
unsigned int _currentBg; ///< Current background
@@ -163,10 +165,14 @@ public:
void selectBg(unsigned int idx);
void selectScrollBg(unsigned int idx);
void setScroll(unsigned int shift);
+ uint getScroll() const;
void removeBg(unsigned int idx);
+ void saveBgNames(Common::OutSaveFile &fHandle);
void refreshPalette();
void reloadPalette();
+ void restorePalette(Common::SeekableReadStream &fHandle);
+ void savePalette(Common::OutSaveFile &fHandle);
void rotatePalette(int a, int b, int c);
void transformPalette(int first, int last, int r, int g, int b);
diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp
index cfb828cf3c..e5e670c973 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -179,6 +179,20 @@ int getKeyData() {
return k;
}
+/** Removes elements from seqList that have their member variable var4 set to value -1. */
+void purgeSeqList() {
+ Common::List<SeqListElement>::iterator it = seqList.begin();
+ while (it != seqList.end()) {
+ if (it->var4 == -1) {
+ // Erase the element and jump to the next element
+ it = seqList.erase(it);
+ } else {
+ // Let the element be and jump to the next element
+ it++;
+ }
+ }
+}
+
void CineEngine::mainLoop(int bootScriptIdx) {
bool playerAction;
uint16 quitFlag;
@@ -186,6 +200,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
uint16 mouseButton;
quitFlag = 0;
+ exitEngine = 0;
if (_preLoad == false) {
resetBgIncrustList();
@@ -194,7 +209,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
errorVar = 0;
- addScriptToList0(bootScriptIdx);
+ addScriptToGlobalScripts(bootScriptIdx);
menuVar = 0;
@@ -234,13 +249,25 @@ void CineEngine::mainLoop(int bootScriptIdx) {
do {
stopMusicAfterFadeOut();
di = executePlayerInput();
+
+ // Clear the zoneQuery table (Operation Stealth specific)
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ for (uint i = 0; i < NUM_MAX_ZONE; i++) {
+ zoneQuery[i] = 0;
+ }
+ }
- processSeqList();
- executeList1();
- executeList0();
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ processSeqList();
+ }
+ executeObjectScripts();
+ executeGlobalScripts();
- purgeList1();
- purgeList0();
+ purgeObjectScripts();
+ purgeGlobalScripts();
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ purgeSeqList();
+ }
if (playerCommand == -1) {
setMouseCursor(MOUSE_CURSOR_NORMAL);
diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp
index 7666f05352..c02e01c8ce 100644
--- a/engines/cine/object.cpp
+++ b/engines/cine/object.cpp
@@ -99,21 +99,36 @@ int removeOverlay(uint16 objIdx, uint16 param) {
/*! \brief Add new overlay sprite to the list
* \param objIdx Associate the overlay with this object
- * \param param Type of new overlay
+ * \param type Type of new overlay
* \todo Why are x, y, width and color left uninitialized?
*/
-void addOverlay(uint16 objIdx, uint16 param) {
+void addOverlay(uint16 objIdx, uint16 type) {
Common::List<overlay>::iterator it;
overlay tmp;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ // This is done for both Future Wars and Operation Stealth
if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask) {
break;
}
+
+ // There are additional checks in Operation Stealth's implementation
+ if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) {
+ break;
+ }
+ }
+
+ // In Operation Stealth's implementation we might bail out early
+ if (g_cine->getGameType() == Cine::GType_OS && it != overlayList.end() && it->objIdx == objIdx && it->type == type) {
+ return;
}
tmp.objIdx = objIdx;
- tmp.type = param;
+ tmp.type = type;
+ tmp.x = 0;
+ tmp.y = 0;
+ tmp.width = 0;
+ tmp.color = 0;
overlayList.insert(it, tmp);
}
@@ -122,24 +137,22 @@ void addOverlay(uint16 objIdx, uint16 param) {
* \param objIdx Associate the overlay with this object
* \param param source background index
*/
-void addGfxElementA0(int16 objIdx, int16 param) {
+void addGfxElement(int16 objIdx, int16 param, int16 type) {
Common::List<overlay>::iterator it;
overlay tmp;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- // wtf?!
- if (objectTable[it->objIdx].mask == objectTable[objIdx].mask &&
- (it->type == 2 || it->type == 3)) {
+ if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask || it->type == 2 || it->type == 3) {
break;
}
}
- if (it != overlayList.end() && it->objIdx == objIdx && it->type == 20 && it->x == param) {
+ if (it != overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) {
return;
}
tmp.objIdx = objIdx;
- tmp.type = 20;
+ tmp.type = type;
tmp.x = param;
tmp.y = 0;
tmp.width = 0;
@@ -153,11 +166,11 @@ void addGfxElementA0(int16 objIdx, int16 param) {
* \param param Remove overlay using this background
* \todo Check that it works
*/
-void removeGfxElementA0(int16 objIdx, int16 param) {
+void removeGfxElement(int16 objIdx, int16 param, int16 type) {
Common::List<overlay>::iterator it;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- if (it->objIdx == objIdx && it->type == 20 && it->x == param) {
+ if (it->objIdx == objIdx && it->type == type && it->x == param) {
overlayList.erase(it);
return;
}
@@ -170,8 +183,12 @@ void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint1
objectTable[objIdx].mask = param3;
objectTable[objIdx].frame = param4;
- if (removeOverlay(objIdx, 0)) {
- addOverlay(objIdx, 0);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ resetGfxEntityEntry(objIdx);
+ } else { // Future Wars
+ if (removeOverlay(objIdx, 0)) {
+ addOverlay(objIdx, 0);
+ }
}
}
@@ -199,9 +216,12 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
case 3:
objectTable[objIdx].mask = newValue;
- // TODO: Check this part against disassembly
- if (removeOverlay(objIdx, 0)) {
- addOverlay(objIdx, 0);
+ if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific
+ resetGfxEntityEntry(objIdx);
+ } else { // Future Wars specific
+ if (removeOverlay(objIdx, 0)) {
+ addOverlay(objIdx, 0);
+ }
}
break;
case 4:
@@ -221,6 +241,29 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
}
}
+/**
+ * Check if at least one of the range B's endpoints is inside range A,
+ * not counting the starting and ending points of range A.
+ * Used at least by Operation Stealth's opcode 0x8D i.e. 141.
+ */
+bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) {
+ return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd);
+}
+
+uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) {
+ assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT);
+ const objectStruct &obj1 = objectTable[objIdx1];
+ const objectStruct &obj2 = objectTable[objIdx2];
+
+ if (compareRanges(obj1.x, obj1.x + xAdd1, obj2.x, obj2.x + xAdd2) &&
+ compareRanges(obj1.y, obj1.y + yAdd1, obj2.y, obj2.y + yAdd2) &&
+ compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) {
+ return kCmpEQ;
+ } else {
+ return 0;
+ }
+}
+
uint16 compareObjectParam(byte objIdx, byte type, int16 value) {
uint16 compareResult = 0;
int16 objectParam = getObjectParam(objIdx, type);
diff --git a/engines/cine/object.h b/engines/cine/object.h
index e7de39649d..7ad65eb75f 100644
--- a/engines/cine/object.h
+++ b/engines/cine/object.h
@@ -50,7 +50,7 @@ struct overlay {
};
#define NUM_MAX_OBJECT 255
-#define NUM_MAX_VAR 256
+#define NUM_MAX_VAR 255
extern objectStruct objectTable[NUM_MAX_OBJECT];
@@ -60,15 +60,17 @@ void loadObject(char *pObjectName);
void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4);
void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue);
-void addOverlay(uint16 objIdx, uint16 param);
+void addOverlay(uint16 objIdx, uint16 type);
int removeOverlay(uint16 objIdx, uint16 param);
-void addGfxElementA0(int16 objIdx, int16 param);
-void removeGfxElementA0(int16 objIdx, int16 param);
+void addGfxElement(int16 objIdx, int16 param, int16 type);
+void removeGfxElement(int16 objIdx, int16 param, int16 type);
int16 getObjectParam(uint16 objIdx, uint16 paramIdx);
void addObjectParam(byte objIdx, byte paramIdx, int16 newValue);
void subObjectParam(byte objIdx, byte paramIdx, int16 newValue);
+bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd);
+uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2);
uint16 compareObjectParam(byte objIdx, byte param1, int16 param2);
} // End of namespace Cine
diff --git a/engines/cine/pal.h b/engines/cine/pal.h
index 70fcc0d98a..768cf0d27d 100644
--- a/engines/cine/pal.h
+++ b/engines/cine/pal.h
@@ -34,6 +34,8 @@ struct PalEntry {
byte pal2[16];
};
+extern PalEntry *palPtr;
+
void loadPal(const char *fileName);
void loadRelatedPalette(const char *fileName);
diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp
index b39f1eff7d..88f2dcef52 100644
--- a/engines/cine/part.cpp
+++ b/engines/cine/part.cpp
@@ -289,8 +289,8 @@ void dumpBundle(const char *fileName) {
debug(0, "%s", partBuffer[i].partName);
- Common::File out;
- if (out.open(Common::String("dumps/") + partBuffer[i].partName, Common::File::kFileWriteMode)) {
+ Common::DumpFile out;
+ if (out.open(Common::String("dumps/") + partBuffer[i].partName)) {
out.write(data, partBuffer[i].unpackedSize);
out.close();
}
diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp
index 402c97b1a6..27b1044620 100644
--- a/engines/cine/prc.cpp
+++ b/engines/cine/prc.cpp
@@ -40,8 +40,9 @@ ScriptList objectScripts;
/*! \todo Is script size of 0 valid?
* \todo Fix script dump code
+ * @return Was the loading successful?
*/
-void loadPrc(const char *pPrcName) {
+bool loadPrc(const char *pPrcName) {
byte i;
uint16 numScripts;
byte *scriptPtr, *dataPtr;
@@ -52,9 +53,9 @@ void loadPrc(const char *pPrcName) {
scriptTable.clear();
// This is copy protection. Used to hang the machine
- if (!scumm_stricmp(pPrcName, "L201.ANI")) {
+ if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) {
exitEngine = 1;
- return;
+ return false;
}
checkDataDisk(-1);
@@ -107,6 +108,8 @@ void loadPrc(const char *pPrcName) {
}
}
#endif
+
+ return true;
}
} // End of namespace Cine
diff --git a/engines/cine/prc.h b/engines/cine/prc.h
index f5129d28b1..05bb240372 100644
--- a/engines/cine/prc.h
+++ b/engines/cine/prc.h
@@ -31,7 +31,7 @@ namespace Cine {
extern ScriptList globalScripts;
extern ScriptList objectScripts;
-void loadPrc(const char *pPrcName);
+bool loadPrc(const char *pPrcName);
} // End of namespace Cine
diff --git a/engines/cine/script.h b/engines/cine/script.h
index eeac0e8809..19576e4c1a 100644
--- a/engines/cine/script.h
+++ b/engines/cine/script.h
@@ -61,7 +61,7 @@ private:
public:
// Explicit to prevent var=0 instead of var[i]=0 typos.
explicit ScriptVars(unsigned int len = 50);
- ScriptVars(Common::InSaveFile &fHandle, unsigned int len = 50);
+ ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len = 50);
ScriptVars(const ScriptVars &src);
~ScriptVars(void);
@@ -71,8 +71,8 @@ public:
void save(Common::OutSaveFile &fHandle) const;
void save(Common::OutSaveFile &fHandle, unsigned int len) const;
- void load(Common::InSaveFile &fHandle);
- void load(Common::InSaveFile &fHandle, unsigned int len);
+ void load(Common::SeekableReadStream &fHandle);
+ void load(Common::SeekableReadStream &fHandle, unsigned int len);
void reset(void);
};
@@ -198,7 +198,7 @@ protected:
int o1_blitAndFade();
int o1_fadeToBlack();
int o1_transformPaletteRange();
- int o1_setDefaultMenuColor2();
+ int o1_setDefaultMenuBgColor();
int o1_palRotate();
int o1_break();
int o1_endScript();
@@ -213,7 +213,7 @@ protected:
int o1_initializeZoneData();
int o1_setZoneDataEntry();
int o1_getZoneDataEntry();
- int o1_setDefaultMenuColor();
+ int o1_setPlayerCommandPosY();
int o1_allowPlayerInput();
int o1_disallowPlayerInput();
int o1_changeDataDisk();
@@ -237,7 +237,7 @@ protected:
int o2_playSample();
int o2_playSampleAlt();
int o2_op81();
- int o2_op82();
+ int o2_modifySeqListElement();
int o2_isSeqRunning();
int o2_gotoIfSupNearest();
int o2_gotoIfSupEquNearest();
@@ -258,10 +258,10 @@ protected:
int o2_useBgScroll();
int o2_setAdditionalBgVScroll();
int o2_op9F();
- int o2_addGfxElementA0();
- int o2_removeGfxElementA0();
- int o2_opA2();
- int o2_opA3();
+ int o2_addGfxElementType20();
+ int o2_removeGfxElementType20();
+ int o2_addGfxElementType21();
+ int o2_removeGfxElementType21();
int o2_loadMask22();
int o2_unloadMask22();
@@ -371,16 +371,16 @@ void dumpScript(char *dumpName);
#define OP_requestCheckPendingDataLoad 0x42
#define OP_endScript 0x50
-void addScriptToList0(uint16 idx);
+void addScriptToGlobalScripts(uint16 idx);
int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx);
void runObjectScript(int16 entryIdx);
-void executeList1(void);
-void executeList0(void);
+void executeObjectScripts(void);
+void executeGlobalScripts(void);
-void purgeList1(void);
-void purgeList0(void);
+void purgeObjectScripts(void);
+void purgeGlobalScripts(void);
} // End of namespace Cine
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 845120c99e..e761a0c8e4 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -38,7 +38,13 @@
namespace Cine {
-ScriptVars globalVars(NUM_MAX_VAR);
+/**
+ * Global variables.
+ * 255 of these are saved, but there's one more that's used for bypassing the copy protection.
+ * In CineEngine::mainLoop(int bootScriptIdx) there's this code: globalVars[VAR_BYPASS_PROTECTION] = 0;
+ * And as VAR_BYPASS_PROTECTION is 255 that's why we're allocating one more than we otherwise would.
+ */
+ScriptVars globalVars(NUM_MAX_VAR + 1);
uint16 compareVars(int16 a, int16 b);
@@ -135,7 +141,7 @@ const Opcode FWScript::_opcodeTable[] = {
{ &FWScript::o1_transformPaletteRange, "bbwww" },
/* 48 */
{ 0, 0 },
- { &FWScript::o1_setDefaultMenuColor2, "b" },
+ { &FWScript::o1_setDefaultMenuBgColor, "b" },
{ &FWScript::o1_palRotate, "bbb" },
{ 0, 0 },
/* 4C */
@@ -174,7 +180,7 @@ const Opcode FWScript::_opcodeTable[] = {
{ &FWScript::o1_setZoneDataEntry, "bw" },
{ &FWScript::o1_getZoneDataEntry, "bb" },
/* 68 */
- { &FWScript::o1_setDefaultMenuColor, "b" },
+ { &FWScript::o1_setPlayerCommandPosY, "b" },
{ &FWScript::o1_allowPlayerInput, "" },
{ &FWScript::o1_disallowPlayerInput, "" },
{ &FWScript::o1_changeDataDisk, "b" },
@@ -230,7 +236,7 @@ ScriptVars::ScriptVars(unsigned int len) : _size(len), _vars(new int16[len]) {
* \param fHandle Savefile open for reading
* \param len Size of array
*/
-ScriptVars::ScriptVars(Common::InSaveFile &fHandle, unsigned int len)
+ScriptVars::ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len)
: _size(len), _vars(new int16[len]) {
assert(_vars);
@@ -306,7 +312,7 @@ void ScriptVars::save(Common::OutSaveFile &fHandle, unsigned int len) const {
/*! \brief Restore array from savefile
* \param fHandle Savefile open for reading
*/
-void ScriptVars::load(Common::InSaveFile &fHandle) {
+void ScriptVars::load(Common::SeekableReadStream &fHandle) {
load(fHandle, _size);
}
@@ -314,7 +320,7 @@ void ScriptVars::load(Common::InSaveFile &fHandle) {
* \param fHandle Savefile open for reading
* \param len Length of data to be read
*/
-void ScriptVars::load(Common::InSaveFile &fHandle, unsigned int len) {
+void ScriptVars::load(Common::SeekableReadStream &fHandle, unsigned int len) {
debug(6, "assert(%d <= %d)", len, _size);
assert(len <= _size);
for (unsigned int i = 0; i < len; i++) {
@@ -1019,6 +1025,20 @@ int FWScript::o1_divVar() {
}
int FWScript::o1_compareVar() {
+ // WORKAROUND: A workaround for a script bug in script file CODE2.PRC
+ // in at least some of the Amiga and Atari ST versions of Future Wars.
+ // Fixes bug #2016647 (FW: crash with italian amiga version). A local
+ // variable 251 is compared against value 0 although it's quite apparent
+ // from the context in the script that instead global variable 251 should
+ // be compared against value 0. So looks like someone made a typo when
+ // making the scripts. Therefore we change that particular comparison
+ // from using the local variable 251 to using the global variable 251.
+ if (g_cine->getGameType() == Cine::GType_FW && scumm_stricmp(currentPrcName, "CODE2.PRC") == 0 &&
+ (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) &&
+ _script.getByte(_pos) == 251 && _script.getByte(_pos + 1) == 0 && _script.getWord(_pos + 2) == 0) {
+ return o1_compareGlobalVar();
+ }
+
byte varIdx = getNextByte();
byte varType = getNextByte();
@@ -1259,7 +1279,7 @@ int FWScript::o1_startGlobalScript() {
assert(param < NUM_MAX_SCRIPT);
debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _line, param);
- addScriptToList0(param);
+ addScriptToGlobalScripts(param);
return 0;
}
@@ -1385,10 +1405,11 @@ int FWScript::o1_transformPaletteRange() {
return 0;
}
-int FWScript::o1_setDefaultMenuColor2() {
+/** Set the default background color used for message boxes. */
+int FWScript::o1_setDefaultMenuBgColor() {
byte param = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _line, param);
+ debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuBgColor(%d)", _line, param);
renderer->_messageBg = param;
return 0;
@@ -1554,10 +1575,11 @@ int FWScript::o1_getZoneDataEntry() {
return 0;
}
-int FWScript::o1_setDefaultMenuColor() {
+/** Set the player command string's vertical position on-screen. */
+int FWScript::o1_setPlayerCommandPosY() {
byte param = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _line, param);
+ debugC(5, kCineDebugScript, "Line: %d: setPlayerCommandPosY(%d)", _line, param);
renderer->_cmdY = param;
return 0;
@@ -1732,7 +1754,7 @@ int FWScript::o1_unloadMask5() {
//-----------------------------------------------------------------------
-void addScriptToList0(uint16 idx) {
+void addScriptToGlobalScripts(uint16 idx) {
ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx));
assert(tmp);
globalScripts.push_back(tmp);
@@ -1764,18 +1786,32 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI
int16 lx = objectTable[objIdx].x + x;
int16 ly = objectTable[objIdx].y + y;
int16 idx;
+ int16 result = 0;
for (int16 i = 0; i < numZones; i++) {
idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320);
- assert(idx >= 0 && idx <= NUM_MAX_ZONE);
+ assert(idx >= 0 && idx < NUM_MAX_ZONE);
+
+ // The zoneQuery table is updated here only in Operation Stealth
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (zoneData[idx] < NUM_MAX_ZONE) {
+ zoneQuery[zoneData[idx]]++;
+ }
+ }
if (zoneData[idx] == zoneIdx) {
- return 1;
+ result = 1;
+ // Future Wars breaks out early on the first match, but
+ // Operation Stealth doesn't because it needs to update
+ // the zoneQuery table for the whole loop's period.
+ if (g_cine->getGameType() == Cine::GType_FW) {
+ break;
+ }
}
}
- return 0;
+ return result;
}
uint16 compareVars(int16 a, int16 b) {
@@ -1792,7 +1828,7 @@ uint16 compareVars(int16 a, int16 b) {
return flag;
}
-void executeList1(void) {
+void executeObjectScripts(void) {
ScriptList::iterator it = objectScripts.begin();
for (; it != objectScripts.end();) {
if ((*it)->_index < 0 || (*it)->execute() < 0) {
@@ -1803,7 +1839,7 @@ void executeList1(void) {
}
}
-void executeList0(void) {
+void executeGlobalScripts(void) {
ScriptList::iterator it = globalScripts.begin();
for (; it != globalScripts.end();) {
if ((*it)->_index < 0 || (*it)->execute() < 0) {
@@ -1814,12 +1850,16 @@ void executeList0(void) {
}
}
-/*! \todo objectScripts.clear()?
+/*! \todo Remove object scripts with script index of -1 (Not script position, but script index!).
+ * This would seem to be valid for both Future Wars and Operation Stealth.
*/
-void purgeList1(void) {
+void purgeObjectScripts(void) {
}
-void purgeList0(void) {
+/*! \todo Remove global scripts with script index of -1 (Not script position, but script index!).
+ * This would seem to be valid for both Future Wars and Operation Stealth.
+ */
+void purgeGlobalScripts(void) {
}
////////////////////////////////////
@@ -2352,7 +2392,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
param = *(localScriptPtr + position);
position++;
- sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param);
+ sprintf(lineBuffer, "setDefaultMenuBgColor(%d)\n", param);
break;
}
@@ -2502,7 +2542,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
param = *(localScriptPtr + position);
position++;
- sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param);
+ sprintf(lineBuffer, "setPlayerCommandPosY(%d)\n", param);
break;
}
@@ -2917,10 +2957,10 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
}
void dumpScript(char *dumpName) {
- Common::File fHandle;
+ Common::DumpFile fHandle;
uint16 i;
- fHandle.open(dumpName, Common::File::kFileWriteMode);
+ fHandle.open(dumpName);
for (i = 0; i < decompileBufferPosition; i++) {
fHandle.writeString(Common::String(decompileBuffer[i]));
diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp
index 319fca5d3c..a764281758 100644
--- a/engines/cine/script_os.cpp
+++ b/engines/cine/script_os.cpp
@@ -131,7 +131,7 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o1_transformPaletteRange, "bbwww" },
/* 48 */
{ 0, 0 },
- { &FWScript::o1_setDefaultMenuColor2, "b" },
+ { &FWScript::o1_setDefaultMenuBgColor, "b" },
{ &FWScript::o1_palRotate, "bbb" },
{ 0, 0 },
/* 4C */
@@ -170,7 +170,7 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o1_setZoneDataEntry, "bw" },
{ &FWScript::o1_getZoneDataEntry, "bb" },
/* 68 */
- { &FWScript::o1_setDefaultMenuColor, "b" },
+ { &FWScript::o1_setPlayerCommandPosY, "b" },
{ &FWScript::o1_allowPlayerInput, "" },
{ &FWScript::o1_disallowPlayerInput, "" },
{ &FWScript::o1_changeDataDisk, "b" }, /* Same as opcodes 0x95 and 0xA9. */
@@ -202,7 +202,7 @@ const Opcode OSScript::_opcodeTable[] = {
/* 80 */
{ &FWScript::o2_removeSeq, "bb" },
{ &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_op82, "bbwwb" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_modifySeqListElement, "bbwwb" },
{ &FWScript::o2_isSeqRunning, "bb" },
/* 84 */
{ &FWScript::o2_gotoIfSupNearest, "b" },
@@ -240,10 +240,10 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o2_setAdditionalBgVScroll, "c" },
{ &FWScript::o2_op9F, "ww" }, /* TODO: Name this opcode properly. */
/* A0 */
- { &FWScript::o2_addGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_removeGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_opA2, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_opA3, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_addGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_removeGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_addGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_removeGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */
/* A4 */
{ &FWScript::o2_loadMask22, "b" }, /* TODO: Name this opcode properly. */
{ &FWScript::o2_unloadMask22, "b" }, /* TODO: Name this opcode properly. */
@@ -442,6 +442,7 @@ int FWScript::o2_removeSeq() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op81() {
warning("STUB: o2_op81()");
@@ -449,23 +450,25 @@ int FWScript::o2_op81() {
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_op82() {
+int FWScript::o2_modifySeqListElement() {
byte a = getNextByte();
byte b = getNextByte();
uint16 c = getNextWord();
uint16 d = getNextWord();
byte e = getNextByte();
- warning("STUB: o2_op82(%x, %x, %x, %x, %x)", a, b, c, d, e);
+ debugC(5, kCineDebugScript, "Line: %d: o2_modifySeqListElement(%d,%d,%d,%d,%d)", _line, a, b, c, d, e);
+
+ modifySeqListElement(a, 0, b, c, d, e);
return 0;
}
+/*! \todo Check whether this opcode's name is backwards (i.e. should it be o2_isSeqNotRunning?)
+ */
int FWScript::o2_isSeqRunning() {
byte a = getNextByte();
byte b = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _line, a, b);
+ debugC(5, kCineDebugScript, "Line: %d: o2_isSeqRunning(%d,%d)", _line, a, b);
if (isSeqRunning(a, 0, b)) {
_compare = 1;
@@ -593,19 +596,18 @@ int FWScript::o2_stopObjectScript() {
return 0;
}
-/*! \todo Implement this instruction
- */
int FWScript::o2_op8D() {
- uint16 a = getNextWord();
- uint16 b = getNextWord();
- uint16 c = getNextWord();
- uint16 d = getNextWord();
- uint16 e = getNextWord();
- uint16 f = getNextWord();
- uint16 g = getNextWord();
- uint16 h = getNextWord();
- warning("STUB: o2_op8D(%x, %x, %x, %x, %x, %x, %x, %x)", a, b, c, d, e, f, g, h);
- // _currentScriptElement->compareResult = ...
+ uint16 objIdx1 = getNextWord();
+ uint16 xAdd1 = getNextWord();
+ uint16 yAdd1 = getNextWord();
+ uint16 maskAdd1 = getNextWord();
+ uint16 objIdx2 = getNextWord();
+ uint16 xAdd2 = getNextWord();
+ uint16 yAdd2 = getNextWord();
+ uint16 maskAdd2 = getNextWord();
+ debugC(5, kCineDebugScript, "Line: %d: o2_op8D(%d, %d, %d, %d, %d, %d, %d, %d)", _line, objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2);
+
+ _compare = compareObjectParamRanges(objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2);
return 0;
}
@@ -649,16 +651,15 @@ int FWScript::o2_loadBg() {
return 0;
}
-/*! \todo Check the current implementation for correctness
- */
int FWScript::o2_wasZoneChecked() {
byte param = getNextByte();
- _compare = (param < 16 && zoneData[param]);
+ _compare = (param < NUM_MAX_ZONE && zoneQuery[param]) ? 1 : 0;
debugC(5, kCineDebugScript, "Line: %d: o2_wasZoneChecked(%d)", _line, param);
return 0;
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9B() {
uint16 a = getNextWord();
@@ -674,6 +675,7 @@ int FWScript::o2_op9B() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9C() {
uint16 a = getNextWord();
@@ -713,6 +715,7 @@ int FWScript::o2_setAdditionalBgVScroll() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9F() {
warning("o2_op9F()");
@@ -721,42 +724,36 @@ int FWScript::o2_op9F() {
return 0;
}
-int FWScript::o2_addGfxElementA0() {
+int FWScript::o2_addGfxElementType20() {
uint16 param1 = getNextWord();
uint16 param2 = getNextWord();
- debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _line, param1, param2);
- addGfxElementA0(param1, param2);
+ debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType20(%d,%d)", _line, param1, param2);
+ addGfxElement(param1, param2, 20);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_removeGfxElementA0() {
+int FWScript::o2_removeGfxElementType20() {
uint16 idx = getNextWord();
uint16 param = getNextWord();
- warning("STUB? o2_removeGfxElementA0(%x, %x)", idx, param);
- removeGfxElementA0(idx, param);
+ debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType20(%d,%d)", _line, idx, param);
+ removeGfxElement(idx, param, 20);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_opA2() {
+int FWScript::o2_addGfxElementType21() {
uint16 a = getNextWord();
uint16 b = getNextWord();
- warning("STUB: o2_opA2(%x, %x)", a, b);
- // addGfxElementA2();
+ debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType21(%d,%d)", _line, a, b);
+ addGfxElement(a, b, 21);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_opA3() {
+int FWScript::o2_removeGfxElementType21() {
uint16 a = getNextWord();
uint16 b = getNextWord();
- warning("STUB: o2_opA3(%x, %x)", a, b);
- // removeGfxElementA2();
+ debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType21(%d,%d)", _line, a, b);
+ removeGfxElement(a, b, 21);
return 0;
}
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index e808de6922..f26032fe98 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -249,6 +249,7 @@ AdlibSoundDriver::AdlibSoundDriver(Audio::Mixer *mixer)
AdlibSoundDriver::~AdlibSoundDriver() {
_mixer->stopHandle(_soundHandle);
+ OPLDestroy(_opl);
}
void AdlibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) {
diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp
index 9b4b83f420..e4fd334926 100644
--- a/engines/cine/texte.cpp
+++ b/engines/cine/texte.cpp
@@ -31,8 +31,6 @@ namespace Cine {
byte *textDataPtr;
-byte textTable[256][2][16 * 8];
-
const char **failureMessages;
const CommandeType *defaultActionCommand;
const CommandeType *systemMenu;
@@ -77,14 +75,14 @@ void loadTextData(const char *pFileName, byte *pDestinationBuffer) {
loadRelatedPalette(pFileName);
for (i = 0; i < numCharacters; i++) {
- gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 16, 8);
- generateMask(textTable[i][0], textTable[i][1], 16 * 8, 0);
+ gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 16, 8);
+ generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 16 * 8, 0);
tempBuffer += dataSize;
}
} else {
for (i = 0; i < 90; i++) {
- gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 8, 8);
- generateMask(textTable[i][0], textTable[i][1], 8 * 8, 0);
+ gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 8, 8);
+ generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 8 * 8, 0);
tempBuffer += 0x40;
}
}
diff --git a/engines/cine/texte.h b/engines/cine/texte.h
index ae82832aea..f471c3c49e 100644
--- a/engines/cine/texte.h
+++ b/engines/cine/texte.h
@@ -34,7 +34,10 @@ namespace Cine {
typedef char CommandeType[20];
extern byte *textDataPtr;
-extern byte textTable[256][2][16 * 8];
+
+struct TextHandler {
+ byte textTable[256][2][16 * 8];
+};
extern const char **failureMessages;
extern const CommandeType *defaultActionCommand;
diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp
index dcd3181242..5d85ff6cab 100644
--- a/engines/cine/unpack.cpp
+++ b/engines/cine/unpack.cpp
@@ -111,7 +111,7 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen)
while (_dst >= _dstBegin && !_error) {
/*
Bits => Action:
- 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..9)
+ 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8)
1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264)
0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2)
1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3)
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index 9b98ddb253..2fcb015fcd 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -95,6 +95,9 @@ int16 saveVar2;
byte isInPause = 0;
+// TODO: Implement inputVar0's changes in the program
+// Currently inputVar0 isn't updated anywhere even though it's used at least in processSeqListElement.
+uint16 inputVar0 = 0;
byte inputVar1 = 0;
uint16 inputVar2 = 0, inputVar3 = 0;
@@ -112,6 +115,7 @@ int16 objListTab[20];
uint16 exitEngine;
uint16 zoneData[NUM_MAX_ZONE];
+uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth
void stopMusicAfterFadeOut(void) {
@@ -132,6 +136,7 @@ void runObjectScript(int16 entryIdx) {
*/
void addPlayerCommandMessage(int16 cmd) {
overlay tmp;
+ memset(&tmp, 0, sizeof(tmp));
tmp.objIdx = cmd;
tmp.type = 3;
@@ -224,6 +229,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
return -1;
}
+bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) {
+ out.writeUint32BE(header.id);
+ out.writeUint32BE(header.version);
+ out.writeUint32BE(header.size);
+ return !out.ioFailed();
+}
+
+bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) {
+ header.id = in.readUint32BE();
+ header.version = in.readUint32BE();
+ header.size = in.readUint32BE();
+ return !in.ioFailed();
+}
+
+void saveObjectTable(Common::OutSaveFile &out) {
+ out.writeUint16BE(NUM_MAX_OBJECT); // Entry count
+ out.writeUint16BE(0x20); // Entry size
+
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ out.writeUint16BE(objectTable[i].x);
+ out.writeUint16BE(objectTable[i].y);
+ out.writeUint16BE(objectTable[i].mask);
+ out.writeUint16BE(objectTable[i].frame);
+ out.writeUint16BE(objectTable[i].costume);
+ out.write(objectTable[i].name, 20);
+ out.writeUint16BE(objectTable[i].part);
+ }
+}
+
+void saveZoneData(Common::OutSaveFile &out) {
+ for (int i = 0; i < 16; i++) {
+ out.writeUint16BE(zoneData[i]);
+ }
+}
+
+void saveCommandVariables(Common::OutSaveFile &out) {
+ for (int i = 0; i < 4; i++) {
+ out.writeUint16BE(commandVar3[i]);
+ }
+}
+
+void saveAnimDataTable(Common::OutSaveFile &out) {
+ out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count
+ out.writeUint16BE(0x1E); // Entry size
+
+ for (int i = 0; i < NUM_MAX_ANIMDATA; i++) {
+ animDataTable[i].save(out);
+ }
+}
+
+void saveScreenParams(Common::OutSaveFile &out) {
+ // Screen parameters, unhandled
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+}
+
+void saveGlobalScripts(Common::OutSaveFile &out) {
+ ScriptList::const_iterator it;
+ out.writeUint16BE(globalScripts.size());
+ for (it = globalScripts.begin(); it != globalScripts.end(); ++it) {
+ (*it)->save(out);
+ }
+}
+
+void saveObjectScripts(Common::OutSaveFile &out) {
+ ScriptList::const_iterator it;
+ out.writeUint16BE(objectScripts.size());
+ for (it = objectScripts.begin(); it != objectScripts.end(); ++it) {
+ (*it)->save(out);
+ }
+}
+
+void saveOverlayList(Common::OutSaveFile &out) {
+ Common::List<overlay>::const_iterator it;
+
+ out.writeUint16BE(overlayList.size());
+
+ for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ out.writeUint32BE(0); // next
+ out.writeUint32BE(0); // previous?
+ out.writeUint16BE(it->objIdx);
+ out.writeUint16BE(it->type);
+ out.writeSint16BE(it->x);
+ out.writeSint16BE(it->y);
+ out.writeSint16BE(it->width);
+ out.writeSint16BE(it->color);
+ }
+}
+
+void saveBgIncrustList(Common::OutSaveFile &out) {
+ Common::List<BGIncrust>::const_iterator it;
+ out.writeUint16BE(bgIncrustList.size());
+
+ for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) {
+ out.writeUint32BE(0); // next
+ out.writeUint32BE(0); // previous?
+ out.writeUint16BE(it->objIdx);
+ out.writeUint16BE(it->param);
+ out.writeUint16BE(it->x);
+ out.writeUint16BE(it->y);
+ out.writeUint16BE(it->frame);
+ out.writeUint16BE(it->part);
+ }
+}
+
+void saveZoneQuery(Common::OutSaveFile &out) {
+ for (int i = 0; i < 16; i++) {
+ out.writeUint16BE(zoneQuery[i]);
+ }
+}
+
+void saveSeqList(Common::OutSaveFile &out) {
+ Common::List<SeqListElement>::const_iterator it;
+ out.writeUint16BE(seqList.size());
+
+ for (it = seqList.begin(); it != seqList.end(); ++it) {
+ out.writeSint16BE(it->var4);
+ out.writeUint16BE(it->objIdx);
+ out.writeSint16BE(it->var8);
+ out.writeSint16BE(it->frame);
+ out.writeSint16BE(it->varC);
+ out.writeSint16BE(it->varE);
+ out.writeSint16BE(it->var10);
+ out.writeSint16BE(it->var12);
+ out.writeSint16BE(it->var14);
+ out.writeSint16BE(it->var16);
+ out.writeSint16BE(it->var18);
+ out.writeSint16BE(it->var1A);
+ out.writeSint16BE(it->var1C);
+ out.writeSint16BE(it->var1E);
+ }
+}
+
bool CineEngine::loadSaveDirectory(void) {
Common::InSaveFile *fHandle;
char tmp[80];
@@ -241,21 +383,143 @@ bool CineEngine::loadSaveDirectory(void) {
return true;
}
+/*! \brief Savegame format detector
+ * \param fHandle Savefile to check
+ * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure
+ *
+ * This function seeks through the savefile and tries to determine the
+ * savegame format it uses. There's a miniscule chance that the detection
+ * algorithm could get confused and think that the file uses both the older
+ * and the newer format but that is such a remote possibility that I wouldn't
+ * worry about it at all.
+ *
+ * Also detects the temporary Operation Stealth savegame format now.
+ */
+enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) {
+ const uint32 prevStreamPos = fHandle.pos();
+
+ // First check for the temporary Operation Stealth savegame format.
+ fHandle.seek(0);
+ ChunkHeader hdr;
+ loadChunkHeader(fHandle, hdr);
+ fHandle.seek(prevStreamPos);
+ if (hdr.id == TEMP_OS_FORMAT_ID) {
+ return TEMP_OS_FORMAT;
+ }
+
+ // Ok, so the savegame isn't using the temporary Operation Stealth savegame format.
+ // Let's check for the plain Future Wars savegame format and its different versions then.
+ // The animDataTable begins at savefile position 0x2315.
+ // Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443)
+ // and 30 bytes in the save format after that (Revision 31444 and onwards).
+ // There are 255 entries in the animDataTable in both of the savefile formats.
+ static const uint animDataTableStart = 0x2315;
+ static const uint animEntriesCount = 255;
+ static const uint oldAnimEntrySize = 23;
+ static const uint newAnimEntrySize = 30;
+ static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize};
+ Common::Array<uint> animEntrySizeMatches;
+
+ // Try to walk through the savefile using different animDataTable entry sizes
+ // and make a list of all the successful entry sizes.
+ for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) {
+ // 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries)
+ // 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries)
+ static const uint sizeofScreenParams = 2 * 6;
+ static const uint globalScriptEntrySize = 206;
+ static const uint objectScriptEntrySize = 206;
+ static const uint overlayEntrySize = 20;
+ static const uint bgIncrustEntrySize = 20;
+ static const uint chainEntrySizes[] = {
+ globalScriptEntrySize,
+ objectScriptEntrySize,
+ overlayEntrySize,
+ bgIncrustEntrySize
+ };
+
+ uint animEntrySize = animEntrySizeChoices[i];
+ // Jump over the animDataTable entries and the screen parameters
+ uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams;
+ // Check that there's data left after the point we're going to jump to
+ if (newPos >= fHandle.size()) {
+ continue;
+ }
+ fHandle.seek(newPos);
+
+ // Jump over the remaining items in the savegame file
+ // (i.e. the global scripts, object scripts, overlays and background incrusts).
+ bool chainWalkSuccess = true;
+ for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) {
+ // Read entry count and jump over the entries
+ int entryCount = fHandle.readSint16BE();
+ newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount;
+ // Check that we didn't go past the end of file.
+ // Note that getting exactly to the end of file is acceptable.
+ if (newPos > fHandle.size()) {
+ chainWalkSuccess = false;
+ break;
+ }
+ fHandle.seek(newPos);
+ }
+
+ // If we could walk the chain successfully and
+ // got exactly to the end of file then we've got a match.
+ if (chainWalkSuccess && fHandle.pos() == fHandle.size()) {
+ // We found a match, let's save it
+ animEntrySizeMatches.push_back(animEntrySize);
+ }
+ }
+
+ // Check that we got only one entry size match.
+ // If we didn't, then return an error.
+ enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN;
+ if (animEntrySizeMatches.size() == 1) {
+ const uint animEntrySize = animEntrySizeMatches[0];
+ assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize);
+ if (animEntrySize == oldAnimEntrySize) {
+ result = ANIMSIZE_23;
+ } else { // animEntrySize == newAnimEntrySize
+ // Check data and mask pointers in all of the animDataTable entries
+ // to see whether we've got the version with the broken data and mask pointers or not.
+ // In the broken format all data and mask pointers were always zero.
+ static const uint relativeDataPos = 2 * 4;
+ bool pointersIntact = false;
+ for (uint i = 0; i < animEntriesCount; i++) {
+ fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos);
+ uint32 data = fHandle.readUint32BE();
+ uint32 mask = fHandle.readUint32BE();
+ if ((data != 0) || (mask != 0)) {
+ pointersIntact = true;
+ break;
+ }
+ }
+ result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN);
+ }
+ } else if (animEntrySizeMatches.size() > 1) {
+ warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format");
+ } else { // animEtrySizeMatches.size() == 0
+ debug(3, "Savegame format detector was unable to detect savegame's format");
+ }
+
+ fHandle.seek(prevStreamPos);
+ return result;
+}
+
/*! \brief Restore script list item from savefile
- * \param fHandle Savefile handlem open for reading
+ * \param fHandle Savefile handle open for reading
* \param isGlobal Restore object or global script?
*/
-void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) {
+void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) {
ScriptVars localVars, labels;
uint16 compare, pos;
int16 idx;
- labels.load(*fHandle);
- localVars.load(*fHandle);
+ labels.load(fHandle);
+ localVars.load(fHandle);
- compare = fHandle->readUint16BE();
- pos = fHandle->readUint16BE();
- idx = fHandle->readUint16BE();
+ compare = fHandle.readUint16BE();
+ pos = fHandle.readUint16BE();
+ idx = fHandle.readUint16BE();
// no way to reinitialize these
if (idx < 0) {
@@ -278,7 +542,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) {
/*! \brief Restore overlay sprites from savefile
* \param fHandle Savefile open for reading
*/
-void loadOverlayFromSave(Common::InSaveFile &fHandle) {
+void loadOverlayFromSave(Common::SeekableReadStream &fHandle) {
overlay tmp;
fHandle.readUint32BE();
@@ -294,127 +558,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) {
overlayList.push_back(tmp);
}
-/*! \brief Savefile format tester
- * \param fHandle Savefile to check
- *
- * This function seeks through savefile and tries to guess if it's the original
- * savegame format or broken format from ScummVM 0.10/0.11
- * The test is incomplete but this should cover 99.99% of cases.
- * If anyone makes a savefile which could confuse this test, assert will
- * report it
- */
-bool brokenSave(Common::InSaveFile &fHandle) {
- // Backward seeking not supported in compressed savefiles
- // if you really want it, finish it yourself
- return false;
-
- // fixed size part: 14093 bytes (12308 bytes in broken save)
- // animDataTable begins at byte 6431
-
- int filesize = fHandle.size();
- int startpos = fHandle.pos();
- int pos, tmp;
- bool correct = false, broken = false;
-
- // check for correct format
- while (filesize > 14093) {
- pos = 14093;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
-
- if (pos == filesize) correct = true;
- break;
- }
- debug(5, "brokenSave: correct format check %s: size=%d, pos=%d",
- correct ? "passed" : "failed", filesize, pos);
-
- // check for broken format
- while (filesize > 12308) {
- pos = 12308;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
-
- if (pos == filesize) broken = true;
- break;
- }
- debug(5, "brokenSave: broken format check %s: size=%d, pos=%d",
- broken ? "passed" : "failed", filesize, pos);
-
- // there's a very small chance that both cases will match
- // if anyone runs into it, you'll have to walk through
- // the animDataTable and try to open part file for each entry
- if (!correct && !broken) {
- error("brokenSave: file format check failed");
- } else if (correct && broken) {
- error("brokenSave: both file formats seem to apply");
- }
-
- fHandle.seek(startpos);
- debug(5, "brokenSave: detected %s file format",
- correct ? "correct" : "broken");
-
- return broken;
-}
-
-/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only
- */
-bool CineEngine::makeLoad(char *saveName) {
- int16 i;
- int16 size;
- bool broken;
- Common::InSaveFile *fHandle;
- char bgName[13];
-
- fHandle = g_saveFileMan->openForLoading(saveName);
-
- if (!fHandle) {
- drawString(otherMessages[0], 0);
- waitPlayerInput();
- // restoreScreen();
- checkDataDisk(-1);
- return false;
- }
-
+void CineEngine::resetEngine() {
g_sound->stopMusic();
freeAnimDataTable();
overlayList.clear();
- // if (g_cine->getGameType() == Cine::GType_OS) {
- // freeUnkList();
- // }
bgIncrustList.clear();
closePart();
@@ -424,7 +571,9 @@ bool CineEngine::makeLoad(char *saveName) {
scriptTable.clear();
messageTable.clear();
- for (i = 0; i < NUM_MAX_OBJECT; i++) {
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ objectTable[i].x = 0;
+ objectTable[i].y = 0;
objectTable[i].part = 0;
objectTable[i].name[0] = 0;
objectTable[i].frame = 0;
@@ -458,124 +607,381 @@ bool CineEngine::makeLoad(char *saveName) {
checkForPendingDataLoadSwitch = 0;
- broken = brokenSave(*fHandle);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ seqList.clear();
+ currentAdditionalBgIdx = 0;
+ currentAdditionalBgIdx2 = 0;
+ // TODO: Add resetting of the following variables
+ // adBgVar1 = 0;
+ // adBgVar0 = 0;
+ // gfxFadeOutCompleted = 0;
+ }
+}
- currentDisk = fHandle->readUint16BE();
+bool loadObjectTable(Common::SeekableReadStream &in) {
+ in.readUint16BE(); // Entry count
+ in.readUint16BE(); // Entry size
- fHandle->read(currentPartName, 13);
- fHandle->read(currentDatName, 13);
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ objectTable[i].x = in.readSint16BE();
+ objectTable[i].y = in.readSint16BE();
+ objectTable[i].mask = in.readUint16BE();
+ objectTable[i].frame = in.readSint16BE();
+ objectTable[i].costume = in.readSint16BE();
+ in.read(objectTable[i].name, 20);
+ objectTable[i].part = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
- saveVar2 = fHandle->readSint16BE();
+bool loadZoneData(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneData[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
- fHandle->read(currentPrcName, 13);
- fHandle->read(currentRelName, 13);
- fHandle->read(currentMsgName, 13);
- fHandle->read(bgName, 13);
- fHandle->read(currentCtName, 13);
+bool loadCommandVariables(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 4; i++) {
+ commandVar3[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
- checkDataDisk(currentDisk);
+bool loadScreenParams(Common::SeekableReadStream &in) {
+ // TODO: handle screen params (really required ?)
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ return !in.ioFailed();
+}
- if (strlen(currentPartName)) {
- loadPart(currentPartName);
+bool loadGlobalScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, true);
}
+ return !in.ioFailed();
+}
- if (strlen(currentPrcName)) {
- loadPrc(currentPrcName);
+bool loadObjectScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, false);
}
+ return !in.ioFailed();
+}
- if (strlen(currentRelName)) {
- loadRel(currentRelName);
+bool loadOverlayList(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadOverlayFromSave(in);
}
+ return !in.ioFailed();
+}
- if (strlen(bgName)) {
- loadBg(bgName);
- }
+bool loadSeqList(Common::SeekableReadStream &in) {
+ uint size = in.readUint16BE();
+ SeqListElement tmp;
+ for (uint i = 0; i < size; i++) {
+ tmp.var4 = in.readSint16BE();
+ tmp.objIdx = in.readUint16BE();
+ tmp.var8 = in.readSint16BE();
+ tmp.frame = in.readSint16BE();
+ tmp.varC = in.readSint16BE();
+ tmp.varE = in.readSint16BE();
+ tmp.var10 = in.readSint16BE();
+ tmp.var12 = in.readSint16BE();
+ tmp.var14 = in.readSint16BE();
+ tmp.var16 = in.readSint16BE();
+ tmp.var18 = in.readSint16BE();
+ tmp.var1A = in.readSint16BE();
+ tmp.var1C = in.readSint16BE();
+ tmp.var1E = in.readSint16BE();
+ seqList.push_back(tmp);
+ }
+ return !in.ioFailed();
+}
- if (strlen(currentCtName)) {
- loadCtFW(currentCtName);
+bool loadZoneQuery(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneQuery[i] = in.readUint16BE();
}
+ return !in.ioFailed();
+}
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
+ char musicName[13];
+ char bgNames[8][13];
- for (i = 0; i < 255; i++) {
- objectTable[i].x = fHandle->readSint16BE();
- objectTable[i].y = fHandle->readSint16BE();
- objectTable[i].mask = fHandle->readUint16BE();
- objectTable[i].frame = fHandle->readSint16BE();
- objectTable[i].costume = fHandle->readSint16BE();
- fHandle->read(objectTable[i].name, 20);
- objectTable[i].part = fHandle->readUint16BE();
+ // First check the temporary Operation Stealth savegame format header.
+ ChunkHeader hdr;
+ loadChunkHeader(in, hdr);
+ if (hdr.id != TEMP_OS_FORMAT_ID) {
+ warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame");
+ return false;
+ } else if (hdr.version > CURRENT_OS_SAVE_VER) {
+ warning("loadTempSaveOS: Detected newer format version. Not loading savegame");
+ return false;
+ } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) {
+ warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break");
+ } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER
+ debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match).");
+ }
+
+ // There shouldn't be any data in the header's chunk currently so it's an error if there is.
+ if (hdr.size > 0) {
+ warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+ return false;
}
- renderer->restorePalette(*fHandle);
+ // Ok, so we've got a correct header for a temporary Operation Stealth savegame.
+ // Let's start loading the plain savegame data then.
+ currentDisk = in.readUint16BE();
+ in.read(currentPartName, 13);
+ in.read(currentPrcName, 13);
+ in.read(currentRelName, 13);
+ in.read(currentMsgName, 13);
+
+ // Load the 8 background names.
+ for (uint i = 0; i < 8; i++) {
+ in.read(bgNames[i], 13);
+ }
+
+ in.read(currentCtName, 13);
+
+ // Moved the loading of current procedure, relation,
+ // backgrounds and Ct here because if they were at the
+ // end of this function then the global scripts loading
+ // made an array out of bounds access. In the original
+ // game's disassembly these aren't here but at the end.
+ // The difference is probably in how we handle loading
+ // the global scripts and some other things (i.e. the
+ // loading routines aren't exactly the same and subtle
+ // semantic differences result in having to do things
+ // in a different order).
+ {
+ // Not sure if this is needed with Operation Stealth...
+ checkDataDisk(currentDisk);
+
+ if (strlen(currentPrcName)) {
+ loadPrc(currentPrcName);
+ }
+
+ if (strlen(currentRelName)) {
+ loadRel(currentRelName);
+ }
+
+ // Load first background (Uses loadBg)
+ if (strlen(bgNames[0])) {
+ loadBg(bgNames[0]);
+ }
- globalVars.load(*fHandle, NUM_MAX_VAR - 1);
+ // Add backgrounds 1-7 (Uses addBackground)
+ for (int i = 1; i < 8; i++) {
+ if (strlen(bgNames[i])) {
+ addBackground(bgNames[i], i);
+ }
+ }
- for (i = 0; i < 16; i++) {
- zoneData[i] = fHandle->readUint16BE();
+ if (strlen(currentCtName)) {
+ loadCtOS(currentCtName);
+ }
}
- for (i = 0; i < 4; i++) {
- commandVar3[i] = fHandle->readUint16BE();
+ loadObjectTable(in);
+ renderer->restorePalette(in);
+ globalVars.load(in, NUM_MAX_VAR);
+ loadZoneData(in);
+ loadCommandVariables(in);
+ in.read(commandBuffer, 0x50);
+ loadZoneQuery(in);
+
+ // TODO: Use the loaded string (Current music name (String, 13 bytes)).
+ in.read(musicName, 13);
+
+ // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)).
+ in.readUint16BE();
+
+ // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)).
+ in.readUint16BE();
+
+ renderer->_cmdY = in.readUint16BE();
+ in.readUint16BE(); // Some unknown variable that seems to always be zero
+ allowPlayerInput = in.readUint16BE();
+ playerCommand = in.readUint16BE();
+ commandVar1 = in.readUint16BE();
+ isDrawCommandEnabled = in.readUint16BE();
+ var5 = in.readUint16BE();
+ var4 = in.readUint16BE();
+ var3 = in.readUint16BE();
+ var2 = in.readUint16BE();
+ commandVar2 = in.readUint16BE();
+ renderer->_messageBg = in.readUint16BE();
+
+ // TODO: Use the loaded value (adBgVar1 (Uint16BE)).
+ in.readUint16BE();
+
+ currentAdditionalBgIdx = in.readSint16BE();
+ currentAdditionalBgIdx2 = in.readSint16BE();
+
+ // TODO: Check whether the scroll value really gets used correctly after this.
+ // Note that the backgrounds are loaded only later than this value is set.
+ renderer->setScroll(in.readUint16BE());
+
+ // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?).
+ in.readUint16BE();
+
+ disableSystemMenu = in.readUint16BE();
+
+ // TODO: adBgVar1 = 1 here
+
+ // Load the animDataTable entries
+ in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth).
+ in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth).
+ loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT);
+
+ loadScreenParams(in);
+ loadGlobalScripts(in);
+ loadObjectScripts(in);
+ loadSeqList(in);
+ loadOverlayList(in);
+ loadBgIncrustFromSave(in);
+
+ // Left this here instead of moving it earlier in this function with
+ // the other current value loadings (e.g. loading of current procedure,
+ // current backgrounds etc). Mostly emulating the way we've handled
+ // Future Wars savegames and hoping that things work out.
+ if (strlen(currentMsgName)) {
+ loadMsg(currentMsgName);
}
- fHandle->read(commandBuffer, 0x50);
- renderer->setCommand(commandBuffer);
+ // TODO: Add current music loading and playing here
+ // TODO: Palette handling?
- renderer->_cmdY = fHandle->readUint16BE();
+ if (in.pos() == in.size()) {
+ debug(3, "loadTempSaveOS: Loaded the whole savefile.");
+ } else {
+ warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over");
+ }
- bgVar0 = fHandle->readUint16BE();
- allowPlayerInput = fHandle->readUint16BE();
- playerCommand = fHandle->readSint16BE();
- commandVar1 = fHandle->readSint16BE();
- isDrawCommandEnabled = fHandle->readUint16BE();
- var5 = fHandle->readUint16BE();
- var4 = fHandle->readUint16BE();
- var3 = fHandle->readUint16BE();
- var2 = fHandle->readUint16BE();
- commandVar2 = fHandle->readSint16BE();
+ return !in.ioFailed();
+}
- renderer->_messageBg = fHandle->readUint16BE();
+bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) {
+ char bgName[13];
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+ // At savefile position 0x0000:
+ currentDisk = in.readUint16BE();
- loadResourcesFromSave(*fHandle, broken);
+ // At 0x0002:
+ in.read(currentPartName, 13);
+ // At 0x000F:
+ in.read(currentDatName, 13);
- // TODO: handle screen params (really required ?)
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+ // At 0x001C:
+ saveVar2 = in.readSint16BE();
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, true);
+ // At 0x001E:
+ in.read(currentPrcName, 13);
+ // At 0x002B:
+ in.read(currentRelName, 13);
+ // At 0x0038:
+ in.read(currentMsgName, 13);
+ // At 0x0045:
+ in.read(bgName, 13);
+ // At 0x0052:
+ in.read(currentCtName, 13);
+
+ checkDataDisk(currentDisk);
+
+ if (strlen(currentPartName)) {
+ loadPart(currentPartName);
}
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, false);
+ if (strlen(currentPrcName)) {
+ loadPrc(currentPrcName);
}
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadOverlayFromSave(*fHandle);
+ if (strlen(currentRelName)) {
+ loadRel(currentRelName);
}
- loadBgIncrustFromSave(*fHandle);
+ if (strlen(bgName)) {
+ loadBg(bgName);
+ }
- delete fHandle;
+ if (strlen(currentCtName)) {
+ loadCtFW(currentCtName);
+ }
+
+ // At 0x005F:
+ loadObjectTable(in);
+
+ // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32):
+ renderer->restorePalette(in);
+
+ // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2):
+ globalVars.load(in, NUM_MAX_VAR);
+
+ // At 0x2281 (i.e. 0x2083 + 255 * 2):
+ loadZoneData(in);
+
+ // At 0x22A1 (i.e. 0x2281 + 16 * 2):
+ loadCommandVariables(in);
+
+ // At 0x22A9 (i.e. 0x22A1 + 4 * 2):
+ in.read(commandBuffer, 0x50);
+ renderer->setCommand(commandBuffer);
+
+ // At 0x22F9 (i.e. 0x22A9 + 0x50):
+ renderer->_cmdY = in.readUint16BE();
+
+ // At 0x22FB:
+ bgVar0 = in.readUint16BE();
+ // At 0x22FD:
+ allowPlayerInput = in.readUint16BE();
+ // At 0x22FF:
+ playerCommand = in.readSint16BE();
+ // At 0x2301:
+ commandVar1 = in.readSint16BE();
+ // At 0x2303:
+ isDrawCommandEnabled = in.readUint16BE();
+ // At 0x2305:
+ var5 = in.readUint16BE();
+ // At 0x2307:
+ var4 = in.readUint16BE();
+ // At 0x2309:
+ var3 = in.readUint16BE();
+ // At 0x230B:
+ var2 = in.readUint16BE();
+ // At 0x230D:
+ commandVar2 = in.readSint16BE();
+
+ // At 0x230F:
+ renderer->_messageBg = in.readUint16BE();
+
+ // At 0x2311:
+ in.readUint16BE();
+ // At 0x2313:
+ in.readUint16BE();
+
+ // At 0x2315:
+ loadResourcesFromSave(in, saveGameFormat);
+
+ loadScreenParams(in);
+ loadGlobalScripts(in);
+ loadObjectScripts(in);
+ loadOverlayList(in);
+ loadBgIncrustFromSave(in);
if (strlen(currentMsgName)) {
loadMsg(currentMsgName);
}
- setMouseCursor(MOUSE_CURSOR_NORMAL);
-
if (strlen(currentDatName)) {
/* i = saveVar2;
saveVar2 = 0;
@@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) {
}*/
}
- return true;
+ return !in.ioFailed();
}
-void makeSave(char *saveFileName) {
- int16 i;
- Common::OutSaveFile *fHandle;
-
- fHandle = g_saveFileMan->openForSaving(saveFileName);
+bool CineEngine::makeLoad(char *saveName) {
+ Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName));
- if (!fHandle) {
- drawString(otherMessages[1], 0);
+ if (!saveFile) {
+ drawString(otherMessages[0], 0);
waitPlayerInput();
// restoreScreen();
checkDataDisk(-1);
- return;
- }
-
- fHandle->writeUint16BE(currentDisk);
- fHandle->write(currentPartName, 13);
- fHandle->write(currentDatName, 13);
- fHandle->writeUint16BE(saveVar2);
- fHandle->write(currentPrcName, 13);
- fHandle->write(currentRelName, 13);
- fHandle->write(currentMsgName, 13);
- renderer->saveBg(*fHandle);
- fHandle->write(currentCtName, 13);
-
- fHandle->writeUint16BE(0xFF);
- fHandle->writeUint16BE(0x20);
-
- for (i = 0; i < 255; i++) {
- fHandle->writeUint16BE(objectTable[i].x);
- fHandle->writeUint16BE(objectTable[i].y);
- fHandle->writeUint16BE(objectTable[i].mask);
- fHandle->writeUint16BE(objectTable[i].frame);
- fHandle->writeUint16BE(objectTable[i].costume);
- fHandle->write(objectTable[i].name, 20);
- fHandle->writeUint16BE(objectTable[i].part);
- }
-
- renderer->savePalette(*fHandle);
-
- globalVars.save(*fHandle, NUM_MAX_VAR - 1);
-
- for (i = 0; i < 16; i++) {
- fHandle->writeUint16BE(zoneData[i]);
+ return false;
}
- for (i = 0; i < 4; i++) {
- fHandle->writeUint16BE(commandVar3[i]);
+ setMouseCursor(MOUSE_CURSOR_DISK);
+
+ uint32 saveSize = saveFile->size();
+ // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format.
+ if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it
+ // Can't get information about the savefile's size so let's try
+ // reading as much as we can from the file up to a predefined upper limit.
+ //
+ // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each):
+ // With 256 global scripts, object scripts, overlays and background incrusts:
+ // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB
+ // With 512 global scripts, object scripts, overlays and background incrusts:
+ // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB
+ //
+ // I think it extremely unlikely that there would be over 512 global scripts, object scripts,
+ // overlays and background incrusts so 256kB seems like quite a safe upper limit.
+ // NOTE: If the savegame format is changed then this value might have to be re-evaluated!
+ // Hopefully devices with more limited memory can also cope with this memory allocation.
+ saveSize = 256 * 1024;
+ }
+ Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize));
+
+ // Try to detect the used savegame format
+ enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in);
+
+ // Handle problematic savegame formats
+ bool load = true; // Should we try to load the savegame?
+ bool result = false;
+ if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) {
+ // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but
+ // that's not implemented here because it was never used in a stable
+ // release of ScummVM but only during development (From revision 31453,
+ // which introduced the problem, until revision 32073, which fixed it).
+ // Therefore be bail out if we detect this particular savegame format.
+ warning("Detected a known broken savegame format, not loading savegame");
+ load = false; // Don't load the savegame
+ } else if (saveGameFormat == ANIMSIZE_UNKNOWN) {
+ // If we can't detect the savegame format
+ // then let's try the default format and hope for the best.
+ warning("Couldn't detect the used savegame format, trying default savegame format. Things may break");
+ saveGameFormat = ANIMSIZE_30_PTRS_INTACT;
+ }
+
+ if (load) {
+ // Reset the engine's state
+ resetEngine();
+
+ if (saveGameFormat == TEMP_OS_FORMAT) {
+ // Load the temporary Operation Stealth savegame format
+ result = loadTempSaveOS(*in);
+ } else {
+ // Load the plain Future Wars savegame format
+ result = loadPlainSaveFW(*in, saveGameFormat);
+ }
}
- fHandle->write(commandBuffer, 0x50);
-
- fHandle->writeUint16BE(renderer->_cmdY);
-
- fHandle->writeUint16BE(bgVar0);
- fHandle->writeUint16BE(allowPlayerInput);
- fHandle->writeUint16BE(playerCommand);
- fHandle->writeUint16BE(commandVar1);
- fHandle->writeUint16BE(isDrawCommandEnabled);
- fHandle->writeUint16BE(var5);
- fHandle->writeUint16BE(var4);
- fHandle->writeUint16BE(var3);
- fHandle->writeUint16BE(var2);
- fHandle->writeUint16BE(commandVar2);
-
- fHandle->writeUint16BE(renderer->_messageBg);
-
- fHandle->writeUint16BE(0xFF);
- fHandle->writeUint16BE(0x1E);
+ setMouseCursor(MOUSE_CURSOR_NORMAL);
- for (i = 0; i < NUM_MAX_ANIMDATA; i++) {
- animDataTable[i].save(*fHandle);
- }
+ return result;
+}
- fHandle->writeUint16BE(0); // Screen params, unhandled
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
+void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
+ out.writeUint16BE(currentDisk);
+ out.write(currentPartName, 13);
+ out.write(currentDatName, 13);
+ out.writeUint16BE(saveVar2);
+ out.write(currentPrcName, 13);
+ out.write(currentRelName, 13);
+ out.write(currentMsgName, 13);
+ renderer->saveBgNames(out);
+ out.write(currentCtName, 13);
+
+ saveObjectTable(out);
+ renderer->savePalette(out);
+ globalVars.save(out, NUM_MAX_VAR);
+ saveZoneData(out);
+ saveCommandVariables(out);
+ out.write(commandBuffer, 0x50);
+
+ out.writeUint16BE(renderer->_cmdY);
+ out.writeUint16BE(bgVar0);
+ out.writeUint16BE(allowPlayerInput);
+ out.writeUint16BE(playerCommand);
+ out.writeUint16BE(commandVar1);
+ out.writeUint16BE(isDrawCommandEnabled);
+ out.writeUint16BE(var5);
+ out.writeUint16BE(var4);
+ out.writeUint16BE(var3);
+ out.writeUint16BE(var2);
+ out.writeUint16BE(commandVar2);
+ out.writeUint16BE(renderer->_messageBg);
+
+ saveAnimDataTable(out);
+ saveScreenParams(out);
+
+ saveGlobalScripts(out);
+ saveObjectScripts(out);
+ saveOverlayList(out);
+ saveBgIncrustList(out);
+}
- {
- ScriptList::iterator it;
- fHandle->writeUint16BE(globalScripts.size());
- for (it = globalScripts.begin(); it != globalScripts.end(); ++it) {
- (*it)->save(*fHandle);
- }
+void CineEngine::makeSave(char *saveFileName) {
+ Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName));
- fHandle->writeUint16BE(objectScripts.size());
- for (it = objectScripts.begin(); it != objectScripts.end(); ++it) {
- (*it)->save(*fHandle);
- }
- }
+ setMouseCursor(MOUSE_CURSOR_DISK);
- {
- Common::List<overlay>::iterator it;
-
- fHandle->writeUint16BE(overlayList.size());
-
- for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- fHandle->writeUint32BE(0);
- fHandle->writeUint32BE(0);
- fHandle->writeUint16BE(it->objIdx);
- fHandle->writeUint16BE(it->type);
- fHandle->writeSint16BE(it->x);
- fHandle->writeSint16BE(it->y);
- fHandle->writeSint16BE(it->width);
- fHandle->writeSint16BE(it->color);
+ if (!fHandle) {
+ drawString(otherMessages[1], 0);
+ waitPlayerInput();
+ // restoreScreen();
+ checkDataDisk(-1);
+ } else {
+ if (g_cine->getGameType() == GType_FW) {
+ makeSaveFW(*fHandle);
+ } else {
+ makeSaveOS(*fHandle);
}
}
- Common::List<BGIncrust>::iterator it;
- fHandle->writeUint16BE(bgIncrustList.size());
-
- for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) {
- fHandle->writeUint32BE(0); // next
- fHandle->writeUint32BE(0); // unkPtr
- fHandle->writeUint16BE(it->objIdx);
- fHandle->writeUint16BE(it->param);
- fHandle->writeUint16BE(it->x);
- fHandle->writeUint16BE(it->y);
- fHandle->writeUint16BE(it->frame);
- fHandle->writeUint16BE(it->part);
- }
-
- delete fHandle;
-
setMouseCursor(MOUSE_CURSOR_NORMAL);
}
@@ -854,6 +1264,89 @@ void CineEngine::makeSystemMenu(void) {
}
}
+/**
+ * Save an Operation Stealth type savegame. WIP!
+ *
+ * NOTE: This is going to be very much a work in progress so the Operation Stealth's
+ * savegame formats that are going to be tried are extremely probably not going
+ * to be supported at all after Operation Stealth becomes officially supported.
+ * This means that the savegame format will hopefully change to something nicer
+ * when official support for Operation Stealth begins.
+ */
+void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
+ int i;
+
+ // Make a temporary Operation Stealth savegame format chunk header and save it.
+ ChunkHeader header;
+ header.id = TEMP_OS_FORMAT_ID;
+ header.version = CURRENT_OS_SAVE_VER;
+ header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
+ writeChunkHeader(out, header);
+
+ // Start outputting the plain savegame data right after the chunk header.
+ out.writeUint16BE(currentDisk);
+ out.write(currentPartName, 13);
+ out.write(currentPrcName, 13);
+ out.write(currentRelName, 13);
+ out.write(currentMsgName, 13);
+ renderer->saveBgNames(out);
+ out.write(currentCtName, 13);
+
+ saveObjectTable(out);
+ renderer->savePalette(out);
+ globalVars.save(out, NUM_MAX_VAR);
+ saveZoneData(out);
+ saveCommandVariables(out);
+ out.write(commandBuffer, 0x50);
+ saveZoneQuery(out);
+
+ // FIXME: Save a proper name here, saving an empty string currently.
+ // 0x2925: Current music name (String, 13 bytes).
+ for (i = 0; i < 13; i++) {
+ out.writeByte(0);
+ }
+ // FIXME: Save proper value for this variable, currently writing zero
+ // 0x2932: Is music loaded? (Uint16BE, Boolean).
+ out.writeUint16BE(0);
+ // FIXME: Save proper value for this variable, currently writing zero
+ // 0x2934: Is music playing? (Uint16BE, Boolean).
+ out.writeUint16BE(0);
+
+ out.writeUint16BE(renderer->_cmdY);
+ out.writeUint16BE(0); // Some unknown variable that seems to always be zero
+ out.writeUint16BE(allowPlayerInput);
+ out.writeUint16BE(playerCommand);
+ out.writeUint16BE(commandVar1);
+ out.writeUint16BE(isDrawCommandEnabled);
+ out.writeUint16BE(var5);
+ out.writeUint16BE(var4);
+ out.writeUint16BE(var3);
+ out.writeUint16BE(var2);
+ out.writeUint16BE(commandVar2);
+ out.writeUint16BE(renderer->_messageBg);
+
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // An unknown variable at 0x295E: adBgVar1 (Uint16BE).
+ out.writeUint16BE(0);
+ out.writeSint16BE(currentAdditionalBgIdx);
+ out.writeSint16BE(currentAdditionalBgIdx2);
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift.
+ out.writeUint16BE(0);
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0?
+ out.writeUint16BE(0);
+ out.writeUint16BE(disableSystemMenu);
+
+ saveAnimDataTable(out);
+ saveScreenParams(out);
+ saveGlobalScripts(out);
+ saveObjectScripts(out);
+ saveSeqList(out);
+ saveOverlayList(out);
+ saveBgIncrustList(out);
+}
+
void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) {
gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top
gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom
@@ -1510,12 +2003,22 @@ void mainLoopSub6(void) {
void checkForPendingDataLoad(void) {
if (newPrcName[0] != 0) {
- loadPrc(newPrcName);
+ bool loadPrcOk = loadPrc(newPrcName);
strcpy(currentPrcName, newPrcName);
strcpy(newPrcName, "");
- addScriptToList0(1);
+ // Check that the loading of the script file was successful before
+ // trying to add script 1 from it to the global scripts list. This
+ // fixes a crash when failing copy protection in Amiga or Atari ST
+ // versions of Future Wars.
+ if (loadPrcOk) {
+ addScriptToGlobalScripts(1);
+ } else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) {
+ // We only show an error here for other files than the file that
+ // is loaded if copy protection fails (i.e. L201.ANI).
+ warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName);
+ }
}
if (newRelName[0] != 0) {
@@ -1582,16 +2085,19 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
}
}
-uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
+bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
Common::List<SeqListElement>::iterator it;
for (it = seqList.begin(); it != seqList.end(); ++it) {
if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) {
- return 1;
+ // Just to be on the safe side there's a restriction of the
+ // addition's result to 16-bit arithmetic here like in the
+ // original. It's possible that it's not strictly needed.
+ return ((it->var14 + it->var16) & 0xFFFF) == 0;
}
}
- return 0;
+ return true;
}
void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) {
@@ -1618,6 +2124,19 @@ void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, i
seqList.insert(it, tmp);
}
+void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) {
+ // Find a suitable list element and modify it
+ for (Common::List<SeqListElement>::iterator it = seqList.begin(); it != seqList.end(); ++it) {
+ if (it->objIdx == objIdx && it->var4 == var4Test) {
+ it->varC = param1;
+ it->var18 = param2;
+ it->var1A = param3;
+ it->var10 = it->var12 = param4;
+ break;
+ }
+ }
+}
+
void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1,
int16 param2, int16 x2, int16 y2) {
element.var16 = 0;
@@ -1662,105 +2181,51 @@ uint16 computeMove2(SeqListElement &element) {
return returnVar;
}
-// sort all the gfx stuff...
-
-void resetGfxEntityEntry(uint16 objIdx) {
-#if 0
- overlayHeadElement* tempHead = &overlayHead;
- byte* var_16 = NULL;
- uint16 var_10 = 0;
- uint16 var_12 = 0;
- overlayHeadElement* currentHead = tempHead->next;
- byte* var_1A = NULL;
- overlayHeadElement* var1E = &overlayHead;
-
- while (currentHead) {
- tempHead2 = currentHead->next;
-
- if (currentHead->objIdx == objIdx && currentHead->type!=2 && currentHead->type!=3 && currentHead->type!=0x14) {
- tempHead->next = tempHead2;
-
- if (tempHead2) {
- tempHead2->previous = currentHead->previous;
- } else {
- seqVar0 = currentHead->previous;
- }
-
- var_22 = var_16;
-
- if (!var_22) {
- // todo: goto?
- }
-
- var_22->previous = currentHead;
- } else {
- }
-
- if (currentHead->type == 0x14) {
- } else {
- }
-
- if (currentHead->type == 0x2 || currentHead->type == 0x3) {
- si = 10000;
- } else {
- si = objectTable[currentHead->objIdx];
- }
-
- if (objectTable[objIdx]>si) {
- var1E = currentHead;
- }
-
- tempHead = tempHead->next;
-
- }
-
- if (var_1A) {
- currentHead = var_16;
- var_22 = var_1E->next;
- var_1E->next = currentHead;
- var_1A->next = var_22;
-
- if (var_1E != &gfxEntityHead) {
- currentHead->previous = var_1E;
- }
-
- if (!var_22) {
- seqVar0 = var_1A;
- } else {
- var_22->previous = var_1A;
- }
-
- }
-#endif
-}
-
-uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &element, uint16 param3, int16 *param4) {
- const byte *currentPtr = ptr;
- const byte *ptrData;
- const byte *ptr2;
+uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) {
+ const int8 *ptrData;
+ const int8 *ptr2;
int16 di;
- assert(ptr);
- assert(param4);
+ debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d",
+ param1, objIdx, ptr, element.var8, element.var14, param3);
- dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8);
+ // In the original an error string is set and 0 is returned if the following doesn't hold
+ assert(ptr);
+ // We probably could just use a local variable here instead of the dummyU16 but
+ // haven't checked if this has any side-effects so keeping it this way still.
+ dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8);
ptrData = ptr + dummyU16;
+ // In the original an error string is set and 0 is returned if the following doesn't hold
assert(*ptrData);
di = (objectTable[objIdx].costume + 1) % (*ptrData);
- ptr2 = (ptrData + (di * 8)) + 1;
-
+ ++ptrData; // Jump over the just read byte
+ // Here ptr2 seems to be indexing a table of structs (8 bytes per struct):
+ // struct {
+ // int8 x; // 0 (Used with checkCollision)
+ // int8 y; // 1 (Used with checkCollision)
+ // int8 numZones; // 2 (Used with checkCollision)
+ // int8 var3; // 3 (Not used in this function)
+ // int8 xAdd; // 4 (Used with an object)
+ // int8 yAdd; // 5 (Used with an object)
+ // int8 maskAdd; // 6 (Used with an object)
+ // int8 frameAdd; // 7 (Used with an object)
+ // };
+ ptr2 = ptrData + di * 8;
+
+ // We might probably safely discard the AND by 1 here because
+ // at least in the original checkCollision returns always 0 or 1.
if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) {
return 0;
}
- objectTable[objIdx].x += (int8)ptr2[4];
- objectTable[objIdx].y += (int8)ptr2[5];
- objectTable[objIdx].mask += (int8)ptr2[6];
+ objectTable[objIdx].x += ptr2[4];
+ objectTable[objIdx].y += ptr2[5];
+ objectTable[objIdx].mask += ptr2[6];
- if (objectTable[objIdx].frame) {
+ if (ptr2[6]) {
resetGfxEntityEntry(objIdx);
}
@@ -1769,19 +2234,79 @@ uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &ele
if (param3 || !element.var14) {
objectTable[objIdx].costume = di;
} else {
+ assert(param4);
*param4 = di;
}
return 1;
}
+/*!
+ * Permutates the overlay list into a different order according to some logic.
+ * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors)
+ */
+void resetGfxEntityEntry(uint16 objIdx) {
+ Common::List<overlay>::iterator it, bObjsCutPoint;
+ Common::List<overlay> aReverseObjs, bObjs;
+ bool foundCutPoint = false;
+
+ // Go through the overlay list and partition the whole list into two categories (Type A and type B objects)
+ for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object
+ aReverseObjs.push_front(*it);
+ } else { // Type B object
+ bObjs.push_back(*it);
+ uint16 objectMask;
+ if (it->type == 2 || it->type == 3) {
+ objectMask = 10000;
+ } else {
+ objectMask = objectTable[it->objIdx].mask;
+ }
+
+ if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point
+ bObjsCutPoint = bObjs.reverse_begin();
+ foundCutPoint = true;
+ }
+ }
+ }
+
+ // Recreate the overlay list in a different order.
+ overlayList.clear();
+ if (foundCutPoint) {
+ // If a cut point was found the order is:
+ // B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point.
+ ++bObjsCutPoint; // Include the cut point in the first list insertion
+ overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint);
+ overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
+ overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end());
+ } else {
+ // If no cut point was found the order is:
+ // A objects in reverse order, B objects.
+ overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
+ overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end());
+ }
+}
+
void processSeqListElement(SeqListElement &element) {
int16 x = objectTable[element.objIdx].x;
int16 y = objectTable[element.objIdx].y;
- const byte *ptr1 = animDataTable[element.frame].data();
+ const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data();
int16 var_10;
int16 var_4;
int16 var_2;
+
+ // Initial interpretations for variables addressed through ptr1 (8-bit addressing):
+ // These may be inaccurate!
+ // 0: ?
+ // 1: xRadius
+ // 2: yRadius
+ // 3: ?
+ // 4: xAdd
+ // 5: yAdd
+ // 6: ?
+ // 7: ?
+ // After this come (At least at positions 0, 1 and 3 in 16-bit addressing)
+ // 16-bit big-endian values used for addressing through ptr1.
if (element.var12 < element.var10) {
element.var12++;
@@ -1791,22 +2316,44 @@ void processSeqListElement(SeqListElement &element) {
element.var12 = 0;
if (ptr1) {
- uint16 param1 = ptr1[1];
- uint16 param2 = ptr1[2];
+ int16 param1 = ptr1[1];
+ int16 param2 = ptr1[2];
if (element.varC != 255) {
- // FIXME: Why is this here? Fingolfin gets lots of these
- // in his copy of Operation Stealth (value 0 or 236) under
- // Mac OS X. Maybe it's a endian issue? At least the graphics
- // in the copy protection screen are partially messed up.
- warning("processSeqListElement: varC = %d", element.varC);
- }
-
- if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) {
- computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]);
+ int16 x2 = element.var18;
+ int16 y2 = element.var1A;
+ if (element.varC) {
+ x2 += objectTable[element.varC].x;
+ y2 += objectTable[element.varC].y;
+ }
+ computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2);
} else {
- element.var16 = 0;
- element.var14 = 0;
+ if (inputVar0 && allowPlayerInput) {
+ int16 adder = param1 + 1;
+ if (inputVar0 != 1) {
+ adder = -adder;
+ }
+ // FIXME: In Operation Stealth's disassembly global variable 251 is used here
+ // but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a
+ // left over from Future Wars's reverse engineering?
+ globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder;
+ }
+
+ if (inputVar1 && allowPlayerInput) {
+ int16 adder = param2 + 1;
+ if (inputVar1 != 1) {
+ adder = -adder;
+ }
+ // TODO: Name currently unnamed global variable 252
+ globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder;
+ }
+
+ if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) {
+ computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]);
+ } else {
+ element.var16 = 0;
+ element.var14 = 0;
+ }
}
var_10 = computeMove2(element);
@@ -1847,14 +2394,14 @@ void processSeqListElement(SeqListElement &element) {
}
}
- if (element.var16 + element.var14) {
+ if (element.var16 + element.var14 == 0) {
if (element.var1C) {
if (element.var1E) {
objectTable[element.objIdx].costume = 0;
element.var1E = 0;
}
- addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, (int16 *) & var2);
+ addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2);
}
}
diff --git a/engines/cine/various.h b/engines/cine/various.h
index 91662c16ff..d87679ca08 100644
--- a/engines/cine/various.h
+++ b/engines/cine/various.h
@@ -44,7 +44,7 @@ extern bool inMenu;
struct SeqListElement {
int16 var4;
- uint16 objIdx;
+ uint16 objIdx; ///< Is this really unsigned?
int16 var8;
int16 frame;
int16 varC;
@@ -130,16 +130,20 @@ struct SelectedObjStruct {
#define NUM_MAX_ZONE 16
extern uint16 zoneData[NUM_MAX_ZONE];
+extern uint16 zoneQuery[NUM_MAX_ZONE];
void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5);
void removeMessages();
void removeSeq(uint16 param1, uint16 param2, uint16 param3);
-uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3);
+bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3);
void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8);
+void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4);
void processSeqList(void);
+void resetGfxEntityEntry(uint16 objIdx);
+
bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);
} // End of namespace Cine
diff --git a/engines/cruise/cruise_main.h b/engines/cruise/cruise_main.h
index 60afe5fa4c..c9c27ada49 100644
--- a/engines/cruise/cruise_main.h
+++ b/engines/cruise/cruise_main.h
@@ -28,7 +28,7 @@
#include <string.h>
#include <stdlib.h>
-#include <assert.h>
+#include <assert.h> // FIXME: WINCE: this is not needed/not portable (probably applies to all above includes)
#include "common/scummsys.h"
diff --git a/engines/cruise/volume.cpp b/engines/cruise/volume.cpp
index e4a3dde78f..b2ff2631c0 100644
--- a/engines/cruise/volume.cpp
+++ b/engines/cruise/volume.cpp
@@ -456,8 +456,8 @@ int16 readVolCnf(void) {
sprintf(nameBuffer, "%s", buffer[j].name);
if (buffer[j].size == buffer[j].extSize) {
- Common::File fout;
- fout.open(nameBuffer, Common::File::kFileWriteMode);
+ Common::DumpFile fout;
+ fout.open(nameBuffer);
if(fout.isOpen())
fout.write(bufferLocal, buffer[j].size);
} else {
diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp
index feb6cb93ca..06868494b5 100644
--- a/engines/drascula/animation.cpp
+++ b/engines/drascula/animation.cpp
@@ -372,7 +372,11 @@ void DrasculaEngine::animation_1_1() {
break;
clearRoom();
- playMusic(2);
+ if (_lang == kSpanish)
+ playMusic(31);
+ else
+ playMusic(2);
+
pause(5);
playFLI("intro.bin", 12);
term_int = 1;
@@ -1669,7 +1673,7 @@ void DrasculaEngine::animation_12_5() {
const int frusky_x[] = {100, 139, 178, 217, 100, 178, 217, 139, 100, 139};
const int elfrusky_x[] = {1, 68, 135, 1, 68, 135, 1, 68, 135, 68, 1, 135, 68, 135, 68};
int color, component;
- char fade;
+ signed char fade;
playMusic(26);
updateRoom();
diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h
index ce67cc2c0e..8bb73d8dd1 100644
--- a/engines/drascula/drascula.h
+++ b/engines/drascula/drascula.h
@@ -245,7 +245,7 @@ public:
void loadPic(const char *NamePcc, byte *targetSurface, int colorCount = 1);
- typedef char DacPalette256[256][3];
+ typedef signed char DacPalette256[256][3];
void setRGB(byte *pal, int plt);
void assignDefaultPalette();
@@ -328,7 +328,7 @@ public:
int curHeight, curWidth, feetHeight;
int talkHeight, talkWidth;
int floorX1, floorY1, floorX2, floorY2;
- int near, far;
+ int lowerLimit, upperLimit;
int trackFinal, walkToObject;
int objExit;
int timeDiff, startTime;
@@ -397,7 +397,7 @@ public:
void playFLI(const char *filefli, int vel);
void fadeFromBlack(int fadeSpeed);
void fadeToBlack(int fadeSpeed);
- char adjustToVGA(char value);
+ signed char adjustToVGA(signed char value);
void color_abc(int cl);
void centerText(const char *,int,int);
void playSound(int soundNum);
diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp
index 64591a856e..67993bfb6c 100644
--- a/engines/drascula/graphics.cpp
+++ b/engines/drascula/graphics.cpp
@@ -89,31 +89,21 @@ void DrasculaEngine::setCursorTable() {
}
void DrasculaEngine::loadPic(const char *NamePcc, byte *targetSurface, int colorCount) {
- unsigned int con, x = 0;
- unsigned int fExit = 0;
- byte ch, rep;
+ uint dataSize = 0;
+ byte *pcxData;
_arj.open(NamePcc);
if (!_arj.isOpen())
error("missing game data %s %c", NamePcc, 7);
- _arj.seek(128);
- while (!fExit) {
- ch = _arj.readByte();
- rep = 1;
- if ((ch & 192) == 192) {
- rep = (ch & 63);
- ch = _arj.readByte();
- }
- for (con = 0; con < rep; con++) {
- x++;
- if (x > 64000) {
- fExit = 1;
- break;
- }
- *targetSurface++ = ch;
- }
- }
+ dataSize = _arj.size() - 128 - (256 * 3);
+ pcxData = (byte *)malloc(dataSize);
+
+ _arj.seek(128, SEEK_SET);
+ _arj.read(pcxData, dataSize);
+
+ decodeRLE(pcxData, targetSurface);
+ free(pcxData);
for (int i = 0; i < 256; i++) {
cPal[i * 3 + 0] = _arj.readByte();
@@ -141,7 +131,6 @@ void DrasculaEngine::showFrame(bool firstFrame) {
memcpy(prevFrame, VGA, 64000);
decodeRLE(pcxData, VGA);
-
free(pcxData);
if (!firstFrame)
diff --git a/engines/drascula/palette.cpp b/engines/drascula/palette.cpp
index ad57bce618..6a93f21e55 100644
--- a/engines/drascula/palette.cpp
+++ b/engines/drascula/palette.cpp
@@ -87,12 +87,12 @@ void DrasculaEngine::color_abc(int cl) {
setPalette((byte *)&gamePalette);
}
-char DrasculaEngine::adjustToVGA(char value) {
+signed char DrasculaEngine::adjustToVGA(signed char value) {
return (value & 0x3F) * (value > 0);
}
void DrasculaEngine::fadeToBlack(int fadeSpeed) {
- char fade;
+ signed char fade;
unsigned int color, component;
DacPalette256 palFade;
@@ -110,7 +110,7 @@ void DrasculaEngine::fadeToBlack(int fadeSpeed) {
}
void DrasculaEngine::fadeFromBlack(int fadeSpeed) {
- char fade;
+ signed char fade;
unsigned int color, component;
DacPalette256 palFade;
@@ -186,7 +186,7 @@ void DrasculaEngine::setDarkPalette() {
}
void DrasculaEngine::setPaletteBase(int darkness) {
- char fade;
+ signed char fade;
unsigned int color, component;
for (fade = darkness; fade >= 0; fade--) {
diff --git a/engines/drascula/rooms.cpp b/engines/drascula/rooms.cpp
index 6fe28bdbdc..37dddf4b7e 100644
--- a/engines/drascula/rooms.cpp
+++ b/engines/drascula/rooms.cpp
@@ -1672,8 +1672,8 @@ void DrasculaEngine::enterRoom(int roomIndex) {
getIntFromLine(buffer, size, &floorY2);
if (currentChapter != 2) {
- getIntFromLine(buffer, size, &far);
- getIntFromLine(buffer, size, &near);
+ getIntFromLine(buffer, size, &upperLimit);
+ getIntFromLine(buffer, size, &lowerLimit);
}
_arj.close();
@@ -1732,27 +1732,27 @@ void DrasculaEngine::enterRoom(int roomIndex) {
if (currentChapter != 2) {
for (l = 0; l <= floorY1; l++)
- factor_red[l] = far;
+ factor_red[l] = upperLimit;
for (l = floorY1; l <= 201; l++)
- factor_red[l] = near;
+ factor_red[l] = lowerLimit;
- chiquez = (float)(near - far) / (float)(floorY2 - floorY1);
+ chiquez = (float)(lowerLimit - upperLimit) / (float)(floorY2 - floorY1);
for (l = floorY1; l <= floorY2; l++) {
- factor_red[l] = (int)(far + pequegnez);
+ factor_red[l] = (int)(upperLimit + pequegnez);
pequegnez = pequegnez + chiquez;
}
}
if (roomNumber == 24) {
for (l = floorY1 - 1; l > 74; l--) {
- factor_red[l] = (int)(far - pequegnez);
+ factor_red[l] = (int)(upperLimit - pequegnez);
pequegnez = pequegnez + chiquez;
}
}
if (currentChapter == 5 && roomNumber == 54) {
for (l = floorY1 - 1; l > 84; l--) {
- factor_red[l] = (int)(far - pequegnez);
+ factor_red[l] = (int)(upperLimit - pequegnez);
pequegnez = pequegnez + chiquez;
}
}
diff --git a/engines/engines.mk b/engines/engines.mk
index cfb8e69f3e..4dba913173 100644
--- a/engines/engines.mk
+++ b/engines/engines.mk
@@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2)
MODULES += engines/sword2
endif
+ifdef ENABLE_TINSEL
+DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL)
+MODULES += engines/tinsel
+endif
+
ifdef ENABLE_TOUCHE
DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE)
MODULES += engines/touche
diff --git a/engines/gob/dataio.cpp b/engines/gob/dataio.cpp
index 8ae11b8755..bcf566d134 100644
--- a/engines/gob/dataio.cpp
+++ b/engines/gob/dataio.cpp
@@ -202,7 +202,7 @@ const Common::File *DataIO::file_getHandle(int16 handle) const {
return &_filesHandles[handle];
}
-int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) {
+int16 DataIO::file_open(const char *path) {
int16 i;
for (i = 0; i < MAX_FILES; i++) {
@@ -212,7 +212,7 @@ int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) {
if (i == MAX_FILES)
return -1;
- file_getHandle(i)->open(path, mode);
+ file_getHandle(i)->open(path);
if (file_getHandle(i)->isOpen())
return i;
@@ -467,17 +467,14 @@ void DataIO::closeData(int16 handle) {
file_getHandle(handle)->close();
}
-int16 DataIO::openData(const char *path, Common::File::AccessMode mode) {
+int16 DataIO::openData(const char *path) {
int16 handle;
- if (mode != Common::File::kFileReadMode)
- return file_open(path, mode);
-
handle = getChunk(path);
if (handle >= 0)
return handle;
- return file_open(path, mode);
+ return file_open(path);
}
DataStream *DataIO::openAsStream(int16 handle, bool dispose) {
diff --git a/engines/gob/dataio.h b/engines/gob/dataio.h
index a990dbeda5..4b4c79d1eb 100644
--- a/engines/gob/dataio.h
+++ b/engines/gob/dataio.h
@@ -79,8 +79,7 @@ public:
void closeDataFile(bool itk = 0);
byte *getUnpackedData(const char *name);
void closeData(int16 handle);
- int16 openData(const char *path,
- Common::File::AccessMode mode = Common::File::kFileReadMode);
+ int16 openData(const char *path);
DataStream *openAsStream(int16 handle, bool dispose = false);
int32 getDataSize(const char *name);
@@ -104,8 +103,7 @@ protected:
class GobEngine *_vm;
- int16 file_open(const char *path,
- Common::File::AccessMode mode = Common::File::kFileReadMode);
+ int16 file_open(const char *path);
Common::File *file_getHandle(int16 handle);
const Common::File *file_getHandle(int16 handle) const;
diff --git a/engines/gob/detection.cpp b/engines/gob/detection.cpp
index 8351f2ecfb..63a0f8f45b 100644
--- a/engines/gob/detection.cpp
+++ b/engines/gob/detection.cpp
@@ -277,6 +277,19 @@ static const GOBGameDescription gameDescriptions[] = {
kFeaturesNone,
"intro"
},
+ { // Supplied by raina in the forums
+ {
+ "gob1",
+ "",
+ AD_ENTRY1s("intro.stk", "6d837c6380d8f4d984c9f6cc0026df4f", 192712),
+ EN_ANY,
+ kPlatformMacintosh,
+ Common::ADGF_NO_FLAGS
+ },
+ kGameTypeGob1,
+ kFeaturesNone,
+ "intro"
+ },
{ // Supplied by paul66 in bug report #1652352
{
"gob1",
diff --git a/engines/gob/driver_vga.cpp b/engines/gob/driver_vga.cpp
index f68ce47783..2d3a10b570 100644
--- a/engines/gob/driver_vga.cpp
+++ b/engines/gob/driver_vga.cpp
@@ -112,7 +112,7 @@ void VGAVideoDriver::drawSprite(SurfaceDesc *source, SurfaceDesc *dest,
if ((width < 1) || (height < 1))
return;
- byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left;
+ const byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left;
byte *destPos = dest->getVidMem() + (y * dest->getWidth()) + x;
uint32 size = width * height;
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index a3fe0ebbe2..34443251d8 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -147,6 +147,15 @@ void GobEngine::validateVideoMode(int16 videoMode) {
error("Video mode 0x%X is not supported!", videoMode);
}
+Endianness GobEngine::getEndianness() const {
+ if ((_vm->getPlatform() == Common::kPlatformAmiga) ||
+ (_vm->getPlatform() == Common::kPlatformMacintosh) ||
+ (_vm->getPlatform() == Common::kPlatformAtariST))
+ return kEndiannessBE;
+
+ return kEndiannessLE;
+}
+
Common::Platform GobEngine::getPlatform() const {
return _platform;
}
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index ae2b53bc31..041658baea 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -79,6 +79,11 @@ class SaveLoad;
#define VAR(var) READ_VAR_UINT32(var)
+enum Endianness {
+ kEndiannessLE,
+ kEndiannessBE
+};
+
enum GameType {
kGameTypeNone = 0,
kGameTypeGob1,
@@ -230,6 +235,7 @@ public:
void validateLanguage();
void validateVideoMode(int16 videoMode);
+ Endianness getEndianness() const;
Common::Platform getPlatform() const;
GameType getGameType() const;
bool isCD() const;
diff --git a/engines/gob/goblin.cpp b/engines/gob/goblin.cpp
index e7aed0790e..5add0b9cea 100644
--- a/engines/gob/goblin.cpp
+++ b/engines/gob/goblin.cpp
@@ -78,58 +78,6 @@ Goblin::Goblin(GobEngine *vm) : _vm(vm) {
_pressedMapY = 0;
_pathExistence = 0;
- _some0ValPtr = 0;
-
- _gobRetVarPtr = 0;
- _curGobVarPtr = 0;
- _curGobXPosVarPtr = 0;
- _curGobYPosVarPtr = 0;
- _itemInPocketVarPtr = 0;
-
- _curGobStateVarPtr = 0;
- _curGobFrameVarPtr = 0;
- _curGobMultStateVarPtr = 0;
- _curGobNextStateVarPtr = 0;
- _curGobScrXVarPtr = 0;
- _curGobScrYVarPtr = 0;
- _curGobLeftVarPtr = 0;
- _curGobTopVarPtr = 0;
- _curGobRightVarPtr = 0;
- _curGobBottomVarPtr = 0;
- _curGobDoAnimVarPtr = 0;
- _curGobOrderVarPtr = 0;
- _curGobNoTickVarPtr = 0;
- _curGobTypeVarPtr = 0;
- _curGobMaxTickVarPtr = 0;
- _curGobTickVarPtr = 0;
- _curGobActStartStateVarPtr = 0;
- _curGobLookDirVarPtr = 0;
- _curGobPickableVarPtr = 0;
- _curGobRelaxVarPtr = 0;
- _curGobMaxFrameVarPtr = 0;
-
- _destItemStateVarPtr = 0;
- _destItemFrameVarPtr = 0;
- _destItemMultStateVarPtr = 0;
- _destItemNextStateVarPtr = 0;
- _destItemScrXVarPtr = 0;
- _destItemScrYVarPtr = 0;
- _destItemLeftVarPtr = 0;
- _destItemTopVarPtr = 0;
- _destItemRightVarPtr = 0;
- _destItemBottomVarPtr = 0;
- _destItemDoAnimVarPtr = 0;
- _destItemOrderVarPtr = 0;
- _destItemNoTickVarPtr = 0;
- _destItemTypeVarPtr = 0;
- _destItemMaxTickVarPtr = 0;
- _destItemTickVarPtr = 0;
- _destItemActStartStVarPtr = 0;
- _destItemLookDirVarPtr = 0;
- _destItemPickableVarPtr = 0;
- _destItemRelaxVarPtr = 0;
- _destItemMaxFrameVarPtr = 0;
-
_destItemType = 0;
_destItemState = 0;
for (int i = 0; i < 20; i++) {
@@ -690,7 +638,7 @@ void Goblin::switchGoblin(int16 index) {
_gobDestY = tmp;
_vm->_map->_curGoblinY = tmp;
- *_curGobVarPtr = _currentGoblin;
+ _curGobVarPtr = (uint32) _currentGoblin;
_pathExistence = 0;
_readyToAct = 0;
}
@@ -1250,172 +1198,172 @@ void Goblin::loadObjects(const char *source) {
void Goblin::saveGobDataToVars(int16 xPos, int16 yPos, int16 someVal) {
Gob_Object *obj;
- *_some0ValPtr = someVal;
- *_curGobXPosVarPtr = xPos;
- *_curGobYPosVarPtr = yPos;
- *_itemInPocketVarPtr = _itemIndInPocket;
+ _some0ValPtr = (uint32) someVal;
+ _curGobXPosVarPtr = (uint32) xPos;
+ _curGobYPosVarPtr = (uint32) yPos;
+ _itemInPocketVarPtr = (uint32) _itemIndInPocket;
obj = _goblins[_currentGoblin];
- *_curGobStateVarPtr = obj->state;
- *_curGobFrameVarPtr = obj->curFrame;
- *_curGobMultStateVarPtr = obj->multState;
- *_curGobNextStateVarPtr = obj->nextState;
- *_curGobScrXVarPtr = obj->xPos;
- *_curGobScrYVarPtr = obj->yPos;
- *_curGobLeftVarPtr = obj->left;
- *_curGobTopVarPtr = obj->top;
- *_curGobRightVarPtr = obj->right;
- *_curGobBottomVarPtr = obj->bottom;
- *_curGobDoAnimVarPtr = obj->doAnim;
- *_curGobOrderVarPtr = obj->order;
- *_curGobNoTickVarPtr = obj->noTick;
- *_curGobTypeVarPtr = obj->type;
- *_curGobMaxTickVarPtr = obj->maxTick;
- *_curGobTickVarPtr = obj->tick;
- *_curGobActStartStateVarPtr = obj->actionStartState;
- *_curGobLookDirVarPtr = obj->curLookDir;
- *_curGobPickableVarPtr = obj->pickable;
- *_curGobRelaxVarPtr = obj->relaxTime;
- *_curGobMaxFrameVarPtr = getObjMaxFrame(obj);
+ _curGobStateVarPtr = (uint32) obj->state;
+ _curGobFrameVarPtr = (uint32) obj->curFrame;
+ _curGobMultStateVarPtr = (uint32) obj->multState;
+ _curGobNextStateVarPtr = (uint32) obj->nextState;
+ _curGobScrXVarPtr = (uint32) obj->xPos;
+ _curGobScrYVarPtr = (uint32) obj->yPos;
+ _curGobLeftVarPtr = (uint32) obj->left;
+ _curGobTopVarPtr = (uint32) obj->top;
+ _curGobRightVarPtr = (uint32) obj->right;
+ _curGobBottomVarPtr = (uint32) obj->bottom;
+ _curGobDoAnimVarPtr = (uint32) obj->doAnim;
+ _curGobOrderVarPtr = (uint32) obj->order;
+ _curGobNoTickVarPtr = (uint32) obj->noTick;
+ _curGobTypeVarPtr = (uint32) obj->type;
+ _curGobMaxTickVarPtr = (uint32) obj->maxTick;
+ _curGobTickVarPtr = (uint32) obj->tick;
+ _curGobActStartStateVarPtr = (uint32) obj->actionStartState;
+ _curGobLookDirVarPtr = (uint32) obj->curLookDir;
+ _curGobPickableVarPtr = (uint32) obj->pickable;
+ _curGobRelaxVarPtr = (uint32) obj->relaxTime;
+ _curGobMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);
if (_actDestItemDesc == 0)
return;
obj = _actDestItemDesc;
- *_destItemStateVarPtr = obj->state;
- *_destItemFrameVarPtr = obj->curFrame;
- *_destItemMultStateVarPtr = obj->multState;
- *_destItemNextStateVarPtr = obj->nextState;
- *_destItemScrXVarPtr = obj->xPos;
- *_destItemScrYVarPtr = obj->yPos;
- *_destItemLeftVarPtr = obj->left;
- *_destItemTopVarPtr = obj->top;
- *_destItemRightVarPtr = obj->right;
- *_destItemBottomVarPtr = obj->bottom;
- *_destItemDoAnimVarPtr = obj->doAnim;
- *_destItemOrderVarPtr = obj->order;
- *_destItemNoTickVarPtr = obj->noTick;
- *_destItemTypeVarPtr = obj->type;
- *_destItemMaxTickVarPtr = obj->maxTick;
- *_destItemTickVarPtr = obj->tick;
- *_destItemActStartStVarPtr = obj->actionStartState;
- *_destItemLookDirVarPtr = obj->curLookDir;
- *_destItemPickableVarPtr = obj->pickable;
- *_destItemRelaxVarPtr = obj->relaxTime;
- *_destItemMaxFrameVarPtr = getObjMaxFrame(obj);
+ _destItemStateVarPtr = (uint32) obj->state;
+ _destItemFrameVarPtr = (uint32) obj->curFrame;
+ _destItemMultStateVarPtr = (uint32) obj->multState;
+ _destItemNextStateVarPtr = (uint32) obj->nextState;
+ _destItemScrXVarPtr = (uint32) obj->xPos;
+ _destItemScrYVarPtr = (uint32) obj->yPos;
+ _destItemLeftVarPtr = (uint32) obj->left;
+ _destItemTopVarPtr = (uint32) obj->top;
+ _destItemRightVarPtr = (uint32) obj->right;
+ _destItemBottomVarPtr = (uint32) obj->bottom;
+ _destItemDoAnimVarPtr = (uint32) obj->doAnim;
+ _destItemOrderVarPtr = (uint32) obj->order;
+ _destItemNoTickVarPtr = (uint32) obj->noTick;
+ _destItemTypeVarPtr = (uint32) obj->type;
+ _destItemMaxTickVarPtr = (uint32) obj->maxTick;
+ _destItemTickVarPtr = (uint32) obj->tick;
+ _destItemActStartStVarPtr = (uint32) obj->actionStartState;
+ _destItemLookDirVarPtr = (uint32) obj->curLookDir;
+ _destItemPickableVarPtr = (uint32) obj->pickable;
+ _destItemRelaxVarPtr = (uint32) obj->relaxTime;
+ _destItemMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);
_destItemState = obj->state;
_destItemType = obj->type;
}
void Goblin::initVarPointers(void) {
- _gobRetVarPtr = (int32 *)VAR_ADDRESS(59);
- _curGobStateVarPtr = (int32 *)VAR_ADDRESS(60);
- _curGobFrameVarPtr = (int32 *)VAR_ADDRESS(61);
- _curGobMultStateVarPtr = (int32 *)VAR_ADDRESS(62);
- _curGobNextStateVarPtr = (int32 *)VAR_ADDRESS(63);
- _curGobScrXVarPtr = (int32 *)VAR_ADDRESS(64);
- _curGobScrYVarPtr = (int32 *)VAR_ADDRESS(65);
- _curGobLeftVarPtr = (int32 *)VAR_ADDRESS(66);
- _curGobTopVarPtr = (int32 *)VAR_ADDRESS(67);
- _curGobRightVarPtr = (int32 *)VAR_ADDRESS(68);
- _curGobBottomVarPtr = (int32 *)VAR_ADDRESS(69);
- _curGobDoAnimVarPtr = (int32 *)VAR_ADDRESS(70);
- _curGobOrderVarPtr = (int32 *)VAR_ADDRESS(71);
- _curGobNoTickVarPtr = (int32 *)VAR_ADDRESS(72);
- _curGobTypeVarPtr = (int32 *)VAR_ADDRESS(73);
- _curGobMaxTickVarPtr = (int32 *)VAR_ADDRESS(74);
- _curGobTickVarPtr = (int32 *)VAR_ADDRESS(75);
- _curGobActStartStateVarPtr = (int32 *)VAR_ADDRESS(76);
- _curGobLookDirVarPtr = (int32 *)VAR_ADDRESS(77);
- _curGobPickableVarPtr = (int32 *)VAR_ADDRESS(80);
- _curGobRelaxVarPtr = (int32 *)VAR_ADDRESS(81);
- _destItemStateVarPtr = (int32 *)VAR_ADDRESS(82);
- _destItemFrameVarPtr = (int32 *)VAR_ADDRESS(83);
- _destItemMultStateVarPtr = (int32 *)VAR_ADDRESS(84);
- _destItemNextStateVarPtr = (int32 *)VAR_ADDRESS(85);
- _destItemScrXVarPtr = (int32 *)VAR_ADDRESS(86);
- _destItemScrYVarPtr = (int32 *)VAR_ADDRESS(87);
- _destItemLeftVarPtr = (int32 *)VAR_ADDRESS(88);
- _destItemTopVarPtr = (int32 *)VAR_ADDRESS(89);
- _destItemRightVarPtr = (int32 *)VAR_ADDRESS(90);
- _destItemBottomVarPtr = (int32 *)VAR_ADDRESS(91);
- _destItemDoAnimVarPtr = (int32 *)VAR_ADDRESS(92);
- _destItemOrderVarPtr = (int32 *)VAR_ADDRESS(93);
- _destItemNoTickVarPtr = (int32 *)VAR_ADDRESS(94);
- _destItemTypeVarPtr = (int32 *)VAR_ADDRESS(95);
- _destItemMaxTickVarPtr = (int32 *)VAR_ADDRESS(96);
- _destItemTickVarPtr = (int32 *)VAR_ADDRESS(97);
- _destItemActStartStVarPtr = (int32 *)VAR_ADDRESS(98);
- _destItemLookDirVarPtr = (int32 *)VAR_ADDRESS(99);
- _destItemPickableVarPtr = (int32 *)VAR_ADDRESS(102);
- _destItemRelaxVarPtr = (int32 *)VAR_ADDRESS(103);
- _destItemMaxFrameVarPtr = (int32 *)VAR_ADDRESS(105);
- _curGobVarPtr = (int32 *)VAR_ADDRESS(106);
- _some0ValPtr = (int32 *)VAR_ADDRESS(107);
- _curGobXPosVarPtr = (int32 *)VAR_ADDRESS(108);
- _curGobYPosVarPtr = (int32 *)VAR_ADDRESS(109);
- _curGobMaxFrameVarPtr = (int32 *)VAR_ADDRESS(110);
-
- _itemInPocketVarPtr = (int32 *)VAR_ADDRESS(114);
-
- *_itemInPocketVarPtr = -2;
+ _gobRetVarPtr.set(*_vm->_inter->_variables, 236);
+ _curGobStateVarPtr.set(*_vm->_inter->_variables, 240);
+ _curGobFrameVarPtr.set(*_vm->_inter->_variables, 244);
+ _curGobMultStateVarPtr.set(*_vm->_inter->_variables, 248);
+ _curGobNextStateVarPtr.set(*_vm->_inter->_variables, 252);
+ _curGobScrXVarPtr.set(*_vm->_inter->_variables, 256);
+ _curGobScrYVarPtr.set(*_vm->_inter->_variables, 260);
+ _curGobLeftVarPtr.set(*_vm->_inter->_variables, 264);
+ _curGobTopVarPtr.set(*_vm->_inter->_variables, 268);
+ _curGobRightVarPtr.set(*_vm->_inter->_variables, 272);
+ _curGobBottomVarPtr.set(*_vm->_inter->_variables, 276);
+ _curGobDoAnimVarPtr.set(*_vm->_inter->_variables, 280);
+ _curGobOrderVarPtr.set(*_vm->_inter->_variables, 284);
+ _curGobNoTickVarPtr.set(*_vm->_inter->_variables, 288);
+ _curGobTypeVarPtr.set(*_vm->_inter->_variables, 292);
+ _curGobMaxTickVarPtr.set(*_vm->_inter->_variables, 296);
+ _curGobTickVarPtr.set(*_vm->_inter->_variables, 300);
+ _curGobActStartStateVarPtr.set(*_vm->_inter->_variables, 304);
+ _curGobLookDirVarPtr.set(*_vm->_inter->_variables, 308);
+ _curGobPickableVarPtr.set(*_vm->_inter->_variables, 320);
+ _curGobRelaxVarPtr.set(*_vm->_inter->_variables, 324);
+ _destItemStateVarPtr.set(*_vm->_inter->_variables, 328);
+ _destItemFrameVarPtr.set(*_vm->_inter->_variables, 332);
+ _destItemMultStateVarPtr.set(*_vm->_inter->_variables, 336);
+ _destItemNextStateVarPtr.set(*_vm->_inter->_variables, 340);
+ _destItemScrXVarPtr.set(*_vm->_inter->_variables, 344);
+ _destItemScrYVarPtr.set(*_vm->_inter->_variables, 348);
+ _destItemLeftVarPtr.set(*_vm->_inter->_variables, 352);
+ _destItemTopVarPtr.set(*_vm->_inter->_variables, 356);
+ _destItemRightVarPtr.set(*_vm->_inter->_variables, 360);
+ _destItemBottomVarPtr.set(*_vm->_inter->_variables, 364);
+ _destItemDoAnimVarPtr.set(*_vm->_inter->_variables, 368);
+ _destItemOrderVarPtr.set(*_vm->_inter->_variables, 372);
+ _destItemNoTickVarPtr.set(*_vm->_inter->_variables, 376);
+ _destItemTypeVarPtr.set(*_vm->_inter->_variables, 380);
+ _destItemMaxTickVarPtr.set(*_vm->_inter->_variables, 384);
+ _destItemTickVarPtr.set(*_vm->_inter->_variables, 388);
+ _destItemActStartStVarPtr.set(*_vm->_inter->_variables, 392);
+ _destItemLookDirVarPtr.set(*_vm->_inter->_variables, 396);
+ _destItemPickableVarPtr.set(*_vm->_inter->_variables, 408);
+ _destItemRelaxVarPtr.set(*_vm->_inter->_variables, 412);
+ _destItemMaxFrameVarPtr.set(*_vm->_inter->_variables, 420);
+ _curGobVarPtr.set(*_vm->_inter->_variables, 424);
+ _some0ValPtr.set(*_vm->_inter->_variables, 428);
+ _curGobXPosVarPtr.set(*_vm->_inter->_variables, 432);
+ _curGobYPosVarPtr.set(*_vm->_inter->_variables, 436);
+ _curGobMaxFrameVarPtr.set(*_vm->_inter->_variables, 440);
+
+ _itemInPocketVarPtr.set(*_vm->_inter->_variables, 456);
+
+ _itemInPocketVarPtr = (uint32) -2;
}
void Goblin::loadGobDataFromVars(void) {
Gob_Object *obj;
- _itemIndInPocket = *_itemInPocketVarPtr;
+ _itemIndInPocket = (int32) _itemInPocketVarPtr;
obj = _goblins[_currentGoblin];
- obj->state = *_curGobStateVarPtr;
- obj->curFrame = *_curGobFrameVarPtr;
- obj->multState = *_curGobMultStateVarPtr;
- obj->nextState = *_curGobNextStateVarPtr;
- obj->xPos = *_curGobScrXVarPtr;
- obj->yPos = *_curGobScrYVarPtr;
- obj->left = *_curGobLeftVarPtr;
- obj->top = *_curGobTopVarPtr;
- obj->right = *_curGobRightVarPtr;
- obj->bottom = *_curGobBottomVarPtr;
- obj->doAnim = *_curGobDoAnimVarPtr;
- obj->order = *_curGobOrderVarPtr;
- obj->noTick = *_curGobNoTickVarPtr;
- obj->type = *_curGobTypeVarPtr;
- obj->maxTick = *_curGobMaxTickVarPtr;
- obj->tick = *_curGobTickVarPtr;
- obj->actionStartState = *_curGobActStartStateVarPtr;
- obj->curLookDir = *_curGobLookDirVarPtr;
- obj->pickable = *_curGobPickableVarPtr;
- obj->relaxTime = *_curGobRelaxVarPtr;
+ obj->state = (int32) _curGobStateVarPtr;
+ obj->curFrame = (int32) _curGobFrameVarPtr;
+ obj->multState = (int32) _curGobMultStateVarPtr;
+ obj->nextState = (int32) _curGobNextStateVarPtr;
+ obj->xPos = (int32) _curGobScrXVarPtr;
+ obj->yPos = (int32) _curGobScrYVarPtr;
+ obj->left = (int32) _curGobLeftVarPtr;
+ obj->top = (int32) _curGobTopVarPtr;
+ obj->right = (int32) _curGobRightVarPtr;
+ obj->bottom = (int32) _curGobBottomVarPtr;
+ obj->doAnim = (int32) _curGobDoAnimVarPtr;
+ obj->order = (int32) _curGobOrderVarPtr;
+ obj->noTick = (int32) _curGobNoTickVarPtr;
+ obj->type = (int32) _curGobTypeVarPtr;
+ obj->maxTick = (int32) _curGobMaxTickVarPtr;
+ obj->tick = (int32) _curGobTickVarPtr;
+ obj->actionStartState = (int32) _curGobActStartStateVarPtr;
+ obj->curLookDir = (int32) _curGobLookDirVarPtr;
+ obj->pickable = (int32) _curGobPickableVarPtr;
+ obj->relaxTime = (int32) _curGobRelaxVarPtr;
if (_actDestItemDesc == 0)
return;
obj = _actDestItemDesc;
- obj->state = *_destItemStateVarPtr;
- obj->curFrame = *_destItemFrameVarPtr;
- obj->multState = *_destItemMultStateVarPtr;
- obj->nextState = *_destItemNextStateVarPtr;
- obj->xPos = *_destItemScrXVarPtr;
- obj->yPos = *_destItemScrYVarPtr;
- obj->left = *_destItemLeftVarPtr;
- obj->top = *_destItemTopVarPtr;
- obj->right = *_destItemRightVarPtr;
- obj->bottom = *_destItemBottomVarPtr;
- obj->doAnim = *_destItemDoAnimVarPtr;
- obj->order = *_destItemOrderVarPtr;
- obj->noTick = *_destItemNoTickVarPtr;
- obj->type = *_destItemTypeVarPtr;
- obj->maxTick = *_destItemMaxTickVarPtr;
- obj->tick = *_destItemTickVarPtr;
- obj->actionStartState = *_destItemActStartStVarPtr;
- obj->curLookDir = *_destItemLookDirVarPtr;
- obj->pickable = *_destItemPickableVarPtr;
- obj->relaxTime = *_destItemRelaxVarPtr;
+ obj->state = (int32) _destItemStateVarPtr;
+ obj->curFrame = (int32) _destItemFrameVarPtr;
+ obj->multState = (int32) _destItemMultStateVarPtr;
+ obj->nextState = (int32) _destItemNextStateVarPtr;
+ obj->xPos = (int32) _destItemScrXVarPtr;
+ obj->yPos = (int32) _destItemScrYVarPtr;
+ obj->left = (int32) _destItemLeftVarPtr;
+ obj->top = (int32) _destItemTopVarPtr;
+ obj->right = (int32) _destItemRightVarPtr;
+ obj->bottom = (int32) _destItemBottomVarPtr;
+ obj->doAnim = (int32) _destItemDoAnimVarPtr;
+ obj->order = (int32) _destItemOrderVarPtr;
+ obj->noTick = (int32) _destItemNoTickVarPtr;
+ obj->type = (int32) _destItemTypeVarPtr;
+ obj->maxTick = (int32) _destItemMaxTickVarPtr;
+ obj->tick = (int32) _destItemTickVarPtr;
+ obj->actionStartState = (int32) _destItemActStartStVarPtr;
+ obj->curLookDir = (int32) _destItemLookDirVarPtr;
+ obj->pickable = (int32) _destItemPickableVarPtr;
+ obj->relaxTime = (int32) _destItemRelaxVarPtr;
if (obj->type != _destItemType)
obj->toRedraw = 1;
diff --git a/engines/gob/goblin.h b/engines/gob/goblin.h
index 3fd8a9f93b..2100bcbdac 100644
--- a/engines/gob/goblin.h
+++ b/engines/gob/goblin.h
@@ -28,6 +28,7 @@
#include "gob/util.h"
#include "gob/mult.h"
+#include "gob/variables.h"
#include "gob/sound/sounddesc.h"
namespace Gob {
@@ -115,57 +116,57 @@ public:
char _pathExistence;
// Pointers to interpreter variables
- int32 *_some0ValPtr;
-
- int32 *_gobRetVarPtr;
- int32 *_curGobVarPtr;
- int32 *_curGobXPosVarPtr;
- int32 *_curGobYPosVarPtr;
- int32 *_itemInPocketVarPtr;
-
- int32 *_curGobStateVarPtr;
- int32 *_curGobFrameVarPtr;
- int32 *_curGobMultStateVarPtr;
- int32 *_curGobNextStateVarPtr;
- int32 *_curGobScrXVarPtr;
- int32 *_curGobScrYVarPtr;
- int32 *_curGobLeftVarPtr;
- int32 *_curGobTopVarPtr;
- int32 *_curGobRightVarPtr;
- int32 *_curGobBottomVarPtr;
- int32 *_curGobDoAnimVarPtr;
- int32 *_curGobOrderVarPtr;
- int32 *_curGobNoTickVarPtr;
- int32 *_curGobTypeVarPtr;
- int32 *_curGobMaxTickVarPtr;
- int32 *_curGobTickVarPtr;
- int32 *_curGobActStartStateVarPtr;
- int32 *_curGobLookDirVarPtr;
- int32 *_curGobPickableVarPtr;
- int32 *_curGobRelaxVarPtr;
- int32 *_curGobMaxFrameVarPtr;
-
- int32 *_destItemStateVarPtr;
- int32 *_destItemFrameVarPtr;
- int32 *_destItemMultStateVarPtr;
- int32 *_destItemNextStateVarPtr;
- int32 *_destItemScrXVarPtr;
- int32 *_destItemScrYVarPtr;
- int32 *_destItemLeftVarPtr;
- int32 *_destItemTopVarPtr;
- int32 *_destItemRightVarPtr;
- int32 *_destItemBottomVarPtr;
- int32 *_destItemDoAnimVarPtr;
- int32 *_destItemOrderVarPtr;
- int32 *_destItemNoTickVarPtr;
- int32 *_destItemTypeVarPtr;
- int32 *_destItemMaxTickVarPtr;
- int32 *_destItemTickVarPtr;
- int32 *_destItemActStartStVarPtr;
- int32 *_destItemLookDirVarPtr;
- int32 *_destItemPickableVarPtr;
- int32 *_destItemRelaxVarPtr;
- int32 *_destItemMaxFrameVarPtr;
+ VariableReference _some0ValPtr;
+
+ VariableReference _gobRetVarPtr;
+ VariableReference _curGobVarPtr;
+ VariableReference _curGobXPosVarPtr;
+ VariableReference _curGobYPosVarPtr;
+ VariableReference _itemInPocketVarPtr;
+
+ VariableReference _curGobStateVarPtr;
+ VariableReference _curGobFrameVarPtr;
+ VariableReference _curGobMultStateVarPtr;
+ VariableReference _curGobNextStateVarPtr;
+ VariableReference _curGobScrXVarPtr;
+ VariableReference _curGobScrYVarPtr;
+ VariableReference _curGobLeftVarPtr;
+ VariableReference _curGobTopVarPtr;
+ VariableReference _curGobRightVarPtr;
+ VariableReference _curGobBottomVarPtr;
+ VariableReference _curGobDoAnimVarPtr;
+ VariableReference _curGobOrderVarPtr;
+ VariableReference _curGobNoTickVarPtr;
+ VariableReference _curGobTypeVarPtr;
+ VariableReference _curGobMaxTickVarPtr;
+ VariableReference _curGobTickVarPtr;
+ VariableReference _curGobActStartStateVarPtr;
+ VariableReference _curGobLookDirVarPtr;
+ VariableReference _curGobPickableVarPtr;
+ VariableReference _curGobRelaxVarPtr;
+ VariableReference _curGobMaxFrameVarPtr;
+
+ VariableReference _destItemStateVarPtr;
+ VariableReference _destItemFrameVarPtr;
+ VariableReference _destItemMultStateVarPtr;
+ VariableReference _destItemNextStateVarPtr;
+ VariableReference _destItemScrXVarPtr;
+ VariableReference _destItemScrYVarPtr;
+ VariableReference _destItemLeftVarPtr;
+ VariableReference _destItemTopVarPtr;
+ VariableReference _destItemRightVarPtr;
+ VariableReference _destItemBottomVarPtr;
+ VariableReference _destItemDoAnimVarPtr;
+ VariableReference _destItemOrderVarPtr;
+ VariableReference _destItemNoTickVarPtr;
+ VariableReference _destItemTypeVarPtr;
+ VariableReference _destItemMaxTickVarPtr;
+ VariableReference _destItemTickVarPtr;
+ VariableReference _destItemActStartStVarPtr;
+ VariableReference _destItemLookDirVarPtr;
+ VariableReference _destItemPickableVarPtr;
+ VariableReference _destItemRelaxVarPtr;
+ VariableReference _destItemMaxFrameVarPtr;
int16 _destItemType;
int16 _destItemState;
diff --git a/engines/gob/goblin_v2.cpp b/engines/gob/goblin_v2.cpp
index 9144e35070..d763aeb01c 100644
--- a/engines/gob/goblin_v2.cpp
+++ b/engines/gob/goblin_v2.cpp
@@ -88,7 +88,7 @@ void Goblin_v2::placeObject(Gob_Object *objDesc, char animated,
(_vm->_scenery->_animBottom - _vm->_scenery->_animTop) - (y + 1) / 2;
*obj->pPosX = x * _vm->_map->_tilesWidth;
} else {
- if (obj->goblinStates[state] != 0) {
+ if ((obj->goblinStates != 0) && (obj->goblinStates[state] != 0)) {
layer = obj->goblinStates[state][0].layer;
animation = obj->goblinStates[state][0].animation;
objAnim->state = state;
diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp
index 9c39653a1d..02e7f99cbd 100644
--- a/engines/gob/inter.cpp
+++ b/engines/gob/inter.cpp
@@ -212,25 +212,35 @@ void Inter::funcBlock(int16 retFlag) {
break;
// WORKAROUND:
- // The EGA version of gob1 doesn't add a delay after showing
+ // The EGA and Mac versions of gob1 doesn't add a delay after showing
// images between levels. We manually add it here.
- if ((_vm->getGameType() == kGameTypeGob1) && _vm->isEGA()) {
+ if ((_vm->getGameType() == kGameTypeGob1) &&
+ (_vm->isEGA() || (_vm->getPlatform() == Common::kPlatformMacintosh))) {
+
int addr = _vm->_global->_inter_execPtr-_vm->_game->_totFileData;
- if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie
+
+ if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) ||
+ (startaddr == 0x188D && addr == 0x1A58 && // Zombie, Mac
!strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) ||
(startaddr == 0x1299 && addr == 0x139A && // Dungeon
!strncmp(_vm->_game->_curTotFile, "avt006.tot", 10)) ||
- (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron
+ (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) ||
+ (startaddr == 0x11C8 && addr == 0x1341 && // Cauldron, Mac
!strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) ||
(startaddr == 0x09F2 && addr == 0x0AF3 && // Statue
!strncmp(_vm->_game->_curTotFile, "avt016.tot", 10)) ||
(startaddr == 0x0B92 && addr == 0x0C93 && // Castle
!strncmp(_vm->_game->_curTotFile, "avt019.tot", 10)) ||
- (startaddr == 0x17D9 && addr == 0x18DA && // Finale
+ (startaddr == 0x17D9 && addr == 0x18DA && // Finale, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10)) ||
+ (startaddr == 0x17E9 && addr == 0x19A8 && // Finale, Mac
!strncmp(_vm->_game->_curTotFile, "avt022.tot", 10))) {
_vm->_util->longDelay(5000);
}
+
} // End of workaround
cmd = *_vm->_global->_inter_execPtr;
@@ -286,9 +296,7 @@ void Inter::callSub(int16 retFlag) {
}
void Inter::allocateVars(uint32 count) {
- if ((_vm->getPlatform() == Common::kPlatformAmiga) ||
- (_vm->getPlatform() == Common::kPlatformMacintosh) ||
- (_vm->getPlatform() == Common::kPlatformAtariST))
+ if (_vm->getEndianness() == kEndiannessBE)
_variables = new VariablesBE(count * 4);
else
_variables = new VariablesLE(count * 4);
diff --git a/engines/gob/inter.h b/engines/gob/inter.h
index 60b3974d6d..b684be6c07 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -79,7 +79,7 @@ protected:
};
struct OpGobParams {
int16 extraData;
- int32 *retVarPtr;
+ VariableReference retVarPtr;
Goblin::Gob_Object *objDesc;
};
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index e2b8d65112..865d188a2e 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -912,12 +912,21 @@ void Inter_v1::o1_initMult() {
animDataVar = _vm->_parse->parseVarIndex();
if (_vm->_mult->_objects && (oldObjCount != _vm->_mult->_objCount)) {
+
warning("Initializing new objects without having "
"cleaned up the old ones at first");
+
+ for (int i = 0; i < _vm->_mult->_objCount; i++) {
+ delete _vm->_mult->_objects[i].pPosX;
+ delete _vm->_mult->_objects[i].pPosY;
+ }
+
delete[] _vm->_mult->_objects;
delete[] _vm->_mult->_renderData;
+
_vm->_mult->_objects = 0;
_vm->_mult->_renderObjs = 0;
+
}
if (_vm->_mult->_objects == 0) {
@@ -933,8 +942,8 @@ void Inter_v1::o1_initMult() {
uint32 offPosY = i * 4 + (posYVar / 4) * 4;
uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize;
- _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX);
- _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY);
+ _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX);
+ _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);
_vm->_mult->_objects[i].pAnimData =
(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim,
@@ -1774,7 +1783,7 @@ bool Inter_v1::o1_goblinFunc(OpFuncParams &params) {
gobParams.extraData = 0;
gobParams.objDesc = 0;
- gobParams.retVarPtr = (int32 *) VAR_ADDRESS(59);
+ gobParams.retVarPtr.set(*_vm->_inter->_variables, 236);
cmd = load16();
_vm->_global->_inter_execPtr += 2;
@@ -2268,49 +2277,49 @@ bool Inter_v1::o1_manageDataFile(OpFuncParams &params) {
void Inter_v1::o1_setState(OpGobParams &params) {
params.objDesc->state = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setCurFrame(OpGobParams &params) {
params.objDesc->curFrame = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemFrameVarPtr = params.extraData;
+ _vm->_goblin->_destItemFrameVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setNextState(OpGobParams &params) {
params.objDesc->nextState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemNextStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemNextStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setMultState(OpGobParams &params) {
params.objDesc->multState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemMultStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemMultStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setOrder(OpGobParams &params) {
params.objDesc->order = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemOrderVarPtr = params.extraData;
+ _vm->_goblin->_destItemOrderVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setActionStartState(OpGobParams &params) {
params.objDesc->actionStartState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemActStartStVarPtr = params.extraData;
+ _vm->_goblin->_destItemActStartStVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setCurLookDir(OpGobParams &params) {
params.objDesc->curLookDir = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemLookDirVarPtr = params.extraData;
+ _vm->_goblin->_destItemLookDirVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setType(OpGobParams &params) {
params.objDesc->type = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemTypeVarPtr = params.extraData;
+ _vm->_goblin->_destItemTypeVarPtr = (uint32) params.extraData;
if (params.extraData == 0)
params.objDesc->toRedraw = 1;
@@ -2319,107 +2328,107 @@ void Inter_v1::o1_setType(OpGobParams &params) {
void Inter_v1::o1_setNoTick(OpGobParams &params) {
params.objDesc->noTick = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemNoTickVarPtr = params.extraData;
+ _vm->_goblin->_destItemNoTickVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setPickable(OpGobParams &params) {
params.objDesc->pickable = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemPickableVarPtr = params.extraData;
+ _vm->_goblin->_destItemPickableVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setXPos(OpGobParams &params) {
params.objDesc->xPos = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemScrXVarPtr = params.extraData;
+ _vm->_goblin->_destItemScrXVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setYPos(OpGobParams &params) {
params.objDesc->yPos = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemScrYVarPtr = params.extraData;
+ _vm->_goblin->_destItemScrYVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setDoAnim(OpGobParams &params) {
params.objDesc->doAnim = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemDoAnimVarPtr = params.extraData;
+ _vm->_goblin->_destItemDoAnimVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setRelaxTime(OpGobParams &params) {
params.objDesc->relaxTime = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemRelaxVarPtr = params.extraData;
+ _vm->_goblin->_destItemRelaxVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setMaxTick(OpGobParams &params) {
params.objDesc->maxTick = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemMaxTickVarPtr = params.extraData;
+ _vm->_goblin->_destItemMaxTickVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_getState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->state;
+ params.retVarPtr = (uint32) params.objDesc->state;
}
void Inter_v1::o1_getCurFrame(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->curFrame;
+ params.retVarPtr = (uint32) params.objDesc->curFrame;
}
void Inter_v1::o1_getNextState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->nextState;
+ params.retVarPtr = (uint32) params.objDesc->nextState;
}
void Inter_v1::o1_getMultState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->multState;
+ params.retVarPtr = (uint32) params.objDesc->multState;
}
void Inter_v1::o1_getOrder(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->order;
+ params.retVarPtr = (uint32) params.objDesc->order;
}
void Inter_v1::o1_getActionStartState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->actionStartState;
+ params.retVarPtr = (uint32) params.objDesc->actionStartState;
}
void Inter_v1::o1_getCurLookDir(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->curLookDir;
+ params.retVarPtr = (uint32) params.objDesc->curLookDir;
}
void Inter_v1::o1_getType(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->type;
+ params.retVarPtr = (uint32) params.objDesc->type;
}
void Inter_v1::o1_getNoTick(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->noTick;
+ params.retVarPtr = (uint32) params.objDesc->noTick;
}
void Inter_v1::o1_getPickable(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->pickable;
+ params.retVarPtr = (uint32) params.objDesc->pickable;
}
void Inter_v1::o1_getObjMaxFrame(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->getObjMaxFrame(params.objDesc);
+ params.retVarPtr = (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);
}
void Inter_v1::o1_getXPos(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->xPos;
+ params.retVarPtr = (uint32) params.objDesc->xPos;
}
void Inter_v1::o1_getYPos(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->yPos;
+ params.retVarPtr = (uint32) params.objDesc->yPos;
}
void Inter_v1::o1_getDoAnim(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->doAnim;
+ params.retVarPtr = (uint32) params.objDesc->doAnim;
}
void Inter_v1::o1_getRelaxTime(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->relaxTime;
+ params.retVarPtr = (uint32) params.objDesc->relaxTime;
}
void Inter_v1::o1_getMaxTick(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->maxTick;
+ params.retVarPtr = (uint32) params.objDesc->maxTick;
}
void Inter_v1::o1_manipulateMap(OpGobParams &params) {
@@ -2435,9 +2444,9 @@ void Inter_v1::o1_getItem(OpGobParams &params) {
int16 yPos = load16();
if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0)
- *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8;
+ params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);
else
- *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos];
+ params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];
}
void Inter_v1::o1_manipulateMapIndirect(OpGobParams &params) {
@@ -2460,9 +2469,9 @@ void Inter_v1::o1_getItemIndirect(OpGobParams &params) {
yPos = VAR(yPos);
if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0)
- *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8;
+ params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);
else
- *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos];
+ params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];
}
void Inter_v1::o1_setPassMap(OpGobParams &params) {
@@ -2500,11 +2509,11 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams &params) {
params.objDesc->curFrame = 0;
params.objDesc->state = 21;
if (_vm->_goblin->_currentGoblin == item) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 18;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 18;
_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;
_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y;
}
@@ -2512,12 +2521,12 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams &params) {
void Inter_v1::o1_getGoblinPosXH(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].x >> 1;
+ params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].x >> 1);
}
void Inter_v1::o1_getGoblinPosYH(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].y >> 1;
+ params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].y >> 1);
}
void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
@@ -2539,14 +2548,14 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
params.objDesc->xPos = animLayer->posX;
params.objDesc->yPos = animLayer->posY;
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_curGobNextStateVarPtr = params.objDesc->nextState;
- *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState;
- *_vm->_goblin->_curGobMaxFrameVarPtr =
- _vm->_goblin->getObjMaxFrame(params.objDesc);
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_curGobNextStateVarPtr = (uint32) params.objDesc->nextState;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState;
+ _vm->_goblin->_curGobMaxFrameVarPtr =
+ (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);
_vm->_goblin->_noPick = 1;
return;
}
@@ -2573,12 +2582,12 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
_vm->_goblin->_pressedMapY = yPos;
_vm->_map->_curGoblinY = yPos;
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 21;
- *_vm->_goblin->_curGobNextStateVarPtr = 21;
- *_vm->_goblin->_curGobMultStateVarPtr = -1;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 21;
+ _vm->_goblin->_curGobNextStateVarPtr = 21;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) -1;
_vm->_goblin->_noPick = 0;
}
@@ -2598,11 +2607,11 @@ void Inter_v1::o1_setItemIndInPocket(OpGobParams &params) {
}
void Inter_v1::o1_getItemIdInPocket(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->_itemIdInPocket;
+ params.retVarPtr = (uint32) _vm->_goblin->_itemIdInPocket;
}
void Inter_v1::o1_getItemIndInPocket(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->_itemIndInPocket;
+ params.retVarPtr = (uint32) _vm->_goblin->_itemIndInPocket;
}
void Inter_v1::o1_setGoblinPos(OpGobParams &params) {
@@ -2632,10 +2641,10 @@ void Inter_v1::o1_setGoblinPos(OpGobParams &params) {
params.objDesc->state = 21;
if (_vm->_goblin->_currentGoblin == item) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 18;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 18;
_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;
_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y;
@@ -2659,11 +2668,11 @@ void Inter_v1::o1_setGoblinState(OpGobParams &params) {
params.objDesc->yPos = animLayer->posY;
if (item == _vm->_goblin->_currentGoblin) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState;
}
}
@@ -2686,13 +2695,13 @@ void Inter_v1::o1_setGoblinStateRedraw(OpGobParams &params) {
params.objDesc->toRedraw = 1;
params.objDesc->type = 0;
if (params.objDesc == _vm->_goblin->_actDestItemDesc) {
- *_vm->_goblin->_destItemScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_destItemScrYVarPtr = params.objDesc->yPos;
+ _vm->_goblin->_destItemScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_destItemScrYVarPtr = (uint32) params.objDesc->yPos;
- *_vm->_goblin->_destItemStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_destItemNextStateVarPtr = -1;
- *_vm->_goblin->_destItemMultStateVarPtr = -1;
- *_vm->_goblin->_destItemFrameVarPtr = 0;
+ _vm->_goblin->_destItemStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_destItemNextStateVarPtr = (uint32) -1;
+ _vm->_goblin->_destItemMultStateVarPtr = (uint32) -1;
+ _vm->_goblin->_destItemFrameVarPtr = 0;
}
}
@@ -2712,12 +2721,12 @@ void Inter_v1::o1_decRelaxTime(OpGobParams &params) {
void Inter_v1::o1_getGoblinPosX(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].x;
+ params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].x;
}
void Inter_v1::o1_getGoblinPosY(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].y;
+ params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].y;
}
void Inter_v1::o1_clearPathExistence(OpGobParams &params) {
@@ -2741,9 +2750,9 @@ void Inter_v1::o1_getObjectIntersect(OpGobParams &params) {
params.objDesc = _vm->_goblin->_objects[params.extraData];
if (_vm->_goblin->objIntersected(params.objDesc,
_vm->_goblin->_goblins[item]))
- *params.retVarPtr = 1;
+ params.retVarPtr = 1;
else
- *params.retVarPtr = 0;
+ params.retVarPtr = 0;
}
void Inter_v1::o1_getGoblinIntersect(OpGobParams &params) {
@@ -2753,9 +2762,9 @@ void Inter_v1::o1_getGoblinIntersect(OpGobParams &params) {
params.objDesc = _vm->_goblin->_goblins[params.extraData];
if (_vm->_goblin->objIntersected(params.objDesc,
_vm->_goblin->_goblins[item]))
- *params.retVarPtr = 1;
+ params.retVarPtr = 1;
else
- *params.retVarPtr = 0;
+ params.retVarPtr = 0;
}
void Inter_v1::o1_setItemPos(OpGobParams &params) {
@@ -2886,7 +2895,7 @@ void Inter_v1::o1_initGoblin(OpGobParams &params) {
_vm->_map->_destY = _vm->_goblin->_gobPositions[0].y;
_vm->_goblin->_gobDestY = _vm->_goblin->_gobPositions[0].y;
- *_vm->_goblin->_curGobVarPtr = 0;
+ _vm->_goblin->_curGobVarPtr = 0;
_vm->_goblin->_pathExistence = 0;
_vm->_goblin->_readyToAct = 0;
}
diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp
index d8c33fcce6..2f1d2ec0be 100644
--- a/engines/gob/inter_v2.cpp
+++ b/engines/gob/inter_v2.cpp
@@ -880,9 +880,15 @@ void Inter_v2::o2_initMult() {
_vm->_mult->clearObjectVideos();
+ for (int i = 0; i < _vm->_mult->_objCount; i++) {
+ delete _vm->_mult->_objects[i].pPosX;
+ delete _vm->_mult->_objects[i].pPosY;
+ }
+
delete[] _vm->_mult->_objects;
delete[] _vm->_mult->_renderObjs;
delete[] _vm->_mult->_orderArray;
+
_vm->_mult->_objects = 0;
_vm->_mult->_renderObjs = 0;
_vm->_mult->_orderArray = 0;
@@ -907,8 +913,8 @@ void Inter_v2::o2_initMult() {
uint32 offPosY = i * 4 + (posYVar / 4) * 4;
uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize;
- _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX);
- _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY);
+ _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX);
+ _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);
_vm->_mult->_objects[i].pAnimData =
(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim,
@@ -1046,7 +1052,7 @@ void Inter_v2::o2_loadMultObject() {
} else if ((objAnim.animType != 100) && (objAnim.animType != 101)) {
- if ((*(obj.pPosX) == -1234) && (*(obj.pPosY) == -4321)) {
+ if ((((int32) *(obj.pPosX)) == -1234) && (((int32) *(obj.pPosY)) == -4321)) {
if (obj.videoSlot > 0)
_vm->_vidPlayer->slotClose(obj.videoSlot - 1);
diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp
index 3d6a7942f9..b9373d48b3 100644
--- a/engines/gob/mult.cpp
+++ b/engines/gob/mult.cpp
@@ -93,12 +93,18 @@ Mult::Mult(GobEngine *vm) : _vm(vm) {
}
Mult::~Mult() {
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _orderArray;
delete[] _renderData;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
delete _multData;
}
@@ -123,6 +129,12 @@ void Mult::freeAll(void) {
void Mult::freeMult() {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
delete[] _renderObjs;
@@ -203,11 +215,17 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,
if (_animDataAllocated) {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
delete[] _orderArray;
diff --git a/engines/gob/mult.h b/engines/gob/mult.h
index aaf2e2826c..3bb3af17b3 100644
--- a/engines/gob/mult.h
+++ b/engines/gob/mult.h
@@ -27,6 +27,7 @@
#define GOB_MULT_H
#include "gob/video.h"
+#include "gob/variables.h"
namespace Gob {
@@ -77,8 +78,8 @@ public:
} PACKED_STRUCT;
struct Mult_Object {
- int32 *pPosX;
- int32 *pPosY;
+ VariableReference *pPosX;
+ VariableReference *pPosY;
Mult_AnimData *pAnimData;
int16 tick;
int16 lastLeft;
@@ -267,8 +268,8 @@ protected:
bool _doPalSubst;
- int32 *_animArrayX;
- int32 *_animArrayY;
+ Variables *_animArrayX;
+ Variables *_animArrayY;
Mult_AnimData *_animArrayData;
int16 _palKeyIndex;
diff --git a/engines/gob/mult_v1.cpp b/engines/gob/mult_v1.cpp
index 22683437e7..a369e7d297 100644
--- a/engines/gob/mult_v1.cpp
+++ b/engines/gob/mult_v1.cpp
@@ -216,10 +216,16 @@ void Mult_v1::freeMultKeys() {
if (_animDataAllocated) {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = 0;
@@ -263,6 +269,14 @@ void Mult_v1::playMultInit() {
_oldPalette = _vm->_global->_pPaletteDesc->vgaPal;
if (!_animSurf) {
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
+ delete[] _objects;
+
_vm->_util->setFrameRate(_multData->frameRate);
_animTop = 0;
_animLeft = 0;
@@ -270,30 +284,27 @@ void Mult_v1::playMultInit() {
_animHeight = 200;
_objCount = 4;
- delete[] _objects;
delete[] _renderData;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = new Mult_Object[_objCount];
_renderData = new int16[9 * _objCount];
- _animArrayX = new int32[_objCount];
- _animArrayY = new int32[_objCount];
+ _animArrayX = new VariablesLE(_objCount * 4);
+ _animArrayY = new VariablesLE(_objCount * 4);
_animArrayData = new Mult_AnimData[_objCount];
memset(_objects, 0, _objCount * sizeof(Mult_Object));
memset(_renderData, 0, _objCount * 9 * sizeof(int16));
- memset(_animArrayX, 0, _objCount * sizeof(int32));
- memset(_animArrayY, 0, _objCount * sizeof(int32));
memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));
for (_counter = 0; _counter < _objCount; _counter++) {
Mult_Object &multObj = _objects[_counter];
Mult_AnimData &animData = _animArrayData[_counter];
- multObj.pPosX = (int32 *) &_animArrayX[_counter];
- multObj.pPosY = (int32 *) &_animArrayY[_counter];
+ multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
+ multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
multObj.pAnimData = &animData;
animData.isStatic = 1;
diff --git a/engines/gob/mult_v2.cpp b/engines/gob/mult_v2.cpp
index 3a83ac1867..20a81174e5 100644
--- a/engines/gob/mult_v2.cpp
+++ b/engines/gob/mult_v2.cpp
@@ -329,8 +329,8 @@ void Mult_v2::freeMultKeys() {
if (_animDataAllocated) {
freeMult();
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_animArrayX = 0;
@@ -510,6 +510,13 @@ void Mult_v2::playMultInit() {
if (!_animSurf) {
int16 width, height;
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
+ delete[] _objects;
+
_vm->_util->setFrameRate(_multData->frameRate);
_animTop = 0;
_animLeft = 0;
@@ -517,33 +524,30 @@ void Mult_v2::playMultInit() {
_animHeight = _vm->_video->_surfHeight;
_objCount = 4;
- delete[] _objects;
delete[] _orderArray;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = new Mult_Object[_objCount];
_orderArray = new int8[_objCount];
_renderObjs = new Mult_Object*[_objCount];
- _animArrayX = new int32[_objCount];
- _animArrayY = new int32[_objCount];
+ _animArrayX = new VariablesLE(_objCount * 4);
+ _animArrayY = new VariablesLE(_objCount * 4);
_animArrayData = new Mult_AnimData[_objCount];
memset(_objects, 0, _objCount * sizeof(Mult_Object));
memset(_orderArray, 0, _objCount * sizeof(int8));
memset(_renderObjs, 0, _objCount * sizeof(Mult_Object *));
- memset(_animArrayX, 0, _objCount * sizeof(int32));
- memset(_animArrayY, 0, _objCount * sizeof(int32));
memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));
for (_counter = 0; _counter < _objCount; _counter++) {
Mult_Object &multObj = _objects[_counter];
Mult_AnimData &animData = _animArrayData[_counter];
- multObj.pPosX = (int32 *) &_animArrayX[_counter];
- multObj.pPosY = (int32 *) &_animArrayY[_counter];
+ multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
+ multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
multObj.pAnimData = &animData;
animData.isStatic = 1;
diff --git a/engines/gob/saveload.cpp b/engines/gob/saveload.cpp
index 2788716858..fa9f8ea7a9 100644
--- a/engines/gob/saveload.cpp
+++ b/engines/gob/saveload.cpp
@@ -153,7 +153,7 @@ bool TempSprite::fromBuffer(const byte *buffer, int32 size, bool palette) {
}
-PlainSave::PlainSave() {
+PlainSave::PlainSave(Endianness endianness) : _endianness(endianness) {
}
PlainSave::~PlainSave() {
@@ -230,7 +230,8 @@ bool PlainSave::save(int16 dataVar, int32 size, int32 offset, const char *name,
}
bool retVal;
- retVal = SaveLoad::saveDataEndian(*out, dataVar, size, variables, variableSizes);
+ retVal = SaveLoad::saveDataEndian(*out, dataVar, size,
+ variables, variableSizes, _endianness);
out->finalize();
if (out->ioFailed()) {
@@ -258,13 +259,14 @@ bool PlainSave::load(int16 dataVar, int32 size, int32 offset, const char *name,
return false;
}
- bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, variables, variableSizes);
+ bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size,
+ variables, variableSizes, _endianness);
delete in;
return retVal;
}
-StagedSave::StagedSave() {
+StagedSave::StagedSave(Endianness endianness) : _endianness(endianness) {
_mode = kModeNone;
_name = 0;
_loaded = false;
@@ -487,7 +489,7 @@ bool StagedSave::write() const {
} else
result = SaveLoad::saveDataEndian(*out, 0, _stages[i].size,
- _stages[i].bufVar, _stages[i].bufVarSizes);
+ _stages[i].bufVar, _stages[i].bufVarSizes, _endianness);
}
if (result) {
@@ -533,7 +535,7 @@ bool StagedSave::read() {
_stages[i].bufVarSizes = new byte[_stages[i].size];
result = SaveLoad::loadDataEndian(*in, 0, _stages[i].size,
- _stages[i].bufVar, _stages[i].bufVarSizes);
+ _stages[i].bufVar, _stages[i].bufVarSizes, _endianness);
}
}
@@ -734,12 +736,14 @@ void SaveLoad::buildIndex(byte *buffer, char *name, int n, int32 size, int32 off
}
}
-bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {
+bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) {
+ bool LE = (endianness == kEndiannessLE);
+
while (count-- > 0) {
if (*sizes == 3)
- *((uint32 *) buf) = READ_LE_UINT32(buf);
+ *((uint32 *) buf) = LE ? READ_LE_UINT32(buf) : READ_BE_UINT32(buf);
else if (*sizes == 1)
- *((uint16 *) buf) = READ_LE_UINT16(buf);
+ *((uint16 *) buf) = LE ? READ_LE_UINT16(buf) : READ_BE_UINT16(buf);
else if (*sizes != 0) {
warning("SaveLoad::fromEndian(): Corrupted variables sizes");
return false;
@@ -753,12 +757,19 @@ bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {
return true;
}
-bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count) {
+bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) {
while (count-- > 0) {
- if (*sizes == 3)
- WRITE_LE_UINT32(buf, *((uint32 *) buf));
- else if (*sizes == 1)
- WRITE_LE_UINT16(buf, *((uint16 *) buf));
+ if (*sizes == 3) {
+ if (endianness == kEndiannessLE)
+ WRITE_LE_UINT32(buf, *((uint32 *) buf));
+ else
+ WRITE_BE_UINT32(buf, *((uint32 *) buf));
+ } else if (*sizes == 1) {
+ if (endianness == kEndiannessLE)
+ WRITE_LE_UINT16(buf, *((uint16 *) buf));
+ else
+ WRITE_BE_UINT16(buf, *((uint16 *) buf));
+ }
else if (*sizes != 0) {
warning("SaveLoad::toEndian(): Corrupted variables sizes");
return false;
@@ -811,7 +822,8 @@ uint32 SaveLoad::write(Common::WriteStream &out,
}
bool SaveLoad::loadDataEndian(Common::ReadStream &in,
- int16 dataVar, uint32 size, byte *variables, byte *variableSizes) {
+ int16 dataVar, uint32 size,
+ byte *variables, byte *variableSizes, Endianness endianness) {
bool retVal = false;
@@ -821,7 +833,7 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,
assert(varBuf && sizeBuf);
if (read(in, varBuf, sizeBuf, size) == size) {
- if (fromEndian(varBuf, sizeBuf, size)) {
+ if (fromEndian(varBuf, sizeBuf, size, endianness)) {
memcpy(variables + dataVar, varBuf, size);
memcpy(variableSizes + dataVar, sizeBuf, size);
retVal = true;
@@ -835,7 +847,8 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,
}
bool SaveLoad::saveDataEndian(Common::WriteStream &out,
- int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes) {
+ int16 dataVar, uint32 size,
+ const byte *variables, const byte *variableSizes, Endianness endianness) {
bool retVal = false;
@@ -847,7 +860,7 @@ bool SaveLoad::saveDataEndian(Common::WriteStream &out,
memcpy(varBuf, variables + dataVar, size);
memcpy(sizeBuf, variableSizes + dataVar, size);
- if (toEndian(varBuf, sizeBuf, size))
+ if (toEndian(varBuf, sizeBuf, size, endianness))
if (write(out, varBuf, sizeBuf, size) == size)
retVal = true;
diff --git a/engines/gob/saveload.h b/engines/gob/saveload.h
index 29f7ee2594..52c3a9b260 100644
--- a/engines/gob/saveload.h
+++ b/engines/gob/saveload.h
@@ -65,7 +65,7 @@ private:
class PlainSave {
public:
- PlainSave();
+ PlainSave(Endianness endianness);
~PlainSave();
bool save(int16 dataVar, int32 size, int32 offset, const char *name,
@@ -77,11 +77,14 @@ public:
const byte *variables, const byte *variableSizes) const;
bool load(int16 dataVar, int32 size, int32 offset, const char *name,
byte *variables, byte *variableSizes) const;
+
+private:
+ Endianness _endianness;
};
class StagedSave {
public:
- StagedSave();
+ StagedSave(Endianness endianness);
~StagedSave();
void addStage(int32 size, bool endianed = true);
@@ -114,6 +117,8 @@ private:
kModeLoad
};
+ Endianness _endianness;
+
Common::Array<Stage> _stages;
enum Mode _mode;
char *_name;
@@ -178,17 +183,19 @@ public:
static const char *stripPath(const char *fileName);
- static bool fromEndian(byte *buf, const byte *sizes, uint32 count);
- static bool toEndian(byte *buf, const byte *sizes, uint32 count);
+ static bool fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness);
+ static bool toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness);
static uint32 read(Common::ReadStream &in,
byte *buf, byte *sizes, uint32 count);
static uint32 write(Common::WriteStream &out,
const byte *buf, const byte *sizes, uint32 count);
static bool loadDataEndian(Common::ReadStream &in,
- int16 dataVar, uint32 size, byte *variables, byte *variableSizes);
+ int16 dataVar, uint32 size,
+ byte *variables, byte *variableSizes, Endianness endianness);
static bool saveDataEndian(Common::WriteStream &out,
- int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes);
+ int16 dataVar, uint32 size,
+ const byte *variables, const byte *variableSizes, Endianness endianness);
protected:
GobEngine *_vm;
@@ -228,8 +235,8 @@ protected:
int32 _varSize;
TempSprite _tmpSprite;
- PlainSave _notes;
- StagedSave _save;
+ PlainSave *_notes;
+ StagedSave *_save;
byte _indexBuffer[600];
bool _hasIndex;
@@ -306,8 +313,8 @@ protected:
TempSprite _screenshot;
TempSprite _tmpSprite;
- PlainSave _notes;
- StagedSave _save;
+ PlainSave *_notes;
+ StagedSave *_save;
byte _propBuffer[1000];
byte _indexBuffer[1200];
@@ -370,7 +377,7 @@ protected:
int32 _varSize;
- StagedSave _save;
+ StagedSave *_save;
byte _propBuffer[1000];
byte _indexBuffer[1200];
diff --git a/engines/gob/saveload_v2.cpp b/engines/gob/saveload_v2.cpp
index a92fe8cf01..fc11950368 100644
--- a/engines/gob/saveload_v2.cpp
+++ b/engines/gob/saveload_v2.cpp
@@ -45,6 +45,9 @@ SaveLoad_v2::SaveFile SaveLoad_v2::_saveFiles[] = {
SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :
SaveLoad(vm, targetName) {
+ _notes = new PlainSave(_vm->getEndianness());
+ _save = new StagedSave(_vm->getEndianness());
+
_saveFiles[0].destName = new char[strlen(targetName) + 5];
_saveFiles[1].destName = _saveFiles[0].destName;
_saveFiles[2].destName = 0;
@@ -58,6 +61,9 @@ SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :
}
SaveLoad_v2::~SaveLoad_v2() {
+ delete _notes;
+ delete _save;
+
delete[] _saveFiles[0].destName;
delete[] _saveFiles[3].destName;
}
@@ -227,7 +233,7 @@ bool SaveLoad_v2::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -268,7 +274,7 @@ bool SaveLoad_v2::loadNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Loading the notes");
- return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
}
bool SaveLoad_v2::saveGame(SaveFile &saveFile,
@@ -313,10 +319,10 @@ bool SaveLoad_v2::saveGame(SaveFile &saveFile,
byte sizes[40];
memset(sizes, 0, 40);
- if(!_save.save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes))
+ if(!_save->save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes))
return false;
- if (!_save.save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -350,7 +356,7 @@ bool SaveLoad_v2::saveNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Saving the notes");
- return _notes.save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
return false;
}
@@ -360,8 +366,8 @@ void SaveLoad_v2::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(40);
- _save.addStage(_varSize);
+ _save->addStage(40);
+ _save->addStage(_varSize);
}
} // End of namespace Gob
diff --git a/engines/gob/saveload_v3.cpp b/engines/gob/saveload_v3.cpp
index 67879db3d1..dab5fd9385 100644
--- a/engines/gob/saveload_v3.cpp
+++ b/engines/gob/saveload_v3.cpp
@@ -48,6 +48,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,
uint32 screenshotSize, int32 indexOffset, int32 screenshotOffset) :
SaveLoad(vm, targetName) {
+ _notes = new PlainSave(_vm->getEndianness());
+ _save = new StagedSave(_vm->getEndianness());
+
_screenshotSize = screenshotSize;
_indexOffset = indexOffset;
_screenshotOffset = screenshotOffset;
@@ -71,6 +74,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,
}
SaveLoad_v3::~SaveLoad_v3() {
+ delete _notes;
+ delete _save;
+
delete[] _saveFiles[0].destName;
delete[] _saveFiles[3].destName;
}
@@ -243,7 +249,7 @@ int32 SaveLoad_v3::getSizeNotes(SaveFile &saveFile) {
int32 SaveLoad_v3::getSizeScreenshot(SaveFile &saveFile) {
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
@@ -312,7 +318,7 @@ bool SaveLoad_v3::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -353,7 +359,7 @@ bool SaveLoad_v3::loadNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Loading the notes");
- return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
}
bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
@@ -363,7 +369,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
if (offset == _indexOffset) {
@@ -395,7 +401,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
byte *buffer = new byte[_screenshotSize];
- if (!_save.load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
+ if (!_save->load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
delete[] buffer;
return false;
}
@@ -483,13 +489,13 @@ bool SaveLoad_v3::saveGame(SaveFile &saveFile,
_hasIndex = false;
- if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
+ if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
return false;
- if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0))
+ if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0))
return false;
- if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -523,7 +529,7 @@ bool SaveLoad_v3::saveNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Saving the notes");
- return _notes.save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables);
return false;
}
@@ -534,7 +540,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
if (offset >= _screenshotOffset) {
@@ -571,7 +577,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,
return false;
}
- if (!_save.save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
+ if (!_save->save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
delete[] buffer;
return false;
}
@@ -588,9 +594,9 @@ void SaveLoad_v3::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(500);
- _save.addStage(40, false);
- _save.addStage(_varSize);
+ _save->addStage(500);
+ _save->addStage(40, false);
+ _save->addStage(_varSize);
}
void SaveLoad_v3::buildScreenshotIndex(byte *buffer, char *name, int n) {
diff --git a/engines/gob/saveload_v4.cpp b/engines/gob/saveload_v4.cpp
index a6548dd82d..0bd3dc03e6 100644
--- a/engines/gob/saveload_v4.cpp
+++ b/engines/gob/saveload_v4.cpp
@@ -50,6 +50,8 @@ SaveLoad_v4::SaveFile SaveLoad_v4::_saveFiles[] = {
SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :
SaveLoad(vm, targetName) {
+ _save = new StagedSave(_vm->getEndianness());
+
_firstSizeGame = true;
_saveFiles[0].destName = 0;
@@ -76,6 +78,8 @@ SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :
}
SaveLoad_v4::~SaveLoad_v4() {
+ delete _save;
+
delete[] _screenProps;
delete[] _saveFiles[1].destName;
}
@@ -297,7 +301,7 @@ bool SaveLoad_v4::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -314,7 +318,7 @@ bool SaveLoad_v4::loadGameScreenProps(SaveFile &saveFile,
setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0');
- if (!_save.load(0, 256000, _varSize + 540, saveFile.destName,
+ if (!_save->load(0, 256000, _varSize + 540, saveFile.destName,
_screenProps, _screenProps + 256000))
return false;
@@ -393,13 +397,13 @@ bool SaveLoad_v4::saveGame(SaveFile &saveFile,
_hasIndex = false;
- if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
+ if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
return false;
- if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0))
+ if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0))
return false;
- if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -417,7 +421,7 @@ bool SaveLoad_v4::saveGameScreenProps(SaveFile &saveFile,
setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0');
- if (!_save.save(0, 256000, _varSize + 540, saveFile.destName,
+ if (!_save->save(0, 256000, _varSize + 540, saveFile.destName,
_screenProps, _screenProps + 256000))
return false;
@@ -430,10 +434,10 @@ void SaveLoad_v4::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(500);
- _save.addStage(40, false);
- _save.addStage(_varSize);
- _save.addStage(256000);
+ _save->addStage(500);
+ _save->addStage(40, false);
+ _save->addStage(_varSize);
+ _save->addStage(256000);
}
} // End of namespace Gob
diff --git a/engines/gob/scenery.cpp b/engines/gob/scenery.cpp
index 6b52cdbbd6..33e540ace4 100644
--- a/engines/gob/scenery.cpp
+++ b/engines/gob/scenery.cpp
@@ -595,7 +595,7 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,
int16 destX;
int16 destY;
- if (animation < 0) {
+ if ((_vm->getGameType() == kGameTypeWoodruff) && (animation < 0)) {
// Object video
if (flags & 1) { // Do capture
@@ -736,6 +736,8 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,
return;
}
+ if ((animation < 0) || (animation >= 10))
+ return;
if ((_animPictCount[animation] == 0) || (layer < 0))
return;
if (layer >= _animations[animation].layersCount)
diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h
index b59510e4bb..07b5a737db 100644
--- a/engines/gob/sound/sound.h
+++ b/engines/gob/sound/sound.h
@@ -144,4 +144,4 @@ private:
} // End of namespace Gob
-#endif // GOB_SOUND_H
+#endif // GOB_SOUND_SOUND_H
diff --git a/engines/gob/sound/soundmixer.h b/engines/gob/sound/soundmixer.h
index 5789885a99..3e8e6b5c1b 100644
--- a/engines/gob/sound/soundmixer.h
+++ b/engines/gob/sound/soundmixer.h
@@ -37,7 +37,7 @@ namespace Gob {
class SoundMixer : public Audio::AudioStream {
public:
- SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType);
+ SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type);
~SoundMixer();
virtual void play(SoundDesc &sndDesc, int16 repCount,
diff --git a/engines/gob/variables.cpp b/engines/gob/variables.cpp
index 0eea2f6547..805aaeb839 100644
--- a/engines/gob/variables.cpp
+++ b/engines/gob/variables.cpp
@@ -308,4 +308,62 @@ uint32 VariablesBE::read32(const byte *buf) const {
return READ_BE_UINT32(buf);
}
+VariableReference::VariableReference() {
+ _vars = 0;
+ _offset = 0;
+}
+
+VariableReference::VariableReference(Variables &vars, uint32 offset, Variables::Type type) {
+ set(vars, offset, type);
+}
+
+VariableReference::~VariableReference() {
+}
+
+void VariableReference::set(Variables &vars, uint32 offset, Variables::Type type) {
+ _vars = &vars;
+ _offset = offset;
+ _type = type;
+}
+
+VariableReference &VariableReference::operator=(uint32 value) {
+ if (_vars) {
+ switch (_type) {
+ case Variables::kVariableType8:
+ _vars->writeOff8(_offset, (uint8) value);
+ break;
+ case Variables::kVariableType16:
+ _vars->writeOff16(_offset, (uint16) value);
+ break;
+ case Variables::kVariableType32:
+ _vars->writeOff32(_offset, value);
+ break;
+ }
+ }
+ return *this;
+}
+
+VariableReference::operator uint32() {
+ if (_vars) {
+ switch (_type) {
+ case Variables::kVariableType8:
+ return (uint32) _vars->readOff8(_offset);
+ case Variables::kVariableType16:
+ return (uint32) _vars->readOff16(_offset);
+ case Variables::kVariableType32:
+ return _vars->readOff32(_offset);
+ }
+ }
+
+ return 0;
+}
+
+VariableReference &VariableReference::operator+=(uint32 value) {
+ return (*this = (*this + value));
+}
+
+VariableReference &VariableReference::operator*=(uint32 value) {
+ return (*this = (*this * value));
+}
+
} // End of namespace Gob
diff --git a/engines/gob/variables.h b/engines/gob/variables.h
index 5989ed38ee..32f160a6bd 100644
--- a/engines/gob/variables.h
+++ b/engines/gob/variables.h
@@ -30,6 +30,12 @@ namespace Gob {
class Variables {
public:
+ enum Type {
+ kVariableType8,
+ kVariableType16,
+ kVariableType32
+ };
+
Variables(uint32 size);
virtual ~Variables();
@@ -142,6 +148,26 @@ protected:
uint32 read32(const byte *buf) const;
};
+class VariableReference {
+ public:
+ VariableReference();
+ VariableReference(Variables &vars, uint32 offset,
+ Variables::Type type = Variables::kVariableType32);
+ ~VariableReference();
+
+ void set(Variables &vars, uint32 offset, Variables::Type type = Variables::kVariableType32);
+
+ VariableReference &operator=(uint32 value);
+ VariableReference &operator+=(uint32 value);
+ VariableReference &operator*=(uint32 value);
+ operator uint32();
+
+ private:
+ Variables *_vars;
+ uint32 _offset;
+ Variables::Type _type;
+};
+
} // End of namespace Gob
#endif // GOB_VARIABLES_H
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 909d39a63b..aa47e6cf84 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -588,12 +588,14 @@ bool VideoPlayer::doPlay(int16 frame, int16 breakKey,
}
void VideoPlayer::copyPalette(CoktelVideo &video, int16 palStart, int16 palEnd) {
- if ((palStart != -1) && (palEnd != -1))
- memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3,
- video.getPalette() + palStart * 3,
- (palEnd - palStart + 1) * 3);
- else
- memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal, video.getPalette(), 768);
+ if (palStart < 0)
+ palStart = 0;
+ if (palEnd < 0)
+ palEnd = 255;
+
+ memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3,
+ video.getPalette() + palStart * 3,
+ (palEnd - palStart + 1) * 3);
}
void VideoPlayer::writeVideoInfo(const char *videoFile, int16 varX, int16 varY,
diff --git a/engines/igor/igor.cpp b/engines/igor/igor.cpp
index 018709f34f..4d4fb97762 100644
--- a/engines/igor/igor.cpp
+++ b/engines/igor/igor.cpp
@@ -404,7 +404,7 @@ void IgorEngine::playSound(int num, int type) {
debugC(9, kDebugEngine, "playSound() %d", num);
--num;
int soundOffset = -1;
- Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType;
+ Audio::Mixer::SoundType soundType;
Audio::SoundHandle *soundHandle = 0;
if (type == 1) {
if (_mixer->isSoundHandleActive(_sfxHandle)) {
diff --git a/engines/kyra/animator_lok.cpp b/engines/kyra/animator_lok.cpp
index 1baa78b203..75d5537e4a 100644
--- a/engines/kyra/animator_lok.cpp
+++ b/engines/kyra/animator_lok.cpp
@@ -665,7 +665,7 @@ void Animator_LoK::animRefreshNPC(int character) {
void Animator_LoK::setCharacterDefaultFrame(int character) {
debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharacterDefaultFrame()");
- static uint16 initFrameTable[] = {
+ static const uint16 initFrameTable[] = {
7, 41, 77, 0, 0
};
assert(character < ARRAYSIZE(initFrameTable));
@@ -678,7 +678,7 @@ void Animator_LoK::setCharacterDefaultFrame(int character) {
void Animator_LoK::setCharactersHeight() {
debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharactersHeight()");
- static int8 initHeightTable[] = {
+ static const int8 initHeightTable[] = {
48, 40, 48, 47, 56,
44, 42, 47, 38, 35,
40
diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp
index 344121b503..2d592069d2 100644
--- a/engines/kyra/detection.cpp
+++ b/engines/kyra/detection.cpp
@@ -26,6 +26,7 @@
#include "kyra/kyra_lok.h"
#include "kyra/kyra_hof.h"
#include "kyra/kyra_mr.h"
+#include "kyra/lol.h"
#include "common/config-manager.h"
#include "common/advancedDetector.h"
@@ -41,9 +42,11 @@ struct KYRAGameDescription {
namespace {
-#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id }
+#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::UNK_LANG, Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id }
+#define FLAGS_FAN(fanLang, repLang, x, y, z, a, b, c, id) { Common::UNK_LANG, fanLang, repLang, Common::kPlatformUnknown, x, y, z, a, b, c, id }
#define KYRA1_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1)
+#define KYRA1_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA1)
#define KYRA1_AMIGA_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1)
#define KYRA1_TOWNS_FLAGS FLAGS(false, true, false, false, false, false, Kyra::GI_KYRA1)
#define KYRA1_TOWNS_SJIS_FLAGS FLAGS(false, true, false, true, false, false, Kyra::GI_KYRA1)
@@ -59,13 +62,38 @@ namespace {
#define KYRA2_TOWNS_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_KYRA2)
#define KYRA3_CD_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3)
-#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3)
+#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, false, Kyra::GI_KYRA3)
+#define KYRA3_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, true, false, Kyra::GI_KYRA3)
+
+#define LOL_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_LOL)
const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
0,
+ AD_ENTRY1("DISK1.EXE", "c8641d0414d6c966d0a3dad79db07bf4"),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA1_FLOPPY_CMP_FLAGS
+ },
+ {
+ {
+ "kyra1",
+ 0,
+ AD_ENTRY1("DISK1.EXE", "5d5cee4c3d0b68d586788b74243d254a"),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA1_FLOPPY_CMP_FLAGS
+ },
+ {
+ {
+ "kyra1",
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "3c244298395520bb62b5edfe41688879"),
Common::EN_ANY,
Common::kPlatformPC,
@@ -76,7 +104,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "796e44863dd22fa635b042df1bf16673"),
Common::EN_ANY,
Common::kPlatformPC,
@@ -87,7 +115,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "abf8eb360e79a6c2a837751fbd4d3d24"),
Common::FR_FRA,
Common::kPlatformPC,
@@ -98,7 +126,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "6018e1dfeaca7fe83f8d0b00eb0dd049"),
Common::DE_DEU,
Common::kPlatformPC,
@@ -109,7 +137,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from Arne.F
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "f0b276781f47c130f423ec9679fe9ed9"),
Common::DE_DEU,
Common::kPlatformPC,
@@ -120,7 +148,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from VooD
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "8909b41596913b3f5deaf3c9f1017b01"),
Common::ES_ESP,
Common::kPlatformPC,
@@ -131,7 +159,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // floppy 1.8 from clemmy
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "747861d2a9c643c59fdab570df5b9093"),
Common::ES_ESP,
Common::kPlatformPC,
@@ -142,7 +170,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from gourry
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "ef08c8c237ee1473fd52578303fc36df"),
Common::IT_ITA,
Common::kPlatformPC,
@@ -323,7 +351,7 @@ const KYRAGameDescription adGameDescs[] = {
KYRA2_FLOPPY_CMP_FLAGS
},
- { // // Floppy version extracted
+ { // Floppy version extracted
{
"kyra2",
"Extracted",
@@ -463,6 +491,28 @@ const KYRAGameDescription adGameDescs[] = {
},
KYRA2_TOWNS_SJIS_FLAGS
},
+ { // PC-9821
+ {
+ "kyra2",
+ 0,
+ AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
+ Common::EN_ANY,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA2_TOWNS_FLAGS
+ },
+ {
+ {
+ "kyra2",
+ 0,
+ AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
+ Common::JA_JPN,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA2_TOWNS_SJIS_FLAGS
+ },
// Kyra3
@@ -480,7 +530,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
{
{
@@ -495,7 +545,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
{
{
@@ -510,7 +560,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
// installed version
@@ -527,7 +577,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
},
{
{
@@ -542,7 +592,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
},
{
{
@@ -557,9 +607,152 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
+ },
+
+ // Spanish fan translation, see fr#1994040 "KYRA3: Add support for Spanish fan translation"
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::ES_ESP,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
},
+ // Itlian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3"
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::IT_ITA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
+ },
+
+ // Lands of Lore CD
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
{ AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) }
};
@@ -567,6 +760,7 @@ const PlainGameDescriptor gameList[] = {
{ "kyra1", "The Legend of Kyrandia" },
{ "kyra2", "The Legend of Kyrandia: The Hand of Fate" },
{ "kyra3", "The Legend of Kyrandia: Malcolm's Revenge" },
+ { "lol", "Lands of Lore: The Throne of Chaos" },
{ 0, 0 }
};
@@ -639,6 +833,9 @@ bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common
case Kyra::GI_KYRA3:
*engine = new Kyra::KyraEngine_MR(syst, flags);
break;
+ case Kyra::GI_LOL:
+ *engine = new Kyra::LoLEngine(syst, flags);
+ break;
default:
res = false;
warning("Kyra engine: unknown gameID");
diff --git a/engines/kyra/gui_hof.cpp b/engines/kyra/gui_hof.cpp
index 555934cb7f..7d56743af5 100644
--- a/engines/kyra/gui_hof.cpp
+++ b/engines/kyra/gui_hof.cpp
@@ -454,14 +454,26 @@ void KyraEngine_HoF::loadBookBkgd() {
void KyraEngine_HoF::showBookPage() {
char filename[16];
- sprintf(filename, "PAGE%.01X.", _bookCurPage);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
+ sprintf(filename, "PAGE%.01X.%s", _bookCurPage, _languageExtension[_lang]);
uint8 *leftPage = _res->fileData(filename, 0);
+ if (!leftPage) {
+ // some floppy version use a TXT extension
+ sprintf(filename, "PAGE%.01X.TXT", _bookCurPage);
+ leftPage = _res->fileData(filename, 0);
+ }
+
int leftPageY = _bookPageYOffset[_bookCurPage];
- sprintf(filename, "PAGE%.01X.", _bookCurPage+1);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
- uint8 *rightPage = (_bookCurPage != _bookMaxPage) ? _res->fileData(filename, 0) : 0;
+ sprintf(filename, "PAGE%.01X.%s", _bookCurPage+1, _languageExtension[_lang]);
+ uint8 *rightPage = 0;
+ if (_bookCurPage != _bookMaxPage) {
+ rightPage = _res->fileData(filename, 0);
+ if (!rightPage) {
+ sprintf(filename, "PAGE%.01X.TXT", _bookCurPage);
+ rightPage = _res->fileData(filename, 0);
+ }
+ }
+
int rightPageY = _bookPageYOffset[_bookCurPage+1];
_screen->hideMouse();
diff --git a/engines/kyra/gui_lok.cpp b/engines/kyra/gui_lok.cpp
index 6fa30c9e9a..35b343fc25 100644
--- a/engines/kyra/gui_lok.cpp
+++ b/engines/kyra/gui_lok.cpp
@@ -188,6 +188,7 @@ int KyraEngine_LoK::buttonAmuletCallback(Button *caller) {
#pragma mark -
GUI_LoK::GUI_LoK(KyraEngine_LoK *vm, Screen_LoK *screen) : GUI(vm), _vm(vm), _screen(screen) {
+ _lastScreenUpdate = 0;
_menu = 0;
initStaticResource();
_scrollUpFunctor = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::scrollUp);
@@ -479,7 +480,6 @@ int GUI_LoK::buttonMenuCallback(Button *caller) {
void GUI_LoK::getInput() {
Common::Event event;
- static uint32 lastScreenUpdate = 0;
uint32 now = _vm->_system->getMillis();
_mouseWheel = 0;
@@ -496,7 +496,7 @@ void GUI_LoK::getInput() {
break;
case Common::EVENT_MOUSEMOVE:
_vm->_system->updateScreen();
- lastScreenUpdate = now;
+ _lastScreenUpdate = now;
break;
case Common::EVENT_WHEELUP:
_mouseWheel = -1;
@@ -512,9 +512,9 @@ void GUI_LoK::getInput() {
}
}
- if (now - lastScreenUpdate > 50) {
+ if (now - _lastScreenUpdate > 50) {
_vm->_system->updateScreen();
- lastScreenUpdate = now;
+ _lastScreenUpdate = now;
}
_vm->_system->delayMillis(3);
@@ -1056,7 +1056,7 @@ void GUI_LoK::fadePalette() {
if (_vm->gameFlags().platform == Common::kPlatformAmiga)
return;
- static int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1};
+ static const int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1};
int index = 0;
memcpy(_screen->getPalette(2), _screen->_currentPalette, 768);
diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h
index 607ef0b605..49081c7ae2 100644
--- a/engines/kyra/gui_lok.h
+++ b/engines/kyra/gui_lok.h
@@ -157,6 +157,8 @@ private:
KyraEngine_LoK *_vm;
Screen_LoK *_screen;
+ uint32 _lastScreenUpdate;
+
bool _menuRestoreScreen;
uint8 _toplevelMenu;
int _savegameOffset;
diff --git a/engines/kyra/items_lok.cpp b/engines/kyra/items_lok.cpp
index 8eb62c20c2..2c2903d0b3 100644
--- a/engines/kyra/items_lok.cpp
+++ b/engines/kyra/items_lok.cpp
@@ -39,7 +39,7 @@
namespace Kyra {
int KyraEngine_LoK::findDuplicateItemShape(int shape) {
- static uint8 dupTable[] = {
+ static const uint8 dupTable[] = {
0x48, 0x46, 0x49, 0x47, 0x4a, 0x46, 0x4b, 0x47,
0x4c, 0x46, 0x4d, 0x47, 0x5b, 0x5a, 0x5c, 0x5a,
0x5d, 0x5a, 0x5e, 0x5a, 0xFF, 0xFF
diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp
index 57f0dcc24a..d3de621707 100644
--- a/engines/kyra/kyra_hof.cpp
+++ b/engines/kyra/kyra_hof.cpp
@@ -230,7 +230,7 @@ int KyraEngine_HoF::init() {
_gui = new GUI_HoF(this);
assert(_gui);
_gui->initStaticData();
- _tim = new TIMInterpreter(this, _system);
+ _tim = new TIMInterpreter(this, _screen, _system);
assert(_tim);
if (_flags.isDemo && !_flags.isTalkie) {
@@ -296,6 +296,9 @@ int KyraEngine_HoF::go() {
_res->loadFileList("FILEDATA.FDT");
else
_res->loadFileList(_ingamePakList, _ingamePakListSize);
+
+ if (_flags.platform == Common::kPlatformPC98)
+ _res->loadPakFile("AUDIO.PAK");
}
_menuDirectlyToLoad = (_menuChoice == 3) ? true : false;
@@ -1561,7 +1564,7 @@ void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) {
int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]);
if (vocIndex != -1)
_sound->voicePlay(_ingameSoundList[vocIndex], true);
- else if (_flags.platform == Common::kPlatformPC)
+ else if (_flags.platform != Common::kPlatformFMTowns)
// TODO ?? Maybe there is a way to let users select whether they want
// voc, midi or adl sfx (even though it makes no sense to choose anything but voc).
KyraEngine_v1::snd_playSoundEffect(track);
@@ -2021,6 +2024,9 @@ void KyraEngine_HoF::writeSettings() {
break;
}
+ if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG)
+ _flags.lang = _flags.fanLang;
+
ConfMan.set("language", Common::getLanguageCode(_flags.lang));
KyraEngine_v1::writeSettings();
diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp
index 8a49b8e155..a4e5b58364 100644
--- a/engines/kyra/kyra_mr.cpp
+++ b/engines/kyra/kyra_mr.cpp
@@ -343,6 +343,14 @@ void KyraEngine_MR::initMainMenu() {
0x80, 0xFF
};
+ if (_flags.lang == Common::ES_ESP) {
+ for (int i = 0; i < 4; ++i)
+ data.strings[i] = _mainMenuSpanishFan[i];
+ } else if (_flags.lang == Common::IT_ITA) {
+ for (int i = 0; i < 4; ++i)
+ data.strings[i] = _mainMenuItalianFan[i];
+ }
+
MainMenu::Animation anim;
anim.anim = _menuAnim;
anim.startFrame = 29;
@@ -1543,6 +1551,9 @@ void KyraEngine_MR::writeSettings() {
break;
}
+ if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG)
+ _flags.lang = _flags.fanLang;
+
ConfMan.set("language", Common::getLanguageCode(_flags.lang));
ConfMan.setBool("studio_audience", _configStudio);
diff --git a/engines/kyra/kyra_mr.h b/engines/kyra/kyra_mr.h
index 5af138373c..5f9f6f91a3 100644
--- a/engines/kyra/kyra_mr.h
+++ b/engines/kyra/kyra_mr.h
@@ -184,9 +184,12 @@ private:
private:
// main menu
- const char *const *_mainMenuStrings;
+ const char * const *_mainMenuStrings;
int _mainMenuStringsSize;
+ static const char * const _mainMenuSpanishFan[];
+ static const char * const _mainMenuItalianFan[];
+
// animator
uint8 *_gamePlayBuffer;
void restorePage3();
diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp
index 1cc1d728bf..85c03dc1bb 100644
--- a/engines/kyra/kyra_v1.cpp
+++ b/engines/kyra/kyra_v1.cpp
@@ -107,14 +107,16 @@ int KyraEngine_v1::init() {
// "KYRA1: Crash on exceeded polyphony" for more information).
int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/);
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
- // TODO: currently we don't support the PC98 sound data,
- // but since it has the FM-Towns data files, we just use the
- // FM-Towns driver
+ if (_flags.platform == Common::kPlatformFMTowns) {
if (_flags.gameID == GI_KYRA1)
_sound = new SoundTowns(this, _mixer);
else
- _sound = new SoundTowns_v2(this, _mixer);
+ _sound = new SoundTownsPC98_v2(this, _mixer);
+ } else if (_flags.platform == Common::kPlatformPC98) {
+ if (_flags.gameID == GI_KYRA1)
+ _sound = new SoundTowns/*SoundPC98*/(this, _mixer);
+ else
+ _sound = new SoundTownsPC98_v2(this, _mixer);
} else if (midiDriver == MD_ADLIB) {
_sound = new SoundAdlibPC(this, _mixer);
assert(_sound);
@@ -171,36 +173,6 @@ int KyraEngine_v1::init() {
_gameToLoad = -1;
}
- _lang = 0;
- Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
-
- if (_flags.gameID == GI_KYRA2 || _flags.gameID == GI_KYRA3) {
- switch (lang) {
- case Common::EN_ANY:
- case Common::EN_USA:
- case Common::EN_GRB:
- _lang = 0;
- break;
-
- case Common::FR_FRA:
- _lang = 1;
- break;
-
- case Common::DE_DEU:
- _lang = 2;
- break;
-
- case Common::JA_JPN:
- _lang = 3;
- break;
-
- default:
- warning("unsupported language, switching back to English");
- _lang = 0;
- break;
- }
- }
-
return 0;
}
@@ -275,6 +247,14 @@ void KyraEngine_v1::delayWithTicks(int ticks) {
void KyraEngine_v1::registerDefaultSettings() {
if (_flags.gameID != GI_KYRA3)
ConfMan.registerDefault("cdaudio", (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98));
+ if (_flags.fanLang != Common::UNK_LANG) {
+ // HACK/WORKAROUND: Since we can't use registerDefault here to overwrite
+ // the global subtitles settings, we're using this hack to enable subtitles
+ // for fan translations
+ const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain();
+ if (!cur || (cur && cur->get("subtitles").empty()))
+ ConfMan.setBool("subtitles", true);
+ }
}
void KyraEngine_v1::readSettings() {
diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h
index 4f38ceca98..50cabc421e 100644
--- a/engines/kyra/kyra_v1.h
+++ b/engines/kyra/kyra_v1.h
@@ -44,6 +44,11 @@ namespace Kyra {
struct GameFlags {
Common::Language lang;
+
+ // language overwrites of fan translations (only needed for multilingual games)
+ Common::Language fanLang;
+ Common::Language replacedLang;
+
Common::Platform platform;
bool isDemo : 1;
@@ -59,7 +64,8 @@ struct GameFlags {
enum {
GI_KYRA1 = 0,
GI_KYRA2 = 1,
- GI_KYRA3 = 2
+ GI_KYRA3 = 2,
+ GI_LOL = 4
};
struct AudioDataStruct {
@@ -212,7 +218,6 @@ protected:
// detection
GameFlags _flags;
- int _lang;
// opcode
virtual void setupOpcodeTable() = 0;
diff --git a/engines/kyra/kyra_v2.cpp b/engines/kyra/kyra_v2.cpp
index 12da338843..2e704f2aa2 100644
--- a/engines/kyra/kyra_v2.cpp
+++ b/engines/kyra/kyra_v2.cpp
@@ -23,6 +23,8 @@
*
*/
+#include "common/config-manager.h"
+
#include "kyra/kyra_v2.h"
#include "kyra/screen_v2.h"
#include "kyra/debugger.h"
@@ -70,6 +72,36 @@ KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const Engi
memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory));
_pauseStart = 0;
+
+ _lang = 0;
+ Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
+ if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG)
+ lang = _flags.replacedLang;
+
+ switch (lang) {
+ case Common::EN_ANY:
+ case Common::EN_USA:
+ case Common::EN_GRB:
+ _lang = 0;
+ break;
+
+ case Common::FR_FRA:
+ _lang = 1;
+ break;
+
+ case Common::DE_DEU:
+ _lang = 2;
+ break;
+
+ case Common::JA_JPN:
+ _lang = 3;
+ break;
+
+ default:
+ warning("unsupported language, switching back to English");
+ _lang = 0;
+ break;
+ }
}
KyraEngine_v2::~KyraEngine_v2() {
diff --git a/engines/kyra/kyra_v2.h b/engines/kyra/kyra_v2.h
index 24f7aad614..6fdf30fff8 100644
--- a/engines/kyra/kyra_v2.h
+++ b/engines/kyra/kyra_v2.h
@@ -94,6 +94,9 @@ protected:
virtual void update() = 0;
virtual void updateWithText() = 0;
+ // detection
+ int _lang;
+
// MainMenu
MainMenu *_menu;
diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp
new file mode 100644
index 0000000000..ef1121baa0
--- /dev/null
+++ b/engines/kyra/lol.cpp
@@ -0,0 +1,806 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "kyra/lol.h"
+#include "kyra/screen_lol.h"
+#include "kyra/resource.h"
+#include "kyra/sound.h"
+
+#include "common/endian.h"
+
+namespace Kyra {
+
+LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags) {
+ _screen = 0;
+
+ switch (_flags.lang) {
+ case Common::EN_ANY:
+ case Common::EN_USA:
+ case Common::EN_GRB:
+ _lang = 0;
+ break;
+
+ case Common::FR_FRA:
+ _lang = 1;
+ break;
+
+ case Common::DE_DEU:
+ _lang = 2;
+ break;
+
+ default:
+ warning("unsupported language, switching back to English");
+ _lang = 0;
+ break;
+ }
+
+ _chargenWSA = 0;
+}
+
+LoLEngine::~LoLEngine() {
+ setupPrologueData(false);
+
+ delete _screen;
+ delete _tim;
+
+ for (Common::Array<const TIMOpcode*>::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i)
+ delete *i;
+ _timIntroOpcodes.clear();
+}
+
+Screen *LoLEngine::screen() {
+ return _screen;
+}
+
+int LoLEngine::init() {
+ _screen = new Screen_LoL(this, _system);
+ assert(_screen);
+ _screen->setResolution();
+
+ KyraEngine_v1::init();
+
+ _tim = new TIMInterpreter(this, _screen, _system);
+ assert(_tim);
+
+ _screen->setAnimBlockPtr(10000);
+ _screen->setScreenDim(0);
+
+ return 0;
+}
+
+int LoLEngine::go() {
+ setupPrologueData(true);
+ showIntro();
+ _sound->playTrack(6);
+ /*int character = */chooseCharacter();
+ _sound->playTrack(1);
+ _screen->fadeToBlack();
+ setupPrologueData(false);
+
+ return 0;
+}
+
+#pragma mark - Input
+
+int LoLEngine::checkInput(Button *buttonList, bool mainLoop) {
+ debugC(9, kDebugLevelMain, "LoLEngine::checkInput(%p, %d)", (const void*)buttonList, mainLoop);
+ updateInput();
+
+ int keys = 0;
+ int8 mouseWheel = 0;
+
+ while (_eventList.size()) {
+ Common::Event event = *_eventList.begin();
+ bool breakLoop = false;
+
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ /*if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' &&
+ (event.kbd.flags == Common::KBD_CTRL || event.kbd.flags == Common::KBD_ALT) && mainLoop) {
+ const char *saveLoadSlot = getSavegameFilename(9 - (event.kbd.keycode - '0') + 990);
+
+ if (event.kbd.flags == Common::KBD_CTRL) {
+ loadGame(saveLoadSlot);
+ _eventList.clear();
+ breakLoop = true;
+ } else {
+ char savegameName[14];
+ sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0');
+ saveGame(saveLoadSlot, savegameName);
+ }
+ } else if (event.kbd.flags == Common::KBD_CTRL) {
+ if (event.kbd.keycode == 'd')
+ _debugger->attach();
+ }*/
+ break;
+
+ case Common::EVENT_MOUSEMOVE: {
+ Common::Point pos = getMousePos();
+ _mouseX = pos.x;
+ _mouseY = pos.y;
+ } break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP: {
+ Common::Point pos = getMousePos();
+ _mouseX = pos.x;
+ _mouseY = pos.y;
+ keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800));
+ breakLoop = true;
+ } break;
+
+ case Common::EVENT_WHEELUP:
+ mouseWheel = -1;
+ break;
+
+ case Common::EVENT_WHEELDOWN:
+ mouseWheel = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ //if (_debugger->isAttached())
+ // _debugger->onFrame();
+
+ if (breakLoop)
+ break;
+
+ _eventList.erase(_eventList.begin());
+ }
+
+ return /*gui_v2()->processButtonList(buttonList, keys | 0x8000, mouseWheel)*/keys;
+}
+
+void LoLEngine::updateInput() {
+ Common::Event event;
+
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ _quitFlag = true;
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.keycode == '.' || event.kbd.keycode == Common::KEYCODE_ESCAPE)
+ _eventList.push_back(Event(event, true));
+ else if (event.kbd.keycode == 'q' && event.kbd.flags == Common::KBD_CTRL)
+ _quitFlag = true;
+ else
+ _eventList.push_back(event);
+ break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ _eventList.push_back(Event(event, true));
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ _screen->updateScreen();
+ // fall through
+
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ _eventList.push_back(event);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void LoLEngine::removeInputTop() {
+ if (!_eventList.empty())
+ _eventList.erase(_eventList.begin());
+}
+
+bool LoLEngine::skipFlag() const {
+ for (Common::List<Event>::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) {
+ if (i->causedSkip)
+ return true;
+ }
+ return false;
+}
+
+void LoLEngine::resetSkipFlag(bool removeEvent) {
+ for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) {
+ if (i->causedSkip) {
+ if (removeEvent)
+ _eventList.erase(i);
+ else
+ i->causedSkip = false;
+ return;
+ }
+ }
+}
+
+#pragma mark - Intro
+
+void LoLEngine::setupPrologueData(bool load) {
+ static const char * const fileList[] = {
+ "xxx/general.pak",
+ "xxx/introvoc.pak",
+ "xxx/startup.pak",
+ "xxx/intro1.pak",
+ "xxx/intro2.pak",
+ "xxx/intro3.pak",
+ "xxx/intro4.pak",
+ "xxx/intro5.pak",
+ "xxx/intro6.pak",
+ "xxx/intro7.pak",
+ "xxx/intro8.pak",
+ "xxx/intro9.pak"
+ };
+
+ char filename[32];
+ for (uint i = 0; i < ARRAYSIZE(fileList); ++i) {
+ strcpy(filename, fileList[i]);
+ memcpy(filename, _languageExt[_lang], 3);
+
+ if (load) {
+ if (!_res->loadPakFile(filename))
+ error("Couldn't load file: '%s'", filename);
+ } else {
+ _res->unloadPakFile(filename);
+ }
+ }
+
+ if (load) {
+ _chargenWSA = new WSAMovie_v2(this, _screen);
+ assert(_chargenWSA);
+
+ _charSelection = -1;
+ _charSelectionInfoResult = -1;
+
+ _selectionAnimFrames[0] = _selectionAnimFrames[2] = 0;
+ _selectionAnimFrames[1] = _selectionAnimFrames[3] = 1;
+
+ memset(_selectionAnimTimers, 0, sizeof(_selectionAnimTimers));
+ memset(_screen->getPalette(1), 0, 768);
+ } else {
+ delete _chargenWSA; _chargenWSA = 0;
+ }
+}
+
+void LoLEngine::showIntro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::showIntro()");
+
+ TIM *intro = _tim->load("LOLINTRO.TIM", &_timIntroOpcodes);
+
+ _screen->loadFont(Screen::FID_8_FNT, "NEW8P.FNT");
+ _screen->loadFont(Screen::FID_INTRO_FNT, "INTRO.FNT");
+ _screen->setFont(Screen::FID_8_FNT);
+
+ _tim->resetFinishedFlag();
+ _tim->setLangData("LOLINTRO.DIP");
+
+ _screen->hideMouse();
+
+ uint32 palNextFadeStep = 0;
+ while (!_tim->finished() && !_quitFlag && !skipFlag()) {
+ updateInput();
+ _tim->exec(intro, false);
+ _screen->checkedPageUpdate(8, 4);
+
+ if (_tim->_palDiff) {
+ if (palNextFadeStep < _system->getMillis()) {
+ _tim->_palDelayAcc += _tim->_palDelayInc;
+ palNextFadeStep = _system->getMillis() + ((_tim->_palDelayAcc >> 8) * _tickLength);
+ _tim->_palDelayAcc &= 0xFF;
+
+ if (!_screen->fadePalStep(_screen->getPalette(0), _tim->_palDiff)) {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ _tim->_palDiff = 0;
+ }
+ }
+ }
+
+ _system->delayMillis(10);
+ _screen->updateScreen();
+ }
+ _screen->showMouse();
+ _sound->voiceStop();
+
+ // HACK: Remove all input events
+ _eventList.clear();
+
+ _tim->unload(intro);
+ _tim->clearLangData();
+
+ _screen->fadePalette(_screen->getPalette(1), 30, 0);
+}
+
+int LoLEngine::chooseCharacter() {
+ debugC(9, kDebugLevelMain, "LoLEngine::chooseCharacter()");
+
+ _tim->setLangData("LOLINTRO.DIP");
+
+ _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT");
+
+ _screen->loadBitmap("ITEMICN.SHP", 3, 3, 0);
+ _screen->setMouseCursor(0, 0, _screen->getPtrToShape(_screen->getCPagePtr(3), 0));
+
+ while (!_screen->isMouseVisible())
+ _screen->showMouse();
+
+ _screen->loadBitmap("CHAR.CPS", 2, 2, _screen->getPalette(0));
+ _screen->loadBitmap("BACKGRND.CPS", 4, 4, _screen->getPalette(0));
+
+ if (!_chargenWSA->open("CHARGEN.WSA", 1, 0))
+ error("Couldn't load CHARGEN.WSA");
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(2);
+ _chargenWSA->displayFrame(0, 0, 0, 0);
+
+ _screen->setFont(Screen::FID_9_FNT);
+ _screen->_curPage = 2;
+
+ for (int i = 0; i < 4; ++i)
+ _screen->fprintStringIntro(_charPreviews[i].name, _charPreviews[i].x + 16, _charPreviews[i].y + 36, 0xC0, 0x00, 0x9C, 0x120);
+
+ for (int i = 0; i < 4; ++i) {
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 48, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[0]);
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 56, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[1]);
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 64, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[2]);
+ }
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(51), 36, 173, 0x98, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(53), 36, 181, 0x98, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(55), 36, 189, 0x98, 0x00, 0x9C, 0x20);
+
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK);
+ _screen->_curPage = 0;
+
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+
+ bool kingIntro = true;
+ while (!_quitFlag) {
+ if (kingIntro)
+ kingSelectionIntro();
+
+ if (_charSelection < 0)
+ processCharacterSelection();
+
+ if (_quitFlag)
+ break;
+
+ if (_charSelection == 100) {
+ kingIntro = true;
+ _charSelection = -1;
+ continue;
+ }
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->updateScreen();
+ _screen->showMouse();
+
+ if (selectionCharInfo(_charSelection) == -1) {
+ _charSelection = -1;
+ kingIntro = false;
+ } else {
+ break;
+ }
+ }
+
+ if (_quitFlag)
+ return -1;
+
+ uint32 waitTime = _system->getMillis() + 420 * _tickLength;
+ while (waitTime > _system->getMillis() && !skipFlag() && !_quitFlag) {
+ updateInput();
+ _system->delayMillis(10);
+ }
+
+ // HACK: Remove all input events
+ _eventList.clear();
+
+ _tim->clearLangData();
+
+ return _charSelection;
+}
+
+void LoLEngine::kingSelectionIntro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionIntro()");
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ int y = 38;
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(57), 8, y, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(58), 8, y + 10, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(59), 8, y + 20, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(60), 8, y + 30, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(61), 8, y + 40, 0x32, 0x00, 0x9C, 0x20);
+
+ _sound->voicePlay("KING01");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 4;
+ while (_sound->voiceIsPlaying("KING01") && _charSelection == -1 && !_quitFlag && !skipFlag()) {
+ index = MAX(index, 4);
+
+ _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar1IdxTable[index]*2+0], _selectionPosTable[_selectionChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar2IdxTable[index]*2+0], _selectionPosTable[_selectionChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar3IdxTable[index]*2+0], _selectionPosTable[_selectionChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar4IdxTable[index]*2+0], _selectionPosTable[_selectionChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 7 * _tickLength;
+ while (waitEnd > _system->getMillis() && _charSelection == -1 && !_quitFlag && !skipFlag()) {
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ resetSkipFlag();
+
+ _chargenWSA->displayFrame(0x10, 0, 0, 0);
+ _screen->updateScreen();
+ _sound->voiceStop("KING01");
+}
+
+void LoLEngine::kingSelectionReminder() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionReminder()");
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ int y = 48;
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(62), 8, y, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(63), 8, y + 10, 0x32, 0x00, 0x9C, 0x20);
+
+ _sound->voicePlay("KING02");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 0;
+ while (_sound->voiceIsPlaying("KING02") && _charSelection == -1 && !_quitFlag && index < 15) {
+ _chargenWSA->displayFrame(_chargenFrameTable[index+9], 0, 0, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar1IdxTable[index]*2+0], _selectionPosTable[_reminderChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar2IdxTable[index]*2+0], _selectionPosTable[_reminderChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar3IdxTable[index]*2+0], _selectionPosTable[_reminderChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar4IdxTable[index]*2+0], _selectionPosTable[_reminderChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 8 * _tickLength;
+ while (waitEnd > _system->getMillis() && !_quitFlag) {
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ _sound->voiceStop("KING02");
+}
+
+void LoLEngine::kingSelectionOutro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionOutro()");
+
+ _sound->voicePlay("KING03");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 0;
+ while (_sound->voiceIsPlaying("KING03") && !_quitFlag && !skipFlag()) {
+ index = MAX(index, 4);
+
+ _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 8 * _tickLength;
+ while (waitEnd > _system->getMillis() && !_quitFlag && !skipFlag()) {
+ updateInput();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ resetSkipFlag();
+
+ _chargenWSA->displayFrame(0x10, 0, 0, 0);
+ _screen->updateScreen();
+ _sound->voiceStop("KING03");
+}
+
+void LoLEngine::processCharacterSelection() {
+ debugC(9, kDebugLevelMain, "LoLEngine::processCharacterSelection()");
+
+ _charSelection = -1;
+ while (!_quitFlag && _charSelection == -1) {
+ uint32 nextKingMessage = _system->getMillis() + 900 * _tickLength;
+
+ while (nextKingMessage > _system->getMillis() && _charSelection == -1 && !_quitFlag) {
+ updateSelectionAnims();
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ if (_charSelection == -1)
+ kingSelectionReminder();
+ }
+}
+
+void LoLEngine::updateSelectionAnims() {
+ debugC(9, kDebugLevelMain, "LoLEngine::updateSelectionAnims()");
+
+ for (int i = 0; i < 4; ++i) {
+ if (_system->getMillis() < _selectionAnimTimers[i])
+ continue;
+
+ const int index = _selectionAnimIndexTable[_selectionAnimFrames[i] + i * 2];
+ _screen->copyRegion(_selectionPosTable[index*2+0], _selectionPosTable[index*2+1], _charPreviews[i].x, _charPreviews[i].y, 32, 32, 4, 0);
+
+ int delayTime = 0;
+ if (_selectionAnimFrames[i] == 1)
+ delayTime = _rnd.getRandomNumberRng(0, 31) + 80;
+ else
+ delayTime = _rnd.getRandomNumberRng(0, 3) + 10;
+
+ _selectionAnimTimers[i] = _system->getMillis() + delayTime * _tickLength;
+ _selectionAnimFrames[i] = (_selectionAnimFrames[i] + 1) % 2;
+ }
+
+ _screen->updateScreen();
+}
+
+int LoLEngine::selectionCharInfo(int character) {
+ debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfo(%d)", character);
+ if (character < 0)
+ return -1;
+
+ char filename[16];
+ char vocFilename[6];
+ strcpy(vocFilename, "000X0");
+
+ switch (character) {
+ case 0:
+ strcpy(filename, "face09.shp");
+ vocFilename[3] = 'A';
+ break;
+
+ case 1:
+ strcpy(filename, "face01.shp");
+ vocFilename[3] = 'M';
+ break;
+
+ case 2:
+ strcpy(filename, "face08.shp");
+ vocFilename[3] = 'K';
+ break;
+
+ case 3:
+ strcpy(filename, "face05.shp");
+ vocFilename[3] = 'C';
+ break;
+
+ default:
+ break;
+ };
+
+ _screen->loadBitmap(filename, 9, 9, 0);
+ _screen->copyRegion(0, 122, 0, 122, 320, 78, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->copyRegion(_charPreviews[character].x - 3, _charPreviews[character].y - 3, 8, 127, 38, 38, 2, 0);
+
+ static const uint8 charSelectInfoIdx[] = { 0x1D, 0x22, 0x27, 0x2C };
+ const int idx = charSelectInfoIdx[character];
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+0), 50, 127, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+1), 50, 137, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+2), 50, 147, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+3), 50, 157, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+4), 50, 167, 0x53, 0x00, 0xCF, 0x20);
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(69), 100, 168, 0x32, 0x00, 0xCF, 0x20);
+
+ selectionCharInfoIntro(vocFilename);
+ if (_charSelectionInfoResult == -1) {
+ while (_charSelectionInfoResult == -1) {
+ _charSelectionInfoResult = selectionCharAccept();
+ _system->delayMillis(10);
+ }
+ }
+
+ if (_charSelectionInfoResult != 1) {
+ _charSelectionInfoResult = -1;
+ _screen->copyRegion(0, 122, 0, 122, 320, 78, 2, 0, Screen::CR_NO_P_CHECK);
+ _screen->updateScreen();
+ return -1;
+ }
+
+ _screen->copyRegion(48, 127, 48, 127, 272, 60, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->hideMouse();
+ _screen->copyRegion(48, 127, 48, 160, 272, 35, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(64), 3, 28, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(65), 3, 38, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(66), 3, 48, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(67), 3, 58, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(68), 3, 68, 0x32, 0x00, 0x9C, 0x20);
+
+ resetSkipFlag();
+ kingSelectionOutro();
+ return character;
+}
+
+void LoLEngine::selectionCharInfoIntro(char *file) {
+ debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfoIntro(%p)", (const void *)file);
+ int index = 0;
+ file[4] = '0';
+
+ while (_charSelectionInfoResult == -1 && !_quitFlag) {
+ if (!_sound->voicePlay(file))
+ break;
+
+ int i = 0;
+ while (_sound->voiceIsPlaying(file) && _charSelectionInfoResult == -1 && !_quitFlag) {
+ _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), _charInfoFrameTable[i]), 11, 130, 0, 0);
+ _screen->updateScreen();
+
+ uint32 nextFrame = _system->getMillis() + 8 * _tickLength;
+ while (nextFrame > _system->getMillis() && _charSelectionInfoResult == -1) {
+ _charSelectionInfoResult = selectionCharAccept();
+ _system->delayMillis(10);
+ }
+
+ i = (i + 1) % 32;
+ }
+
+ _sound->voiceStop(file);
+ file[4] = ++index + '0';
+ }
+
+ _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), 0), 11, 130, 0, 0);
+ _screen->updateScreen();
+}
+
+int LoLEngine::getCharSelection() {
+ int inputFlag = checkInput() & 0xCF;
+ removeInputTop();
+
+ if (inputFlag == 200) {
+ for (int i = 0; i < 4; ++i) {
+ if (_charPreviews[i].x <= _mouseX && _mouseX <= _charPreviews[i].x + 31 &&
+ _charPreviews[i].y <= _mouseY && _mouseY <= _charPreviews[i].y + 31)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int LoLEngine::selectionCharAccept() {
+ int inputFlag = checkInput() & 0xCF;
+ removeInputTop();
+
+ if (inputFlag == 200) {
+ if (88 <= _mouseX && _mouseX <= 128 && 180 <= _mouseY && _mouseY <= 194)
+ return 1;
+ if (196 <= _mouseX && _mouseX <= 236 && 180 <= _mouseY && _mouseY <= 194)
+ return 0;
+ }
+
+ return -1;
+}
+
+#pragma mark - Opcodes
+
+typedef Common::Functor2Mem<const TIM *, const uint16 *, int, LoLEngine> TIMOpcodeLoL;
+#define SetTimOpcodeTable(x) timTable = &x;
+#define OpcodeTim(x) timTable->push_back(new TIMOpcodeLoL(this, &LoLEngine::x))
+#define OpcodeTimUnImpl() timTable->push_back(new TIMOpcodeLoL(this, 0))
+
+void LoLEngine::setupOpcodeTable() {
+ Common::Array<const TIMOpcode*> *timTable = 0;
+
+ SetTimOpcodeTable(_timIntroOpcodes);
+
+ // 0x00
+ OpcodeTim(tlol_setupPaletteFade);
+ OpcodeTimUnImpl();
+ OpcodeTim(tlol_loadPalette);
+ OpcodeTim(tlol_setupPaletteFadeEx);
+
+ // 0x04
+ OpcodeTim(tlol_processWsaFrame);
+ OpcodeTim(tlol_displayText);
+ OpcodeTimUnImpl();
+ OpcodeTimUnImpl();
+}
+
+#pragma mark -
+
+int LoLEngine::tlol_setupPaletteFade(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::t2_playSoundEffect(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff);
+ _tim->_palDelayAcc = 0;
+ return 1;
+}
+
+int LoLEngine::tlol_loadPalette(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_loadPalette(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ const char *palFile = (const char *)(tim->text + READ_LE_UINT16(tim->text + (param[0]<<1)));
+ _res->loadFileToBuf(palFile, _screen->getPalette(0), 768);
+ return 1;
+}
+
+int LoLEngine::tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_setupPaletteFadeEx(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ memcpy(_screen->getPalette(0), _screen->getPalette(1), 768);
+
+ _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff);
+ _tim->_palDelayAcc = 0;
+ return 1;
+}
+
+int LoLEngine::tlol_processWsaFrame(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_processWsaFrame(%p, %p) (%d, %d, %d, %d, %d)",
+ (const void*)tim, (const void*)param, param[0], param[1], param[2], param[3], param[4]);
+ TIMInterpreter::Animation *anim = (TIMInterpreter::Animation *)tim->wsa[param[0]].anim;
+ const int frame = param[1];
+ const int x2 = param[2];
+ const int y2 = param[3];
+ const int factor = MAX<int>(0, (int16)param[4]);
+
+ const int x1 = anim->x;
+ const int y1 = anim->y;
+
+ int w1 = anim->wsa->width();
+ int h1 = anim->wsa->height();
+ int w2 = (w1 * factor) / 100;
+ int h2 = (h1 * factor) / 100;
+
+ anim->wsa->setDrawPage(2);
+ anim->wsa->setX(x1);
+ anim->wsa->setY(y1);
+ anim->wsa->displayFrame(frame, anim->wsaCopyParams & 0xF0FF, 0, 0);
+ _screen->wsaFrameAnimationStep(x1, y1, x2, y2, w1, h1, w2, h2, 2, 8, 0);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+
+ return 1;
+}
+
+int LoLEngine::tlol_displayText(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_displayText(%p, %p) (%d, %d)", (const void*)tim, (const void*)param, param[0], (int16)param[1]);
+ _tim->displayText(param[0], param[1]);
+ return 1;
+}
+
+} // end of namespace Kyra
+
diff --git a/engines/kyra/lol.h b/engines/kyra/lol.h
new file mode 100644
index 0000000000..ee54f8abbb
--- /dev/null
+++ b/engines/kyra/lol.h
@@ -0,0 +1,155 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef KYRA_LOL_H
+#define KYRA_LOL_H
+
+#include "kyra/kyra_v1.h"
+#include "kyra/script_tim.h"
+
+#include "common/list.h"
+
+namespace Kyra {
+
+class Screen_LoL;
+class WSAMovie_v2;
+struct Button;
+
+class LoLEngine : public KyraEngine_v1 {
+public:
+ LoLEngine(OSystem *system, const GameFlags &flags);
+ ~LoLEngine();
+
+ Screen *screen();
+private:
+ Screen_LoL *_screen;
+ TIMInterpreter *_tim;
+
+ int init();
+ int go();
+
+ // input
+ void updateInput();
+ int checkInput(Button *buttonList = 0, bool mainLoop = false);
+ void removeInputTop();
+
+ int _mouseX, _mouseY;
+
+ struct Event {
+ Common::Event event;
+ bool causedSkip;
+
+ Event() : event(), causedSkip(false) {}
+ Event(Common::Event e) : event(e), causedSkip(false) {}
+ Event(Common::Event e, bool skip) : event(e), causedSkip(skip) {}
+
+ operator Common::Event() const { return event; }
+ };
+ Common::List<Event> _eventList;
+
+ virtual bool skipFlag() const;
+ virtual void resetSkipFlag(bool removeEvent = true);
+
+ // intro
+ void setupPrologueData(bool load);
+
+ void showIntro();
+
+ struct CharacterPrev {
+ const char *name;
+ int x, y;
+ int attrib[3];
+ };
+
+ static const CharacterPrev _charPreviews[];
+
+ WSAMovie_v2 *_chargenWSA;
+ static const uint8 _chargenFrameTable[];
+ int chooseCharacter();
+
+ void kingSelectionIntro();
+ void kingSelectionReminder();
+ void kingSelectionOutro();
+ void processCharacterSelection();
+ void updateSelectionAnims();
+ int selectionCharInfo(int character);
+ void selectionCharInfoIntro(char *file);
+
+ int getCharSelection();
+ int selectionCharAccept();
+
+ int _charSelection;
+ int _charSelectionInfoResult;
+
+ uint32 _selectionAnimTimers[4];
+ uint8 _selectionAnimFrames[4];
+ static const uint8 _selectionAnimIndexTable[];
+
+ static const uint16 _selectionPosTable[];
+
+ static const uint8 _selectionChar1IdxTable[];
+ static const uint8 _selectionChar2IdxTable[];
+ static const uint8 _selectionChar3IdxTable[];
+ static const uint8 _selectionChar4IdxTable[];
+
+ static const uint8 _reminderChar1IdxTable[];
+ static const uint8 _reminderChar2IdxTable[];
+ static const uint8 _reminderChar3IdxTable[];
+ static const uint8 _reminderChar4IdxTable[];
+
+ static const uint8 _charInfoFrameTable[];
+
+ // timer
+ void setupTimers() {}
+
+ // sound
+ void snd_playVoiceFile(int) { /* XXX */ }
+
+ // opcode
+ void setupOpcodeTable();
+
+ Common::Array<const TIMOpcode*> _timIntroOpcodes;
+ int tlol_setupPaletteFade(const TIM *tim, const uint16 *param);
+ int tlol_loadPalette(const TIM *tim, const uint16 *param);
+ int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param);
+ int tlol_processWsaFrame(const TIM *tim, const uint16 *param);
+ int tlol_displayText(const TIM *tim, const uint16 *param);
+
+ // translation
+ int _lang;
+
+ static const char * const _languageExt[];
+
+ // unneeded
+ void setWalkspeed(uint8) {}
+ void setHandItem(uint16) {}
+ void removeHandItem() {}
+ bool lineIsPassable(int, int) { return false; }
+};
+
+} // end of namespace Kyra
+
+#endif
+
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk
index ebb63b4b4e..e059a8ce4b 100644
--- a/engines/kyra/module.mk
+++ b/engines/kyra/module.mk
@@ -21,6 +21,7 @@ MODULE_OBJS := \
kyra_v2.o \
kyra_hof.o \
kyra_mr.o \
+ lol.o \
resource.o \
saveload.o \
saveload_lok.o \
@@ -33,6 +34,7 @@ MODULE_OBJS := \
scene_mr.o \
screen.o \
screen_lok.o \
+ screen_lol.o \
screen_v2.o \
screen_hof.o \
screen_mr.o \
diff --git a/engines/kyra/resource.cpp b/engines/kyra/resource.cpp
index afd7eacfda..92818aafe1 100644
--- a/engines/kyra/resource.cpp
+++ b/engines/kyra/resource.cpp
@@ -55,10 +55,12 @@ bool Resource::reset() {
if (!dir.exists() || !dir.isDirectory())
error("invalid game path '%s'", dir.getPath().c_str());
- if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) {
- Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website";
- _vm->GUIErrorMessage(errorMessage);
- error(errorMessage.c_str());
+ if (_vm->game() != GI_LOL) {
+ if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) {
+ Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website";
+ _vm->GUIErrorMessage(errorMessage);
+ error(errorMessage.c_str());
+ }
}
if (_vm->game() == GI_KYRA1) {
@@ -99,6 +101,8 @@ bool Resource::reset() {
loadFileList("FILEDATA.FDT");
return true;
+ } else if (_vm->game() == GI_LOL) {
+ return true;
}
FSList fslist;
@@ -1120,7 +1124,7 @@ bool FileExpander::process(uint8 *dst, const uint8 *src, uint32 outsize, uint32
void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt) {
const uint8 *tbl1 = _tables[srcIndex];
- const uint8 *tbl2 = _tables[dstIndex];
+ uint8 *tbl2 = _tables[dstIndex];
const uint8 *tbl3 = dstIndex2 == 0xff ? 0 : _tables[dstIndex2];
if (!cnt)
@@ -1185,7 +1189,7 @@ void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex
}
}
- memset((void*) tbl2, 0, 512);
+ memset(tbl2, 0, 512);
cnt--;
s = tbl1 + cnt;
diff --git a/engines/kyra/scene_hof.cpp b/engines/kyra/scene_hof.cpp
index 1882386b03..62df683ea2 100644
--- a/engines/kyra/scene_hof.cpp
+++ b/engines/kyra/scene_hof.cpp
@@ -723,7 +723,7 @@ void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) {
bool KyraEngine_HoF::lineIsPassable(int x, int y) {
debugC(9, kDebugLevelMain, "KyraEngine_HoF::lineIsPassable(%d, %d)", x, y);
- static int unkTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 };
+ static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 };
if (_pathfinderFlag & 2) {
if (x >= 320)
@@ -743,7 +743,7 @@ bool KyraEngine_HoF::lineIsPassable(int x, int y) {
if (y > 143)
return false;
- int unk1 = unkTable[getScale(x, y) >> 5];
+ int unk1 = widthTable[getScale(x, y) >> 5];
if (y < 0)
y = 0;
diff --git a/engines/kyra/scene_lok.cpp b/engines/kyra/scene_lok.cpp
index e4ae67f751..53c269a926 100644
--- a/engines/kyra/scene_lok.cpp
+++ b/engines/kyra/scene_lok.cpp
@@ -1144,11 +1144,11 @@ void KyraEngine_LoK::setCharactersInDefaultScene() {
}
void KyraEngine_LoK::setCharactersPositions(int character) {
- static uint16 initXPosTable[] = {
+ static const uint16 initXPosTable[] = {
0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B,
0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042
};
- static uint8 initYPosTable[] = {
+ static const uint8 initYPosTable[] = {
0x00, 0xA2, 0x00, 0x42, 0x00,
0x67, 0x67, 0x60, 0x5A, 0x71,
0x76
diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp
index f00c47ceea..74f7bc6de9 100644
--- a/engines/kyra/screen.cpp
+++ b/engines/kyra/screen.cpp
@@ -92,9 +92,19 @@ bool Screen::init() {
if (_useSJIS) {
if (!_sjisFontData) {
- _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0);
- if (!_sjisFontData)
- error("missing font rom ('FMT_FNT.ROM') required for this version");
+ // we use the FM-Towns font rom for PC-98, too, until we feel
+ // like adding support for the PC-98 font
+ //if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
+ // FM-Towns
+ _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0);
+ if (!_sjisFontData)
+ error("missing font rom ('FMT_FNT.ROM') required for this version");
+ /*} else {
+ // PC-98
+ _sjisFontData = _vm->resource()->fileData("FONT.ROM", 0);
+ if (!_sjisFontData)
+ error("missing font rom ('FONT.ROM') required for this version");
+ }*/
}
if (!_sjisTempPage) {
@@ -370,61 +380,23 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u
debugC(9, kDebugLevelScreen, "Screen::fadePalette(%p, %d, %p)", (const void *)palData, delay, (const void*)upFunc);
updateScreen();
- uint8 fadePal[768];
- memcpy(fadePal, _screenPalette, 768);
- uint8 diff, maxDiff = 0;
- for (int i = 0; i < 768; ++i) {
- diff = ABS(palData[i] - fadePal[i]);
- if (diff > maxDiff) {
- maxDiff = diff;
- }
- }
-
- int16 delayInc = delay << 8;
- if (maxDiff != 0)
- delayInc /= maxDiff;
-
- delay = delayInc;
- for (diff = 1; diff <= maxDiff; ++diff) {
- if (delayInc >= 512)
- break;
- delayInc += delay;
- }
+ int diff = 0, delayInc = 0;
+ getFadeParams(palData, delay, delayInc, diff);
int delayAcc = 0;
while (!_vm->quit()) {
delayAcc += delayInc;
- bool needRefresh = false;
- for (int i = 0; i < 768; ++i) {
- int c1 = palData[i];
- int c2 = fadePal[i];
- if (c1 != c2) {
- needRefresh = true;
- if (c1 > c2) {
- c2 += diff;
- if (c1 < c2)
- c2 = c1;
- }
-
- if (c1 < c2) {
- c2 -= diff;
- if (c1 > c2)
- c2 = c1;
- }
-
- fadePal[i] = (uint8)c2;
- }
- }
- if (!needRefresh)
- break;
+ int refreshed = fadePalStep(palData, diff);
- setScreenPalette(fadePal);
if (upFunc && upFunc->isValid())
(*upFunc)();
else
_system->updateScreen();
- //_system->delayMillis((delayAcc >> 8) * 1000 / 60);
+
+ if (!refreshed)
+ break;
+
_vm->delay((delayAcc >> 8) * 1000 / 60);
delayAcc &= 0xFF;
}
@@ -438,6 +410,61 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u
}
}
+void Screen::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) {
+ debugC(9, kDebugLevelScreen, "Screen::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff);
+ uint8 maxDiff = 0;
+ for (int i = 0; i < 768; ++i) {
+ diff = ABS(palette[i] - _screenPalette[i]);
+ maxDiff = MAX<uint8>(maxDiff, diff);
+ }
+
+ delayInc = delay << 8;
+ if (maxDiff != 0)
+ delayInc /= maxDiff;
+ delayInc &= 0x7FFF;
+
+ delay = delayInc;
+ for (diff = 1; diff <= maxDiff; ++diff) {
+ if (delayInc >= 512)
+ break;
+ delayInc += delay;
+ }
+}
+
+int Screen::fadePalStep(const uint8 *palette, int diff) {
+ debugC(9, kDebugLevelScreen, "Screen::fadePalStep(%p, %d)", (const void *)palette, diff);
+
+ uint8 fadePal[768];
+ memcpy(fadePal, _screenPalette, 768);
+
+ bool needRefresh = false;
+ for (int i = 0; i < 768; ++i) {
+ int c1 = palette[i];
+ int c2 = fadePal[i];
+ if (c1 != c2) {
+ needRefresh = true;
+ if (c1 > c2) {
+ c2 += diff;
+ if (c1 < c2)
+ c2 = c1;
+ }
+
+ if (c1 < c2) {
+ c2 -= diff;
+ if (c1 > c2)
+ c2 = c1;
+ }
+
+ fadePal[i] = (uint8)c2;
+ }
+ }
+
+ if (needRefresh)
+ setScreenPalette(fadePal);
+
+ return needRefresh ? 1 : 0;
+}
+
void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) {
debugC(9, kDebugLevelScreen, "Screen::setPaletteIndex(%u, %u, %u, %u)", index, red, green, blue);
_currentPalette[index * 3 + 0] = red;
@@ -2432,7 +2459,7 @@ void Screen::setShapePages(int page1, int page2, int minY, int maxY) {
_maskMaxY = maxY;
}
-void Screen::setMouseCursor(int x, int y, byte *shape) {
+void Screen::setMouseCursor(int x, int y, const byte *shape) {
debugC(9, kDebugLevelScreen, "Screen::setMouseCursor(%d, %d, %p)", x, y, (const void *)shape);
if (!shape)
return;
diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h
index f8c85a2bac..99ba2d7c5f 100644
--- a/engines/kyra/screen.h
+++ b/engines/kyra/screen.h
@@ -89,10 +89,12 @@ public:
enum FontId {
FID_6_FNT = 0,
FID_8_FNT,
+ FID_9_FNT,
FID_CRED6_FNT,
FID_CRED8_FNT,
FID_BOOKFONT_FNT,
FID_GOLDFONT_FNT,
+ FID_INTRO_FNT,
FID_NUM
};
@@ -145,6 +147,8 @@ public:
void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0);
void fadePalette(const uint8 *palData, int delay, const UpdateFunctor *upFunc = 0);
+ virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff);
+ int fadePalStep(const uint8 *palette, int diff);
void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue);
void setScreenPalette(const uint8 *palData);
@@ -189,7 +193,7 @@ public:
void hideMouse();
void showMouse();
bool isMouseVisible() const;
- void setMouseCursor(int x, int y, byte *shape);
+ void setMouseCursor(int x, int y, const byte *shape);
// rect handling
virtual int getRectSize(int w, int h) = 0;
diff --git a/engines/kyra/screen_lol.cpp b/engines/kyra/screen_lol.cpp
new file mode 100644
index 0000000000..c6b47a9ca9
--- /dev/null
+++ b/engines/kyra/screen_lol.cpp
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "kyra/screen_lol.h"
+#include "kyra/lol.h"
+
+namespace Kyra {
+
+Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system), _vm(vm) {
+}
+
+void Screen_LoL::setScreenDim(int dim) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::setScreenDim(%d)", dim);
+ assert(dim < _screenDimTableCount);
+ _curDim = &_screenDimTable[dim];
+}
+
+const ScreenDim *Screen_LoL::getScreenDim(int dim) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::getScreenDim(%d)", dim);
+ assert(dim < _screenDimTableCount);
+ return &_screenDimTable[dim];
+}
+
+void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::fprintStringIntro('%s', %d, %d, %d, %d, %d, %d, ...)", format, x, y, c1, c2, c3, flags);
+ char buffer[400];
+
+ va_list args;
+ va_start(args, flags);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ if ((flags & 0x0F00) == 0x100)
+ x -= getTextWidth(buffer) >> 1;
+ if ((flags & 0x0F00) == 0x200)
+ x -= getTextWidth(buffer);
+
+ if ((flags & 0x00F0) == 0x20) {
+ printText(buffer, x-1, y, c3, c2);
+ printText(buffer, x, y+1, c3, c2);
+ }
+
+ printText(buffer, x, y, c1, c2);
+}
+
+} // end of namespace Kyra
+
diff --git a/engines/kyra/screen_lol.h b/engines/kyra/screen_lol.h
new file mode 100644
index 0000000000..38df3ca897
--- /dev/null
+++ b/engines/kyra/screen_lol.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef KYRA_SCREEN_LOL_H
+#define KYRA_SCREEN_LOL_H
+
+#include "kyra/screen_v2.h"
+
+namespace Kyra {
+
+class LoLEngine;
+
+class Screen_LoL : public Screen_v2 {
+public:
+ Screen_LoL(LoLEngine *vm, OSystem *system);
+
+ void setScreenDim(int dim);
+ const ScreenDim *getScreenDim(int dim);
+
+ void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...);
+private:
+ LoLEngine *_vm;
+
+ static const ScreenDim _screenDimTable[];
+ static const int _screenDimTableCount;
+};
+
+} // end of namespace Kyra
+
+#endif
+
diff --git a/engines/kyra/screen_v2.cpp b/engines/kyra/screen_v2.cpp
index e26ef87bad..c6ea6a93e8 100644
--- a/engines/kyra/screen_v2.cpp
+++ b/engines/kyra/screen_v2.cpp
@@ -111,6 +111,30 @@ int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *p
return r;
}
+void Screen_v2::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) {
+ debugC(9, kDebugLevelScreen, "Screen_v2::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff);
+
+ int maxDiff = 0;
+ diff = 0;
+ for (int i = 0; i < 768; ++i) {
+ diff = ABS(palette[i] - _screenPalette[i]);
+ maxDiff = MAX(maxDiff, diff);
+ }
+
+ delayInc = delay << 8;
+ if (maxDiff != 0) {
+ delayInc /= maxDiff;
+ delayInc = MIN(delayInc, 0x7FFF);
+ }
+
+ delay = delayInc;
+ for (diff = 1; diff <= maxDiff; ++diff) {
+ if (delayInc >= 256)
+ break;
+ delayInc += delay;
+ }
+}
+
void Screen_v2::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,
int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) {
uint8 *dstPtr = getPagePtr(_curPage);
@@ -369,7 +393,7 @@ void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2,
int t = (nb * h1) / h2;
if (t != u) {
u = t;
- const uint8 *s = src + (x1 + t) * 320;
+ const uint8 *s = src + x1 + t * 320;
uint8 *dt = (uint8 *)_wsaFrameAnimBuffer;
t = w2 - w1;
@@ -461,5 +485,27 @@ bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, i
return (w1 == -1) ? false : true;
}
+void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) {
+ debugC(9, kDebugLevelScreen, "Screen_v2::checkedPageUpdate(%d, %d)", srcPage, dstPage);
+
+ const uint32 *src = (const uint32 *)getPagePtr(srcPage);
+ uint32 *dst = (uint32 *)getPagePtr(dstPage);
+ uint32 *page0 = (uint32 *)getPagePtr(0);
+
+ bool updated = false;
+
+ for (int y = 0; y < 200; ++y) {
+ for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) {
+ if (*src != *dst) {
+ updated = true;
+ *dst = *page0 = *src;
+ }
+ }
+ }
+
+ if (updated)
+ addDirtyRect(0, 0, 320, 200);
+}
+
} // end of namespace Kyra
diff --git a/engines/kyra/screen_v2.h b/engines/kyra/screen_v2.h
index f624228445..7bbdc4b6c3 100644
--- a/engines/kyra/screen_v2.h
+++ b/engines/kyra/screen_v2.h
@@ -40,10 +40,14 @@ public:
void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,
int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2);
+ void checkedPageUpdate(int srcPage, int dstPage);
+
// palette handling
uint8 *generateOverlay(const uint8 *palette, uint8 *buffer, int color, uint16 factor);
void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay);
int findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *palette, uint16 numColors);
+
+ virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff);
// shape handling
uint8 *getPtrToShape(uint8 *shpFile, int shape);
diff --git a/engines/kyra/script.cpp b/engines/kyra/script.cpp
index ef3e259cfa..b10a4b32bf 100644
--- a/engines/kyra/script.cpp
+++ b/engines/kyra/script.cpp
@@ -35,7 +35,7 @@
namespace Kyra {
EMCInterpreter::EMCInterpreter(KyraEngine_v1 *vm) : _vm(vm) {
#define COMMAND(x) { &EMCInterpreter::x, #x }
- static CommandEntry commandProcs[] = {
+ static const CommandEntry commandProcs[] = {
// 0x00
COMMAND(cmd_jmpTo),
COMMAND(cmd_setRetValue),
@@ -132,6 +132,8 @@ bool EMCInterpreter::load(const char *filename, EMCData *scriptData, const Commo
scriptData->opcodes = opcodes;
+ strncpy(scriptData->filename, filename, 13);
+
return true;
}
@@ -205,7 +207,7 @@ bool EMCInterpreter::run(EMCState *script) {
}
if (opcode > 18) {
- error("Script unknown command: %d", opcode);
+ error("Script unknown command: %d in file '%s' at offset 0x%.08X", opcode, script->dataPtr->filename, instOffset);
} else {
debugC(5, kDebugLevelScript, "[0x%.08X] EMCInterpreter::%s([%d/%u])", instOffset, _commands[opcode].desc, _parameter, (uint)_parameter);
(this->*(_commands[opcode].proc))(script);
@@ -388,7 +390,7 @@ void EMCInterpreter::cmd_execOpcode(EMCState* script) {
script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script);
} else {
script->retValue = 0;
- warning("calling unimplemented opcode(0x%.02X/%d)", opcode, opcode);
+ warning("Calling unimplemented opcode(0x%.02X/%d) from file '%s'", opcode, opcode, script->dataPtr->filename);
}
}
diff --git a/engines/kyra/script.h b/engines/kyra/script.h
index de52093f66..2b97a83289 100644
--- a/engines/kyra/script.h
+++ b/engines/kyra/script.h
@@ -36,6 +36,8 @@ struct EMCState;
typedef Common::Functor1<EMCState*, int> Opcode;
struct EMCData {
+ char filename[13];
+
byte *text;
uint16 *data;
uint16 *ordr;
diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp
index 91fbfb3e49..e3a8bf95bc 100644
--- a/engines/kyra/script_hof.cpp
+++ b/engines/kyra/script_hof.cpp
@@ -799,10 +799,14 @@ int KyraEngine_HoF::o2_showLetter(EMCState *script) {
_screen->fadeToBlack(0x14);
- sprintf(filename, "LETTER%.1d.", letter);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
-
+ sprintf(filename, "LETTER%.1d.%s", letter, _languageExtension[_lang]);
uint8 *letterBuffer = _res->fileData(filename, 0);
+ if (!letterBuffer) {
+ // some floppy versions use a TXT extension
+ sprintf(filename, "LETTER%.1d.TXT", letter);
+ letterBuffer = _res->fileData(filename, 0);
+ }
+
if (letterBuffer) {
bookDecodeText(letterBuffer);
bookPrintText(2, letterBuffer, 0xC, 0xA, 0x20);
@@ -1488,7 +1492,7 @@ typedef Common::Functor1Mem<EMCState*, int, KyraEngine_HoF> OpcodeV2;
typedef Common::Functor2Mem<const TIM*, const uint16*, int, KyraEngine_HoF> TIMOpcodeV2;
#define OpcodeTim(x) _timOpcodes.push_back(new TIMOpcodeV2(this, &KyraEngine_HoF::x))
-#define OpcodeTimUnImpl() _timOpcodes.push_back(TIMOpcodeV2(this, 0))
+#define OpcodeTimUnImpl() _timOpcodes.push_back(new TIMOpcodeV2(this, 0))
void KyraEngine_HoF::setupOpcodeTable() {
Common::Array<const Opcode*> *table = 0;
diff --git a/engines/kyra/script_tim.cpp b/engines/kyra/script_tim.cpp
index 4ad6464424..7993fb8de6 100644
--- a/engines/kyra/script_tim.cpp
+++ b/engines/kyra/script_tim.cpp
@@ -26,58 +26,75 @@
#include "kyra/script_tim.h"
#include "kyra/script.h"
#include "kyra/resource.h"
+#include "kyra/sound.h"
+#include "kyra/wsamovie.h"
#include "common/endian.h"
namespace Kyra {
-TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _system(system), _currentTim(0) {
+TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system) : _vm(vm), _screen(screen), _system(system), _currentTim(0) {
#define COMMAND(x) { &TIMInterpreter::x, #x }
#define COMMAND_UNIMPL() { 0, 0 }
- static CommandEntry commandProcs[] = {
+#define cmd_return(n) cmd_return_##n
+ static const CommandEntry commandProcs[] = {
// 0x00
COMMAND(cmd_initFunc0),
COMMAND(cmd_stopCurFunc),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_initWSA),
+ COMMAND(cmd_uninitWSA),
// 0x04
COMMAND(cmd_initFunc),
COMMAND(cmd_stopFunc),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_wsaDisplayFrame),
COMMAND_UNIMPL(),
// 0x08
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_loadVocFile),
+ COMMAND(cmd_unloadVocFile),
+ COMMAND(cmd_playVocFile),
COMMAND_UNIMPL(),
// 0x0C
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_loadSoundFile),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_playMusicTrack),
COMMAND_UNIMPL(),
// 0x10
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(1)),
COMMAND_UNIMPL(),
COMMAND_UNIMPL(),
// 0x14
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_setLoopIp),
+ COMMAND(cmd_continueLoop),
+ COMMAND(cmd_resetLoopIp),
COMMAND(cmd_resetAllRuntimes),
// 0x18
- COMMAND(cmd_return<1>),
+ COMMAND(cmd_return(1)),
COMMAND(cmd_execOpcode),
COMMAND(cmd_initFuncNow),
COMMAND(cmd_stopFuncNow),
// 0x1C
- COMMAND(cmd_return<1>),
- COMMAND(cmd_return<1>),
- COMMAND(cmd_return<-1>)
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(n1))
};
+#undef cmd_return
_commands = commandProcs;
_commandsSize = ARRAYSIZE(commandProcs);
+
+ memset(&_animations, 0, sizeof(_animations));
+ _langData = 0;
+ _textDisplayed = false;
+ _textAreaBuffer = new uint8[320*40];
+ assert(_textAreaBuffer);
+
+ _palDelayInc = _palDiff = _palDelayAcc = 0;
+}
+
+TIMInterpreter::~TIMInterpreter() {
+ delete[] _langData;
+ delete[] _textAreaBuffer;
}
TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes) {
@@ -122,6 +139,8 @@ TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpc
for (int i = 0; i < num; ++i)
tim->func[i].avtl = tim->avtl + tim->avtl[i];
+ strncpy(tim->filename, filename, 13);
+
return tim;
}
@@ -135,6 +154,11 @@ void TIMInterpreter::unload(TIM *&tim) const {
tim = 0;
}
+void TIMInterpreter::setLangData(const char *filename) {
+ delete[] _langData;
+ _langData = _vm->resource()->fileData(filename, 0);
+}
+
void TIMInterpreter::exec(TIM *tim, bool loop) {
if (!tim)
return;
@@ -171,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) {
_currentTim->procFunc = _currentFunc;
break;
+ case 22:
+ cur.loopIp = 0;
+ break;
+
default:
break;
}
@@ -197,22 +225,224 @@ void TIMInterpreter::refreshTimersAfterPause(uint32 elapsedTime) {
}
}
+void TIMInterpreter::displayText(uint16 textId, int16 flags) {
+ char *text = getTableEntry(textId);
+
+ if (_textDisplayed) {
+ _screen->copyBlockToPage(0, 0, 160, 320, 40, _textAreaBuffer);
+ _textDisplayed = false;
+ }
+
+ if (!text)
+ return;
+ if (!text[0])
+ return;
+
+ char filename[16];
+ memset(filename, 0, sizeof(filename));
+
+ if (text[0] == '$') {
+ const char *end = strchr(text+1, '$');
+ if (end)
+ memcpy(filename, text+1, end-1-text);
+ }
+
+ if (filename[0])
+ _vm->sound()->voicePlay(filename);
+
+ if (text[0] == '$')
+ text = strchr(text + 1, '$') + 1;
+
+ setupTextPalette((flags < 0) ? 1 : flags, 0);
+
+ if (flags < 0) {
+ static const uint8 colorMap[] = { 0x00, 0xF0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ _screen->setFont(Screen::FID_8_FNT);
+ _screen->setTextColorMap(colorMap);
+ _screen->_charWidth = -2;
+ }
+
+ _screen->_charOffset = -4;
+ _screen->copyRegionToBuffer(0, 0, 160, 320, 40, _textAreaBuffer);
+ _textDisplayed = true;
+
+ char backupChar = 0;
+ char *str = text;
+ int heightAdd = 0;
+
+ while (str[0]) {
+ char *nextLine = strchr(str, '\r');
+
+ backupChar = 0;
+ if (nextLine) {
+ backupChar = nextLine[0];
+ nextLine[0] = '\0';
+ }
+
+ int width = _screen->getTextWidth(str);
+
+ if (flags >= 0)
+ _screen->printText(str, (320 - width) >> 1, 160 + heightAdd, 0xF0, 0x00);
+ else
+ _screen->printText(str, (320 - width) >> 1, 188, 0xF0, 0x00);
+
+ heightAdd += _screen->getFontHeight();
+ str += strlen(str);
+
+ if (backupChar) {
+ nextLine[0] = backupChar;
+ ++str;
+ }
+ }
+
+ _screen->_charOffset = 0;
+
+ if (flags < 0) {
+ static const uint8 colorMap[] = { 0x00, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x00, 0x00, 0x00, 0x00 };
+
+ _screen->setFont(Screen::FID_INTRO_FNT);
+ _screen->setTextColorMap(colorMap);
+ _screen->_charWidth = 0;
+ }
+}
+
+void TIMInterpreter::setupTextPalette(uint index, int fadePalette) {
+ static const uint16 palTable[] = {
+ 0x00, 0x00, 0x00,
+ 0x64, 0x64, 0x64,
+ 0x61, 0x51, 0x30,
+ 0x29, 0x48, 0x64,
+ 0x00, 0x4B, 0x3B,
+ 0x64, 0x1E, 0x1E,
+ };
+
+ for (int i = 0; i < 15; ++i) {
+ uint8 *palette = _screen->getPalette(0) + (240 + i) * 3;
+
+ uint8 c1 = (((15 - i) << 2) * palTable[index*3+0]) / 100;
+ uint8 c2 = (((15 - i) << 2) * palTable[index*3+1]) / 100;
+ uint8 c3 = (((15 - i) << 2) * palTable[index*3+2]) / 100;
+
+ palette[0] = c1;
+ palette[1] = c2;
+ palette[2] = c3;
+ }
+
+ if (!fadePalette && !_palDiff) {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ } else {
+ _screen->getFadeParams(_screen->getPalette(0), fadePalette, _palDelayInc, _palDiff);
+ _palDelayAcc = 0;
+ }
+}
+
+TIMInterpreter::Animation *TIMInterpreter::initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags) {
+ Animation *anim = &_animations[index];
+ anim->x = x;
+ anim->y = y;
+ anim->wsaCopyParams = wsaFlags;
+
+ uint16 wsaOpenFlags = ((wsaFlags & 0x10) != 0) ? 2 : 0;
+
+ char file[32];
+ snprintf(file, 32, "%s.WSA", filename);
+
+ if (_vm->resource()->exists(file)) {
+ anim->wsa = new WSAMovie_v2(_vm, _screen);
+ assert(anim->wsa);
+
+ anim->wsa->open(file, wsaOpenFlags, (index == 1) ? _screen->getPalette(0) : 0);
+ }
+
+ if (anim->wsa && anim->wsa->opened()) {
+ if (x == -1)
+ anim->x = x = 0;
+ if (y == -1)
+ anim->y = y = 0;
+
+ if (wsaFlags & 2) {
+ _screen->fadePalette(_screen->getPalette(1), 15, 0);
+ _screen->clearPage(8);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ if (wsaFlags & 4) {
+ snprintf(file, 32, "%s.CPS", filename);
+
+ if (_vm->resource()->exists(file)) {
+ _screen->loadBitmap(file, 3, 3, _screen->getPalette(0));
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ anim->wsa->setX(x);
+ anim->wsa->setY(y);
+ anim->wsa->setDrawPage(0);
+ anim->wsa->displayFrame(0, 0, 0, 0);
+ }
+
+ if (wsaFlags & 2)
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+ } else {
+ if (wsaFlags & 2) {
+ _screen->fadePalette(_screen->getPalette(1), 15, 0);
+ _screen->clearPage(8);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ snprintf(file, 32, "%s.CPS", filename);
+
+ if (_vm->resource()->exists(file)) {
+ _screen->loadBitmap(file, 3, 3, _screen->getPalette(0));
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ if (wsaFlags & 2)
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+ }
+
+ return anim;
+}
+
+char *TIMInterpreter::getTableEntry(uint idx) {
+ if (!_langData)
+ return 0;
+ else
+ return (char *)(_langData + READ_LE_UINT16(_langData + (idx<<1)));
+}
+
+const char *TIMInterpreter::getCTableEntry(uint idx) const {
+ if (!_langData)
+ return 0;
+ else
+ return (const char *)(_langData + READ_LE_UINT16(_langData + (idx<<1)));
+}
+
int TIMInterpreter::execCommand(int cmd, const uint16 *param) {
if (cmd < 0 || cmd >= _commandsSize) {
- warning("Calling unimplemented TIM command %d", cmd);
+ warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);
return 0;
}
if (_commands[cmd].proc == 0) {
- warning("Calling unimplemented TIM command %d", cmd);
+ warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);
return 0;
}
- debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void*)param);
+ debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void* )param);
return (this->*_commands[cmd].proc)(param);
}
int TIMInterpreter::cmd_initFunc0(const uint16 *param) {
+ for (int i = 0; i < TIM::kWSASlots; ++i)
+ memset(&_currentTim->wsa[i], 0, sizeof(TIM::WSASlot));
+
_currentTim->func[0].ip = _currentTim->func[0].avtl;
_currentTim->func[0].lastTime = _system->getMillis();
return 1;
@@ -226,6 +456,46 @@ int TIMInterpreter::cmd_stopCurFunc(const uint16 *param) {
return -2;
}
+int TIMInterpreter::cmd_initWSA(const uint16 *param) {
+ const int index = param[0];
+
+ TIM::WSASlot &slot = _currentTim->wsa[index];
+
+ slot.x = int16(param[2]);
+ slot.y = int16(param[3]);
+ slot.offscreen = param[4];
+ slot.wsaFlags = param[5];
+ const char *filename = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[1]<<1)));
+
+ slot.anim = initAnimStruct(index, filename, slot.x, slot.y, 10, slot.offscreen, slot.wsaFlags);
+ return 1;
+}
+
+int TIMInterpreter::cmd_uninitWSA(const uint16 *param) {
+ const int index = param[0];
+
+ TIM::WSASlot &slot = _currentTim->wsa[index];
+
+ if (!slot.anim)
+ return 0;
+
+ Animation &anim = _animations[index];
+
+ if (slot.offscreen) {
+ delete anim.wsa;
+ anim.wsa = 0;
+ slot.anim = 0;
+ } else {
+ //XXX
+
+ delete anim.wsa;
+ memset(&anim, 0, sizeof(Animation));
+ memset(&slot, 0, sizeof(TIM::WSASlot));
+ }
+
+ return 1;
+}
+
int TIMInterpreter::cmd_initFunc(const uint16 *param) {
uint16 func = *param;
assert(func < TIM::kCountFuncs);
@@ -243,6 +513,97 @@ int TIMInterpreter::cmd_stopFunc(const uint16 *param) {
return 1;
}
+int TIMInterpreter::cmd_wsaDisplayFrame(const uint16 *param) {
+ Animation &anim = _animations[param[0]];
+ const int frame = param[1];
+
+ anim.wsa->setX(anim.x);
+ anim.wsa->setY(anim.y);
+ anim.wsa->setDrawPage((anim.wsaCopyParams & 0x4000) != 0 ? 2 : 8);
+ anim.wsa->displayFrame(frame, anim.wsaCopyParams & 0xF0FF, 0, 0);
+ return 1;
+}
+
+int TIMInterpreter::cmd_displayText(const uint16 *param) {
+ displayText(param[0], param[1]);
+ return 1;
+}
+
+int TIMInterpreter::cmd_loadVocFile(const uint16 *param) {
+ const int stringId = param[0];
+ const int index = param[1];
+
+ _vocFiles[index] = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (stringId << 1)));
+ for (int i = 0; i < 4; ++i)
+ _vocFiles[index].deleteLastChar();
+ return 1;
+}
+
+int TIMInterpreter::cmd_unloadVocFile(const uint16 *param) {
+ const int index = param[0];
+ _vocFiles[index].clear();
+ return 1;
+}
+
+int TIMInterpreter::cmd_playVocFile(const uint16 *param) {
+ const int index = param[0];
+ const int volume = (param[1] * 255) / 100;
+
+ if (index < ARRAYSIZE(_vocFiles) && !_vocFiles[index].empty())
+ _vm->sound()->voicePlay(_vocFiles[index].c_str()/*, volume*/, true);
+ else
+ _vm->snd_playSoundEffect(index, volume);
+
+ return 1;
+}
+
+int TIMInterpreter::cmd_loadSoundFile(const uint16 *param) {
+ const char *file = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[0]<<1)));
+
+ static char * fileList[] = { 0 };
+ fileList[0] = _audioFilename;
+ static AudioDataStruct audioList = { fileList, 1, 0, 0 };
+
+ strncpy(_audioFilename, file, sizeof(_audioFilename));
+
+ _vm->sound()->setSoundList(&audioList);
+ _vm->sound()->loadSoundFile(0);
+ return 1;
+}
+
+int TIMInterpreter::cmd_playMusicTrack(const uint16 *param) {
+ _vm->sound()->playTrack(param[0]);
+ return 1;
+}
+
+int TIMInterpreter::cmd_setLoopIp(const uint16 *param) {
+ _currentTim->func[_currentFunc].loopIp = _currentTim->func[_currentFunc].ip;
+ return 1;
+}
+
+int TIMInterpreter::cmd_continueLoop(const uint16 *param) {
+ TIM::Function &func = _currentTim->func[_currentFunc];
+
+ if (!func.loopIp)
+ return -2;
+
+ func.ip = func.loopIp;
+
+ uint16 factor = param[0];
+ if (factor) {
+ const uint32 random = _vm->_rnd.getRandomNumberRng(0, 0x8000);
+ uint32 waitTime = (random * factor) / 0x8000;
+ func.nextTime += waitTime * _vm->tickLength();
+ }
+
+ return 1;
+}
+
+int TIMInterpreter::cmd_resetLoopIp(const uint16 *param) {
+ _currentTim->func[_currentFunc].loopIp = 0;
+ return 1;
+}
+
int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {
for (int i = 0; i < TIM::kCountFuncs; ++i) {
if (_currentTim->func[i].ip)
@@ -252,14 +613,20 @@ int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {
}
int TIMInterpreter::cmd_execOpcode(const uint16 *param) {
+ const uint16 opcode = *param++;
+
if (!_currentTim->opcodes) {
- warning("Trying to execute TIM opcode without opcode list");
+ warning("Trying to execute TIM opcode %d without opcode list (file '%s')", opcode, _currentTim->filename);
return 0;
}
- uint16 opcode = *param++;
if (opcode > _currentTim->opcodes->size()) {
- warning("Calling unimplemented TIM opcode(0x%.02X/%d)", opcode, opcode);
+ warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename);
+ return 0;
+ }
+
+ if (!(*_currentTim->opcodes)[opcode]->isValid()) {
+ warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename);
return 0;
}
diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h
index cd715ff4ef..68ef23fd6c 100644
--- a/engines/kyra/script_tim.h
+++ b/engines/kyra/script_tim.h
@@ -30,13 +30,18 @@
#include "common/array.h"
#include "common/func.h"
+#include "common/str.h"
namespace Kyra {
+class WSAMovie_v2;
+class Screen_v2;
struct TIM;
typedef Common::Functor2<const TIM*, const uint16*, int> TIMOpcode;
struct TIM {
+ char filename[13];
+
int16 procFunc;
uint16 procParam;
@@ -50,9 +55,23 @@ struct TIM {
uint32 lastTime;
uint32 nextTime;
+ const uint16 *loopIp;
+
const uint16 *avtl;
} func[kCountFuncs];
+ enum {
+ kWSASlots = 10
+ };
+
+ struct WSASlot {
+ void *anim;
+
+ int16 x, y;
+ uint16 wsaFlags;
+ uint16 offscreen;
+ } wsa[kWSASlots];
+
uint16 *avtl;
uint8 *text;
@@ -61,10 +80,22 @@ struct TIM {
class TIMInterpreter {
public:
- TIMInterpreter(KyraEngine_v1 *vm, OSystem *system);
+ struct Animation {
+ WSAMovie_v2 *wsa;
+ int16 x, y;
+ uint16 wsaCopyParams;
+ };
+
+ TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system);
+ ~TIMInterpreter();
TIM *load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes);
void unload(TIM *&tim) const;
+
+ void setLangData(const char *filename);
+ void clearLangData() { delete[] _langData; _langData = 0; }
+
+ const char *getCTableEntry(uint idx) const;
void resetFinishedFlag() { _finished = false; }
bool finished() const { return _finished; }
@@ -72,10 +103,15 @@ public:
void exec(TIM *tim, bool loop);
void stopCurFunc() { if (_currentTim) cmd_stopCurFunc(0); }
- void play(const char *filename);
void refreshTimersAfterPause(uint32 elapsedTime);
+
+ void displayText(uint16 textId, int16 flags);
+ void setupTextPalette(uint index, int fadePalette);
+
+ int _palDelayInc, _palDiff, _palDelayAcc;
private:
KyraEngine_v1 *_vm;
+ Screen_v2 *_screen;
OSystem *_system;
TIM *_currentTim;
@@ -83,6 +119,19 @@ private:
bool _finished;
+ Common::String _vocFiles[120];
+
+ Animation _animations[TIM::kWSASlots];
+
+ Animation *initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags);
+
+ char _audioFilename[32];
+
+ uint8 *_langData;
+ char *getTableEntry(uint idx);
+ bool _textDisplayed;
+ uint8 *_textAreaBuffer;
+
int execCommand(int cmd, const uint16 *param);
typedef int (TIMInterpreter::*CommandProc)(const uint16 *);
@@ -96,14 +145,30 @@ private:
int cmd_initFunc0(const uint16 *param);
int cmd_stopCurFunc(const uint16 *param);
+ int cmd_initWSA(const uint16 *param);
+ int cmd_uninitWSA(const uint16 *param);
int cmd_initFunc(const uint16 *param);
int cmd_stopFunc(const uint16 *param);
+ int cmd_wsaDisplayFrame(const uint16 *param);
+ int cmd_displayText(const uint16 *param);
+ int cmd_loadVocFile(const uint16 *param);
+ int cmd_unloadVocFile(const uint16 *param);
+ int cmd_playVocFile(const uint16 *param);
+ int cmd_loadSoundFile(const uint16 *param);
+ int cmd_playMusicTrack(const uint16 *param);
+ int cmd_setLoopIp(const uint16 *param);
+ int cmd_continueLoop(const uint16 *param);
+ int cmd_resetLoopIp(const uint16 *param);
int cmd_resetAllRuntimes(const uint16 *param);
int cmd_execOpcode(const uint16 *param);
int cmd_initFuncNow(const uint16 *param);
int cmd_stopFuncNow(const uint16 *param);
- template<int T>
- int cmd_return(const uint16 *) { return T; }
+#define cmd_return(n, v) \
+ int cmd_return_##n(const uint16 *) { return v; }
+
+ cmd_return( 1, 1);
+ cmd_return(n1, -1);
+#undef cmd_return
};
} // end of namespace Kyra
diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp
index 73d69ef10c..dfda5bf859 100644
--- a/engines/kyra/seqplayer.cpp
+++ b/engines/kyra/seqplayer.cpp
@@ -500,7 +500,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {
debugC(9, kDebugLevelSequence, "SeqPlayer::seq_playSequence(%p, %d)", (const void *)seqData, skipSeq);
assert(seqData);
- static SeqEntry floppySeqProcs[] = {
+ static const SeqEntry floppySeqProcs[] = {
// 0x00
SEQOP(3, s1_wsaOpen),
SEQOP(2, s1_wsaClose),
@@ -541,7 +541,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {
SEQOP(1, s1_endOfScript)
};
- static SeqEntry cdromSeqProcs[] = {
+ static const SeqEntry cdromSeqProcs[] = {
// 0x00
SEQOP(3, s1_wsaOpen),
SEQOP(2, s1_wsaClose),
diff --git a/engines/kyra/sequences_lok.cpp b/engines/kyra/sequences_lok.cpp
index b30568c7e2..3a497a258f 100644
--- a/engines/kyra/sequences_lok.cpp
+++ b/engines/kyra/sequences_lok.cpp
@@ -1083,7 +1083,7 @@ void KyraEngine_LoK::seq_playCredits() {
_screen->_charWidth = -1;
// we only need this for the fm-towns version
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)
+ if (_flags.platform == Common::kPlatformFMTowns && _configMusic == 1)
snd_playWanderScoreViaMap(53, 1);
uint8 *buffer = 0;
diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp
index f56c43aabd..c8749dc06b 100644
--- a/engines/kyra/sound.cpp
+++ b/engines/kyra/sound.cpp
@@ -202,8 +202,8 @@ bool SoundMidiPC::init() {
}
void SoundMidiPC::updateVolumeSettings() {
- _musicVolume = ConfMan.getInt("music_volume");
- _sfxVolume = ConfMan.getInt("sfx_volume");
+ _musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255);
+ _sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255);
updateChannelVolume(_musicVolume);
}
@@ -243,27 +243,14 @@ int SoundMidiPC::open() {
}
void SoundMidiPC::close() {
- if (_driver)
+ if (_driver) {
_driver->close();
+ delete _driver;
+ }
_driver = 0;
}
void SoundMidiPC::send(uint32 b) {
- // HACK: For Kyrandia, we make the simplifying assumption that a song
- // either loops in its entirety, or not at all. So if we see a FOR_LOOP
- // controller event, we turn on looping even if there isn't any
- // corresponding NEXT_BREAK event.
- //
- // This is a gross over-simplification of how XMIDI handles loops. If
- // anyone feels like doing a proper implementation, please refer to
- // the Exult project, and do it in midiparser_xmidi.cpp
-
- if ((b & 0xFFF0) == 0x74B0 && _eventFromMusic) {
- debugC(9, kDebugLevelMain | kDebugLevelSound, "SoundMidiPC: Looping song");
- _musicParser->property(MidiParser::mpAutoLoop, true);
- return;
- }
-
if (_passThrough) {
if ((b & 0xFFF0) == 0x007BB0)
return;
diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h
index 1baeb3064a..cebfdf491f 100644
--- a/engines/kyra/sound.h
+++ b/engines/kyra/sound.h
@@ -73,7 +73,8 @@ public:
kAdlib,
kMidiMT32,
kMidiGM,
- kTowns
+ kTowns,
+ kPC98
};
virtual kType getMusicType() const = 0;
@@ -382,7 +383,9 @@ private:
Common::Mutex _mutex;
};
-class SoundTowns_EuphonyDriver;
+class Towns_EuphonyDriver;
+class TownsPC98_OpnDriver;
+
class SoundTowns : public MidiDriver, public Sound {
public:
SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer);
@@ -417,6 +420,7 @@ public:
static float semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey,
uint32 sampleRate, uint32 outputRate, int32 pitchWheel);
+
private:
bool loadInstruments();
void playEuphonyTrack(uint32 offset, int loop);
@@ -430,7 +434,7 @@ private:
uint _sfxFileIndex;
uint8 *_sfxFileData;
- SoundTowns_EuphonyDriver * _driver;
+ Towns_EuphonyDriver * _driver;
MidiParser * _parser;
Common::Mutex _mutex;
@@ -439,13 +443,38 @@ private:
const uint8 *_sfxWDTable;
};
-//class SoundTowns_v2_TwnDriver;
-class SoundTowns_v2 : public Sound {
+class SoundPC98 : public Sound {
public:
- SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer);
- ~SoundTowns_v2();
+ SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ ~SoundPC98();
- kType getMusicType() const { return kTowns; }
+ virtual kType getMusicType() const { return kPC98; }
+
+ bool init();
+
+ void process() {}
+ void loadSoundFile(uint file) {}
+
+ void playTrack(uint8 track);
+ void haltTrack();
+ void beginFadeOut();
+
+ int32 voicePlay(const char *file, bool isSfx = false) { return -1; }
+ void playSoundEffect(uint8);
+
+protected:
+ int _lastTrack;
+ uint8 *_musicTrackData;
+ uint8 *_sfxTrackData;
+ TownsPC98_OpnDriver *_driver;
+};
+
+class SoundTownsPC98_v2 : public Sound {
+public:
+ SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ ~SoundTownsPC98_v2();
+
+ kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; }
bool init();
void process();
@@ -457,15 +486,15 @@ public:
void beginFadeOut();
int32 voicePlay(const char *file, bool isSfx = false);
- void playSoundEffect(uint8) {}
-
-private:
- int _lastTrack;
+ void playSoundEffect(uint8 track);
+protected:
Audio::AudioStream *_currentSFX;
+ int _lastTrack;
+ bool _useFmSfx;
- //SoundTowns_v2_TwnDriver *_driver;
- uint8 *_twnTrackData;
+ uint8 *_musicTrackData;
+ TownsPC98_OpnDriver *_driver;
};
class MixedSoundDriver : public Sound {
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp
index 68a2f0be9c..0ceb288b8a 100644
--- a/engines/kyra/sound_adlib.cpp
+++ b/engines/kyra/sound_adlib.cpp
@@ -235,6 +235,10 @@ private:
// * One for instruments, starting at offset 500.
uint8 *getProgram(int progId) {
+ uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
+ //TODO: Check in LoL CD Adlib driver
+ if (offset == 0xFFFF)
+ return 0;
return _soundData + READ_LE_UINT16(_soundData + 2 * progId);
}
@@ -1282,6 +1286,9 @@ int AdlibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 va
return 0;
uint8 *ptr = getProgram(value);
+ //TODO: Check in LoL CD Adlib driver
+ if (!ptr)
+ return 0;
uint8 chan = *ptr++;
uint8 priority = *ptr++;
@@ -2213,7 +2220,7 @@ const int SoundAdlibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdlibPC::_kyra1S
SoundAdlibPC::SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {
memset(_trackEntries, 0, sizeof(_trackEntries));
- _v2 = (_vm->gameFlags().gameID == GI_KYRA2);
+ _v2 = (_vm->gameFlags().gameID == GI_KYRA2) || (_vm->gameFlags().gameID == GI_LOL);
_driver = new AdlibDriver(mixer, _v2);
assert(_driver);
diff --git a/engines/kyra/sound_lok.cpp b/engines/kyra/sound_lok.cpp
index 8a1d16a6b1..b43d72ebce 100644
--- a/engines/kyra/sound_lok.cpp
+++ b/engines/kyra/sound_lok.cpp
@@ -43,19 +43,29 @@ void KyraEngine_LoK::snd_playWanderScoreViaMap(int command, int restart) {
if (restart)
_lastMusicCommand = -1;
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
+ if (_flags.platform == Common::kPlatformFMTowns) {
if (command == 1) {
_sound->beginFadeOut();
} else if (command >= 35 && command <= 38) {
snd_playSoundEffect(command-20);
} else if (command >= 2) {
- if (_lastMusicCommand != command) {
+ if (_lastMusicCommand != command)
// the original does -2 here we handle this inside _sound->playTrack()
_sound->playTrack(command);
- }
} else {
_sound->haltTrack();
}
+ _lastMusicCommand = command;
+ } else if (_flags.platform == Common::kPlatformPC98) {
+ if (command == 1) {
+ _sound->beginFadeOut();
+ } else if (command >= 2) {
+ if (_lastMusicCommand != command)
+ _sound->playTrack(command);
+ } else {
+ _sound->haltTrack();
+ }
+ _lastMusicCommand = command;
} else {
KyraEngine_v1::snd_playWanderScoreViaMap(command, restart);
}
diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp
index 4265533507..0f2b916c9d 100644
--- a/engines/kyra/sound_towns.cpp
+++ b/engines/kyra/sound_towns.cpp
@@ -34,18 +34,16 @@
#include "common/util.h"
-#include <math.h>
-
#define EUPHONY_FADEOUT_TICKS 600
namespace Kyra {
-enum ChannelState { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing };
+enum EnvelopeState { s_ready, s_attacking, s_decaying, s_sustaining, s_releasing };
-class MidiChannel_EuD : public MidiChannel {
+class Towns_EuphonyChannel : public MidiChannel {
public:
- MidiChannel_EuD() {}
- ~MidiChannel_EuD() {}
+ Towns_EuphonyChannel() {}
+ ~Towns_EuphonyChannel() {}
virtual void nextTick(int32 *outbuf, int buflen) = 0;
virtual void rate(uint16 r) = 0;
@@ -54,10 +52,10 @@ protected:
uint16 _rate;
};
-class MidiChannel_EuD_FM : public MidiChannel_EuD {
+class Towns_EuphonyFmChannel : public Towns_EuphonyChannel {
public:
- MidiChannel_EuD_FM();
- virtual ~MidiChannel_EuD_FM();
+ Towns_EuphonyFmChannel();
+ virtual ~Towns_EuphonyFmChannel();
void nextTick(int32 *outbuf, int buflen);
void rate(uint16 r);
@@ -79,13 +77,13 @@ protected:
Voice2612 *_voice;
};
-class MidiChannel_EuD_WAVE : public MidiChannel_EuD {
+class Towns_EuphonyPcmChannel : public Towns_EuphonyChannel {
public:
void nextTick(int32 *outbuf, int buflen);
void rate(uint16 r);
- MidiChannel_EuD_WAVE();
- virtual ~MidiChannel_EuD_WAVE();
+ Towns_EuphonyPcmChannel();
+ virtual ~Towns_EuphonyPcmChannel();
// MidiChannel interface
MidiDriver *device() { return 0; }
@@ -126,9 +124,9 @@ protected:
int32 keyOffset;
int32 keyNote;
const int8 *_samples;
- } * _snd[8];
+ } *_snd[8];
struct Env {
- ChannelState state;
+ EnvelopeState state;
int32 currentLevel;
int32 rate;
int32 tickCount;
@@ -141,40 +139,39 @@ protected:
int32 releaseRate;
int32 rootKeyOffset;
int32 size;
- } * _env[8];
- } * _voice;
+ } *_env[8];
+ } *_voice;
};
-class SoundTowns_EuphonyTrackQueue {
+class Towns_EuphonyTrackQueue {
public:
- SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver *driver, SoundTowns_EuphonyTrackQueue *last);
- ~SoundTowns_EuphonyTrackQueue() {}
+ Towns_EuphonyTrackQueue(Towns_EuphonyDriver *driver, Towns_EuphonyTrackQueue *last);
+ ~Towns_EuphonyTrackQueue() {}
- void release();
+ Towns_EuphonyTrackQueue *release();
void initDriver();
- void loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop = 0);
- void loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop = 0);
+ void loadDataToCurrentPosition(uint8 *trackdata, uint32 size, bool loop = 0);
+ void loadDataToEndOfQueue(uint8 *trackdata, uint32 size, bool loop = 0);
void setPlayBackStatus(bool playing);
- SoundTowns_EuphonyTrackQueue * reset();
bool isPlaying() {return _playing; }
- uint8 * trackData() {return _trackData; }
+ uint8 *trackData() {return _trackData; }
bool _loop;
- SoundTowns_EuphonyTrackQueue * _next;
+ Towns_EuphonyTrackQueue *_next;
private:
- uint8 * _trackData;
- uint8 * _used;
- uint8 * _fchan;
- uint8 * _wchan;
+ uint8 *_trackData;
+ uint8 *_used;
+ uint8 *_fchan;
+ uint8 *_wchan;
bool _playing;
- SoundTowns_EuphonyDriver * _driver;
- SoundTowns_EuphonyTrackQueue * _last;
+ Towns_EuphonyDriver *_driver;
+ Towns_EuphonyTrackQueue *_last;
};
-class MidiParser_EuD : public MidiParser {
+class Towns_EuphonyParser : public MidiParser {
public:
- MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue);
+ Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue);
bool loadMusic (byte *data, uint32 size);
int32 calculateTempo(int16 val);
@@ -183,11 +180,11 @@ protected:
void resetTracking();
void setup();
- byte * _enable;
- byte * _mode;
- byte * _channel;
- byte * _adjVelo;
- int8 * _adjNote;
+ byte *_enable;
+ byte *_mode;
+ byte *_channel;
+ byte *_adjVelo;
+ int8 *_adjNote;
uint8 _firstBaseTickStep;
uint8 _nextBaseTickStep;
@@ -195,13 +192,13 @@ protected:
uint32 _baseTick;
byte _tempo[3];
- SoundTowns_EuphonyTrackQueue * _queue;
+ Towns_EuphonyTrackQueue *_queue;
};
-class SoundTowns_EuphonyDriver : public MidiDriver_Emulated {
+class Towns_EuphonyDriver : public MidiDriver_Emulated {
public:
- SoundTowns_EuphonyDriver(Audio::Mixer *mixer);
- virtual ~SoundTowns_EuphonyDriver();
+ Towns_EuphonyDriver(Audio::Mixer *mixer);
+ virtual ~Towns_EuphonyDriver();
int open();
void close();
@@ -213,7 +210,7 @@ public:
void loadFmInstruments(const byte *instr);
void loadWaveInstruments(const byte *instr);
- SoundTowns_EuphonyTrackQueue * queue() { return _queue; }
+ Towns_EuphonyTrackQueue *queue() { return _queue; }
MidiChannel *allocateChannel() { return 0; }
MidiChannel *getPercussionChannel() { return 0; }
@@ -237,10 +234,10 @@ protected:
void generateSamples(int16 *buf, int len);
- MidiChannel_EuD_FM *_fChannel[6];
- MidiChannel_EuD_WAVE *_wChannel[8];
- MidiChannel_EuD * _channel[16];
- SoundTowns_EuphonyTrackQueue * _queue;
+ Towns_EuphonyFmChannel *_fChannel[6];
+ Towns_EuphonyPcmChannel *_wChannel[8];
+ Towns_EuphonyChannel *_channel[16];
+ Towns_EuphonyTrackQueue *_queue;
int _volume;
bool _fading;
@@ -251,23 +248,23 @@ protected:
int8 * _waveSounds[10];
};
-MidiChannel_EuD_FM::MidiChannel_EuD_FM() {
+Towns_EuphonyFmChannel::Towns_EuphonyFmChannel() {
_voice = new Voice2612;
}
-MidiChannel_EuD_FM::~MidiChannel_EuD_FM() {
+Towns_EuphonyFmChannel::~Towns_EuphonyFmChannel() {
delete _voice;
}
-void MidiChannel_EuD_FM::noteOn(byte note, byte onVelo) {
+void Towns_EuphonyFmChannel::noteOn(byte note, byte onVelo) {
_voice->noteOn(note, onVelo);
}
-void MidiChannel_EuD_FM::noteOff(byte note) {
+void Towns_EuphonyFmChannel::noteOff(byte note) {
_voice->noteOff(note);
}
-void MidiChannel_EuD_FM::controlChange(byte control, byte value) {
+void Towns_EuphonyFmChannel::controlChange(byte control, byte value) {
if (control == 121) {
// Reset controller
delete _voice;
@@ -279,25 +276,25 @@ void MidiChannel_EuD_FM::controlChange(byte control, byte value) {
}
}
-void MidiChannel_EuD_FM::sysEx_customInstrument(uint32, const byte *fmInst) {
+void Towns_EuphonyFmChannel::sysEx_customInstrument(uint32, const byte *fmInst) {
_voice->_rate = _rate;
_voice->setInstrument(fmInst);
}
-void MidiChannel_EuD_FM::pitchBend(int16 value) {
+void Towns_EuphonyFmChannel::pitchBend(int16 value) {
_voice->pitchBend(value);
}
-void MidiChannel_EuD_FM::nextTick(int32 *outbuf, int buflen) {
+void Towns_EuphonyFmChannel::nextTick(int32 *outbuf, int buflen) {
_voice->nextTick((int*) outbuf, buflen);
}
-void MidiChannel_EuD_FM::rate(uint16 r) {
+void Towns_EuphonyFmChannel::rate(uint16 r) {
_rate = r;
_voice->_rate = r;
}
-MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() {
+Towns_EuphonyPcmChannel::Towns_EuphonyPcmChannel() {
_voice = new Voice;
for (uint8 i = 0; i < 8; i++) {
_voice->_env[i] = new Voice::Env;
@@ -310,7 +307,7 @@ MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() {
_current = -1;
}
-MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() {
+Towns_EuphonyPcmChannel::~Towns_EuphonyPcmChannel() {
for (uint8 i = 0; i < 8; i++) {
if (_voice->_snd[i])
delete _voice->_snd[i];
@@ -319,7 +316,7 @@ MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() {
delete _voice;
}
-void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) {
+void Towns_EuphonyPcmChannel::noteOn(byte note, byte onVelo) {
_note = note;
velocity(onVelo);
_phase = 0;
@@ -329,24 +326,24 @@ void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) {
break;
}
- _voice->_env[_current]->state = _s_attacking;
+ _voice->_env[_current]->state = s_attacking;
_voice->_env[_current]->currentLevel = 0;
_voice->_env[_current]->rate = _rate;
_voice->_env[_current]->tickCount = 0;
}
-void MidiChannel_EuD_WAVE::noteOff(byte note) {
+void Towns_EuphonyPcmChannel::noteOff(byte note) {
if (_current == -1)
return;
- if (_voice->_env[_current]->state == _s_ready)
+ if (_voice->_env[_current]->state == s_ready)
return;
- _voice->_env[_current]->state = _s_releasing;
+ _voice->_env[_current]->state = s_releasing;
_voice->_env[_current]->releaseLevel = _voice->_env[_current]->currentLevel;
_voice->_env[_current]->tickCount = 0;
}
-void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) {
+void Towns_EuphonyPcmChannel::controlChange(byte control, byte value) {
switch (control) {
case 0x07:
// volume
@@ -377,7 +374,7 @@ void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) {
}
}
-void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmInst) {
+void Towns_EuphonyPcmChannel::sysEx_customInstrument(uint32 type, const byte *fmInst) {
if (type == 0x80) {
for (uint8 i = 0; i < 8; i++) {
const byte * const* pos = (const byte * const*) fmInst;
@@ -406,7 +403,7 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns
_voice->split[i] = READ_LE_UINT16(fmInst + 16 + 2 * i);
_voice->id[i] = READ_LE_UINT32(fmInst + 32 + 4 * i);
_voice->_snd[i] = 0;
- _voice->_env[i]->state = _s_ready;
+ _voice->_env[i]->state = s_ready;
_voice->_env[i]->currentLevel = 0;
_voice->_env[i]->totalLevel = *(fmInst + 64 + 8 * i);
_voice->_env[i]->attackRate = *(fmInst + 65 + 8 * i) * 10;
@@ -419,11 +416,11 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns
}
}
-void MidiChannel_EuD_WAVE::pitchBend(int16 value) {
+void Towns_EuphonyPcmChannel::pitchBend(int16 value) {
_frequencyOffs = value;
}
-void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) {
+void Towns_EuphonyPcmChannel::nextTick(int32 *outbuf, int buflen) {
if (_current == -1 || !_voice->_snd[_current] || !_voice->_env[_current]->state || !_velocity) {
velocity(0);
_current = -1;
@@ -475,13 +472,13 @@ void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) {
}
}
-void MidiChannel_EuD_WAVE::evpNextTick() {
+void Towns_EuphonyPcmChannel::evpNextTick() {
switch (_voice->_env[_current]->state) {
- case _s_ready:
+ case s_ready:
_voice->_env[_current]->currentLevel = 0;
return;
- case _s_attacking:
+ case s_attacking:
if (_voice->_env[_current]->attackRate == 0)
_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel;
else if (_voice->_env[_current]->attackRate >= 1270)
@@ -493,12 +490,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel >= _voice->_env[_current]->totalLevel) {
_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel;
- _voice->_env[_current]->state = _s_decaying;
+ _voice->_env[_current]->state = s_decaying;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_decaying:
+ case s_decaying:
if (_voice->_env[_current]->decayRate == 0)
_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel;
else if (_voice->_env[_current]->decayRate >= 1270)
@@ -512,12 +509,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= _voice->_env[_current]->sustainLevel) {
_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel;
- _voice->_env[_current]->state = _s_sustaining;
+ _voice->_env[_current]->state = s_sustaining;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_sustaining:
+ case s_sustaining:
if (_voice->_env[_current]->sustainRate == 0)
_voice->_env[_current]->currentLevel = 0;
else if (_voice->_env[_current]->sustainRate >= 2540)
@@ -531,12 +528,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= 0) {
_voice->_env[_current]->currentLevel = 0;
- _voice->_env[_current]->state = _s_ready;
+ _voice->_env[_current]->state = s_ready;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_releasing:
+ case s_releasing:
if (_voice->_env[_current]->releaseRate == 0)
_voice->_env[_current]->currentLevel = 0;
else if (_voice->_env[_current]->releaseRate >= 1270)
@@ -550,7 +547,7 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= 0) {
_voice->_env[_current]->currentLevel = 0;
- _voice->_env[_current]->state = _s_ready;
+ _voice->_env[_current]->state = s_ready;
}
break;
@@ -559,15 +556,15 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
}
}
-void MidiChannel_EuD_WAVE::rate(uint16 r) {
+void Towns_EuphonyPcmChannel::rate(uint16 r) {
_rate = r;
}
-void MidiChannel_EuD_WAVE::velocity(int velo) {
+void Towns_EuphonyPcmChannel::velocity(int velo) {
_velocity = velo;
}
-SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
+Towns_EuphonyDriver::Towns_EuphonyDriver(Audio::Mixer *mixer)
: MidiDriver_Emulated(mixer) {
_volume = 255;
_fadestate = EUPHONY_FADEOUT_TICKS;
@@ -576,9 +573,9 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
MidiDriver_YM2612::createLookupTables();
for (uint8 i = 0; i < 6; i++)
- _channel[i] = _fChannel[i] = new MidiChannel_EuD_FM;
+ _channel[i] = _fChannel[i] = new Towns_EuphonyFmChannel;
for (uint8 i = 0; i < 8; i++)
- _channel[i + 6] = _wChannel[i] = new MidiChannel_EuD_WAVE;
+ _channel[i + 6] = _wChannel[i] = new Towns_EuphonyPcmChannel;
_channel[14] = _channel[15] = 0;
_fmInstruments = _waveInstruments = 0;
@@ -587,10 +584,10 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
rate(getRate());
fading(0);
- _queue = new SoundTowns_EuphonyTrackQueue(this, 0);
+ _queue = new Towns_EuphonyTrackQueue(this, 0);
}
-SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() {
+Towns_EuphonyDriver::~Towns_EuphonyDriver() {
for (int i = 0; i < 6; i++)
delete _fChannel[i];
for (int i = 0; i < 8; i++)
@@ -622,7 +619,7 @@ SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() {
}
}
-int SoundTowns_EuphonyDriver::open() {
+int Towns_EuphonyDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
@@ -633,18 +630,18 @@ int SoundTowns_EuphonyDriver::open() {
return 0;
}
-void SoundTowns_EuphonyDriver::close() {
+void Towns_EuphonyDriver::close() {
if (!_isOpen)
return;
_isOpen = false;
_mixer->stopHandle(_mixerSoundHandle);
}
-void SoundTowns_EuphonyDriver::send(uint32 b) {
+void Towns_EuphonyDriver::send(uint32 b) {
send(b & 0xF, b & 0xFFFFFFF0);
}
-void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) {
+void Towns_EuphonyDriver::send(byte chan, uint32 b) {
byte param2 = (byte) ((b >> 16) & 0xFF);
byte param1 = (byte) ((b >> 8) & 0xFF);
byte cmd = (byte) (b & 0xF0);
@@ -703,18 +700,18 @@ void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) {
_channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000);
break;
default:
- warning("SoundTowns_EuphonyDriver: Unknown send() command 0x%02X", cmd);
+ warning("Towns_EuphonyDriver: Unknown send() command 0x%02X", cmd);
}
}
-void SoundTowns_EuphonyDriver::loadFmInstruments(const byte *instr) {
+void Towns_EuphonyDriver::loadFmInstruments(const byte *instr) {
if (_fmInstruments)
delete[] _fmInstruments;
_fmInstruments = new uint8[0x1800];
memcpy(_fmInstruments, instr, 0x1800);
}
-void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
+void Towns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
if (_waveInstruments)
delete[] _waveInstruments;
_waveInstruments = new uint8[0x1000];
@@ -739,24 +736,24 @@ void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
}
-void SoundTowns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) {
+void Towns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) {
_channel[midiChannelNumber] = _fChannel[fmChannelNumber];
}
-void SoundTowns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) {
+void Towns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) {
_channel[midiChannelNumber] = _wChannel[waveChannelNumber];
}
-void SoundTowns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) {
+void Towns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) {
_channel[midiChannelNumber] = 0;
}
-void SoundTowns_EuphonyDriver::generateSamples(int16 *data, int len) {
+void Towns_EuphonyDriver::generateSamples(int16 *data, int len) {
memset(data, 0, 2 * sizeof(int16) * len);
nextTick(data, len);
}
-void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
+void Towns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
int32 *buf0 = (int32 *)buf1;
for (int i = 0; i < ARRAYSIZE(_channel); i++) {
@@ -779,26 +776,26 @@ void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
}
}
-void SoundTowns_EuphonyDriver::rate(uint16 r) {
+void Towns_EuphonyDriver::rate(uint16 r) {
for (uint8 i = 0; i < 16; i++) {
if (_channel[i])
_channel[i]->rate(r);
}
}
-void SoundTowns_EuphonyDriver::fading(bool status) {
+void Towns_EuphonyDriver::fading(bool status) {
_fading = status;
if (!_fading)
_fadestate = EUPHONY_FADEOUT_TICKS;
}
-MidiParser_EuD::MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue) : MidiParser(),
+Towns_EuphonyParser::Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue) : MidiParser(),
_firstBaseTickStep(0x33), _nextBaseTickStep(0x33) {
_initialTempo = calculateTempo(0x5a);
_queue = queue;
}
-void MidiParser_EuD::parseNextEvent(EventInfo &info) {
+void Towns_EuphonyParser::parseNextEvent(EventInfo &info) {
byte *pos = _position._play_pos;
if (_queue->_next) {
@@ -878,7 +875,7 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {
pos += 6;
}
} else if (cmd == 0xF2) {
- static uint16 tickTable [] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
+ static const uint16 tickTable[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
_baseTick += tickTable[_nextBaseTickStep >> 4] * ((_nextBaseTickStep & 0x0f) + 1);
_nextBaseTickStep = pos[1];
pos += 6;
@@ -920,15 +917,14 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {
_position._play_pos = pos;
}
-bool MidiParser_EuD::loadMusic(byte *data, uint32 size) {
+bool Towns_EuphonyParser::loadMusic(byte *data, uint32 size) {
bool loop = _autoLoop;
if (_queue->isPlaying() && !_queue->_loop) {
_queue->loadDataToEndOfQueue(data, size, loop);
} else {
unloadMusic();
- _queue = _queue->reset();
- _queue->release();
+ _queue = _queue->release();
_queue->loadDataToCurrentPosition(data, size, loop);
setup();
setTrack(0);
@@ -937,7 +933,7 @@ bool MidiParser_EuD::loadMusic(byte *data, uint32 size) {
return true;
}
-int32 MidiParser_EuD::calculateTempo(int16 val) {
+int32 Towns_EuphonyParser::calculateTempo(int16 val) {
int32 tempo = val;
if (tempo < 0)
@@ -953,7 +949,7 @@ int32 MidiParser_EuD::calculateTempo(int16 val) {
return tempo;
}
-void MidiParser_EuD::resetTracking() {
+void Towns_EuphonyParser::resetTracking() {
MidiParser::resetTracking();
_nextBaseTickStep = _firstBaseTickStep;
@@ -962,7 +958,7 @@ void MidiParser_EuD::resetTracking() {
_queue->setPlayBackStatus(false);
}
-void MidiParser_EuD::setup() {
+void Towns_EuphonyParser::setup() {
uint8 *data = _queue->trackData();
if (!data)
return;
@@ -984,7 +980,7 @@ void MidiParser_EuD::setup() {
_tracks[0] = data + 0x806;
}
-SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver * driver, SoundTowns_EuphonyTrackQueue * last) {
+Towns_EuphonyTrackQueue::Towns_EuphonyTrackQueue(Towns_EuphonyDriver * driver, Towns_EuphonyTrackQueue * last) {
_trackData = 0;
_next = 0;
_driver = driver;
@@ -993,22 +989,15 @@ SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDri
_playing = false;
}
-void SoundTowns_EuphonyTrackQueue::setPlayBackStatus(bool playing) {
- SoundTowns_EuphonyTrackQueue * i = this;
+void Towns_EuphonyTrackQueue::setPlayBackStatus(bool playing) {
+ Towns_EuphonyTrackQueue * i = this;
do {
i->_playing = playing;
i = i->_next;
} while (i);
}
-SoundTowns_EuphonyTrackQueue * SoundTowns_EuphonyTrackQueue::reset() {
- SoundTowns_EuphonyTrackQueue * i = this;
- while (i->_last)
- i = i->_last;
- return i;
-}
-
-void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) {
+void Towns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) {
if (_trackData)
delete[] _trackData;
_trackData = new uint8[0xC58A];
@@ -1022,17 +1011,17 @@ void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata,
_playing = false;
}
-void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) {
+void Towns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) {
if (!_trackData) {
loadDataToCurrentPosition(trackdata, size, loop);
return;
}
- SoundTowns_EuphonyTrackQueue * i = this;
+ Towns_EuphonyTrackQueue * i = this;
while (i->_next)
i = i->_next;
- i = i->_next = new SoundTowns_EuphonyTrackQueue(_driver, i);
+ i = i->_next = new Towns_EuphonyTrackQueue(_driver, i);
i->_trackData = new uint8[0xC58A];
memset(i->_trackData, 0, 0xC58A);
Screen::decodeFrame4(trackdata, i->_trackData, size);
@@ -1044,29 +1033,39 @@ void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint3
i->_playing = _playing;
}
-void SoundTowns_EuphonyTrackQueue::release() {
- SoundTowns_EuphonyTrackQueue * i = _next;
- _next = 0;
- _playing = false;
- _used = _fchan = _wchan = 0;
+Towns_EuphonyTrackQueue *Towns_EuphonyTrackQueue::release() {
+ Towns_EuphonyTrackQueue *i = this;
+ while (i->_next)
+ i = i->_next;
- if (_trackData) {
- delete[] _trackData;
- _trackData = 0;
- }
+ Towns_EuphonyTrackQueue *res = i;
while (i) {
+ i->_playing = false;
+ i->_used = i->_fchan = i->_wchan = 0;
if (i->_trackData) {
delete[] i->_trackData;
i->_trackData = 0;
}
- i = i->_next;
- if (i)
- delete i->_last;
+ i = i->_last;
+ if (i) {
+ res = i;
+ if (i->_next) {
+ delete i->_next;
+ i->_next = 0;
+ }
+ }
}
+
+ if (res->_trackData) {
+ delete[] res->_trackData;
+ res->_trackData = 0;
+ }
+
+ return res;
}
-void SoundTowns_EuphonyTrackQueue::initDriver() {
+void Towns_EuphonyTrackQueue::initDriver() {
for (uint8 i = 0; i < 6; i++) {
if (_used[_fchan[i]])
_driver->assignFmChannel(_fchan[i], i);
@@ -1084,11 +1083,1685 @@ void SoundTowns_EuphonyTrackQueue::initDriver() {
_driver->send(0x79B0);
}
+class TownsPC98_OpnOperator {
+public:
+ TownsPC98_OpnOperator(double rate, const uint8 *rateTable,
+ const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
+ const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable);
+ ~TownsPC98_OpnOperator() {}
+
+ void keyOn();
+ void keyOff();
+ void frequency(int freq);
+ void updatePhaseIncrement();
+ void recalculateRates();
+ void generateOutput(int phasebuf, int *_feedbuf, int &out);
+
+ void feedbackLevel(int32 level) {_feedbackLevel = level ? level + 6 : 0; }
+ void detune(int value) { _detn = &_detnTbl[value << 5]; }
+ void multiple(uint32 value) { _multiple = value ? (value << 1) : 1; }
+ void attackRate(uint32 value) { _specifiedAttackRate = value; }
+ bool scaleRate(uint8 value);
+ void decayRate(uint32 value) { _specifiedDecayRate = value; recalculateRates(); }
+ void sustainRate(uint32 value) { _specifiedSustainRate = value; recalculateRates(); }
+ void sustainLevel(uint32 value) { _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; }
+ void releaseRate(uint32 value) { _specifiedReleaseRate = value; recalculateRates(); }
+ void totalLevel(uint32 value) { _totalLevel = value << 3; }
+ void reset();
+
+protected:
+ EnvelopeState _state;
+ uint32 _feedbackLevel;
+ uint32 _multiple;
+ uint32 _totalLevel;
+ uint8 _keyScale1;
+ uint8 _keyScale2;
+ uint32 _specifiedAttackRate;
+ uint32 _specifiedDecayRate;
+ uint32 _specifiedSustainRate;
+ uint32 _specifiedReleaseRate;
+ uint32 _tickCount;
+ uint32 _sustainLevel;
+
+ uint32 _frequency;
+ uint8 _kcode;
+ uint32 _phase;
+ uint32 _phaseIncrement;
+ const int32 *_detn;
+
+ const uint8 *_rateTbl;
+ const uint8 *_rshiftTbl;
+ const uint8 *_adTbl;
+ const uint32 *_fTbl;
+ const uint32 *_sinTbl;
+ const int32 *_tLvlTbl;
+ const int32 *_detnTbl;
+
+ const double _tickLength;
+ double _tick;
+ int32 _currentLevel;
+
+ struct EvpState {
+ uint8 rate;
+ uint8 shift;
+ } fs_a, fs_d, fs_s, fs_r;
+};
+
+TownsPC98_OpnOperator::TownsPC98_OpnOperator(double rate, const uint8 *rateTable,
+ const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
+ const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) :
+ _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable),
+ _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(rate * 65536.0),
+ _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0),
+ _phase(0), _state(s_ready) {
+
+ reset();
+}
+
+void TownsPC98_OpnOperator::keyOn() {
+ _state = s_attacking;
+ _phase = 0;
+}
+
+void TownsPC98_OpnOperator::keyOff() {
+ if (_state != s_ready)
+ _state = s_releasing;
+}
+
+void TownsPC98_OpnOperator::frequency(int freq) {
+ uint8 block = (freq >> 11);
+ uint16 pos = (freq & 0x7ff);
+ uint8 c = pos >> 7;
+ _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6 ));
+ _frequency = _fTbl[pos << 1] >> (7 - block);
+}
+
+void TownsPC98_OpnOperator::updatePhaseIncrement() {
+ _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1;
+ uint8 keyscale = _kcode >> _keyScale1;
+ if (_keyScale2 != keyscale) {
+ _keyScale2 = keyscale;
+ recalculateRates();
+ }
+}
+
+void TownsPC98_OpnOperator::recalculateRates() {
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+
+ r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0;
+ fs_d.rate = _rateTbl[r + k];
+ fs_d.shift = _rshiftTbl[r + k];
+
+ r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0;
+ fs_s.rate = _rateTbl[r + k];
+ fs_s.shift = _rshiftTbl[r + k];
+
+ r = (_specifiedReleaseRate << 2) + 0x22;
+ fs_r.rate = _rateTbl[r + k];
+ fs_r.shift = _rshiftTbl[r + k];
+}
+
+void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *_feedbuf, int &out) {
+ if (_state == s_ready)
+ return;
+
+ _tick += _tickLength;
+ while (_tick > 0x30000) {
+ _tick -= 0x30000;
+ ++_tickCount;
+
+ int32 levelIncrement = 0;
+ uint32 targetTime = 0;
+ int32 targetLevel = 0;
+ EnvelopeState next_state = s_ready;
+
+ switch (_state) {
+ case s_ready:
+ return;
+ case s_attacking:
+ next_state = s_decaying;
+ targetTime = (1 << fs_a.shift) - 1;
+ targetLevel = 0;
+ levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4;
+ break;
+ case s_decaying:
+ targetTime = (1 << fs_d.shift) - 1;
+ next_state = s_sustaining;
+ targetLevel = _sustainLevel;
+ levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)];
+ break;
+ case s_sustaining:
+ targetTime = (1 << fs_s.shift) - 1;
+ next_state = s_ready;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)];
+ break;
+ case s_releasing:
+ targetTime = (1 << fs_r.shift) - 1;
+ next_state = s_ready;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)];
+ break;
+ }
+
+ if (!(_tickCount & targetTime)) {
+ _currentLevel += levelIncrement;
+ if ((!targetLevel && _currentLevel <= targetLevel) || (targetLevel && _currentLevel >= targetLevel)) {
+ if (_state != s_decaying)
+ _currentLevel = targetLevel;
+ if (_state != s_sustaining)
+ _state = next_state;
+ }
+ }
+ }
+
+ uint32 lvlout = _totalLevel + (uint32) _currentLevel;
+
+ int outp = 0;
+ int *i = &outp, *o = &outp;
+ int phaseShift = 0;
+
+ if (_feedbuf) {
+ o = &_feedbuf[0];
+ i = &_feedbuf[1];
+ phaseShift = _feedbackLevel ? ((_feedbuf[0] + _feedbuf[1]) << _feedbackLevel) : 0;
+ if (phasebuf == -1)
+ *i = 0;
+ *o = *i;
+ } else {
+ phaseShift = phasebuf << 15;
+ }
+
+ if (lvlout < 832) {
+ uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000)
+ + phaseShift)) >> 16) & 0x3ff];
+ *i = ((index < 6656) ? _tLvlTbl[index] : 0);
+ } else {
+ *i = 0;
+ }
+
+ _phase += _phaseIncrement;
+ out += *o;
+ if (out > 32767)
+ out = 32767;
+ if (out < -32767)
+ out = -32767;
+}
+
+void TownsPC98_OpnOperator::reset(){
+ keyOff();
+ _tick = 0;
+ _keyScale2 = 0;
+ _currentLevel = 1023;
+
+ frequency(0);
+ detune(0);
+ scaleRate(0);
+ multiple(0);
+ updatePhaseIncrement();
+ attackRate(0);
+ decayRate(0);
+ releaseRate(0);
+ sustainRate(0);
+ feedbackLevel(0);
+ totalLevel(127);
+}
+
+bool TownsPC98_OpnOperator::scaleRate(uint8 value) {
+ value = 3 - value;
+ if (_keyScale1 != value) {
+ _keyScale1 = value;
+ return true;
+ }
+
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+ return false;
+}
+
+class TownsPC98_OpnDriver;
+class TownsPC98_OpnChannel {
+public:
+ TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id);
+ virtual ~TownsPC98_OpnChannel();
+ virtual void init();
+
+ typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para);
+
+ typedef enum channelState {
+ CHS_RECALCFREQ = 0x01,
+ CHS_KEYOFF = 0x02,
+ CHS_SSG = 0x04,
+ CHS_PITCHWHEELOFF = 0x08,
+ CHS_ALL_BUT_EOT = 0x0f,
+ CHS_EOT = 0x80
+ } ChannelState;
+
+ virtual void loadData(uint8 *data);
+ virtual void processEvents();
+ virtual void processFrequency();
+ bool processControlEvent(uint8 cmd);
+ void writeReg(uint8 regAdress, uint8 value);
+
+ virtual void keyOn();
+ virtual void keyOff();
+
+ void setOutputLevel();
+ void fadeStep();
+ void reset();
+
+ void updateEnv();
+ void generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed);
+
+ bool _enableLeft;
+ bool _enableRight;
+ bool _updateEnvelopes;
+ const uint8 _idFlag;
+ int _feedbuf[3];
+
+protected:
+ bool control_dummy(uint8 para);
+ bool control_f0_setPatch(uint8 para);
+ bool control_f1_presetOutputLevel(uint8 para);
+ bool control_f2_setKeyOffTime(uint8 para);
+ bool control_f3_setFreqLSB(uint8 para);
+ bool control_f4_setOutputLevel(uint8 para);
+ bool control_f5_setTempo(uint8 para);
+ bool control_f6_repeatSection(uint8 para);
+ bool control_f7_setupPitchWheel(uint8 para);
+ bool control_f8_togglePitchWheel(uint8 para);
+ bool control_fa_writeReg(uint8 para);
+ bool control_fb_incOutLevel(uint8 para);
+ bool control_fc_decOutLevel(uint8 para);
+ bool control_fd_jump(uint8 para);
+ bool control_ff_endOfTrack(uint8 para);
+
+ bool control_f0_setPatchSSG(uint8 para);
+ bool control_f1_setTotalLevel(uint8 para);
+ bool control_f4_setAlgorithm(uint8 para);
+ bool control_f9_unkSSG(uint8 para);
+ bool control_fb_incOutLevelSSG(uint8 para);
+ bool control_fc_decOutLevelSSG(uint8 para);
+ bool control_ff_endOfTrackSSG(uint8 para);
+
+ uint8 _ticksLeft;
+ uint8 _algorithm;
+ uint8 _instrID;
+ uint8 _totalLevel;
+ uint8 _frqBlockMSB;
+ int8 _frqLSB;
+ uint8 _keyOffTime;
+ bool _protect;
+ uint8 *_dataPtr;
+ uint8 _ptchWhlInitDelayLo;
+ uint8 _ptchWhlInitDelayHi;
+ int16 _ptchWhlModInitVal;
+ uint8 _ptchWhlDuration;
+ uint8 _ptchWhlCurDelay;
+ int16 _ptchWhlModCurVal;
+ uint8 _ptchWhlDurLeft;
+ uint16 frequency;
+ uint8 _regOffset;
+ uint8 _flags;
+ uint8 _ssg1;
+ uint8 _ssg2;
+
+ const uint8 _chanNum;
+ const uint8 _keyNum;
+ const uint8 _part;
+
+ TownsPC98_OpnDriver *_drv;
+ TownsPC98_OpnOperator **_opr;
+ uint16 _frqTemp;
+
+ const ControlEventFunc *controlEvents;
+};
+
+class TownsPC98_OpnChannelSSG : public TownsPC98_OpnChannel {
+public:
+ TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
+ ~TownsPC98_OpnChannelSSG() {}
+ void init();
+
+ void processEvents();
+ void processFrequency();
+
+ void keyOn();
+ void keyOff();
+ void loadData(uint8 *data);
+
+private:
+ void opn_SSG_UNK(uint8 a);
+};
+
+
+class TownsPC98_OpnDriver : public Audio::AudioStream {
+friend class TownsPC98_OpnChannel;
+friend class TownsPC98_OpnChannelSSG;
+public:
+ enum OpnType {
+ OD_TOWNS,
+ OD_TYPE26,
+ OD_TYPE86
+ };
+
+ TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type);
+ ~TownsPC98_OpnDriver();
+
+ bool init();
+ void loadData(uint8 *data, bool loadPaused = false);
+ void reset();
+ void fadeOut();
+
+ void pause() { _playing = false; }
+ void cont() { _playing = true; }
+
+ void callback();
+ void nextTick(int16 *buffer, uint32 bufferSize);
+
+ bool looping() { return _looping == _updateChannelsFlag ? true : false; }
+
+ // AudioStream interface
+ int inline readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+protected:
+ void generateTables();
+
+ TownsPC98_OpnChannel **_channels;
+ TownsPC98_OpnChannelSSG **_ssgChannels;
+ //TownsPC98_OpnChannel *_adpcmChannel;
+
+ void setTempo(uint8 tempo);
+
+ void lock() { _mutex.lock(); }
+ void unlock() { _mutex.unlock(); }
+
+ Audio::Mixer *_mixer;
+ Common::Mutex _mutex;
+ Audio::SoundHandle _soundHandle;
+
+ const uint8 *_opnCarrier;
+ const uint8 *_opnFreqTable;
+ const uint8 *_opnFxCmdLen;
+ const uint8 *_opnLvlPresets;
+
+ uint8 *_oprRates;
+ uint8 *_oprRateshift;
+ uint8 *_oprAttackDecay;
+ uint32 *_oprFrq;
+ uint32 *_oprSinTbl;
+ int32 *_oprLevelOut;
+ int32 *_oprDetune;
+
+ uint8 *_trackData;
+ uint8 *_patches;
+
+ uint8 _cbCounter;
+ uint8 _updateChannelsFlag;
+ uint8 _finishedChannelsFlag;
+ uint16 _tempo;
+ bool _playing;
+ bool _fading;
+ uint8 _looping;
+ uint32 _tickCounter;
+
+ bool _updateEnvelopes;
+ int _ssgFlag;
+
+ int32 _samplesTillCallback;
+ int32 _samplesTillCallbackRemainder;
+ int32 _samplesPerCallback;
+ int32 _samplesPerCallbackRemainder;
+
+ const int _numChan;
+ const int _numSSG;
+ const bool _hasADPCM;
+ const bool _hasStereo;
+
+ double _baserate;
+ static const uint8 _drvTables[];
+ static const uint32 _adtStat[];
+ bool _ready;
+};
+
+TownsPC98_OpnChannel::TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key),
+ _part(prt), _idFlag(id) {
+
+ _ticksLeft = _algorithm = _instrID = _totalLevel = _frqBlockMSB = _keyOffTime = _ssg1 = _ssg2 = 0;
+ _ptchWhlInitDelayLo = _ptchWhlInitDelayHi = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0;
+ _frqLSB = 0;
+ _protect = _updateEnvelopes = false;
+ _enableLeft = _enableRight = true;
+ _dataPtr = 0;
+ _ptchWhlModInitVal = _ptchWhlModCurVal = 0;
+ frequency = _frqTemp = 0;
+ memset(&_feedbuf, 0, sizeof(int) * 3);
+ _opr = 0;
+}
+
+TownsPC98_OpnChannel::~TownsPC98_OpnChannel() {
+ if (_opr) {
+ for (int i = 0; i < 4; i++)
+ delete _opr[i];
+ delete [] _opr;
+ }
+}
+
+void TownsPC98_OpnChannel::init() {
+
+ _opr = new TownsPC98_OpnOperator*[4];
+ for (int i = 0; i < 4; i++)
+ _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
+ _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune);
+
+ #define Control(x) &TownsPC98_OpnChannel::control_##x
+ static const ControlEventFunc ctrlEvents[] = {
+ Control(f0_setPatch),
+ Control(f1_presetOutputLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setOutputLevel),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupPitchWheel),
+ Control(f8_togglePitchWheel),
+ Control(dummy),
+ Control(fa_writeReg),
+ Control(fb_incOutLevel),
+ Control(fc_decOutLevel),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrack)
+ };
+ #undef Control
+
+ controlEvents = ctrlEvents;
+}
+
+void TownsPC98_OpnChannel::keyOff() {
+ // all operators off
+ uint8 value = _keyNum & 0x0f;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+ _flags |= CHS_KEYOFF;
+}
+
+void TownsPC98_OpnChannel::keyOn() {
+ // all operators on
+ uint8 value = _keyNum | 0xf0;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+}
+
+void TownsPC98_OpnChannel::loadData(uint8 *data) {
+ _flags = (_flags & ~CHS_EOT) | CHS_ALL_BUT_EOT;
+ _ticksLeft = 1;
+ _dataPtr = data;
+ _totalLevel = 0x7F;
+
+ uint8 *src_b = _dataPtr;
+ int loop = 1;
+ uint8 cmd = 0;
+ while (loop) {
+ if (loop == 1) {
+ cmd = *src_b++;
+ if (cmd < 0xf0) {
+ src_b++;
+ loop = 1;
+ } else {
+ if (cmd == 0xff) {
+ loop = *src_b ? 2 : 0;
+ if (READ_LE_UINT16(src_b))
+ _drv->_looping |= _idFlag;
+ } else if (cmd == 0xf6) {
+ loop = 3;
+ } else {
+ loop = 2;
+ }
+ }
+ } else if (loop == 2) {
+ src_b += _drv->_opnFxCmdLen[cmd - 240];
+ loop = 1;
+ } else if (loop == 3) {
+ src_b[0] = src_b[1];
+ src_b += 4;
+ loop = 1;
+ }
+ }
+}
+
+void TownsPC98_OpnChannel::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ if (_protect == false && _ticksLeft == _keyOffTime)
+ keyOff();
+
+ if (--_ticksLeft)
+ return;
+
+ if (_protect == false)
+ keyOff();
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ keyOff();
+ _protect = false;
+ } else {
+ keyOn();
+
+ if (_protect == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _protect = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+}
+
+void TownsPC98_OpnChannel::processFrequency() {
+ if (_flags & CHS_RECALCFREQ) {
+ uint8 block = (_frqBlockMSB & 0x70) >> 1;
+ uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
+ frequency = (bfreq + _frqLSB) | (block << 8);
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ if (_flags & CHS_KEYOFF) {
+ _ptchWhlModCurVal = _ptchWhlModInitVal;
+ _ptchWhlCurDelay += _ptchWhlInitDelayLo;
+ }
+
+ _ptchWhlDurLeft = (_ptchWhlDuration >> 1);
+ _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+ }
+
+ if (!(_flags & CHS_PITCHWHEELOFF)) {
+ if (--_ptchWhlCurDelay)
+ return;
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ frequency += _ptchWhlModCurVal;
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ if(!--_ptchWhlDurLeft) {
+ _ptchWhlDurLeft = _ptchWhlDuration;
+ _ptchWhlModCurVal = -_ptchWhlModCurVal;
+ }
+ }
+}
+
+bool TownsPC98_OpnChannel::processControlEvent(uint8 cmd) {
+ uint8 para = *_dataPtr++;
+ return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+void TownsPC98_OpnChannel::setOutputLevel() {
+ uint8 outopr = _drv->_opnCarrier[_algorithm];
+ uint8 reg = 0x40 + _regOffset;
+
+ for (int i = 0; i < 4; i++) {
+ if (outopr & 1)
+ writeReg(reg, _totalLevel);
+ outopr >>= 1;
+ reg += 4;
+ }
+}
+
+void TownsPC98_OpnChannel::fadeStep() {
+ _totalLevel += 3;
+ if (_totalLevel > 0x7f)
+ _totalLevel = 0x7f;
+ setOutputLevel();
+}
+
+void TownsPC98_OpnChannel::reset() {
+ for (int i = 0; i < 4; i++)
+ _opr[i]->reset();
+
+ _updateEnvelopes = false;
+ _enableLeft = _enableRight = true;
+ memset(&_feedbuf, 0, sizeof(int) * 3);
+}
+
+void TownsPC98_OpnChannel::updateEnv() {
+ for (int i = 0; i < 4 ; i++)
+ _opr[i]->updatePhaseIncrement();
+}
+
+void TownsPC98_OpnChannel::generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed) {
+ int phbuf1, phbuf2, output;
+ phbuf1 = phbuf2 = output = 0;
+
+ switch (_algorithm) {
+ case 0:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ *del = 0;
+ _opr[1]->generateOutput(phbuf1, 0, *del);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ break;
+ case 1:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ _opr[1]->generateOutput(0, 0, phbuf1);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 2:
+ _opr[0]->generateOutput(0, feed, phbuf2);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ _opr[1]->generateOutput(0, 0, phbuf1);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 3:
+ _opr[0]->generateOutput(0, feed, phbuf2);
+ _opr[2]->generateOutput(0, 0, *del);
+ _opr[1]->generateOutput(phbuf2, 0, phbuf1);
+ _opr[3]->generateOutput(*del, 0, output);
+ *del = phbuf1;
+ break;
+ case 4:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(0, 0, phbuf2);
+ _opr[1]->generateOutput(phbuf1, 0, output);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = 0;
+ break;
+ case 5:
+ *del = feed[1];
+ _opr[0]->generateOutput(-1, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, output);
+ _opr[1]->generateOutput(*del, 0, output);
+ _opr[3]->generateOutput(*del, 0, output);
+ break;
+ case 6:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(0, 0, output);
+ _opr[1]->generateOutput(phbuf1, 0, output);
+ _opr[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ case 7:
+ _opr[0]->generateOutput(0, feed, output);
+ _opr[2]->generateOutput(0, 0, output);
+ _opr[1]->generateOutput(0, 0, output);
+ _opr[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ };
+
+ if (_enableLeft) {
+ int l = output + leftSample;
+ if (l > 32767)
+ l = 32767;
+ if (l < -32767)
+ l = -32767;
+ leftSample = (int16) l;
+ }
+
+ if (_enableRight) {
+ int r = output + rightSample;
+ if (r > 32767)
+ r = 32767;
+ if (r < -32767)
+ r = -32767;
+ rightSample = (int16) r;
+ }
+}
+
+void TownsPC98_OpnChannel::writeReg(uint8 regAdress, uint8 value) {
+ uint8 h = regAdress & 0xf0;
+ uint8 l = (regAdress & 0x0f);
+ static const uint8 oprOrdr[] = { 0, 2, 1, 3 };
+ uint8 o = oprOrdr[(l - _regOffset) >> 2];
+
+ switch (h) {
+ case 0x00:
+ // ssg
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ case 0x10:
+ // adpcm
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ case 0x20:
+ if (l == 8) {
+ // Key on/off
+ for (int i = 0; i < 4; i++) {
+ if ((value >> (4 + i)) & 1)
+ _opr[i]->keyOn();
+ else
+ _opr[i]->keyOff();
+ }
+ } else if (l == 2) {
+ // LFO
+ warning("TownsPC98_OpnDriver: TRYING TO USE LFO (NOT SUPPORTED)");
+ } else if (l == 7) {
+ // Timers; Ch 3/6 special mode
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE (NOT SUPPORTED)");
+ } else if (l == 4 || l == 5) {
+ // Timer A
+ warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_A (NOT SUPPORTED)");
+ } else if (l == 6) {
+ // Timer B
+ warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_B (NOT SUPPORTED)");
+ } else if (l == 10 || l == 11) {
+ // DAC
+ warning("TownsPC98_OpnDriver: TRYING TO USE DAC (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0x30:
+ // detune, multiple
+ _opr[o]->detune((value >> 4) & 7);
+ _opr[o]->multiple(value & 0x0f);
+ _updateEnvelopes = true;
+ break;
+
+ case 0x40:
+ // total level
+ _opr[o]->totalLevel(value & 0x7f);
+ break;
+
+ case 0x50:
+ // rate scaling, attack rate
+ _opr[o]->attackRate(value & 0x1f);
+ if (_opr[o]->scaleRate(value >> 6))
+ _updateEnvelopes = true;
+ break;
+
+ case 0x60:
+ // first decay rate, amplitude modulation
+ _opr[o]->decayRate(value & 0x1f);
+ if (value & 0x80)
+ warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION (NOT SUPPORTED)");
+
+ break;
+
+ case 0x70:
+ // secondary decay rate
+ _opr[o]->sustainRate(value & 0x1f);
+ break;
+
+ case 0x80:
+ // secondary amplitude, release rate;
+ _opr[o]->sustainLevel(value >> 4);
+ _opr[o]->releaseRate(value & 0x0f);
+ break;
+
+ case 0x90:
+ // ssg
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+
+ case 0xa0:
+ // frequency
+ l -= _regOffset;
+ if (l == 0) {
+ _frqTemp = (_frqTemp & 0xff00) | value;
+ _updateEnvelopes = true;
+ for (int i = 0; i < 4; i++)
+ _opr[i]->frequency(_frqTemp);
+ } else if (l == 4) {
+ _frqTemp = (_frqTemp & 0xff) | (value << 8);
+ } else if (l == 8) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ } else if (l == 12) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0xb0:
+ l -= _regOffset;
+ if (l == 0) {
+ // feedback, _algorithm
+ _opr[0]->feedbackLevel((value >> 3) & 7);
+ _opr[1]->feedbackLevel(0);
+ _opr[2]->feedbackLevel(0);
+ _opr[3]->feedbackLevel(0);
+ } else if (l == 4) {
+ // stereo, LFO sensitivity
+ _enableLeft = value & 0x80 ? true : false;
+ _enableRight = value & 0x40 ? true : false;
+ uint8 ams = (value & 0x3F) >> 3;
+ if (ams)
+ warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION SENSITIVITY (NOT SUPPORTED)");
+ uint8 fms = value & 3;
+ if (fms)
+ warning("TownsPC98_OpnDriver: TRYING TO USE FREQ MODULATION SENSITIVITY (NOT SUPPORTED)");
+ }
+ break;
+
+ default:
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ }
+}
+
+bool TownsPC98_OpnChannel::control_f0_setPatch(uint8 para) {
+ _instrID = para;
+ uint8 reg = _regOffset + 0x80;
+
+ for (int i = 0; i < 4; i++) {
+ // set release rate for each operator
+ writeReg(reg, 0x0f);
+ reg += 4;
+ }
+
+ const uint8 *tptr = _drv->_patches + ((uint32)_instrID << 5);
+ reg = _regOffset + 0x30;
+
+ // write registers 0x30 to 0x8f
+ for (int i = 0; i < 6; i++) {
+ writeReg(reg, tptr[0]);
+ reg += 4;
+ writeReg(reg, tptr[2]);
+ reg += 4;
+ writeReg(reg, tptr[1]);
+ reg += 4;
+ writeReg(reg, tptr[3]);
+ reg += 4;
+ tptr += 4;
+ }
+
+ reg = _regOffset + 0xB0;
+ _algorithm = tptr[0] & 7;
+ // set feedback and algorithm
+ writeReg(reg, tptr[0]);
+
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f1_presetOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = _drv->_opnLvlPresets[para];
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f2_setKeyOffTime(uint8 para) {
+ _keyOffTime = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f3_setFreqLSB(uint8 para) {
+ _frqLSB = (int8) para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f4_setOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = para;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f5_setTempo(uint8 para) {
+ _drv->setTempo(para);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f6_repeatSection(uint8 para) {
+ _dataPtr--;
+ _dataPtr[0]--;
+
+ if (*_dataPtr) {
+ // repeat section until counter has reached zero
+ _dataPtr = _drv->_trackData + READ_LE_UINT16(_dataPtr + 2);
+ } else {
+ // reset counter, advance to next section
+ _dataPtr[0] = _dataPtr[1];
+ _dataPtr += 4;
+ }
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f7_setupPitchWheel(uint8 para) {
+ _ptchWhlInitDelayLo = _dataPtr[0];
+ _ptchWhlInitDelayHi = para;
+ _ptchWhlModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1);
+ _ptchWhlDuration = _dataPtr[3];
+ _dataPtr += 4;
+ _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF | CHS_RECALCFREQ;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f8_togglePitchWheel(uint8 para) {
+ if (para == 0x10) {
+ if (*_dataPtr++) {
+ _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF;
+ } else {
+ _flags |= CHS_PITCHWHEELOFF;
+ }
+ } else {
+ //uint8 skipChannels = para / 36;
+ //uint8 entry = para % 36;
+ //TownsPC98_OpnDriver::TownsPC98_OpnChannel *t = &chan[skipChannels];
+ ////// NOT IMPLEMENTED
+ //t->unnamedEntries[entry] = *_dataPtr++;
+ }
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fa_writeReg(uint8 para) {
+ writeReg(para, *_dataPtr++);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fb_incOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ uint8 val = (_totalLevel + 3);
+ if (val > 0x7f)
+ val = 0x7f;
+
+ _totalLevel = val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fc_decOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ int8 val = (int8) (_totalLevel - 3);
+ if (val < 0)
+ val = 0;
+
+ _totalLevel = (uint8) val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fd_jump(uint8 para) {
+ uint8 *tmp = _drv->_trackData + READ_LE_UINT16(_dataPtr - 1);
+ _dataPtr = (tmp[1] == 1) ? tmp : ++_dataPtr;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_dummy(uint8 para) {
+ _dataPtr--;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_ff_endOfTrack(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackData + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ _drv->_finishedChannelsFlag |= _idFlag;
+ keyOff();
+ return false;
+ }
+}
+
+bool TownsPC98_OpnChannel::control_f0_setPatchSSG(uint8 para) {
+ _instrID = para << 4;
+ para = (para >> 3) & 0x1e;
+ if (para)
+ return control_f4_setAlgorithm(para | 0x40);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f1_setTotalLevel(uint8 para) {
+ if (!_drv->_fading)
+ _totalLevel = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f4_setAlgorithm(uint8 para) {
+ _algorithm = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f9_unkSSG(uint8 para) {
+ _dataPtr += 5;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fb_incOutLevelSSG(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel--;
+ if ((int8)_totalLevel < 0)
+ _totalLevel = 0;
+
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fc_decOutLevelSSG(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ if(_totalLevel + 1 < 0x10)
+ _totalLevel++;
+
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_ff_endOfTrackSSG(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackData + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ //_finishedChannelsFlag |= _idFlag;
+ keyOff();
+ return false;
+ }
+}
+
+TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+ TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) {
+}
+
+void TownsPC98_OpnChannelSSG::init() {
+ _algorithm = 0x80;
+
+ _opr = new TownsPC98_OpnOperator*[4];
+ for (int i = 0; i < 4; i++)
+ _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
+ _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune);
+
+ #define Control(x) &TownsPC98_OpnChannelSSG::control_##x
+ static const ControlEventFunc ctrlEventsSSG[] = {
+ Control(f0_setPatchSSG),
+ Control(f1_setTotalLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setOutputLevel),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupPitchWheel),
+ Control(f8_togglePitchWheel),
+ Control(f9_unkSSG),
+ Control(fa_writeReg),
+ Control(fb_incOutLevelSSG),
+ Control(fc_decOutLevelSSG),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrackSSG)
+ };
+ #undef Control
+
+ controlEvents = ctrlEventsSSG;
+}
+
+void TownsPC98_OpnChannelSSG::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
+
+ if (_protect == false && _ticksLeft == _keyOffTime)
+ keyOff();
+
+ if (--_ticksLeft)
+ return;
+
+ if (_protect == false)
+ keyOff();
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ keyOff();
+ _protect = false;
+ } else {
+ keyOn();
+
+ if (_protect == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _protect = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+
+ if (!(_flags & CHS_SSG)) {
+
+ }
+}
+
+void TownsPC98_OpnChannelSSG::processFrequency() {
+ if (_flags & CHS_RECALCFREQ) {
+ uint8 block = (_frqBlockMSB & 0x70) >> 1;
+ uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
+ frequency = (bfreq + _frqLSB) | (block << 8);
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ if (_flags & CHS_KEYOFF) {
+ _ptchWhlModCurVal = _ptchWhlModInitVal;
+ _ptchWhlCurDelay += _ptchWhlInitDelayLo;
+ }
+
+ _ptchWhlDurLeft = (_ptchWhlDuration >> 1);
+ _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+ }
+
+ if (!(_flags & CHS_PITCHWHEELOFF)) {
+ if (--_ptchWhlCurDelay)
+ return;
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ frequency += _ptchWhlModCurVal;
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ if(!--_ptchWhlDurLeft) {
+ _ptchWhlDurLeft = _ptchWhlDuration;
+ _ptchWhlModCurVal = -_ptchWhlModCurVal;
+ }
+ }
+}
+
+void TownsPC98_OpnChannelSSG::keyOff() {
+ // all operators off
+ uint8 value = _keyNum & 0x0f;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+ _flags |= CHS_KEYOFF;
+}
+
+void TownsPC98_OpnChannelSSG::keyOn() {
+ // all operators on
+ uint8 value = _keyNum | 0xf0;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+}
+
+void TownsPC98_OpnChannelSSG::loadData(uint8 *data) {
+ _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
+ opn_SSG_UNK(0);
+ TownsPC98_OpnChannel::loadData(data);
+ _algorithm = 0x80;
+}
+
+void TownsPC98_OpnChannelSSG::opn_SSG_UNK(uint8 a) {
+ _ssg1 = a;
+ uint16 h = (_totalLevel + 1) * a;
+ if ((h >> 8) == _ssg2)
+ return;
+ _ssg2 = (h >> 8);
+ writeReg(8 + _regOffset, _ssg2);
+}
+
+TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) :
+ _mixer(mixer), _trackData(0), _playing(false), _fading(false), _channels(0), _ssgChannels(0),
+ _looping(0), _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 84),
+ _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 220)) ,
+ _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0),
+ _oprDetune(0), _cbCounter(4), _tickCounter(0), _updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F),
+ _finishedChannelsFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0), _ready(false),
+ _numSSG(type == OD_TOWNS ? 0 : 3), _hasADPCM(type == OD_TYPE86 ? true : false),
+ _numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true) {
+ setTempo(84);
+ _baserate = (double)getRate() / 10368.0;
+}
+
+TownsPC98_OpnDriver::~TownsPC98_OpnDriver() {
+ _mixer->stopHandle(_soundHandle);
+
+ if (_channels) {
+ for (int i = 0; i < _numChan; i++)
+ delete _channels[i];
+ delete [] _channels;
+ }
+
+ if (_ssgChannels) {
+ for (int i = 0; i < _numSSG; i++)
+ delete _ssgChannels[i];
+ delete [] _ssgChannels;
+ }
+
+ delete [] _oprRates;
+ delete [] _oprRateshift;
+ delete [] _oprFrq;
+ delete [] _oprAttackDecay;
+ delete [] _oprSinTbl;
+ delete [] _oprLevelOut;
+ delete [] _oprDetune;
+}
+
+bool TownsPC98_OpnDriver::init() {
+ if (_ready) {
+ reset();
+ return true;
+ }
+
+ generateTables();
+
+ if (_channels) {
+ for (int i = 0; i < _numChan; i++) {
+ if (_channels[i])
+ delete _channels[i];
+ }
+ delete [] _channels;
+ }
+ _channels = new TownsPC98_OpnChannel*[_numChan];
+ for (int i = 0; i < _numChan; i++) {
+ int ii = i * 6;
+ _channels[i] = new TownsPC98_OpnChannel(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _channels[i]->init();
+ }
+
+ if (_ssgChannels) {
+ for (int i = 0; i < _numSSG; i++) {
+ if (_ssgChannels[i])
+ delete _ssgChannels[i];
+ }
+ delete [] _ssgChannels;
+ }
+ if (_numSSG) {
+ _ssgChannels = new TownsPC98_OpnChannelSSG*[_numSSG];
+ for (int i = 0; i < _numSSG; i++) {
+ int ii = i * 6;
+ _ssgChannels[i] = new TownsPC98_OpnChannelSSG(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _ssgChannels[i]->init();
+ }
+ }
+
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType,
+ &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true);
+
+ _ready = true;
+ return true;
+}
+
+int inline TownsPC98_OpnDriver::readBuffer(int16 *buffer, const int numSamples) {
+ memset(buffer, 0, sizeof(int16) * numSamples);
+ int32 samplesLeft = numSamples >> 1;
+ while (samplesLeft) {
+ if (!_samplesTillCallback) {
+ callback();
+ _samplesTillCallback = _samplesPerCallback;
+ _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
+ if (_samplesTillCallbackRemainder >= _tempo) {
+ _samplesTillCallback++;
+ _samplesTillCallbackRemainder -= _tempo;
+ }
+ }
+
+ int32 render = MIN(samplesLeft, _samplesTillCallback);
+ samplesLeft -= render;
+ _samplesTillCallback -= render;
+
+ nextTick(buffer, render);
+
+ for (int i = 0; i < render; ++i) {
+ buffer[i << 1] <<= 2;
+ buffer[(i << 1) + 1] <<= 2;
+ }
+
+ buffer += (render << 1);
+ }
+
+ return numSamples;
+}
+
+void TownsPC98_OpnDriver::loadData(uint8 *data, bool loadPaused) {
+ if (!_ready) {
+ warning("TownsPC98_OpnDriver: Driver must be initialized before loading data");
+ return;
+ }
+
+ if (!data) {
+ warning("TownsPC98_OpnDriver: Invalid music file data");
+ return;
+ }
+
+ lock();
+ _trackData = data;
+
+ reset();
+
+ uint8 *src_a = data;
+
+ for (uint8 i = 0; i < 3; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (int i = 0; i < _numSSG; i++) {
+ _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (uint8 i = 3; i < _numChan; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ if (_hasADPCM) {
+ //_adpcmChannel->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ _ssgFlag = 0;
+
+ _patches = src_a + 4;
+ _cbCounter = 4;
+ _finishedChannelsFlag = 0;
+
+ // AH 0x17
+ unlock();
+ _playing = (loadPaused ? false : true);
+}
+
+void TownsPC98_OpnDriver::reset() {
+ for (int i = 0; i < (_numChan); i++)
+ _channels[i]->reset();
+ for (int i = 0; i < (_numSSG); i++)
+ _ssgChannels[i]->reset();
+
+ _playing = _fading = false;
+ _looping = 0;
+ _tickCounter = 0;
+}
+
+void TownsPC98_OpnDriver::fadeOut() {
+ if (!_playing)
+ return;
+
+ _fading = true;
+
+ for (int i = 0; i < 20; i++) {
+ lock();
+ uint32 dTime = _tickCounter + 2;
+ for (int j = 0; j < _numChan; j++) {
+ if (_updateChannelsFlag & _channels[j]->_idFlag)
+ _channels[j]->fadeStep();
+ }
+ for (int j = 0; j < _numSSG; j++)
+ _ssgChannels[j]->fadeStep();
+
+ unlock();
+
+ while (_playing) {
+ if (_tickCounter >= dTime)
+ break;
+ }
+ }
+
+ _fading = false;
+
+ reset();
+}
+
+void TownsPC98_OpnDriver::callback() {
+ if (!_playing || --_cbCounter)
+ return;
+
+ _cbCounter = 4;
+ _tickCounter++;
+
+ lock();
+
+ for (int i = 0; i < _numChan; i++) {
+ if (_updateChannelsFlag & _channels[i]->_idFlag) {
+ _channels[i]->processEvents();
+ _channels[i]->processFrequency();
+ }
+ }
+
+ if (_numSSG) {
+ for (int i = 0; i < _numSSG; i++) {
+ _ssgChannels[i]->processEvents();
+ _ssgChannels[i]->processFrequency();
+ }
+ }
+
+ _ssgFlag = 0;
+
+ unlock();
+
+ if (_finishedChannelsFlag == _updateChannelsFlag)
+ reset();
+}
+
+void TownsPC98_OpnDriver::nextTick(int16 *buffer, uint32 bufferSize) {
+ if (!_playing)
+ return;
+
+ for (int i = 0; i < _numChan ; i++) {
+ if (_channels[i]->_updateEnvelopes) {
+ _channels[i]->_updateEnvelopes = false;
+ _channels[i]->updateEnv();
+ }
+
+ for (uint32 ii = 0; ii < bufferSize ; ii++)
+ _channels[i]->generateOutput(buffer[ii * 2],
+ buffer[ii * 2 + 1], &_channels[i]->_feedbuf[2], _channels[i]->_feedbuf);
+ }
+
+ for (int i = 0; i < _numSSG ; i++) {
+ if (_ssgChannels[i]->_updateEnvelopes) {
+ _ssgChannels[i]->_updateEnvelopes = false;
+ _ssgChannels[i]->updateEnv();
+ }
+
+ for (uint32 ii = 0; ii < bufferSize ; ii++)
+ _ssgChannels[i]->generateOutput(buffer[ii * 2],
+ buffer[ii * 2 + 1], &_ssgChannels[i]->_feedbuf[2], _ssgChannels[i]->_feedbuf);
+ }
+}
+
+void TownsPC98_OpnDriver::generateTables() {
+ delete [] _oprRates;
+ _oprRates = new uint8[128];
+ memset(_oprRates, 0x90, 32);
+ uint8 *dst = (uint8*) _oprRates + 32;
+ for (int i = 0; i < 48; i += 4)
+ WRITE_BE_UINT32(dst + i, 0x00081018);
+ dst += 48;
+ for (uint8 i = 0; i < 16; i ++) {
+ uint8 v = (i < 12) ? i : 12;
+ *dst++ = ((4 + v) << 3);
+ }
+ memset(dst, 0x80, 32);
+
+ delete [] _oprRateshift;
+ _oprRateshift = new uint8[128];
+ memset(_oprRateshift, 0, 128);
+ dst = (uint8*) _oprRateshift + 32;
+ for (int i = 11; i; i--) {
+ memset(dst, i, 4);
+ dst += 4;
+ }
+
+ delete [] _oprFrq;
+ _oprFrq = new uint32[0x1000];
+ for (uint32 i = 0; i < 0x1000; i++)
+ _oprFrq[i] = (uint32)(_baserate * (double)(i << 11));
+
+ delete [] _oprAttackDecay;
+ _oprAttackDecay = new uint8[152];
+ memset(_oprAttackDecay, 0, 152);
+ for (int i = 0; i < 36; i++)
+ WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]);
+
+ delete [] _oprSinTbl;
+ _oprSinTbl = new uint32[1024];
+ for (int i = 0; i < 1024; i++) {
+ double val = sin((double) (((i << 1) + 1) * PI / 1024.0));
+ double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0;
+ int32 i_dcb = (int32)(2.0 * d_dcb);
+ i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1);
+ _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1);
+ }
+
+ delete [] _oprLevelOut;
+ _oprLevelOut = new int32[0x1a00];
+ for (int i = 0; i < 256; i++) {
+ double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i)));
+ int32 val_int = ((int32) val) >> 4;
+ _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2;
+ _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1];
+ for (int ii = 1; ii < 13; ii++) {
+ _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii;
+ _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)];
+ }
+ }
+
+ uint8 *dtt = new uint8[128];
+ memset(dtt, 0, 36);
+ memset(dtt + 36, 1, 8);
+ memcpy(dtt + 44, _drvTables + 144, 84);
+
+ delete [] _oprDetune;
+ _oprDetune = new int32[256];
+ for (int i = 0; i < 128; i++) {
+ _oprDetune[i] = (int32) ((double)dtt[i] * _baserate * 64.0);
+ _oprDetune[i + 128] = -_oprDetune[i];
+ }
+
+ delete [] dtt;
+}
+
+void TownsPC98_OpnDriver::setTempo(uint8 tempo) {
+ _tempo = tempo;
+ _samplesPerCallback = getRate() / _tempo;
+ _samplesPerCallbackRemainder = getRate() % _tempo;
+}
+
+const uint8 TownsPC98_OpnDriver::_drvTables[] = {
+ // channel presets
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x80, 0x01, 0x01, 0x00, 0x02,
+ 0x02, 0x80, 0x02, 0x02, 0x00, 0x04,
+ 0x00, 0x80, 0x03, 0x04, 0x01, 0x08,
+ 0x01, 0x80, 0x04, 0x05, 0x01, 0x10,
+ 0x02, 0x80, 0x05, 0x06, 0x01, 0x20,
+
+ // control event size
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05,
+ 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02,
+
+ // fmt level presets
+ 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38,
+ 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18,
+ 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90,
+
+ // carriers
+ 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F,
+
+ // frequencies
+ 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02,
+ 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03,
+ 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+
+ // unused
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+
+ // detune
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05,
+ 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14,
+ 0x16, 0x16, 0x16, 0x16,
+
+ // pc98 level presets
+ 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25,
+ 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10,
+ 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90
+};
+
+const uint32 TownsPC98_OpnDriver::_adtStat[] = {
+ 0x00010001, 0x00010001, 0x00010001, 0x01010001,
+ 0x00010101, 0x00010101, 0x00010101, 0x01010101,
+ 0x01010101, 0x01010101, 0x01010102, 0x01010102,
+ 0x01020102, 0x01020102, 0x01020202, 0x01020202,
+ 0x02020202, 0x02020202, 0x02020204, 0x02020204,
+ 0x02040204, 0x02040204, 0x02040404, 0x02040404,
+ 0x04040404, 0x04040404, 0x04040408, 0x04040408,
+ 0x04080408, 0x04080408, 0x04080808, 0x04080808,
+ 0x08080808, 0x08080808, 0x10101010, 0x10101010
+};
+
SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), _sfxFileData(0),
_sfxFileIndex((uint)-1), _sfxWDTable(0), _sfxBTTable(0), _parser(0) {
- _driver = new SoundTowns_EuphonyDriver(_mixer);
+ _driver = new Towns_EuphonyDriver(_mixer);
int ret = open();
if (ret != MERR_ALREADY_OPEN && ret != 0)
error("couldn't open midi driver");
@@ -1124,7 +2797,7 @@ void SoundTowns::playTrack(uint8 track) {
return;
track -= 2;
- const int32 * const tTable = (const int32 * const) cdaData();
+ const int32 *const tTable = (const int32 *const) cdaData();
int tTableIndex = 3 * track;
int trackNum = (int) READ_LE_UINT32(&tTable[tTableIndex + 2]);
@@ -1195,12 +2868,12 @@ void SoundTowns::playSoundEffect(uint8 track) {
}
}
- uint8 * fileBody = _sfxFileData + 0x01b8;
+ uint8 *fileBody = _sfxFileData + 0x01b8;
int32 offset = (int32)READ_LE_UINT32(_sfxFileData + (track - 0x0b) * 4);
if (offset == -1)
return;
- uint32 * sfxHeader = (uint32*)(fileBody + offset);
+ uint32 *sfxHeader = (uint32*)(fileBody + offset);
uint32 sfxHeaderID = READ_LE_UINT32(sfxHeader);
uint32 sfxHeaderInBufferSize = READ_LE_UINT32(&sfxHeader[1]);
@@ -1220,7 +2893,7 @@ void SoundTowns::playSoundEffect(uint8 track) {
} else if (sfxHeaderID == 1) {
Screen::decodeFrame4(sfxBody, sfxPlaybackBuffer, playbackBufferSize);
} else if (_sfxWDTable) {
- uint8 * tgt = sfxPlaybackBuffer;
+ uint8 *tgt = sfxPlaybackBuffer;
uint32 sfx_BtTable_Offset = 0;
uint32 sfx_WdTable_Offset = 0;
uint32 sfx_WdTable_Number = 5;
@@ -1285,7 +2958,7 @@ uint32 SoundTowns::getBaseTempo(void) {
}
bool SoundTowns::loadInstruments() {
- uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0);
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
if (!twm)
return false;
_driver->queue()->loadDataToCurrentPosition(twm, 0x8BF0);
@@ -1300,11 +2973,11 @@ bool SoundTowns::loadInstruments() {
}
void SoundTowns::playEuphonyTrack(uint32 offset, int loop) {
- uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0);
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
Common::StackLock lock(_mutex);
if (!_parser) {
- _parser = new MidiParser_EuD(_driver->queue());
+ _parser = new Towns_EuphonyParser(_driver->queue());
_parser->setMidiDriver(this);
_parser->setTimerRate(getBaseTempo());
}
@@ -1315,7 +2988,7 @@ void SoundTowns::playEuphonyTrack(uint32 offset, int loop) {
delete[] twm;
}
-void SoundTowns::onTimer(void * data) {
+void SoundTowns::onTimer(void *data) {
SoundTowns *music = (SoundTowns *)data;
Common::StackLock lock(music->_mutex);
if (music->_parser)
@@ -1356,22 +3029,75 @@ float SoundTowns::semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiTo
return (float) sampleRate * 10.0f * rateshift / outputRate;
}
+SoundPC98::SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0) {
+}
+
+SoundPC98::~SoundPC98() {
+ delete[] _musicTrackData;
+ delete[] _sfxTrackData;
+ delete _driver;
+}
+
+bool SoundPC98::init() {
+ _driver = new TownsPC98_OpnDriver(_mixer, TownsPC98_OpnDriver::OD_TYPE26);
+ _sfxTrackData = _vm->resource()->fileData("se.dat", 0);
+ if (!_sfxTrackData)
+ return false;
+ return _driver->init();
+}
+
+void SoundPC98::playTrack(uint8 track) {
+ if (--track >= 56)
+ track -= 55;
+
+ if (track == _lastTrack && _musicEnabled)
+ return;
+
+ haltTrack();
+
+ char musicfile[13];
+ sprintf(musicfile, fileListEntry(0), track);
+ delete[] _musicTrackData;
+ _musicTrackData = _vm->resource()->fileData(musicfile, 0);
+ if (_musicEnabled)
+ _driver->loadData(_musicTrackData);
+
+ _lastTrack = track;
+}
+
+void SoundPC98::haltTrack() {
+ _lastTrack = -1;
+ AudioCD.stop();
+ AudioCD.updateCD();
+ _driver->reset();
+}
+
+void SoundPC98::beginFadeOut() {
+ _driver->fadeOut();
+ haltTrack();
+}
+
+void SoundPC98::playSoundEffect(uint8) {
+ /// TODO ///
+}
+
+
// KYRA 2
-SoundTowns_v2::SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer)
- : Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), /*_driver(0),*/
- _twnTrackData(0) {
+SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) {
}
-SoundTowns_v2::~SoundTowns_v2() {
- /*if (_driver)
- delete _driver;*/
- if (_twnTrackData)
- delete[] _twnTrackData;
+SoundTownsPC98_v2::~SoundTownsPC98_v2() {
+ delete[] _musicTrackData;
+ delete _driver;
}
-bool SoundTowns_v2::init() {
- //_driver = new SoundTowns_v2_TwnDriver(_mixer);
+bool SoundTownsPC98_v2::init() {
+ _driver = new TownsPC98_OpnDriver(_mixer, /*_vm->gameFlags().platform == Common::kPlatformPC98 ?
+ TownsPC98_OpnDriver::OD_TYPE86 :*/ TownsPC98_OpnDriver::OD_TOWNS);
+ _useFmSfx = _vm->gameFlags().platform == Common::kPlatformPC98 ? true : false;
_vm->checkCD();
// FIXME: While checking for 'track1.XXX(X)' looks like
// a good idea, we should definitely not be doing this
@@ -1384,55 +3110,61 @@ bool SoundTowns_v2::init() {
(Common::File::exists("track1.mp3") || Common::File::exists("track1.ogg") ||
Common::File::exists("track1.flac") || Common::File::exists("track1.fla")))
_musicEnabled = 2;
- return true;//_driver->init();
+ return _driver->init();
}
-void SoundTowns_v2::process() {
+void SoundTownsPC98_v2::process() {
AudioCD.updateCD();
}
-void SoundTowns_v2::playTrack(uint8 track) {
+void SoundTownsPC98_v2::playTrack(uint8 track) {
if (track == _lastTrack && _musicEnabled)
return;
- const uint16 * const cdaTracks = (const uint16 * const) cdaData();
+ const uint16 *const cdaTracks = (const uint16 *const) cdaData();
int trackNum = -1;
- for (int i = 0; i < cdaTrackNum(); i++) {
- if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) {
- trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1;
- break;
+ if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
+ for (int i = 0; i < cdaTrackNum(); i++) {
+ if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) {
+ trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1;
+ break;
+ }
}
}
- haltTrack();
+ beginFadeOut();
- // TODO: figure out when to loop and when not for CD Audio
- bool loop = false;
+ char musicfile[13];
+ sprintf(musicfile, fileListEntry(0), track);
+ delete[] _musicTrackData;
+
+ _musicTrackData = _vm->resource()->fileData(musicfile, 0);
+ _driver->loadData(_musicTrackData, true);
if (_musicEnabled == 2 && trackNum != -1) {
- AudioCD.play(trackNum+1, loop ? -1 : 1, 0, 0);
+ AudioCD.play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0);
AudioCD.updateCD();
} else if (_musicEnabled) {
- char musicfile[13];
- sprintf(musicfile, fileListEntry(0), track);
- if (_twnTrackData)
- delete[] _twnTrackData;
- _twnTrackData = _vm->resource()->fileData(musicfile, 0);
- //_driver->loadData(_twnTrackData);
+ _driver->cont();
}
_lastTrack = track;
}
-void SoundTowns_v2::haltTrack() {
+void SoundTownsPC98_v2::haltTrack() {
_lastTrack = -1;
AudioCD.stop();
AudioCD.updateCD();
- //_driver->reset();
+ _driver->reset();
}
-int32 SoundTowns_v2::voicePlay(const char *file, bool) {
+void SoundTownsPC98_v2::beginFadeOut() {
+ _driver->fadeOut();
+ haltTrack();
+}
+
+int32 SoundTownsPC98_v2::voicePlay(const char *file, bool) {
static const uint16 rates[] = { 0x10E1, 0x0CA9, 0x0870, 0x0654, 0x0438, 0x032A, 0x021C, 0x0194 };
int h = 0;
@@ -1443,11 +3175,11 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {
return 0;
}
- char filename [13];
+ char filename[13];
sprintf(filename, "%s.PCM", file);
- uint8 * data = _vm->resource()->fileData(filename, 0);
- uint8 * src = data;
+ uint8 *data = _vm->resource()->fileData(filename, 0);
+ uint8 *src = data;
uint16 sfxRate = rates[READ_LE_UINT16(src)];
src += 2;
@@ -1500,9 +3232,16 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {
return 1;
}
-void SoundTowns_v2::beginFadeOut() {
- //_driver->fadeOut();
- haltTrack();
+void SoundTownsPC98_v2::playSoundEffect(uint8 track) {
+ if (!_useFmSfx)
+ return;
+
+ uint8 *sd = _vm->resource()->fileData("sound.dat", 0);
+
+
+ //TODO
+
+ delete [] sd;
}
} // end of namespace Kyra
diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp
index abdf115c1e..9a4b40902e 100644
--- a/engines/kyra/staticres.cpp
+++ b/engines/kyra/staticres.cpp
@@ -23,16 +23,17 @@
*
*/
-
#include "common/endian.h"
#include "common/md5.h"
#include "kyra/kyra_v1.h"
#include "kyra/kyra_lok.h"
+#include "kyra/lol.h"
#include "kyra/kyra_v2.h"
#include "kyra/kyra_hof.h"
#include "kyra/kyra_mr.h"
#include "kyra/screen.h"
#include "kyra/screen_lok.h"
+#include "kyra/screen_lol.h"
#include "kyra/screen_hof.h"
#include "kyra/screen_mr.h"
#include "kyra/resource.h"
@@ -287,8 +288,10 @@ bool StaticResource::init() {
} else if (_vm->game() == GI_KYRA3) {
_builtIn = 0;
_filenameTable = kyra3StaticRes;
+ } else if (_vm->game() == GI_LOL) {
+ return true;
} else {
- error("unknown game ID");
+ error("StaticResource: Unknown game ID");
}
char errorBuffer[100];
@@ -1034,6 +1037,11 @@ void KyraEngine_LoK::initStaticResource() {
}
// audio data tables
+#if 0
+ static const char *tIntro98[] = { "intro%d.dat" };
+ static const char *tIngame98[] = { "kyram%d.dat" };
+#endif
+
static const AudioDataStruct soundData_PC[] = {
{ _soundFilesIntro, _soundFilesIntroSize, 0, 0 },
{ _soundFiles, _soundFilesSize, 0, 0 },
@@ -1045,7 +1053,22 @@ void KyraEngine_LoK::initStaticResource() {
{ _soundFiles, _soundFilesSize, _cdaTrackTable, _cdaTrackTableSize },
{ 0, 0, 0, 0}
};
- _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS;
+
+#if 0
+ static const AudioDataStruct soundData_PC98[] = {
+ { tIntro98, 1, 0, 0 },
+ { tIngame98, 1, 0, 0 },
+ { 0, 0, 0, 0}
+ };
+#endif
+
+ if (_flags.platform == Common::kPlatformPC)
+ _soundData = soundData_PC;
+ else if (_flags.platform == Common::kPlatformFMTowns)
+ _soundData = soundData_TOWNS;
+ else if (_flags.platform == Common::kPlatformPC98)
+ _soundData = soundData_TOWNS/*soundData_PC98*/;
+
}
void KyraEngine_LoK::loadMouseShapes() {
@@ -1243,6 +1266,12 @@ void KyraEngine_HoF::initStaticResource() {
static const char *fmtMusicFileListFinale[] = { "finale%d.twn" };
static const char *fmtMusicFileListIngame[] = { "km%02d.twn" };
+#if 0
+ static const char *pc98MusicFileListIntro[] = { "intro%d.86" };
+ static const char *pc98MusicFileListFinale[] = { "finale%d.86" };
+ static const char *pc98MusicFileListIngame[] = { "km%02d.86" };
+#endif
+
static const AudioDataStruct soundData_PC[] = {
{ _musicFileListIntro, _musicFileListIntroSize, 0, 0 },
{ _musicFileListIngame, _musicFileListIngameSize, 0, 0},
@@ -1254,7 +1283,21 @@ void KyraEngine_HoF::initStaticResource() {
{ fmtMusicFileListIngame, 1, _cdaTrackTableIngame, _cdaTrackTableIngameSize >> 1 },
{ fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 }
};
- _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS;
+
+#if 0
+ static const AudioDataStruct soundData_PC98[] = {
+ { pc98MusicFileListIntro, 1, 0, 0 },
+ { pc98MusicFileListIngame, 1, 0, 0 },
+ { pc98MusicFileListFinale, 1, 0, 0 }
+ };
+#endif
+
+ if (_flags.platform == Common::kPlatformPC)
+ _soundData = soundData_PC;
+ else if (_flags.platform == Common::kPlatformFMTowns)
+ _soundData = soundData_TOWNS;
+ else if (_flags.platform == Common::kPlatformPC98)
+ _soundData = soundData_TOWNS/*soundData_PC98*/;
// setup sequence data
_sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize);
@@ -1944,12 +1987,26 @@ const char *KyraEngine_MR::_languageExtension[] = {
"TRE",
"TRF",
"TRG"/*,
- "TRI", Italian and Spanish were never included
- "TRS"*/
+ "TRI", Italian and Spanish were never included, the supported fan translations are using
+ "TRS" English/French extensions thus overwriting these languages */
};
const int KyraEngine_MR::_languageExtensionSize = ARRAYSIZE(KyraEngine_MR::_languageExtension);
+const char * const KyraEngine_MR::_mainMenuSpanishFan[] = {
+ "Nueva Partida",
+ "Ver Intro",
+ "Restaurar",
+ "Finalizar"
+};
+
+const char * const KyraEngine_MR::_mainMenuItalianFan[] = {
+ "Nuova Partita",
+ "Introduzione",
+ "Carica una partita",
+ "Esci dal gioco"
+};
+
const KyraEngine_MR::ShapeDesc KyraEngine_MR::_shapeDescs[] = {
{ 57, 91, -31, -82 },
{ 57, 91, -31, -82 },
@@ -2182,5 +2239,105 @@ const int8 KyraEngine_MR::_albumWSAY[] = {
-1, -2, 2, 2, -6, -6, -6, 0
};
+// lands of lore static res
+
+const ScreenDim Screen_LoL::_screenDimTable[] = {
+ { 0x00, 0x00, 0x28, 0xC8, 0xC7, 0xCF, 0x00, 0x00 }
+};
+
+const int Screen_LoL::_screenDimTableCount = ARRAYSIZE(Screen_LoL::_screenDimTable);
+
+const char * const LoLEngine::_languageExt[] = {
+ "ENG",
+ "FRE",
+ "GER"
+};
+
+const LoLEngine::CharacterPrev LoLEngine::_charPreviews[] = {
+ { "Ak\'shel", 0x060, 0x7F, { 0x0F, 0x08, 0x05 } },
+ { "Michael", 0x09A, 0x7F, { 0x06, 0x0A, 0x0F } },
+ { "Kieran", 0x0D4, 0x7F, { 0x08, 0x06, 0x08 } },
+ { "Conrad", 0x10F, 0x7F, { 0x0A, 0x0C, 0x0A } }
+};
+
+const uint8 LoLEngine::_chargenFrameTable[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x04, 0x03, 0x02, 0x01,
+ 0x00, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
+ 0x0E, 0x0F, 0x10, 0x11, 0x12
+};
+
+const uint16 LoLEngine::_selectionPosTable[] = {
+ 0x6F, 0x00, 0x8F, 0x00, 0xAF, 0x00, 0xCF, 0x00,
+ 0xEF, 0x00, 0x6F, 0x20, 0x8F, 0x20, 0xAF, 0x20,
+ 0xCF, 0x20, 0xEF, 0x20, 0x6F, 0x40, 0x8F, 0x40,
+ 0xAF, 0x40, 0xCF, 0x40, 0xEF, 0x40, 0x10F, 0x00
+};
+
+const uint8 LoLEngine::_selectionChar1IdxTable[] = {
+ 0, 0, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 0, 0, 5, 5, 5,
+ 5, 5, 5, 5, 0, 0, 5, 5,
+ 5, 5, 5
+};
+
+const uint8 LoLEngine::_selectionChar2IdxTable[] = {
+ 1, 1, 6, 6, 1, 1, 6, 6,
+ 6, 6, 6, 6, 6, 1, 1, 6,
+ 6, 6, 1, 1, 6, 6, 6, 6,
+ 6, 6, 6
+};
+
+const uint8 LoLEngine::_selectionChar3IdxTable[] = {
+ 2, 2, 7, 7, 7, 7, 2, 2,
+ 7, 7, 7, 7, 7, 7, 7, 2,
+ 2, 7, 7, 7, 7, 2, 2, 7,
+ 7, 7, 7
+};
+
+const uint8 LoLEngine::_selectionChar4IdxTable[] = {
+ 3, 3, 8, 8, 8, 8, 3, 3,
+ 8, 8, 3, 3, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 3, 3, 8,
+ 8, 8, 8
+};
+
+const uint8 LoLEngine::_reminderChar1IdxTable[] = {
+ 4, 4, 4, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5
+};
+
+const uint8 LoLEngine::_reminderChar2IdxTable[] = {
+ 9, 9, 9, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6
+};
+
+const uint8 LoLEngine::_reminderChar3IdxTable[] = {
+ 0xE, 0xE, 0xE, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7
+};
+
+const uint8 LoLEngine::_reminderChar4IdxTable[] = {
+ 0xF, 0xF, 0xF, 0x8, 0x8, 0x8, 0x8, 0x8,
+ 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,
+ 0x8
+};
+
+const uint8 LoLEngine::_selectionAnimIndexTable[] = {
+ 0, 5, 1, 6, 2, 7, 3, 8
+};
+
+const uint8 LoLEngine::_charInfoFrameTable[] = {
+ 0x0, 0x7, 0x8, 0x9, 0xA, 0xB, 0xA, 0x9,
+ 0x8, 0x7, 0x0, 0x0, 0x7, 0x8, 0x9, 0xA,
+ 0xB, 0xA, 0x9, 0x8, 0x7, 0x0, 0x0, 0x7,
+ 0x8, 0x9, 0xA, 0xB, 0xA, 0x9, 0x8, 0x7
+};
+
} // End of namespace Kyra
diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h
index 36cd75b1ab..1db8ee8474 100644
--- a/engines/kyra/wsamovie.h
+++ b/engines/kyra/wsamovie.h
@@ -109,7 +109,7 @@ private:
class WSAMovie_v2 : public WSAMovie_v1 {
public:
- WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *scren);
+ WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *screen);
int open(const char *filename, int unk1, uint8 *palette);
diff --git a/engines/lure/lure.cpp b/engines/lure/lure.cpp
index 06d3b1984e..ea760ddb4f 100644
--- a/engines/lure/lure.cpp
+++ b/engines/lure/lure.cpp
@@ -103,6 +103,7 @@ LureEngine::~LureEngine() {
if (_initialised) {
// Delete and deinitialise subsystems
Surface::deinitialise();
+ Sound.destroy();
delete _fights;
delete _room;
delete _menu;
@@ -164,10 +165,6 @@ void LureEngine::pauseEngineIntern(bool pause) {
}
}
-void LureEngine::quitGame() {
- _system->quit();
-}
-
const char *LureEngine::generateSaveName(int slotNumber) {
static char buffer[15];
diff --git a/engines/lure/lure.h b/engines/lure/lure.h
index d66f446247..1c5b40e54b 100644
--- a/engines/lure/lure.h
+++ b/engines/lure/lure.h
@@ -70,7 +70,6 @@ public:
virtual int init();
virtual int go();
virtual void pauseEngineIntern(bool pause);
- void quitGame();
Disk &disk() { return *_disk; }
diff --git a/engines/lure/luredefs.h b/engines/lure/luredefs.h
index 603102a099..922e1207d0 100644
--- a/engines/lure/luredefs.h
+++ b/engines/lure/luredefs.h
@@ -36,7 +36,7 @@ namespace Lure {
#define LURE_DAT_MAJOR 1
#define LURE_DAT_MINOR 29
#define LURE_MIN_SAVEGAME_MINOR 25
-#define LURE_SAVEGAME_MINOR 32
+#define LURE_SAVEGAME_MINOR 33
#define LURE_DEBUG 1
diff --git a/engines/lure/menu.cpp b/engines/lure/menu.cpp
index cecc415499..0b4ef06081 100644
--- a/engines/lure/menu.cpp
+++ b/engines/lure/menu.cpp
@@ -57,6 +57,11 @@ MenuRecord::MenuRecord(const MenuRecordBounds *bounds, int numParams, ...) {
_width = (bounds->contentsWidth + 3) << 3;
}
+MenuRecord::~MenuRecord() {
+ free(_entries);
+ _entries = NULL;
+}
+
const char *MenuRecord::getEntry(uint8 index) {
if (index >= _numEntries) error("Invalid menuitem index specified: %d", index);
return _entries[index];
diff --git a/engines/lure/menu.h b/engines/lure/menu.h
index b5b7769e34..fcc6308375 100644
--- a/engines/lure/menu.h
+++ b/engines/lure/menu.h
@@ -56,6 +56,7 @@ private:
uint8 _numEntries;
public:
MenuRecord(const MenuRecordBounds *bounds, int numParams, ...);
+ ~MenuRecord();
uint16 xstart() { return _xstart; }
uint16 width() { return _width; }
diff --git a/engines/lure/palette.cpp b/engines/lure/palette.cpp
index 03161032c0..badc3c96b0 100644
--- a/engines/lure/palette.cpp
+++ b/engines/lure/palette.cpp
@@ -106,6 +106,12 @@ Palette::Palette(uint16 resourceId, PaletteSource paletteSource) {
delete srcData;
}
+// Destructor
+
+Palette::~Palette() {
+ delete _palette;
+}
+
void Palette::convertRgb64Palette(const byte *srcPalette, uint16 srcNumEntries) {
byte *pDest = _palette->data();
const byte *pSrc = srcPalette;
diff --git a/engines/lure/palette.h b/engines/lure/palette.h
index 1481e22775..9420079346 100644
--- a/engines/lure/palette.h
+++ b/engines/lure/palette.h
@@ -46,6 +46,7 @@ public:
Palette(uint16 srcNumEntries, const byte *srcData, PaletteSource paletteSource);
Palette(Palette &src);
Palette(uint16 resourceId, PaletteSource paletteSource = DEFAULT);
+ ~Palette();
uint8 *data() { return _palette->data(); }
MemoryBlock *palette() { return _palette; }
diff --git a/engines/lure/res.cpp b/engines/lure/res.cpp
index f2997d5d17..68de260061 100644
--- a/engines/lure/res.cpp
+++ b/engines/lure/res.cpp
@@ -349,6 +349,7 @@ void Resources::reloadData() {
_indexedRoomExitHospots.push_back(RoomExitIndexedHotspotList::value_type(new RoomExitIndexedHotspotData(indexedRec)));
indexedRec++;
}
+ delete mb;
// Initialise delay list
_delayList.clear(true);
diff --git a/engines/lure/res_struct.cpp b/engines/lure/res_struct.cpp
index de09f982d1..92cea948f9 100644
--- a/engines/lure/res_struct.cpp
+++ b/engines/lure/res_struct.cpp
@@ -456,6 +456,8 @@ void HotspotData::saveToStream(WriteStream *stream) {
stream->writeSint16LE(startY);
stream->writeUint16LE(roomNumber);
stream->writeByte(layer);
+ stream->writeUint16LE(walkX);
+ stream->writeUint16LE(walkY);
stream->writeUint16LE(width);
stream->writeUint16LE(height);
@@ -503,6 +505,10 @@ void HotspotData::loadFromStream(ReadStream *stream) {
uint8 saveVersion = LureEngine::getReference().saveVersion();
if (saveVersion >= 29)
layer = stream->readByte();
+ if (saveVersion >= 33) {
+ walkX = stream->readUint16LE();
+ walkY = stream->readUint16LE();
+ }
width = stream->readUint16LE();
height = stream->readUint16LE();
diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp
index 839298d1c5..285f66e4e2 100644
--- a/engines/lure/sound.cpp
+++ b/engines/lure/sound.cpp
@@ -85,8 +85,10 @@ SoundManager::~SoundManager() {
if (_soundData)
delete _soundData;
- if (_driver)
+ if (_driver) {
_driver->close();
+ delete _driver;
+ }
_driver = NULL;
g_system->deleteMutex(_soundMutex);
@@ -143,7 +145,7 @@ void SoundManager::bellsBodge() {
Room &room = Room::getReference();
RoomData *roomData = res.getRoom(room.roomNumber());
- if (roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
+ if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
res.fieldList().setField(AREA_FLAG, roomData->areaFlag);
switch (roomData->areaFlag) {
diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp
index 80b21119ff..0488f17d8f 100644
--- a/engines/m4/assets.cpp
+++ b/engines/m4/assets.cpp
@@ -201,6 +201,7 @@ void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream*
Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3);
SpriteAssetFrame frame;
for (curFrame = 0; curFrame < _frameCount; curFrame++) {
+ frame.stream = 0;
frame.comp = 0;
frameOffset = spriteStream->readUint32LE();
_frameOffsets.push_back(frameOffset);
diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp
index 024cd591f5..5b8bdab9d6 100644
--- a/engines/m4/converse.cpp
+++ b/engines/m4/converse.cpp
@@ -153,7 +153,7 @@ void ConversationView::setNode(int32 nodeIndex) {
void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) {
//if (!this->isVisible())
// return;
- empty();
+ clear();
if (_entriesShown) {
// Write out the conversation options
diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp
index 12d9a24d37..58c68979d1 100644
--- a/engines/m4/globals.cpp
+++ b/engines/m4/globals.cpp
@@ -75,7 +75,7 @@ bool Kernel::sendTrigger(int32 triggerNum) {
bool Kernel::handleTrigger(int32 triggerNum) {
- printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum);
+ printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, (uint)triggerNum);
if (betweenRooms)
return true;
@@ -271,11 +271,13 @@ Globals::Globals(M4Engine *vm): _vm(vm) {
}
Globals::~Globals() {
- for(uint32 i = 0; i < _madsVocab.size(); i++)
+ uint32 i;
+
+ for(i = 0; i < _madsVocab.size(); i++)
free(_madsVocab[i]);
_madsVocab.clear();
- for(uint32 i = 0; i < _madsQuotes.size(); i++)
+ for(i = 0; i < _madsQuotes.size(); i++)
free(_madsQuotes[i]);
_madsQuotes.clear();
@@ -351,7 +353,7 @@ void Globals::loadMadsMessagesInfo() {
_vm->res()->toss("messages.dat");
}
-char* Globals::loadMessage(uint32 index) {
+char* Globals::loadMessage(uint index) {
if (index > _madsMessages.size() - 1) {
warning("Invalid message index: %i", index);
return NULL;
diff --git a/engines/m4/globals.h b/engines/m4/globals.h
index a0133db2d6..a80e8bf710 100644
--- a/engines/m4/globals.h
+++ b/engines/m4/globals.h
@@ -177,7 +177,7 @@ public:
void loadMadsMessagesInfo();
uint32 getMessagesSize() { return _madsMessages.size(); }
- char* loadMessage(uint32 index);
+ char* loadMessage(uint index);
};
#define PLAYER_FIELD_LENGTH 40
diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp
index beda178344..1846f1c1e7 100644
--- a/engines/m4/graphics.cpp
+++ b/engines/m4/graphics.cpp
@@ -320,7 +320,7 @@ byte *M4Surface::getBasePtr(int x, int y) {
void M4Surface::freeData() {
}
-void M4Surface::empty() {
+void M4Surface::clear() {
Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK);
}
@@ -389,7 +389,7 @@ void M4Surface::loadBackgroundRiddle(const char *sceneName) {
}
void M4Surface::loadBackground(int sceneNumber, RGBList **palData) {
- this->empty(); // clear previous scene
+ clear(); // clear previous scene
if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) {
char resourceName[20];
@@ -502,7 +502,7 @@ void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) {
//printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize);
- newTile->empty();
+ newTile->clear();
byte *compressedTileData = new byte[compressedTileDataSize];
diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h
index 60e608c148..84fc77656f 100644
--- a/engines/m4/graphics.h
+++ b/engines/m4/graphics.h
@@ -128,7 +128,7 @@ public:
byte *getData();
byte *getBasePtr(int x, int y);
void freeData();
- void empty();
+ void clear();
void frameRect(const Common::Rect &r, uint8 color);
void fillRect(const Common::Rect &r, uint8 color);
void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY,
diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp
index 9bf964ee96..777356467b 100644
--- a/engines/m4/m4_views.cpp
+++ b/engines/m4/m4_views.cpp
@@ -331,7 +331,7 @@ bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y,
}
void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) {
- empty();
+ clear();
_statusText.onRefresh();
_inventory.onRefresh();
diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp
index 3e80d0f1e0..c51daa84c4 100644
--- a/engines/m4/mads_anim.cpp
+++ b/engines/m4/mads_anim.cpp
@@ -61,9 +61,9 @@ TextviewView::TextviewView(M4Engine *vm):
_vm->_font->setColors(5, 6, 4);
- empty();
- _bgSurface.empty();
- _textSurface.empty();
+ clear();
+ _bgSurface.clear();
+ _textSurface.clear();
int y = (height() - MADS_SURFACE_HEIGHT) / 2;
setColor(2);
@@ -83,8 +83,8 @@ TextviewView::~TextviewView() {
}
void TextviewView::reset() {
- _bgSurface.empty();
- _textSurface.empty();
+ _bgSurface.clear();
+ _textSurface.clear();
_animating = false;
_panX = 0;
_panY = 0;
@@ -456,8 +456,8 @@ AnimviewView::AnimviewView(M4Engine *vm):
// Set up system palette colors
_vm->_palette->setMadsSystemPalette();
- empty();
- _bgSurface.empty();
+ clear();
+ _bgSurface.clear();
int y = (height() - MADS_SURFACE_HEIGHT) / 2;
setColor(2);
@@ -471,7 +471,7 @@ AnimviewView::~AnimviewView() {
}
void AnimviewView::reset() {
- _bgSurface.empty();
+ _bgSurface.clear();
_soundDriverLoaded = false;
}
diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp
index 3a8b5d24a8..b74e598c6c 100644
--- a/engines/m4/viewmgr.cpp
+++ b/engines/m4/viewmgr.cpp
@@ -380,7 +380,7 @@ void ViewManager::updateState() {
}
void ViewManager::refreshAll() {
- _vm->_screen->empty();
+ _vm->_screen->clear();
for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
View *v = *i;
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index 55e0e90732..3497b5b46f 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -88,10 +88,7 @@ int16 Object::getVectorItem(int16 index) {
if (getClass() == 0x7FFF) {
byte *vector = (byte*)getData();
return vector[index];
- } else if (getClass() == 0x7FFE) {
- int16 *vector = (int16*)getData();
- return READ_LE_UINT16(&vector[index]);
- } else if (getClass() < 0x7FFE) {
+ } else if (getClass() <= 0x7FFE) {
int16 *vector = (int16*)getData();
return READ_LE_UINT16(&vector[index]);
} else {
@@ -372,7 +369,7 @@ void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) {
debug(2, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize);
_gameState = new byte[_gameStateSize + 2];
- memset(_gameState, 0, _gameStateSize);
+ memset(_gameState, 0, _gameStateSize + 2);
setVar(1, objectCount);
sourceS.seek(textOffs);
@@ -441,7 +438,7 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
int16 *propPtr2 = prop + count2;
// First see if the property exists in the given object
- while (count2-- > 0) {
+ while (count2--) {
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
propertyFlag = obj->getFlags() & 1;
return propPtr1;
@@ -467,8 +464,8 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
propPtr1 = propPtr2 + count1 - count2;
int16 *propertyPtr = prop + count1;
- while (count2-- > 0) {
- if (!(READ_LE_UINT16(prop) & 0x8000)) {
+ while (count2--) {
+ if ((READ_LE_UINT16(prop) & 0x8000) == 0) {
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
propertyFlag = obj->getFlags() & 1;
return propPtr1;
diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp
index dc7dbdee87..e5870bfeec 100644
--- a/engines/made/detection.cpp
+++ b/engines/made/detection.cpp
@@ -65,7 +65,7 @@ static const PlainGameDescriptor madeGames[] = {
{"manhole", "The Manhole"},
{"rtz", "Return to Zork"},
{"lgop2", "Leather Goddesses of Phobos 2"},
- {"rodney", "Rodney's Fun Screen"},
+ {"rodney", "Rodney's Funscreen"},
{0, 0}
};
@@ -278,7 +278,7 @@ static const MadeGameDescription gameDescriptions[] = {
},
{
- // Rodney's Fun Screen
+ // Rodney's Funscreen
{
"rodney",
"",
diff --git a/engines/made/made.cpp b/engines/made/made.cpp
index 59ec487c37..dc45dc4d2f 100644
--- a/engines/made/made.cpp
+++ b/engines/made/made.cpp
@@ -148,27 +148,31 @@ int MadeEngine::init() {
return 0;
}
+int16 MadeEngine::getTicks() {
+ return g_system->getMillis() * 30 / 1000;
+}
+
int16 MadeEngine::getTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers) && _timers[timerNum - 1] != -1)
- return (_system->getMillis() - _timers[timerNum - 1]) / kTimerResolution;
+ return (getTicks() - _timers[timerNum - 1]);
else
return 32000;
}
void MadeEngine::setTimer(int16 timerNum, int16 value) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
- _timers[timerNum - 1] = value * kTimerResolution;
+ _timers[timerNum - 1] = value;
}
void MadeEngine::resetTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
- _timers[timerNum - 1] = _system->getMillis();
+ _timers[timerNum - 1] = getTicks();
}
int16 MadeEngine::allocTimer() {
for (int i = 0; i < ARRAYSIZE(_timers); i++) {
if (_timers[i] == -1) {
- _timers[i] = _system->getMillis();
+ _timers[i] = getTicks();
return i + 1;
}
}
@@ -202,24 +206,20 @@ void MadeEngine::handleEvents() {
break;
case Common::EVENT_LBUTTONDOWN:
- _eventNum = 1;
+ _eventNum = 2;
break;
- /*
case Common::EVENT_LBUTTONUP:
- _eventNum = 2; // TODO: Is this correct?
+ _eventNum = 1;
break;
- */
case Common::EVENT_RBUTTONDOWN:
- _eventNum = 3;
+ _eventNum = 4;
break;
- /*
case Common::EVENT_RBUTTONUP:
- eventNum = 4; // TODO: Is this correct?
+ _eventNum = 3;
break;
- */
case Common::EVENT_KEYDOWN:
_eventKey = event.kbd.ascii;
@@ -239,7 +239,7 @@ void MadeEngine::handleEvents() {
}
}
-
+
AudioCD.updateCD();
}
diff --git a/engines/made/made.h b/engines/made/made.h
index 461941e5cf..971961c867 100644
--- a/engines/made/made.h
+++ b/engines/made/made.h
@@ -120,6 +120,7 @@ public:
int _engineVersion;
int32 _timers[50];
+ int16 getTicks();
int16 getTimer(int16 timerNum);
void setTimer(int16 timerNum, int16 value);
void resetTimer(int16 timerNum);
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 1a8ca9c50a..831f1fab8e 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -40,7 +40,10 @@ void PmvPlayer::play(const char *filename) {
_surface = NULL;
_fd = new Common::File();
- _fd->open(filename);
+ if (!_fd->open(filename)) {
+ delete _fd;
+ return;
+ }
uint32 chunkType, chunkSize;
diff --git a/engines/made/screen.cpp b/engines/made/screen.cpp
index cecd0c8968..0c22d40259 100644
--- a/engines/made/screen.cpp
+++ b/engines/made/screen.cpp
@@ -688,7 +688,7 @@ void Screen::printText(const char *text) {
for (int textPos = 0; textPos < textLen; textPos++) {
- uint c = text[textPos];
+ uint c = ((byte*)text)[textPos];
int charWidth = _font->getCharWidth(c);
if (c == 9) {
@@ -822,6 +822,8 @@ SpriteListItem Screen::getFromSpriteList(int16 index) {
if (((uint) index) > _spriteList.size()) {
SpriteListItem emptyItem;
emptyItem.index = 0;
+ emptyItem.xofs = 0;
+ emptyItem.yofs = 0;
return emptyItem;
} else {
return _spriteList[index - 1];
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 932447a1eb..d697e24b04 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -106,7 +106,7 @@ void ScriptFunctions::setupExternalsTable() {
External(sfStopSound);
External(sfPlayVoice);
- if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ) {
+ if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RODNEY) {
External(sfPlayCd);
External(sfStopCd);
External(sfGetCdStatus);
@@ -332,7 +332,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
// Unused in RTZ
return 0;
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
return _vm->_screen->addToSpriteList(argv[2], argv[1], argv[0]);
} else {
return 0;
@@ -341,7 +341,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {
_vm->_screen->clearChannels();
- if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
_vm->_screen->clearSpriteList();
}
return 0;
@@ -350,7 +350,7 @@ int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
return _vm->_screen->drawSprite(argv[2], argv[1], argv[0]);
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
SpriteListItem item = _vm->_screen->getFromSpriteList(argv[2]);
int16 channelIndex = _vm->_screen->drawSprite(item.index, argv[1] - item.xofs, argv[0] - item.yofs);
_vm->_screen->setChannelUseMask(channelIndex);
@@ -409,7 +409,7 @@ int16 ScriptFunctions::sfDrawText(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
text = _vm->_dat->getObjectString(argv[argc - 1]);
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
text = _vm->_dat->getString(argv[argc - 1]);
}
diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp
new file mode 100644
index 0000000000..81b32adb15
--- /dev/null
+++ b/engines/parallaction/balloons.cpp
@@ -0,0 +1,728 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/util.h"
+
+#include "parallaction/graphics.h"
+#include "parallaction/parallaction.h"
+
+namespace Parallaction {
+
+
+#define BALLOON_TRANSPARENT_COLOR_NS 2
+#define BALLOON_TRANSPARENT_COLOR_BR 0
+
+#define BALLOON_TAIL_WIDTH 12
+#define BALLOON_TAIL_HEIGHT 10
+
+
+byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = {
+ {
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ },
+ {
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02
+ }
+};
+
+class BalloonManager_ns : public BalloonManager {
+
+ static int16 _dialogueBalloonX[5];
+
+ struct Balloon {
+ Common::Rect outerBox;
+ Common::Rect innerBox;
+ Graphics::Surface *surface;
+ GfxObj *obj;
+ } _intBalloons[5];
+
+ uint _numBalloons;
+
+ void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height);
+ void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
+ int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
+ Balloon *getBalloon(uint id);
+
+ Gfx *_gfx;
+
+public:
+ BalloonManager_ns(Gfx *gfx);
+ ~BalloonManager_ns();
+
+ void freeBalloons();
+ int setLocationBalloon(char *text, bool endGame);
+ int setDialogueBalloon(char *text, uint16 winding, byte textColor);
+ int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
+ void setBalloonText(uint id, char *text, byte textColor);
+ int hitTestDialogueBalloon(int x, int y);
+};
+
+int16 BalloonManager_ns::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };
+
+BalloonManager_ns::BalloonManager_ns(Gfx *gfx) : _numBalloons(0), _gfx(gfx) {
+
+}
+
+BalloonManager_ns::~BalloonManager_ns() {
+
+}
+
+
+BalloonManager_ns::Balloon* BalloonManager_ns::getBalloon(uint id) {
+ assert(id < _numBalloons);
+ return &_intBalloons[id];
+}
+
+int BalloonManager_ns::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) {
+ assert(_numBalloons < 5);
+
+ int id = _numBalloons;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ int16 real_h = (winding == -1) ? h : h + 9;
+ balloon->surface = new Graphics::Surface;
+ balloon->surface->create(w, real_h, 1);
+ balloon->surface->fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR_NS);
+
+ Common::Rect r(w, h);
+ balloon->surface->fillRect(r, 0);
+ balloon->outerBox = r;
+
+ r.grow(-borderThickness);
+ balloon->surface->fillRect(r, 1);
+ balloon->innerBox = r;
+
+ if (winding != -1) {
+ // draws tail
+ // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill.
+ winding = (winding == 0 ? 1 : 0);
+ Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT);
+ s.moveTo(r.width()/2 - 5, r.bottom - 1);
+ _gfx->blt(s, _resBalloonTail[winding], balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR_NS);
+ }
+
+ _numBalloons++;
+
+ return id;
+}
+
+
+int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
+
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+5, h, winding, 1);
+ Balloon *balloon = &_intBalloons[id];
+
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = x;
+ balloon->obj->y = y;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ return id;
+}
+
+int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
+
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+5, h, winding, 1);
+ Balloon *balloon = &_intBalloons[id];
+
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = _dialogueBalloonX[id];
+ balloon->obj->y = 10;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ if (id > 0) {
+ balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].outerBox.height();
+ }
+
+
+ return id;
+}
+
+void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) {
+ Balloon *balloon = getBalloon(id);
+ balloon->surface->fillRect(balloon->innerBox, 1);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+}
+
+
+int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) {
+
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR_NS);
+ Balloon *balloon = &_intBalloons[id];
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = 5;
+ balloon->obj->y = 5;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ return id;
+}
+
+int BalloonManager_ns::hitTestDialogueBalloon(int x, int y) {
+
+ Common::Point p;
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ p.x = x - _intBalloons[i].obj->x;
+ p.y = y - _intBalloons[i].obj->y;
+
+ if (_intBalloons[i].innerBox.contains(p))
+ return i;
+ }
+
+ return -1;
+}
+
+void BalloonManager_ns::freeBalloons() {
+ _gfx->destroyBalloons();
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ _intBalloons[i].obj = 0;
+ _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons
+ }
+
+ _numBalloons = 0;
+}
+
+void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) {
+
+ uint16 lines = 0;
+ uint16 linewidth = 0;
+
+ uint16 rx = 10;
+ uint16 ry = 4;
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ char token[MAX_TOKEN_LEN];
+
+ if (wrapwidth == -1)
+ wrapwidth = _vm->_screenWidth;
+
+ while (strlen(text) > 0) {
+
+ text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
+
+ if (!scumm_stricmp(token, "%p")) {
+ lines++;
+ rx = 10;
+ ry = 4 + lines*10; // y
+
+ strcpy(token, "> .......");
+ strncpy(token+2, _password, strlen(_password));
+ tokenWidth = font->getStringWidth(token);
+ } else {
+ tokenWidth = font->getStringWidth(token);
+
+ linewidth += tokenWidth;
+
+ if (linewidth > wrapwidth) {
+ // wrap line
+ lines++;
+ rx = 10; // x
+ ry = 4 + lines*10; // y
+ linewidth = tokenWidth;
+ }
+
+ if (!scumm_stricmp(token, "%s")) {
+ sprintf(token, "%d", _score);
+ }
+
+ }
+
+ _gfx->drawText(font, surf, rx, ry, token, color);
+
+ rx += tokenWidth + blankWidth;
+ linewidth += blankWidth;
+
+ text = Common::ltrim(text);
+ }
+
+}
+
+void BalloonManager_ns::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {
+
+ uint16 lines = 0;
+ uint16 w = 0;
+ *width = 0;
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ char token[MAX_TOKEN_LEN];
+
+ while (strlen(text) != 0) {
+
+ text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
+ tokenWidth = font->getStringWidth(token);
+
+ w += tokenWidth;
+
+ if (!scumm_stricmp(token, "%p")) {
+ lines++;
+ } else {
+ if (w > maxwidth) {
+ w -= tokenWidth;
+ lines++;
+ if (w > *width)
+ *width = w;
+
+ w = tokenWidth;
+ }
+ }
+
+ w += blankWidth;
+ text = Common::ltrim(text);
+ }
+
+ if (*width < w) *width = w;
+ *width += 10;
+
+ *height = lines * 10 + 20;
+
+ return;
+}
+
+
+
+
+
+class BalloonManager_br : public BalloonManager {
+
+ struct Balloon {
+ Common::Rect box;
+ Graphics::Surface *surface;
+ GfxObj *obj;
+ } _intBalloons[3];
+
+ uint _numBalloons;
+
+ Disk *_disk;
+ Gfx *_gfx;
+
+ Frames *_leftBalloon;
+ Frames *_rightBalloon;
+
+ void cacheAnims();
+ void getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height);
+ void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
+ int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
+ Balloon *getBalloon(uint id);
+ Graphics::Surface *expandBalloon(Frames *data, int frameNum);
+
+ void textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color);
+ void textEmitCenteredLine();
+ void textAccum(const Common::String &token, uint16 width);
+ void textNewLine();
+
+ Common::String _textLine;
+ Graphics::Surface *_textSurf;
+ Font *_textFont;
+ uint16 _textX, _textY;
+ byte _textColor;
+ uint16 _textLines, _textWidth;
+
+ void extentSetup(Font *font, int16 *width, int16 *height);
+ void extentAction();
+
+ int16 *_extentWidth, *_extentHeight;
+
+
+public:
+ BalloonManager_br(Disk *disk, Gfx *gfx);
+ ~BalloonManager_br();
+
+ void freeBalloons();
+ int setLocationBalloon(char *text, bool endGame);
+ int setDialogueBalloon(char *text, uint16 winding, byte textColor);
+ int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
+ void setBalloonText(uint id, char *text, byte textColor);
+ int hitTestDialogueBalloon(int x, int y);
+};
+
+
+
+BalloonManager_br::Balloon* BalloonManager_br::getBalloon(uint id) {
+ assert(id < _numBalloons);
+ return &_intBalloons[id];
+}
+
+Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum) {
+
+ Common::Rect rect;
+ data->getRect(frameNum, rect);
+
+ rect.translate(-rect.left, -rect.top);
+
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(rect.width(), rect.height(), 1);
+
+ _gfx->unpackBlt(rect, data->getData(frameNum), data->getRawSize(frameNum), surf, 0, BALLOON_TRANSPARENT_COLOR_BR);
+
+ return surf;
+}
+
+int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
+ cacheAnims();
+
+ int id = _numBalloons;
+ Frames *src = 0;
+ int srcFrame = 0;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ if (winding == 0) {
+ src = _rightBalloon;
+ srcFrame = 0;
+ } else
+ if (winding == 1) {
+ src = _leftBalloon;
+ srcFrame = 0;
+ }
+
+ assert(src);
+
+ balloon->surface = expandBalloon(src, srcFrame);
+ src->getRect(srcFrame, balloon->box);
+
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = x + balloon->box.left;
+ balloon->obj->y = y + balloon->box.top;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
+
+ printf("balloon (%i, %i)\n", balloon->obj->x, balloon->obj->y);
+
+ _numBalloons++;
+
+ return id;
+}
+
+int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
+ cacheAnims();
+
+ int id = _numBalloons;
+ Frames *src = 0;
+ int srcFrame = 0;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ if (winding == 0) {
+ src = _rightBalloon;
+ srcFrame = id;
+ } else
+ if (winding == 1) {
+ src = _leftBalloon;
+ srcFrame = 0;
+ }
+
+ assert(src);
+
+ balloon->surface = expandBalloon(src, srcFrame);
+ src->getRect(srcFrame, balloon->box);
+
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = balloon->box.left;
+ balloon->obj->y = balloon->box.top;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
+
+ if (id > 0) {
+ balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].box.height();
+ }
+
+ _numBalloons++;
+
+ return id;
+}
+
+void BalloonManager_br::setBalloonText(uint id, char *text, byte textColor) { }
+
+int BalloonManager_br::setLocationBalloon(char *text, bool endGame) {
+/*
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR);
+ Balloon *balloon = &_intBalloons[id];
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH);
+
+ // TODO: extract some text to make a name for obj
+ balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
+ balloon->obj->x = 5;
+ balloon->obj->y = 5;
+*/
+ return 0;
+}
+
+int BalloonManager_br::hitTestDialogueBalloon(int x, int y) {
+
+ Common::Point p;
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ p.x = x - _intBalloons[i].obj->x;
+ p.y = y - _intBalloons[i].obj->y;
+
+ if (_intBalloons[i].box.contains(p))
+ return i;
+ }
+
+ return -1;
+}
+
+void BalloonManager_br::freeBalloons() {
+ _gfx->destroyBalloons();
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ _intBalloons[i].obj = 0;
+ _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons
+ }
+
+ _numBalloons = 0;
+}
+
+void BalloonManager_br::cacheAnims() {
+ if (!_leftBalloon) {
+ _leftBalloon = _disk->loadFrames("fumetto.ani");
+ _rightBalloon = _disk->loadFrames("fumdx.ani");
+ }
+}
+
+
+void BalloonManager_br::extentSetup(Font *font, int16 *width, int16 *height) {
+ _extentWidth = width;
+ _extentHeight = height;
+
+ _textLine.clear();
+ _textLines = 0;
+ _textWidth = 0;
+ _textFont = font;
+}
+
+void BalloonManager_br::extentAction() {
+ if (_textWidth > *_extentWidth) {
+ *_extentWidth = _textWidth;
+ }
+ *_extentHeight = _textLines * _textFont->height();
+}
+
+void BalloonManager_br::textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color) {
+ uint16 maxWidth = 216;
+
+ int16 w, h;
+ getStringExtent(font, text.c_str(), maxWidth, &w, &h);
+
+ w += 10;
+ h += 12;
+
+ _textLine.clear();
+ _textSurf = dest;
+ _textFont = font;
+ _textX = 0;
+ _textY = (_textSurf->h - h) / 2;
+ _textColor = color;
+ _textLines = 0;
+ _textWidth = 0;
+}
+
+void BalloonManager_br::textEmitCenteredLine() {
+ if (_textLine.empty()) {
+ return;
+ }
+ uint16 rx = _textX + (_textSurf->w - _textWidth) / 2;
+ uint16 ry = _textY + _textLines * _textFont->height(); // y
+ _gfx->drawText(_textFont, _textSurf, rx, ry, _textLine.c_str(), _textColor);
+}
+
+void BalloonManager_br::textAccum(const Common::String &token, uint16 width) {
+ if (token.empty()) {
+ return;
+ }
+
+ _textWidth += width;
+ _textLine += token;
+}
+
+void BalloonManager_br::textNewLine() {
+ _textLines++;
+ _textWidth = 0;
+ _textLine.clear();
+}
+
+
+// TODO: really, base this and getStringExtent on some kind of LineTokenizer, instead of
+// repeating the algorithm and changing a couple of lines.
+void BalloonManager_br::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapWidth) {
+ textSetupRendering(text, surf, font, color);
+
+ wrapWidth = 216;
+
+ Common::StringTokenizer tokenizer(text, " ");
+ Common::String token;
+ Common::String blank(" ");
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ while (!tokenizer.empty()) {
+ token = tokenizer.nextToken();
+
+ if (token == '/') {
+ tokenWidth = 0;
+ textEmitCenteredLine();
+ textNewLine();
+ } else {
+ // todo: expand '%'
+ tokenWidth = font->getStringWidth(token.c_str());
+
+ if (_textWidth == 0) {
+ textAccum(token, tokenWidth);
+ } else {
+ if (_textWidth + blankWidth + tokenWidth <= wrapWidth) {
+ textAccum(blank, blankWidth);
+ textAccum(token, tokenWidth);
+ } else {
+ textEmitCenteredLine();
+ textNewLine();
+ textAccum(token, tokenWidth);
+ }
+ }
+ }
+ }
+
+ textEmitCenteredLine();
+}
+
+
+
+void BalloonManager_br::getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height) {
+ extentSetup(font, width, height);
+
+ Common::StringTokenizer tokenizer(text, " ");
+ Common::String token;
+ Common::String blank(" ");
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ while (!tokenizer.empty()) {
+ token = tokenizer.nextToken();
+
+ if (token == '/') {
+ tokenWidth = 0;
+ extentAction();
+ textNewLine();
+ } else {
+ // todo: expand '%'
+ tokenWidth = font->getStringWidth(token.c_str());
+
+ if (_textWidth == 0) {
+ textAccum(token, tokenWidth);
+ } else {
+ if (_textWidth + blankWidth + tokenWidth <= maxwidth) {
+ textAccum(blank, blankWidth);
+ textAccum(token, tokenWidth);
+ } else {
+ extentAction();
+ textNewLine();
+ textAccum(token, tokenWidth);
+ }
+ }
+ }
+ }
+
+ extentAction();
+}
+
+
+
+
+BalloonManager_br::BalloonManager_br(Disk *disk, Gfx *gfx) : _numBalloons(0), _disk(disk), _gfx(gfx), _leftBalloon(0), _rightBalloon(0) {
+}
+
+BalloonManager_br::~BalloonManager_br() {
+ delete _leftBalloon;
+ delete _rightBalloon;
+}
+
+void Parallaction::setupBalloonManager() {
+ if (_vm->getGameType() == GType_Nippon) {
+ _balloonMan = new BalloonManager_ns(_vm->_gfx);
+ } else
+ if (_vm->getGameType() == GType_BRA) {
+ _balloonMan = new BalloonManager_br(_vm->_disk, _vm->_gfx);
+ } else {
+ error("Unknown game type");
+ }
+}
+
+
+
+} // namespace Parallaction
diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp
index 68e6a70ffb..761e11dc7d 100644
--- a/engines/parallaction/callables_ns.cpp
+++ b/engines/parallaction/callables_ns.cpp
@@ -37,18 +37,6 @@
namespace Parallaction {
-// part completion messages
-static const char *endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
-static const char *endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"};
-static const char *endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"};
-static const char *endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"};
-// game completion messages
-static const char *endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
-static const char *endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"};
-static const char *endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"};
-static const char *endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"};
-
-
/*
intro callables data members
*/
@@ -143,18 +131,6 @@ static uint16 _rightHandPositions[684] = {
0x00e0, 0x007b, 0x00e0, 0x0077
};
-struct Credit {
- const char *_role;
- const char *_name;
-} _credits[] = {
- {"Music and Sound Effects", "MARCO CAPRELLI"},
- {"PC Version", "RICCARDO BALLARINO"},
- {"Project Manager", "LOVRANO CANEPA"},
- {"Production", "BRUNO BOZ"},
- {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"},
- {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"}
-};
-
/*
game callables
*/
@@ -304,23 +280,19 @@ void Parallaction_ns::_c_trasformata(void *parm) {
}
void Parallaction_ns::_c_offMouse(void *parm) {
- _input->showCursor(false);
- _engineFlags |= kEngineBlockInput;
- return;
+ _input->setMouseState(MOUSE_DISABLED);
}
void Parallaction_ns::_c_onMouse(void *parm) {
- _engineFlags &= ~kEngineBlockInput;
- _input->showCursor(true);
- return;
+ _input->setMouseState(MOUSE_ENABLED_SHOW);
}
void Parallaction_ns::_c_setMask(void *parm) {
- memset(_gfx->_backgroundInfo.mask.data + 3600, 0, 3600);
- _gfx->_backgroundInfo.layers[1] = 500;
+ memset(_gfx->_backgroundInfo->mask.data + 3600, 0, 3600);
+ _gfx->_backgroundInfo->layers[1] = 500;
return;
}
@@ -340,8 +312,8 @@ void Parallaction_ns::_c_endComment(void *param) {
g_system->delayMillis(20);
}
- _input->waitUntilLeftClick();
- _gfx->freeBalloons();
+ _input->waitForButtonEvent(kMouseLeftUp);
+ _balloonMan->freeBalloons();
return;
}
@@ -376,37 +348,12 @@ void Parallaction_ns::_c_finito(void *parm) {
setPartComplete(_char);
cleanInventory();
- _gfx->setPalette(_gfx->_palette);
-
- uint id[4];
-
- if (allPartsComplete()) {
- id[0] = _gfx->createLabel(_menuFont, endMsg4[_language], 1);
- id[1] = _gfx->createLabel(_menuFont, endMsg5[_language], 1);
- id[2] = _gfx->createLabel(_menuFont, endMsg6[_language], 1);
- id[3] = _gfx->createLabel(_menuFont, endMsg7[_language], 1);
- } else {
- id[0] = _gfx->createLabel(_menuFont, endMsg0[_language], 1);
- id[1] = _gfx->createLabel(_menuFont, endMsg1[_language], 1);
- id[2] = _gfx->createLabel(_menuFont, endMsg2[_language], 1);
- id[3] = _gfx->createLabel(_menuFont, endMsg3[_language], 1);
- }
-
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
- _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130);
- _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160);
- _input->waitUntilLeftClick();
+ cleanupGame();
- _gfx->freeLabels();
+ _gfx->setPalette(_gfx->_palette);
- if (allPartsComplete()) {
- scheduleLocationSwitch("estgrotta.drki");
- } else {
- selectStartLocation();
- }
+ startEndPartSequence();
- cleanupGame();
return;
}
@@ -417,6 +364,14 @@ void Parallaction_ns::_c_ridux(void *parm) {
}
void Parallaction_ns::_c_testResult(void *parm) {
+ if (_inTestResult) { // NOTE: _inTestResult has been added because the scripts call _c_testResult multiple times to cope with
+ // the multiple buffering that was used in the original engine. _inTestResult now prevents the engine
+ // from crashing when the scripts are executed.
+ return;
+ }
+ _inTestResult = true;
+
+ _gfx->freeLabels();
_gfx->updateScreen();
_disk->selectArchive("disk1");
@@ -459,52 +414,11 @@ void Parallaction_ns::_c_startIntro(void *parm) {
_soundMan->playMusic();
}
- _engineFlags |= kEngineBlockInput;
-
- return;
+ _input->setMouseState(MOUSE_DISABLED);
}
void Parallaction_ns::_c_endIntro(void *parm) {
-
- debugC(1, kDebugExec, "endIntro()");
-
- uint id[2];
- for (uint16 _si = 0; _si < 6; _si++) {
- id[0] = _gfx->createLabel(_menuFont, _credits[_si]._role, 1);
- id[1] = _gfx->createLabel(_menuFont, _credits[_si]._name, 1);
-
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
-
- _gfx->updateScreen();
-
- _input->waitForButtonEvent(kMouseLeftUp, 5500);
-
- _gfx->freeLabels();
- }
- debugC(1, kDebugExec, "endIntro(): done showing credits");
-
- _soundMan->stopMusic();
-
- if ((getFeatures() & GF_DEMO) == 0) {
-
- id[0] = _gfx->createLabel(_menuFont, "CLICK MOUSE BUTTON TO START", 1);
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
-
- _input->waitUntilLeftClick();
-
- _gfx->freeLabels();
-
- _engineFlags &= ~kEngineBlockInput;
- selectStartLocation();
-
- cleanupGame();
-
- } else {
- _input->waitUntilLeftClick();
- }
-
- return;
+ startCreditSequence();
}
void Parallaction_ns::_c_moveSheet(void *parm) {
@@ -588,11 +502,11 @@ void Parallaction_ns::_c_shade(void *parm) {
_rightHandAnim->_top
);
- uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo.mask.internalWidth;
+ uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo->mask.internalWidth;
for (uint16 _si = r.top; _si < r.bottom; _si++) {
- memset(_gfx->_backgroundInfo.mask.data + _di, 0, r.width()/4+1);
- _di += _gfx->_backgroundInfo.mask.internalWidth;
+ memset(_gfx->_backgroundInfo->mask.data + _di, 0, r.width()/4+1);
+ _di += _gfx->_backgroundInfo->mask.internalWidth;
}
return;
diff --git a/engines/parallaction/debug.cpp b/engines/parallaction/debug.cpp
index 3c90a76f61..f57976594e 100644
--- a/engines/parallaction/debug.cpp
+++ b/engines/parallaction/debug.cpp
@@ -188,17 +188,15 @@ bool Debugger::Cmd_GfxObjects(int argc, const char **argv) {
const char *objType[] = { "DOOR", "GET", "ANIM" };
DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n"
- "| name | x | y | z | f | type | flag |\n"
+ "| name | x | y | z | f | type | visi |\n"
"+--------------------+-----+-----+-----+-----+--------+--------+\n");
- for (uint i = 0; i < 3; i++) {
- GfxObjList::iterator b = _vm->_gfx->_gfxobjList[i].begin();
- GfxObjList::iterator e = _vm->_gfx->_gfxobjList[i].end();
+ GfxObjList::iterator b = _vm->_gfx->_gfxobjList.begin();
+ GfxObjList::iterator e = _vm->_gfx->_gfxobjList.end();
- for ( ; b != e; b++) {
- GfxObj *obj = *b;
- DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], 6 );
- }
+ for ( ; b != e; b++) {
+ GfxObj *obj = *b;
+ DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], obj->isVisible() );
}
DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n");
diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp
index 8841b9ca40..0476b01454 100644
--- a/engines/parallaction/detection.cpp
+++ b/engines/parallaction/detection.cpp
@@ -154,7 +154,23 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::ADGF_NO_FLAGS
},
GType_BRA,
- GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT
+ GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
+ },
+
+ {
+ {
+ "bra",
+ "Demo",
+ {
+ { "russia.fnt", 0, "0dd55251d2886d6783718df2b184bf97", 10649 },
+ { NULL, 0, NULL, 0}
+ },
+ Common::UNK_LANG,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_BRA,
+ GF_LANG_EN | GF_DEMO,
},
// TODO: Base the detection of Amiga BRA on actual data file, not executable file.
@@ -171,9 +187,25 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::ADGF_NO_FLAGS
},
GType_BRA,
- GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT
+ GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
},
+ // TODO: Base the detection of Amiga BRA demo on actual data file, not executable file.
+ {
+ {
+ "bra",
+ "Demo",
+ {
+ { "bigred", 0, "b62a7b589fb5e9071f021227640893bf", 97004 },
+ { NULL, 0, NULL, 0}
+ },
+ Common::UNK_LANG,
+ Common::kPlatformAmiga,
+ Common::ADGF_DEMO
+ },
+ GType_BRA,
+ GF_LANG_EN | GF_DEMO,
+ },
{ AD_TABLE_END_MARKER, 0, 0 }
};
diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp
index 70db637699..21584a0525 100644
--- a/engines/parallaction/dialogue.cpp
+++ b/engines/parallaction/dialogue.cpp
@@ -33,7 +33,7 @@
namespace Parallaction {
#define MAX_PASSWORD_LENGTH 7
-
+/*
#define QUESTION_BALLOON_X 140
#define QUESTION_BALLOON_Y 10
#define QUESTION_CHARACTER_X 190
@@ -41,118 +41,127 @@ namespace Parallaction {
#define ANSWER_CHARACTER_X 10
#define ANSWER_CHARACTER_Y 80
+*/
+struct BalloonPositions {
+ Common::Point _questionBalloon;
+ Common::Point _questionChar;
+
+ Common::Point _answerChar;
+};
+
+BalloonPositions _balloonPositions_NS = {
+ Common::Point(140, 10),
+ Common::Point(190, 80),
+ Common::Point(10, 80)
+};
+
+BalloonPositions _balloonPositions_BR = {
+ Common::Point(0, 0),
+ Common::Point(380, 80),
+ Common::Point(10, 80)
+};
+
class DialogueManager {
+ enum {
+ RUN_QUESTION,
+ RUN_ANSWER,
+ NEXT_QUESTION,
+ NEXT_ANSWER,
+ DIALOGUE_OVER
+ } _state;
+
Parallaction *_vm;
- SpeakData *_data;
Dialogue *_dialogue;
bool _askPassword;
+ int _passwordLen;
+ bool _passwordChanged;
bool isNpc;
- Frames *_questioner;
- Frames *_answerer;
+ GfxObj *_questioner;
+ GfxObj *_answerer;
Question *_q;
uint16 _visAnswers[5];
int _numVisAnswers;
+ int _answerId;
+
+ int _selection, _oldSelection;
+
+ uint32 _mouseButtons;
+ Common::Point _mousePos;
+ bool _isKeyDown;
+ uint16 _downKey;
+
+ BalloonPositions _ballonPos;
+
public:
- DialogueManager(Parallaction *vm, SpeakData *data) : _vm(vm), _data(data) {
- _dialogue = _data->_dialogue;
- isNpc = scumm_stricmp(_data->_name, "yourself") && _data->_name[0] != '\0';
- _questioner = isNpc ? _vm->_disk->loadTalk(_data->_name) : _vm->_char._talk;
- _answerer = _vm->_char._talk;
- }
+ DialogueManager(Parallaction *vm, ZonePtr z);
+ ~DialogueManager();
- ~DialogueManager() {
- if (isNpc) {
- delete _questioner;
- }
+ bool isOver() {
+ return _state == DIALOGUE_OVER;
}
-
void run();
+ ZonePtr _z;
+ CommandList *_cmdList;
+
protected:
- void displayQuestion();
+ bool displayQuestion();
bool displayAnswers();
bool displayAnswer(uint16 i);
- uint16 getAnswer();
- int16 selectAnswer();
- uint16 askPassword();
+ int16 selectAnswer1();
+ int16 selectAnswerN();
+ int16 askPassword();
int16 getHoverAnswer(int16 x, int16 y);
-};
-
-uint16 DialogueManager::askPassword() {
- debugC(3, kDebugExec, "checkDialoguePassword()");
-
- uint16 passwordLen = 0;
- _password[0] = '\0';
-
- _vm->_gfx->setDialogueBalloon(_q->_answers[0]->_text, 1, 3);
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
- _vm->_gfx->setItemFrame(id, 0);
-
- Common::Event e;
- bool changed = true; // force first refresh
-
- while (true) {
- e.kbd.ascii = 0;
-
- if (g_system->getEventManager()->pollEvent(e)) {
- if (e.type == Common::EVENT_QUIT) {
- // TODO: don't quit() here, just have caller routines to check
- // on kEngineQuit and exit gracefully to allow the engine to shut down
- _engineFlags |= kEngineQuit;
- g_system->quit();
- }
-
- if ((e.type == Common::EVENT_KEYDOWN) && isdigit(e.kbd.ascii)) {
- _password[passwordLen] = e.kbd.ascii;
- passwordLen++;
- _password[passwordLen] = '\0';
- changed = true;
- }
- }
-
- if (changed) {
- _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 3);
- _vm->_gfx->updateScreen();
- changed = false;
- }
-
- if ((passwordLen == MAX_PASSWORD_LENGTH) || (e.kbd.ascii == Common::KEYCODE_RETURN)) {
+ void runQuestion();
+ void runAnswer();
+ void nextQuestion();
+ void nextAnswer();
- if ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) ||
- (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) ||
- (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3))) {
+ bool checkPassword();
+ void resetPassword();
+ void accumPassword(uint16 ascii);
+};
- break;
+DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) {
+ int gtype = vm->getGameType();
+ if (gtype == GType_Nippon) {
+ _ballonPos = _balloonPositions_NS;
+ } else
+ if (gtype == GType_BRA) {
+ _ballonPos = _balloonPositions_BR;
+ } else
+ error("unsupported game in DialogueManager");
+
+ _dialogue = _z->u.speak->_dialogue;
+ isNpc = scumm_stricmp(_z->u.speak->_name, "yourself") && _z->u.speak->_name[0] != '\0';
+ _questioner = isNpc ? _vm->_disk->loadTalk(_z->u.speak->_name) : _vm->_char._talk;
+ _answerer = _vm->_char._talk;
- } else {
- passwordLen = 0;
- _password[0] = '\0';
- changed = true;
- }
+ _askPassword = false;
+ _q = _dialogue->_questions[0];
- }
+ _cmdList = 0;
+ _answerId = 0;
- g_system->delayMillis(20);
+ _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER;
+}
+DialogueManager::~DialogueManager() {
+ if (isNpc) {
+ delete _questioner;
}
-
- _vm->_gfx->hideDialogueStuff();
-
- return 0;
-
+ _z = nullZonePtr;
}
-
-
bool DialogueManager::displayAnswer(uint16 i) {
Answer *a = _q->_answers[i];
@@ -164,11 +173,11 @@ bool DialogueManager::displayAnswer(uint16 i) {
// display suitable answers
if (((a->_yesFlags & flags) == a->_yesFlags) && ((a->_noFlags & ~flags) == a->_noFlags)) {
- int id = _vm->_gfx->setDialogueBalloon(a->_text, 1, 3);
+ int id = _vm->_balloonMan->setDialogueBalloon(a->_text, 1, 3);
assert(id >= 0);
_visAnswers[id] = i;
- _askPassword = (strstr(a->_text, "%p") != NULL);
+ _askPassword = (strstr(a->_text, "%P") != NULL);
_numVisAnswers++;
return true;
@@ -185,126 +194,243 @@ bool DialogueManager::displayAnswers() {
displayAnswer(i);
}
+ if (_askPassword) {
+ resetPassword();
+// _vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3);
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
+ _vm->_gfx->setItemFrame(id, 0);
+ } else
+ if (_numVisAnswers == 1) {
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
+ _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF);
+ _vm->_balloonMan->setBalloonText(0, _q->_answers[_visAnswers[0]]->_text, 0);
+ } else
+ if (_numVisAnswers > 1) {
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
+ _vm->_gfx->setItemFrame(id, _q->_answers[_visAnswers[0]]->_mood & 0xF);
+ _oldSelection = -1;
+ _selection = 0;
+ }
+
return _numVisAnswers > 0;
}
-void DialogueManager::displayQuestion() {
-
- if (!scumm_stricmp(_q->_text, "NULL")) return;
+bool DialogueManager::displayQuestion() {
+ if (!scumm_stricmp(_q->_text, "NULL")) return false;
- _vm->_gfx->setSingleBalloon(_q->_text, QUESTION_BALLOON_X, QUESTION_BALLOON_Y, _q->_mood & 0x10, 0);
- int id = _vm->_gfx->setItem(_questioner, QUESTION_CHARACTER_X, QUESTION_CHARACTER_Y);
+ _vm->_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->_mood & 0x10, 0);
+ int id = _vm->_gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y);
_vm->_gfx->setItemFrame(id, _q->_mood & 0xF);
- _vm->_gfx->updateScreen();
- _vm->_input->waitUntilLeftClick();
- _vm->_gfx->hideDialogueStuff();
+ return true;
+}
- return;
+
+bool DialogueManager::checkPassword() {
+ return ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) ||
+ (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) ||
+ (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3)));
}
-uint16 DialogueManager::getAnswer() {
+void DialogueManager::resetPassword() {
+ _passwordLen = 0;
+ _password[0] = '\0';
+ _passwordChanged = true;
+}
+
+void DialogueManager::accumPassword(uint16 ascii) {
+ if (!isdigit(ascii)) {
+ return;
+ }
- uint16 answer = 0;
+ _password[_passwordLen] = ascii;
+ _passwordLen++;
+ _password[_passwordLen] = '\0';
+ _passwordChanged = true;
+}
- if (_askPassword == false) {
- answer = selectAnswer();
- } else {
- answer = askPassword();
+int16 DialogueManager::askPassword() {
+
+ if (_isKeyDown) {
+ accumPassword(_downKey);
+ }
+
+ if (_passwordChanged) {
+ _vm->_balloonMan->setBalloonText(0, _q->_answers[0]->_text, 3);
+ _passwordChanged = false;
}
- debugC(3, kDebugExec, "runDialogue: user selected answer #%i", answer);
+ if ((_passwordLen == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) {
+ if (checkPassword()) {
+ return 0;
+ } else {
+ resetPassword();
+ }
+ }
- return answer;
+ return -1;
}
-void DialogueManager::run() {
+int16 DialogueManager::selectAnswer1() {
- _askPassword = false;
- CommandList *cmdlist = NULL;
+ if (_mouseButtons == kMouseLeftUp) {
+ return 0;
+ }
- _q = _dialogue->_questions[0];
- int16 answer;
+ return -1;
+}
- while (_q) {
+int16 DialogueManager::selectAnswerN() {
- answer = 0;
+ _selection = _vm->_balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y);
- displayQuestion();
- if (_q->_answers[0] == NULL) break;
+ if (_selection != _oldSelection) {
+ if (_oldSelection != -1) {
+ _vm->_balloonMan->setBalloonText(_oldSelection, _q->_answers[_visAnswers[_oldSelection]]->_text, 3);
+ }
- if (scumm_stricmp(_q->_answers[0]->_text, "NULL")) {
- if (!displayAnswers()) break;
- answer = getAnswer();
- cmdlist = &_q->_answers[answer]->_commands;
+ if (_selection != -1) {
+ _vm->_balloonMan->setBalloonText(_selection, _q->_answers[_visAnswers[_selection]]->_text, 0);
+ _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[_selection]]->_mood & 0xF);
}
+ }
+
+ _oldSelection = _selection;
- _q = _q->_answers[answer]->_following._question;
+ if ((_mouseButtons == kMouseLeftUp) && (_selection != -1)) {
+ return _visAnswers[_selection];
}
- if (cmdlist)
- _vm->runCommands(*cmdlist);
+ return -1;
+}
+
+void DialogueManager::runQuestion() {
+ debugC(9, kDebugDialogue, "runQuestion\n");
+
+ if (_mouseButtons == kMouseLeftUp) {
+ _vm->hideDialogueStuff();
+ _state = NEXT_ANSWER;
+ }
}
-int16 DialogueManager::selectAnswer() {
- int16 numAvailableAnswers = _numVisAnswers;
+void DialogueManager::nextAnswer() {
+ debugC(9, kDebugDialogue, "nextAnswer\n");
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
- _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF);
+ if (_q->_answers[0] == NULL) {
+ _state = DIALOGUE_OVER;
+ return;
+ }
- if (numAvailableAnswers == 1) {
- _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 0);
- _vm->_input->waitUntilLeftClick();
- _vm->_gfx->hideDialogueStuff();
- return 0;
+ if (!scumm_stricmp(_q->_answers[0]->_text, "NULL")) {
+ _answerId = 0;
+ _state = NEXT_QUESTION;
+ return;
+ }
+
+ _state = displayAnswers() ? RUN_ANSWER : DIALOGUE_OVER;
+}
+
+void DialogueManager::runAnswer() {
+ debugC(9, kDebugDialogue, "runAnswer\n");
+
+ if (_askPassword) {
+ _answerId = askPassword();
+ } else
+ if (_numVisAnswers == 1) {
+ _answerId = selectAnswer1();
+ } else {
+ _answerId = selectAnswerN();
+ }
+
+ if (_answerId != -1) {
+ _cmdList = &_q->_answers[_answerId]->_commands;
+ _vm->hideDialogueStuff();
+ _state = NEXT_QUESTION;
}
+}
+
+void DialogueManager::nextQuestion() {
+ debugC(9, kDebugDialogue, "nextQuestion\n");
- int oldSelection = -1;
- int selection;
+ _q = _q->_answers[_answerId]->_following._question;
+ if (_q == 0) {
+ _state = DIALOGUE_OVER;
+ } else {
+ _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER;
+ }
+}
- uint32 event;
- Common::Point p;
- while (true) {
- _vm->_input->readInput();
- _vm->_input->getCursorPos(p);
- event = _vm->_input->getLastButtonEvent();
- selection = _vm->_gfx->hitTestDialogueBalloon(p.x, p.y);
+void DialogueManager::run() {
- if (selection != oldSelection) {
- if (oldSelection != -1) {
- _vm->_gfx->setBalloonText(oldSelection, _q->_answers[_visAnswers[oldSelection]]->_text, 3);
- }
+ // cache event data
+ _mouseButtons = _vm->_input->getLastButtonEvent();
+ _vm->_input->getCursorPos(_mousePos);
+ _isKeyDown = _vm->_input->getLastKeyDown(_downKey);
- if (selection != -1) {
- _vm->_gfx->setBalloonText(selection, _q->_answers[_visAnswers[selection]]->_text, 0);
- _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[selection]]->_mood & 0xF);
- }
- }
+ switch (_state) {
+ case RUN_QUESTION:
+ runQuestion();
+ break;
- if ((selection != -1) && (event == kMouseLeftUp)) {
- break;
- }
+ case NEXT_ANSWER:
+ nextAnswer();
+ break;
- _vm->_gfx->updateScreen();
- g_system->delayMillis(20);
+ case NEXT_QUESTION:
+ nextQuestion();
+ break;
- oldSelection = selection;
+ case RUN_ANSWER:
+ runAnswer();
+ break;
+
+ case DIALOGUE_OVER:
+ break;
+
+ default:
+ error("unknown state in DialogueManager");
+
+ }
+
+}
+
+void Parallaction::enterDialogueMode(ZonePtr z) {
+ debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u.speak->_name);
+ _dialogueMan = new DialogueManager(this, z);
+ _input->_inputMode = Input::kInputModeDialogue;
+}
+
+void Parallaction::exitDialogueMode() {
+ debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()");
+ _input->_inputMode = Input::kInputModeGame;
+
+ if (_dialogueMan->_cmdList) {
+ _vm->_cmdExec->run(*_dialogueMan->_cmdList);
}
- _vm->_gfx->hideDialogueStuff();
+ // The current instance of _dialogueMan must be destroyed before the zone commands
+ // are executed, because they may create another instance of _dialogueMan that
+ // overwrite the current one. This would cause headaches (and it did, actually).
+ ZonePtr z = _dialogueMan->_z;
+ delete _dialogueMan;
+ _dialogueMan = 0;
- return _visAnswers[selection];
+ _cmdExec->run(z->_commands, z);
}
+void Parallaction::runDialogueFrame() {
+ if (_input->_inputMode != Input::kInputModeDialogue) {
+ return;
+ }
-void Parallaction::runDialogue(SpeakData *data) {
- debugC(1, kDebugExec, "runDialogue: starting dialogue '%s'", data->_name);
+ _dialogueMan->run();
- DialogueManager man(this, data);
- man.run();
+ if (_dialogueMan->isOver()) {
+ exitDialogueMode();
+ }
return;
}
diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h
index b76c66aead..341229a649 100644
--- a/engines/parallaction/disk.h
+++ b/engines/parallaction/disk.h
@@ -28,6 +28,8 @@
#define PATH_LEN 200
+#include "common/fs.h"
+
#include "common/file.h"
#include "graphics/surface.h"
@@ -55,12 +57,12 @@ public:
virtual Script* loadLocation(const char *name) = 0;
virtual Script* loadScript(const char* name) = 0;
- virtual Frames* loadTalk(const char *name) = 0;
- virtual Frames* loadObjects(const char *name) = 0;
+ virtual GfxObj* loadTalk(const char *name) = 0;
+ virtual GfxObj* loadObjects(const char *name) = 0;
virtual Frames* loadPointer(const char *name) = 0;
- virtual Frames* loadHead(const char* name) = 0;
+ virtual GfxObj* loadHead(const char* name) = 0;
virtual Font* loadFont(const char* name) = 0;
- virtual Frames* loadStatic(const char* name) = 0;
+ virtual GfxObj* loadStatic(const char* name) = 0;
virtual Frames* loadFrames(const char* name) = 0;
virtual void loadSlide(BackgroundInfo& info, const char *filename) = 0;
virtual void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) = 0;
@@ -147,12 +149,12 @@ public:
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path);
@@ -181,12 +183,12 @@ public:
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path);
@@ -202,15 +204,29 @@ public:
class DosDisk_br : public Disk {
protected:
+ uint16 _language;
+
Parallaction *_vm;
- char _partPath[PATH_LEN];
- char _languageDir[2];
+
+ FilesystemNode _baseDir;
+ FilesystemNode _partDir;
+
+ FilesystemNode _aniDir;
+ FilesystemNode _bkgDir;
+ FilesystemNode _mscDir;
+ FilesystemNode _mskDir;
+ FilesystemNode _pthDir;
+ FilesystemNode _rasDir;
+ FilesystemNode _scrDir;
+ FilesystemNode _sfxDir;
+ FilesystemNode _talDir;
protected:
- void errorFileNotFound(const char *s);
+ void errorFileNotFound(const FilesystemNode &dir, const Common::String &filename);
Font *createFont(const char *name, Common::ReadStream &stream);
Sprites* createSprites(Common::ReadStream &stream);
void loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette);
+ GfxObj* createInventoryObjects(Common::SeekableReadStream &stream);
public:
DosDisk_br(Parallaction *vm);
@@ -220,12 +236,12 @@ public:
void setLanguage(uint16 language);
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path);
@@ -234,26 +250,49 @@ public:
Common::ReadStream* loadSound(const char* name);
};
+class DosDemo_br : public DosDisk_br {
+
+public:
+ DosDemo_br(Parallaction *vm);
+ virtual ~DosDemo_br();
+
+ Common::String selectArchive(const Common::String& name);
+
+};
+
class AmigaDisk_br : public DosDisk_br {
protected:
BackgroundInfo _backgroundTemp;
- Sprites* createSprites(const char *name);
+ Sprites* createSprites(Common::ReadStream &stream);
Font *createFont(const char *name, Common::SeekableReadStream &stream);
- void loadMask(BackgroundInfo& info, const char *name);
- void loadBackground(BackgroundInfo& info, const char *name);
+ void loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream);
+
+ FilesystemNode _baseBkgDir;
+ FilesystemNode _fntDir;
+ FilesystemNode _commonAniDir;
+ FilesystemNode _commonBkgDir;
+ FilesystemNode _commonMscDir;
+ FilesystemNode _commonMskDir;
+ FilesystemNode _commonPthDir;
+ FilesystemNode _commonTalDir;
public:
AmigaDisk_br(Parallaction *vm);
virtual ~AmigaDisk_br();
- Frames* loadTalk(const char *name);
+ GfxObj* loadTalk(const char *name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path);
+ GfxObj* loadObjects(const char *name);
+ Common::SeekableReadStream* loadMusic(const char* name);
+ Common::ReadStream* loadSound(const char* name);
+ Common::String selectArchive(const Common::String& name);
+
};
} // namespace Parallaction
diff --git a/engines/parallaction/disk_br.cpp b/engines/parallaction/disk_br.cpp
index 5e88327879..cd57ec8822 100644
--- a/engines/parallaction/disk_br.cpp
+++ b/engines/parallaction/disk_br.cpp
@@ -25,6 +25,7 @@
#include "graphics/iff.h"
+#include "common/config-manager.h"
#include "parallaction/parallaction.h"
@@ -58,7 +59,7 @@ struct Sprites : public Frames {
}
~Sprites() {
- delete _sprites;
+ delete[] _sprites;
}
uint16 getNum() {
@@ -90,101 +91,110 @@ struct Sprites : public Frames {
-void DosDisk_br::errorFileNotFound(const char *s) {
- error("File '%s' not found", s);
+void DosDisk_br::errorFileNotFound(const FilesystemNode &dir, const Common::String &filename) {
+ error("File '%s' not found in directory '%s'", filename.c_str(), dir.getDisplayName().c_str());
}
Common::String DosDisk_br::selectArchive(const Common::String& name) {
debugC(5, kDebugDisk, "DosDisk_br::selectArchive");
- Common::String oldPath(_partPath);
- strcpy(_partPath, name.c_str());
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
+
+ _partDir = _baseDir.getChild(name);
+
+ _aniDir = _partDir.getChild("ani");
+ _bkgDir = _partDir.getChild("bkg");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("tal");
return oldPath;
}
void DosDisk_br::setLanguage(uint16 language) {
debugC(5, kDebugDisk, "DosDisk_br::setLanguage");
-
- switch (language) {
- case 0:
- strcpy(_languageDir, "it");
- break;
-
- case 1:
- strcpy(_languageDir, "fr");
- break;
-
- case 2:
- strcpy(_languageDir, "en");
- break;
-
- case 3:
- strcpy(_languageDir, "ge");
- break;
-
- default:
- error("unknown language");
-
- }
-
- return;
+ assert(language < 4);
+ _language = language;
}
-DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm) {
-
+DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm), _baseDir(ConfMan.get("path")) {
}
DosDisk_br::~DosDisk_br() {
}
-Frames* DosDisk_br::loadTalk(const char *name) {
+GfxObj* DosDisk_br::loadTalk(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTalk(%s)", name);
- Common::File stream;
-
- char path[PATH_LEN];
- sprintf(path, "%s/tal/%s", _partPath, name);
- if (!stream.open(path)) {
- sprintf(path, "%s/tal/%s.tal", _partPath, name);
- if (!stream.open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _talDir.getChild(path);
+ if (!node.exists())
+ errorFileNotFound(_talDir, path);
}
- return createSprites(stream);
+ Common::File stream;
+ stream.open(node);
+
+ // talk position is set to (0,0), because talks are always displayed at
+ // absolute coordinates, set in the dialogue manager. The original used
+ // to null out coordinates every time they were needed. We do it better!
+ Sprites *spr = createSprites(stream);
+ for (int i = 0; i < spr->getNum(); i++) {
+ spr->_sprites[i].x = 0;
+ spr->_sprites[i].y = 0;
+ }
+ return new GfxObj(0, spr, name);
}
Script* DosDisk_br::loadLocation(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadLocation");
- Common::File *stream = new Common::File;
-
- char path[PATH_LEN];
- sprintf(path, "%s/%s/%s.slf", _partPath, _languageDir, name);
- if (!stream->open(path)) {
- sprintf(path, "%s/%s/%s.loc", _partPath, _languageDir, name);
- if (!stream->open(path))
- errorFileNotFound(path);
+ Common::String langs[4] = { "it", "fr", "en", "ge" };
+ FilesystemNode locDir = _partDir.getChild(langs[_language]);
+
+ Common::String path(name);
+ path += ".slf";
+ FilesystemNode node = locDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name) + ".loc";
+ node = locDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(locDir, path);
+ }
}
+ Common::File *stream = new Common::File;
+ stream->open(node);
return new Script(stream, true);
}
Script* DosDisk_br::loadScript(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadScript");
- Common::File *stream = new Common::File;
-
- char path[PATH_LEN];
- sprintf(path, "%s/scripts/%s.scr", _partPath, name);
- if (!stream->open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ path += ".scr";
+ FilesystemNode node = _scrDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_scrDir, path);
+ }
+ Common::File *stream = new Common::File;
+ stream->open(node);
return new Script(stream, true);
}
// there are no Head resources in Big Red Adventure
-Frames* DosDisk_br::loadHead(const char* name) {
+GfxObj* DosDisk_br::loadHead(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadHead");
return 0;
}
@@ -192,6 +202,7 @@ Frames* DosDisk_br::loadHead(const char* name) {
void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette) {
stream.skip(4);
uint width = stream.readUint32BE();
+ if (width & 1) width++;
uint height = stream.readUint32BE();
stream.skip(20);
@@ -208,12 +219,15 @@ void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surfac
Frames* DosDisk_br::loadPointer(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadPointer");
- char path[PATH_LEN];
- sprintf(path, "%s.ras", name);
+ Common::String path(name);
+ path += ".ras";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(stream, *surf, 0);
@@ -224,39 +238,53 @@ Frames* DosDisk_br::loadPointer(const char *name) {
Font* DosDisk_br::loadFont(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadFont");
- char path[PATH_LEN];
- sprintf(path, "%s.fnt", name);
+ Common::String path(name);
+ path += ".fnt";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
-
+ stream.open(node);
return createFont(name, stream);
}
-Frames* DosDisk_br::loadObjects(const char *name) {
+GfxObj* DosDisk_br::loadObjects(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadObjects");
- return 0;
+
+ Common::String path(name);
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+
+ Common::File stream;
+ stream.open(node);
+
+ return createInventoryObjects(stream);
}
void genSlidePath(char *path, const char* name) {
sprintf(path, "%s.bmp", name);
}
-Frames* DosDisk_br::loadStatic(const char* name) {
+GfxObj* DosDisk_br::loadStatic(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadStatic");
- char path[PATH_LEN];
- sprintf(path, "%s/ras/%s", _partPath, name);
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _rasDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_rasDir, path);
}
+ Common::File stream;
+ stream.open(node);
+
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(stream, *surf, 0);
- return new SurfaceToFrames(surf);
+ return new GfxObj(0, new SurfaceToFrames(surf), name);
}
Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) {
@@ -283,14 +311,18 @@ Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) {
Frames* DosDisk_br::loadFrames(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadFrames");
- char path[PATH_LEN];
- sprintf(path, "%s/ani/%s", _partPath, name);
+ Common::String path(name);
+ FilesystemNode node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_aniDir, path);
+ }
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
-
-
+ stream.open(node);
return createSprites(stream);
}
@@ -302,12 +334,15 @@ Frames* DosDisk_br::loadFrames(const char* name) {
void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadSlide");
- char path[PATH_LEN];
- genSlidePath(path, name);
+ Common::String path(name);
+ path += ".bmp";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
byte rgb[768];
@@ -325,13 +360,17 @@ void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
debugC(5, kDebugDisk, "DosDisk_br::loadScenery");
- char filename[PATH_LEN];
+ Common::String filepath;
+ FilesystemNode node;
Common::File stream;
if (name) {
- sprintf(filename, "%s/bkg/%s.bkg", _partPath, name);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(name) + ".bkg";
+ node = _bkgDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_bkgDir, filepath);
+ }
+ stream.open(node);
byte rgb[768];
@@ -347,9 +386,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
}
if (mask) {
- sprintf(filename, "%s/msk/%s.msk", _partPath, mask);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(mask) + ".msk";
+ node = _mskDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_mskDir, filepath);
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
@@ -360,9 +402,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
}
if (path) {
- sprintf(filename, "%s/pth/%s.pth", _partPath, path);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(path) + ".pth";
+ node = _pthDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_pthDir, filepath);
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
@@ -377,15 +422,16 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
Table* DosDisk_br::loadTable(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTable");
- char path[PATH_LEN];
- sprintf(path, "%s/%s.tab", _partPath, name);
-
- Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ path += ".tab";
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+ Common::File stream;
+ stream.open(node);
Table *t = createTableFromStream(100, stream);
-
stream.close();
return t;
@@ -407,56 +453,68 @@ Common::ReadStream* DosDisk_br::loadSound(const char* name) {
-AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) {
+DosDemo_br::DosDemo_br(Parallaction *vm) : DosDisk_br(vm) {
}
-AmigaDisk_br::~AmigaDisk_br() {
+DosDemo_br::~DosDemo_br() {
}
+Common::String DosDemo_br::selectArchive(const Common::String& name) {
+ debugC(5, kDebugDisk, "DosDemo_br::selectArchive");
-/*
- FIXME: mask values are not computed correctly for level 1 and 2
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
- NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded
- into the main loop.
-*/
-void buildMask2(byte* buf) {
+ _partDir = _baseDir;
- byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA };
- byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 };
+ _aniDir = _partDir.getChild("ani");
+ _bkgDir = _partDir.getChild("bkg");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("tal");
- byte plane0[40];
- byte plane1[40];
+ return oldPath;
+}
- for (int32 i = 0; i < _vm->_screenHeight; i++) {
- memcpy(plane0, buf, 40);
- memcpy(plane1, buf+40, 40);
- for (uint32 j = 0; j < 40; j++) {
- *buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4];
- *buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF];
- }
- }
+
+
+AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) {
+ _fntDir = _baseDir.getChild("fonts");
+
+ _baseBkgDir = _baseDir.getChild("backs");
+
+ FilesystemNode commonDir = _baseDir.getChild("common");
+ _commonAniDir = commonDir.getChild("anims");
+ _commonBkgDir = commonDir.getChild("backs");
+ _commonMscDir = commonDir.getChild("msc");
+ _commonMskDir = commonDir.getChild("msk");
+ _commonPthDir = commonDir.getChild("pth");
+ _commonTalDir = commonDir.getChild("talks");
}
-void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) {
- char path[PATH_LEN];
- sprintf(path, "%s", name);
+AmigaDisk_br::~AmigaDisk_br() {
+
+}
- Common::File s;
- if (!s.open(path))
- errorFileNotFound(path);
+void AmigaDisk_br::loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream) {
byte *pal;
- Graphics::ILBMDecoder decoder(s, info.bg, pal);
+ Graphics::ILBMDecoder decoder(stream, info.bg, pal);
decoder.decode();
uint i;
@@ -480,58 +538,60 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) {
return;
}
-void AmigaDisk_br::loadMask(BackgroundInfo& info, const char *name) {
- debugC(5, kDebugDisk, "AmigaDisk_br::loadMask(%s)", name);
-
- Common::File s;
-
- if (!s.open(name))
- return;
-
- s.seek(0x30, SEEK_SET);
-
- byte r, g, b;
- for (uint i = 0; i < 4; i++) {
- r = s.readByte();
- g = s.readByte();
- b = s.readByte();
-
- info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;
- }
-
- s.seek(0x126, SEEK_SET); // HACK: skipping IFF/ILBM header should be done by analysis, not magic
- Graphics::PackBitsReadStream stream(s);
-
- info.mask.create(info.width, info.height);
- stream.read(info.mask.data, info.mask.size);
- buildMask2(info.mask.data);
-
- return;
-}
void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadScenery '%s', '%s' '%s'", name, mask, path);
- char filename[PATH_LEN];
+ Common::String filepath;
+ FilesystemNode node;
Common::File stream;
if (name) {
- sprintf(filename, "%s/backs/%s.bkg", _partPath, name);
-
- loadBackground(info, filename);
+ filepath = Common::String(name) + ".bkg";
+ node = _bkgDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(name) + ".bkg";
+ node = _commonBkgDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_bkgDir, filepath);
+ }
+ }
+ stream.open(node);
+ loadBackground(info, stream);
+ stream.close();
}
+#if 0
+ if (mask && _mskDir.exists()) {
+ filepath = Common::String(mask) + ".msk";
+ node = _mskDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(mask) + ".msk";
+ node = _commonMskDir.getChild(filepath);
+ }
- if (mask) {
- sprintf(filename, "%s/msk/%s.msk", _partPath, name);
-
- loadMask(info, filename);
+ if (node.exists()) {
+ stream.open(node);
+ stream.seek(0x30, SEEK_SET);
+ Graphics::PackBitsReadStream unpackedStream(stream);
+ info.mask.create(info.width, info.height);
+ unpackedStream.read(info.mask.data, info.mask.size);
+ // TODO: there is another step to do after decompression...
+ loadMask(info, stream);
+ stream.close();
+ }
}
-
- if (path) {
- sprintf(filename, "%s/pth/%s.pth", _partPath, path);
- if (!stream.open(filename))
- errorFileNotFound(filename);
-
+#endif
+ if (path && _pthDir.exists()) {
+ filepath = Common::String(path) + ".pth";
+ node = _pthDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(path) + ".pth";
+ node = _commonPthDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_pthDir, filepath);
+ }
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
info.path.create(info.width, info.height);
@@ -545,22 +605,28 @@ void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const cha
void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadSlide '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "backs/%s.bkg", name);
-
- loadBackground(info, path);
+ Common::String path(name);
+ path += ".bkg";
+ FilesystemNode node = _baseBkgDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseBkgDir, path);
+ }
+ Common::File stream;
+ stream.open(node);
+ loadBackground(info, stream);
return;
}
-Frames* AmigaDisk_br::loadStatic(const char* name) {
+GfxObj* AmigaDisk_br::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/ras/%s", _partPath, name);
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _rasDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_rasDir, path);
}
+ Common::File stream;
+ stream.open(node);
byte *pal = 0;
Graphics::Surface* surf = new Graphics::Surface;
@@ -570,16 +636,10 @@ Frames* AmigaDisk_br::loadStatic(const char* name) {
free(pal);
- return new SurfaceToFrames(surf);
+ return new GfxObj(0, new SurfaceToFrames(surf));
}
-Sprites* AmigaDisk_br::createSprites(const char *path) {
-
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
- }
-
+Sprites* AmigaDisk_br::createSprites(Common::ReadStream &stream) {
uint16 num = stream.readUint16BE();
Sprites *sprites = new Sprites(num);
@@ -603,32 +663,165 @@ Sprites* AmigaDisk_br::createSprites(const char *path) {
Frames* AmigaDisk_br::loadFrames(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadFrames '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/anims/%s", _partPath, name);
+ Common::String path(name);
+ FilesystemNode node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name);
+ node = _commonAniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _commonAniDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_aniDir, path);
+ }
+ }
+ }
+ }
- return createSprites(path);
+ Common::File stream;
+ stream.open(node);
+ return createSprites(stream);
}
-Frames* AmigaDisk_br::loadTalk(const char *name) {
+GfxObj* AmigaDisk_br::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadTalk '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/talks/%s.tal", _partPath, name);
+ Common::String path(name);
+ FilesystemNode node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name);
+ node = _commonTalDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _commonTalDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_talDir, path);
+ }
+ }
+ }
+ }
- return createSprites(path);
+ Common::File stream;
+ stream.open(node);
+ return new GfxObj(0, createSprites(stream));
}
Font* AmigaDisk_br::loadFont(const char* name) {
debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s", name);
+ Common::String path(name);
+ path += ".font";
+ FilesystemNode node = _fntDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_fntDir, path);
+ }
+
+ Common::String fontDir;
+ Common::String fontFile;
+ byte ch;
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
+ stream.seek(4, SEEK_SET);
+ while ((ch = stream.readByte()) != 0x2F) fontDir += ch;
+ while ((ch = stream.readByte()) != 0) fontFile += ch;
+ stream.close();
+
+ printf("fontDir = %s, fontFile = %s\n", fontDir.c_str(), fontFile.c_str());
+
+ node = _fntDir.getChild(fontDir);
+ if (!node.exists()) {
+ errorFileNotFound(_fntDir, fontDir);
+ }
+ node = node.getChild(fontFile);
+ if (!node.exists()) {
+ errorFileNotFound(node, fontFile);
+ }
+ stream.open(node);
return createFont(name, stream);
}
+Common::SeekableReadStream* AmigaDisk_br::loadMusic(const char* name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadMusic");
+
+ Common::String path(name);
+ FilesystemNode node = _mscDir.getChild(path);
+ if (!node.exists()) {
+ // TODO (Kirben): error out when music file is not found?
+ return 0;
+ }
+
+ Common::File *stream = new Common::File;
+ stream->open(node);
+ return stream;
+}
+
+
+Common::ReadStream* AmigaDisk_br::loadSound(const char* name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadSound");
+
+ Common::String path(name);
+ FilesystemNode node = _sfxDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_sfxDir, path);
+ }
+
+ Common::File *stream = new Common::File;
+ stream->open(node);
+ return stream;
+}
+
+GfxObj* AmigaDisk_br::loadObjects(const char *name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects");
+
+ Common::String path(name);
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+
+ Common::File stream;
+ stream.open(node);
+
+ byte *pal = 0;
+ Graphics::Surface* surf = new Graphics::Surface;
+
+ Graphics::ILBMDecoder decoder(stream, *surf, pal);
+ decoder.decode();
+
+ free(pal);
+
+ return new GfxObj(0, new SurfaceToFrames(surf));
+}
+
+Common::String AmigaDisk_br::selectArchive(const Common::String& name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive");
+
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
+
+ _partDir = _baseDir.getChild(name);
+
+ _aniDir = _partDir.getChild("anims");
+ _bkgDir = _partDir.getChild("backs");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("talks");
+
+ return oldPath;
+}
+
} // namespace Parallaction
diff --git a/engines/parallaction/disk_ns.cpp b/engines/parallaction/disk_ns.cpp
index cdbe3458a7..55e6fc5e77 100644
--- a/engines/parallaction/disk_ns.cpp
+++ b/engines/parallaction/disk_ns.cpp
@@ -385,12 +385,12 @@ Cnv* DosDisk_ns::loadCnv(const char *filename) {
return new Cnv(numFrames, width, height, data);
}
-Frames* DosDisk_ns::loadTalk(const char *name) {
+GfxObj* DosDisk_ns::loadTalk(const char *name) {
const char *ext = strstr(name, ".talk");
if (ext != NULL) {
// npc talk
- return loadCnv(name);
+ return new GfxObj(0, loadCnv(name), name);
}
@@ -401,7 +401,7 @@ Frames* DosDisk_ns::loadTalk(const char *name) {
sprintf(v20, "%stal", name);
}
- return loadExternalCnv(v20);
+ return new GfxObj(0, loadExternalCnv(v20), name);
}
Script* DosDisk_ns::loadLocation(const char *name) {
@@ -434,14 +434,14 @@ Script* DosDisk_ns::loadScript(const char* name) {
return new Script(new DummyArchiveStream(_resArchive), true);
}
-Frames* DosDisk_ns::loadHead(const char* name) {
+GfxObj* DosDisk_ns::loadHead(const char* name) {
char path[PATH_LEN];
sprintf(path, "%shead", name);
path[8] = '\0';
- return loadExternalStaticCnv(path);
+ return new GfxObj(0, loadExternalStaticCnv(path));
}
@@ -457,15 +457,15 @@ Font* DosDisk_ns::loadFont(const char* name) {
}
-Frames* DosDisk_ns::loadObjects(const char *name) {
+GfxObj* DosDisk_ns::loadObjects(const char *name) {
char path[PATH_LEN];
sprintf(path, "%sobj", name);
- return loadExternalCnv(path);
+ return new GfxObj(0, loadExternalCnv(path), name);
}
-Frames* DosDisk_ns::loadStatic(const char* name) {
+GfxObj* DosDisk_ns::loadStatic(const char* name) {
char path[PATH_LEN];
@@ -487,7 +487,7 @@ Frames* DosDisk_ns::loadStatic(const char* name) {
Graphics::PackBitsReadStream decoder(_resArchive);
decoder.read(cnv->pixels, w*h);
- return new SurfaceToFrames(cnv);
+ return new GfxObj(0, new SurfaceToFrames(cnv), name);
}
Frames* DosDisk_ns::loadFrames(const char* name) {
@@ -1025,7 +1025,7 @@ Frames* AmigaDisk_ns::loadPointer(const char* name) {
return makeStaticCnv(stream);
}
-Frames* AmigaDisk_ns::loadStatic(const char* name) {
+GfxObj* AmigaDisk_ns::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name);
Common::SeekableReadStream *s = openArchivedFile(name, true);
@@ -1033,7 +1033,7 @@ Frames* AmigaDisk_ns::loadStatic(const char* name) {
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
Common::SeekableReadStream *AmigaDisk_ns::openArchivedFile(const char* name, bool errorOnFileNotFound) {
@@ -1276,7 +1276,7 @@ Frames* AmigaDisk_ns::loadFrames(const char* name) {
return cnv;
}
-Frames* AmigaDisk_ns::loadHead(const char* name) {
+GfxObj* AmigaDisk_ns::loadHead(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name);
char path[PATH_LEN];
@@ -1287,11 +1287,11 @@ Frames* AmigaDisk_ns::loadHead(const char* name) {
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
-Frames* AmigaDisk_ns::loadObjects(const char *name) {
+GfxObj* AmigaDisk_ns::loadObjects(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects");
char path[PATH_LEN];
@@ -1305,11 +1305,11 @@ Frames* AmigaDisk_ns::loadObjects(const char *name) {
Cnv *cnv = makeCnv(*s);
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
-Frames* AmigaDisk_ns::loadTalk(const char *name) {
+GfxObj* AmigaDisk_ns::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name);
Common::SeekableReadStream *s;
@@ -1328,7 +1328,7 @@ Frames* AmigaDisk_ns::loadTalk(const char *name) {
Cnv *cnv = makeCnv(*s);
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
Table* AmigaDisk_ns::loadTable(const char* name) {
@@ -1395,9 +1395,7 @@ Common::ReadStream* AmigaDisk_ns::loadSound(const char* name) {
char path[PATH_LEN];
sprintf(path, "%s.snd", name);
- openArchivedFile(path);
-
- return new DummyArchiveStream(_resArchive);
+ return openArchivedFile(path);
}
} // namespace Parallaction
diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h
new file mode 100644
index 0000000000..22e75744f1
--- /dev/null
+++ b/engines/parallaction/exec.h
@@ -0,0 +1,255 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+
+#ifndef PARALLACTION_EXEC_H
+#define PARALLACTION_EXEC_H
+
+#include "common/util.h"
+#include "parallaction/objects.h"
+
+
+namespace Parallaction {
+
+typedef Common::Functor0<void> Opcode;
+typedef Common::Array<const Opcode*> OpcodeSet;
+
+#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op()
+#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op()
+
+class Parallaction_ns;
+class Parallaction_br;
+
+class CommandExec {
+protected:
+ struct ParallactionStruct1 {
+ CommandPtr cmd;
+ ZonePtr z;
+ bool suspend;
+ } _ctxt;
+
+ OpcodeSet _opcodes;
+
+ struct SuspendedContext {
+ bool valid;
+ CommandList::iterator first;
+ CommandList::iterator last;
+ ZonePtr zone;
+ } _suspendedCtxt;
+
+ ZonePtr _execZone;
+ void runList(CommandList::iterator first, CommandList::iterator last);
+ void createSuspendList(CommandList::iterator first, CommandList::iterator last);
+ void cleanSuspendedList();
+
+public:
+ virtual void init() = 0;
+ virtual void run(CommandList &list, ZonePtr z = nullZonePtr);
+ void runSuspended();
+
+ CommandExec() {
+ _suspendedCtxt.valid = false;
+ }
+ virtual ~CommandExec() {
+ for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i)
+ delete *i;
+ _opcodes.clear();
+ }
+};
+
+class CommandExec_ns : public CommandExec {
+
+ Parallaction_ns *_vm;
+
+protected:
+ void updateGetZone(ZonePtr z, bool visible);
+
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
+
+public:
+ void init();
+
+ CommandExec_ns(Parallaction_ns* vm);
+ ~CommandExec_ns();
+};
+
+class CommandExec_br : public CommandExec_ns {
+
+protected:
+ Parallaction_br *_vm;
+
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(character);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(add);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(let);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(music);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(give);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(text);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(part);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave);
+
+public:
+ void init();
+
+ CommandExec_br(Parallaction_br* vm);
+ ~CommandExec_br();
+};
+
+class ProgramExec {
+protected:
+ struct ParallactionStruct2 {
+ AnimationPtr anim;
+ ProgramPtr program;
+ InstructionList::iterator inst;
+ InstructionList::iterator ip;
+ uint16 modCounter;
+ bool suspend;
+ } _ctxt;
+
+ const char **_instructionNames;
+
+ OpcodeSet _opcodes;
+
+ uint16 _modCounter;
+ void runScript(ProgramPtr script, AnimationPtr a);
+
+public:
+ virtual void init() = 0;
+ virtual void runScripts(ProgramList::iterator first, ProgramList::iterator last);
+ ProgramExec() : _modCounter(0) {
+ }
+ virtual ~ProgramExec() {
+ for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i)
+ delete *i;
+ _opcodes.clear();
+ }
+};
+
+class ProgramExec_ns : public ProgramExec {
+
+ Parallaction_ns *_vm;
+
+protected:
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
+
+public:
+ void init();
+
+ ProgramExec_ns(Parallaction_ns *vm);
+ ~ProgramExec_ns();
+};
+
+class ProgramExec_br : public ProgramExec_ns {
+
+ Parallaction_br *_vm;
+
+protected:
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
+
+public:
+ void init();
+ ProgramExec_br(Parallaction_br *vm);
+ ~ProgramExec_br();
+};
+
+} // namespace Parallaction
+
+#endif
diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp
index 3b67b4c370..0b7400f0f7 100644
--- a/engines/parallaction/exec_br.cpp
+++ b/engines/parallaction/exec_br.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
@@ -60,16 +61,17 @@ namespace Parallaction {
#define INST_STOP 30
#define INST_ENDSCRIPT 31
-
-
#define SetOpcodeTable(x) table = &x;
-typedef Common::Functor0Mem<void, Parallaction_br> OpcodeV2;
-#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::cmdOp_##op))
-#define DECLARE_COMMAND_OPCODE(op) void Parallaction_br::cmdOp_##op()
+typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1;
+#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_br::cmdOp_##op))
+#define DECLARE_COMMAND_OPCODE(op) void CommandExec_br::cmdOp_##op()
-#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::instOp_##op))
-#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_br::instOp_##op()
+typedef Common::Functor0Mem<void, ProgramExec_br> OpcodeV2;
+#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_br::instOp_##op))
+#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_br::instOp_##op()
+
+extern const char *_instructionNamesRes_br[];
void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {
debugC(5, kDebugExec, "setupSubtitles(%s, %s, %i)", s, s2, y);
@@ -100,8 +102,13 @@ void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {
}
void Parallaction_br::clearSubtitles() {
- _gfx->freeLabels();
- _subtitle[0] = _subtitle[1] = -1;
+ if (_subtitle[0] != -1) {
+ _gfx->hideLabel(_subtitle[0]);
+ }
+
+ if (_subtitle[1] != -1) {
+ _gfx->hideLabel(_subtitle[1]);
+ }
}
@@ -109,22 +116,30 @@ DECLARE_COMMAND_OPCODE(location) {
warning("Parallaction_br::cmdOp_location command not yet implemented");
// TODO: handle startPos and startPos2
- scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string);
+ _vm->scheduleLocationSwitch(_ctxt.cmd->u._string);
}
DECLARE_COMMAND_OPCODE(open) {
warning("Parallaction_br::cmdOp_open command not yet implemented");
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
+ }
}
DECLARE_COMMAND_OPCODE(close) {
warning("Parallaction_br::cmdOp_close not yet implemented");
+ _ctxt.cmd->u._zone->_flags |= kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
+ }
}
DECLARE_COMMAND_OPCODE(on) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
+ CommandData *data = &_ctxt.cmd->u;
ZonePtr z = data->_zone;
if (z) {
@@ -132,52 +147,53 @@ DECLARE_COMMAND_OPCODE(on) {
z->_flags &= ~kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, true);
}
}
}
DECLARE_COMMAND_OPCODE(off) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
+ CommandData *data = &_ctxt.cmd->u;
ZonePtr z = data->_zone;
if (z) {
z->_flags |= kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, false);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, false);
}
}
}
DECLARE_COMMAND_OPCODE(call) {
- callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z);
+ _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);
}
DECLARE_COMMAND_OPCODE(drop) {
- warning("Parallaction_br::cmdOp_drop not yet implemented");
+ _vm->dropItem(_ctxt.cmd->u._object);
}
DECLARE_COMMAND_OPCODE(move) {
- warning("Parallaction_br::cmdOp_move not yet implemented");
+ _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y);
+ _ctxt.suspend = true;
}
DECLARE_COMMAND_OPCODE(start) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing;
+ _ctxt.cmd->u._zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(stop) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsActing;
}
DECLARE_COMMAND_OPCODE(character) {
- debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _cmdRunCtxt.cmd->u._string);
- changeCharacter(_cmdRunCtxt.cmd->u._string);
+ debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _ctxt.cmd->u._string);
+ _vm->changeCharacter(_ctxt.cmd->u._string);
}
@@ -187,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) {
DECLARE_COMMAND_OPCODE(onmouse) {
- _input->showCursor(true);
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
DECLARE_COMMAND_OPCODE(offmouse) {
- _input->showCursor(false);
+ _vm->_input->setMouseState(MOUSE_DISABLED);
}
DECLARE_COMMAND_OPCODE(add) {
- warning("Parallaction_br::cmdOp_add not yet implemented");
+ _vm->addInventoryItem(_ctxt.cmd->u._object);
}
@@ -207,42 +223,42 @@ DECLARE_COMMAND_OPCODE(leave) {
DECLARE_COMMAND_OPCODE(inc) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] += _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] += _ctxt.cmd->u._rvalue;
}
DECLARE_COMMAND_OPCODE(dec) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] -= _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] -= _ctxt.cmd->u._rvalue;
}
DECLARE_COMMAND_OPCODE(ifeq) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] == _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] == _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(iflt) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] < _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] < _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(ifgt) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] > _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] > _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(let) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] = _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] = _ctxt.cmd->u._rvalue;
}
@@ -252,25 +268,25 @@ DECLARE_COMMAND_OPCODE(music) {
DECLARE_COMMAND_OPCODE(fix) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsFixed;
+ _ctxt.cmd->u._zone->_flags |= kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(unfix) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(zeta) {
- _location._zeta0 = _cmdRunCtxt.cmd->u._zeta0;
- _location._zeta1 = _cmdRunCtxt.cmd->u._zeta1;
- _location._zeta2 = _cmdRunCtxt.cmd->u._zeta2;
+ _vm->_location._zeta0 = _ctxt.cmd->u._zeta0;
+ _vm->_location._zeta1 = _ctxt.cmd->u._zeta1;
+ _vm->_location._zeta2 = _ctxt.cmd->u._zeta2;
}
DECLARE_COMMAND_OPCODE(scroll) {
warning("Parallaction_br::cmdOp_scroll not yet implemented");
- _gfx->setVar("scroll_x", _cmdRunCtxt.cmd->u._rvalue );
+ _vm->_gfx->setVar("scroll_x", _ctxt.cmd->u._rvalue );
}
@@ -285,8 +301,8 @@ DECLARE_COMMAND_OPCODE(give) {
DECLARE_COMMAND_OPCODE(text) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
- setupSubtitles(data->_string, data->_string2, data->_zeta0);
+ CommandData *data = &_ctxt.cmd->u;
+ _vm->setupSubtitles(data->_string, data->_string2, data->_zeta0);
}
@@ -297,7 +313,7 @@ DECLARE_COMMAND_OPCODE(part) {
DECLARE_COMMAND_OPCODE(testsfx) {
warning("Parallaction_br::cmdOp_testsfx not completely implemented");
- clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled
+ _vm->clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled
}
@@ -319,7 +335,7 @@ DECLARE_COMMAND_OPCODE(offsave) {
DECLARE_INSTRUCTION_OPCODE(on) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
ZonePtr z = inst->_z;
if (z) {
@@ -327,28 +343,28 @@ DECLARE_INSTRUCTION_OPCODE(on) {
z->_flags &= ~kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, true);
}
}
}
DECLARE_INSTRUCTION_OPCODE(off) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
ZonePtr z = inst->_z;
if (z) {
z->_flags |= kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, false);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, false);
}
}
}
DECLARE_INSTRUCTION_OPCODE(set) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 rvalue = inst->_opB.getRValue();
int16* lvalue = inst->_opA.getLValue();
@@ -358,22 +374,15 @@ DECLARE_INSTRUCTION_OPCODE(set) {
}
-DECLARE_INSTRUCTION_OPCODE(loop) {
- InstructionPtr inst = *_instRunCtxt.inst;
-
- _instRunCtxt.program->_loopCounter = inst->_opB.getRValue();
- _instRunCtxt.program->_loopStart = _instRunCtxt.inst;
-}
-
DECLARE_INSTRUCTION_OPCODE(inc) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 rvalue = inst->_opB.getRValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (rvalue > 0 ? rvalue : -rvalue);
- if (_instRunCtxt.modCounter % _bx != 0) return;
+ if (_ctxt.modCounter % _bx != 0) return;
rvalue = (rvalue > 0 ? 1 : -1);
}
@@ -420,12 +429,12 @@ DECLARE_INSTRUCTION_OPCODE(wait) {
DECLARE_INSTRUCTION_OPCODE(start) {
- (*_instRunCtxt.inst)->_z->_flags |= kFlagsActing;
+ (*_ctxt.inst)->_z->_flags |= kFlagsActing;
}
DECLARE_INSTRUCTION_OPCODE(process) {
- _activeZone2 = (*_instRunCtxt.inst)->_z;
+ _vm->_activeZone2 = (*_ctxt.inst)->_z;
}
@@ -435,18 +444,18 @@ DECLARE_INSTRUCTION_OPCODE(move) {
DECLARE_INSTRUCTION_OPCODE(color) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 entry = inst->_opB.getRValue();
- _gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]);
+ _vm->_gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]);
}
DECLARE_INSTRUCTION_OPCODE(mask) {
#if 0
- Instruction *inst = *_instRunCtxt.inst;
+ Instruction *inst = *_ctxt.inst;
_gfx->_bgLayers[0] = inst->_opA.getRValue();
_gfx->_bgLayers[1] = inst->_opB.getRValue();
_gfx->_bgLayers[2] = inst->_opC.getRValue();
@@ -459,8 +468,8 @@ DECLARE_INSTRUCTION_OPCODE(print) {
}
DECLARE_INSTRUCTION_OPCODE(text) {
- InstructionPtr inst = (*_instRunCtxt.inst);
- setupSubtitles(inst->_text, inst->_text2, inst->_y);
+ InstructionPtr inst = (*_ctxt.inst);
+ _vm->setupSubtitles(inst->_text, inst->_text2, inst->_y);
}
@@ -488,22 +497,11 @@ DECLARE_INSTRUCTION_OPCODE(stop) {
warning("Parallaction_br::instOp_stop not yet implemented");
}
-DECLARE_INSTRUCTION_OPCODE(endscript) {
- if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) {
- _instRunCtxt.anim->_flags &= ~kFlagsActing;
- runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim);
- _instRunCtxt.program->_status = kProgramDone;
- }
- _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin();
-
- _instRunCtxt.suspend = true;
-}
-
-void Parallaction_br::initOpcodes() {
+void CommandExec_br::init() {
Common::Array<const Opcode*> *table = 0;
- SetOpcodeTable(_commandOpcodes);
+ SetOpcodeTable(_opcodes);
COMMAND_OPCODE(invalid);
COMMAND_OPCODE(set);
COMMAND_OPCODE(clear);
@@ -546,8 +544,21 @@ void Parallaction_br::initOpcodes() {
COMMAND_OPCODE(ret);
COMMAND_OPCODE(onsave);
COMMAND_OPCODE(offsave);
+}
+
+CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec_ns(vm), _vm(vm) {
+
+}
+
+CommandExec_br::~CommandExec_br() {
+
+}
- SetOpcodeTable(_instructionOpcodes);
+void ProgramExec_br::init() {
+
+ Common::Array<const Opcode*> *table = 0;
+
+ SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
@@ -557,7 +568,7 @@ void Parallaction_br::initOpcodes() {
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
- INSTRUCTION_OPCODE(null); // show
+ INSTRUCTION_OPCODE(show); // show
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
@@ -582,6 +593,13 @@ void Parallaction_br::initOpcodes() {
INSTRUCTION_OPCODE(endscript);
}
+ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) {
+ _instructionNames = _instructionNamesRes_br;
+}
+
+ProgramExec_br::~ProgramExec_br() {
+}
+
#if 0
void Parallaction_br::jobWaitRemoveLabelJob(void *parm, Job *job) {
diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp
index a4b372f42a..99a492863b 100644
--- a/engines/parallaction/exec_ns.cpp
+++ b/engines/parallaction/exec_ns.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
@@ -52,18 +53,19 @@ namespace Parallaction {
#define SetOpcodeTable(x) table = &x;
-typedef Common::Functor0Mem<void, Parallaction_ns> OpcodeV2;
-#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::cmdOp_##op))
-#define DECLARE_COMMAND_OPCODE(op) void Parallaction_ns::cmdOp_##op()
-
-#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::instOp_##op))
-#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_ns::instOp_##op()
+typedef Common::Functor0Mem<void, CommandExec_ns> OpcodeV1;
+#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_ns::cmdOp_##op))
+#define DECLARE_COMMAND_OPCODE(op) void CommandExec_ns::cmdOp_##op()
+typedef Common::Functor0Mem<void, ProgramExec_ns> OpcodeV2;
+#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_ns::instOp_##op))
+#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_ns::instOp_##op()
+extern const char *_instructionNamesRes_ns[];
DECLARE_INSTRUCTION_OPCODE(on) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
inst->_a->_flags |= kFlagsActive;
inst->_a->_flags &= ~kFlagsRemove;
@@ -71,31 +73,31 @@ DECLARE_INSTRUCTION_OPCODE(on) {
DECLARE_INSTRUCTION_OPCODE(off) {
- (*_instRunCtxt.inst)->_a->_flags |= kFlagsRemove;
+ (*_ctxt.inst)->_a->_flags |= kFlagsRemove;
}
DECLARE_INSTRUCTION_OPCODE(loop) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
- _instRunCtxt.program->_loopCounter = inst->_opB.getRValue();
- _instRunCtxt.program->_loopStart = _instRunCtxt.inst;
+ _ctxt.program->_loopCounter = inst->_opB.getRValue();
+ _ctxt.program->_loopStart = _ctxt.ip;
}
DECLARE_INSTRUCTION_OPCODE(endloop) {
- if (--_instRunCtxt.program->_loopCounter > 0) {
- _instRunCtxt.inst = _instRunCtxt.program->_loopStart;
+ if (--_ctxt.program->_loopCounter > 0) {
+ _ctxt.ip = _ctxt.program->_loopStart;
}
}
DECLARE_INSTRUCTION_OPCODE(inc) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 _si = inst->_opB.getRValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (_si > 0 ? _si : -_si);
- if (_instRunCtxt.modCounter % _bx != 0) return;
+ if (_ctxt.modCounter % _bx != 0) return;
_si = (_si > 0 ? 1 : -1);
}
@@ -116,7 +118,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) {
DECLARE_INSTRUCTION_OPCODE(set) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 _si = inst->_opB.getRValue();
int16 *lvalue = inst->_opA.getLValue();
@@ -127,7 +129,7 @@ DECLARE_INSTRUCTION_OPCODE(set) {
DECLARE_INSTRUCTION_OPCODE(put) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
Graphics::Surface v18;
v18.w = inst->_a->width();
v18.h = inst->_a->height();
@@ -137,162 +139,175 @@ DECLARE_INSTRUCTION_OPCODE(put) {
int16 y = inst->_opB.getRValue();
bool mask = (inst->_flags & kInstMaskedPut) == kInstMaskedPut;
- _gfx->patchBackground(v18, x, y, mask);
+ _vm->_gfx->patchBackground(v18, x, y, mask);
}
-DECLARE_INSTRUCTION_OPCODE(null) {
-
+DECLARE_INSTRUCTION_OPCODE(show) {
+ _ctxt.suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(invalid) {
- error("Can't execute invalid opcode %i", (*_instRunCtxt.inst)->_index);
+ error("Can't execute invalid opcode %i", (*_ctxt.inst)->_index);
}
DECLARE_INSTRUCTION_OPCODE(call) {
- callFunction((*_instRunCtxt.inst)->_immediate, 0);
+ _vm->callFunction((*_ctxt.inst)->_immediate, 0);
}
DECLARE_INSTRUCTION_OPCODE(wait) {
- if (_engineFlags & kEngineWalking)
- _instRunCtxt.suspend = true;
+ if (_engineFlags & kEngineWalking) {
+ _ctxt.ip--;
+ _ctxt.suspend = true;
+ }
}
DECLARE_INSTRUCTION_OPCODE(start) {
- (*_instRunCtxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive);
+ (*_ctxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive);
}
DECLARE_INSTRUCTION_OPCODE(sound) {
- _activeZone = (*_instRunCtxt.inst)->_z;
+ _vm->_activeZone = (*_ctxt.inst)->_z;
}
DECLARE_INSTRUCTION_OPCODE(move) {
- InstructionPtr inst = (*_instRunCtxt.inst);
+ InstructionPtr inst = (*_ctxt.inst);
int16 x = inst->_opA.getRValue();
int16 y = inst->_opB.getRValue();
- _char.scheduleWalk(x, y);
+ _vm->_char.scheduleWalk(x, y);
}
DECLARE_INSTRUCTION_OPCODE(endscript) {
- if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) {
- _instRunCtxt.anim->_flags &= ~kFlagsActing;
- runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim);
- _instRunCtxt.program->_status = kProgramDone;
+ if ((_ctxt.anim->_flags & kFlagsLooping) == 0) {
+ _ctxt.anim->_flags &= ~kFlagsActing;
+ _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim);
+ _ctxt.program->_status = kProgramDone;
}
- _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin();
- _instRunCtxt.suspend = true;
+ _ctxt.ip = _ctxt.program->_instructions.begin();
+ _ctxt.suspend = true;
}
DECLARE_COMMAND_OPCODE(invalid) {
- error("Can't execute invalid command '%i'", _cmdRunCtxt.cmd->_id);
+ error("Can't execute invalid command '%i'", _ctxt.cmd->_id);
}
DECLARE_COMMAND_OPCODE(set) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags |= _cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags |= _ctxt.cmd->u._flags;
} else {
- setLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->setLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(clear) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags &= ~_cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags &= ~_ctxt.cmd->u._flags;
} else {
- clearLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->clearLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(start) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing;
+ _ctxt.cmd->u._zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(speak) {
- _activeZone = _cmdRunCtxt.cmd->u._zone;
+ if ((_ctxt.cmd->u._zone->_type & 0xFFFF) == kZoneSpeak) {
+ _vm->enterDialogueMode(_ctxt.cmd->u._zone);
+ } else {
+ _vm->_activeZone = _ctxt.cmd->u._zone;
+ }
}
DECLARE_COMMAND_OPCODE(get) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed;
- runZone(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed;
+ _vm->runZone(_ctxt.cmd->u._zone);
}
DECLARE_COMMAND_OPCODE(location) {
- scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string);
+ _vm->scheduleLocationSwitch(_ctxt.cmd->u._string);
}
DECLARE_COMMAND_OPCODE(open) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsClosed;
- if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) {
- updateDoor(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
}
}
DECLARE_COMMAND_OPCODE(close) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsClosed;
- if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) {
- updateDoor(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags |= kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
}
}
+void CommandExec_ns::updateGetZone(ZonePtr z, bool visible) {
+ if (!z) {
+ return;
+ }
+
+ if ((z->_type & 0xFFFF) == kZoneGet) {
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, visible);
+ }
+}
DECLARE_COMMAND_OPCODE(on) {
- ZonePtr z = _cmdRunCtxt.cmd->u._zone;
- // WORKAROUND: the original DOS-based engine didn't check u->_zone before dereferencing
- // the pointer to get structure members, thus leading to crashes in systems with memory
- // protection.
- // As a side note, the overwritten address is the 5th entry in the DOS interrupt table
- // (print screen handler): this suggests that a system would hang when the print screen
- // key is pressed after playing Nippon Safes, provided that this code path is taken.
+ ZonePtr z = _ctxt.cmd->u._zone;
+
if (z) {
z->_flags &= ~kFlagsRemove;
z->_flags |= kFlagsActive;
- if ((z->_type & 0xFFFF) == kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
- }
+ updateGetZone(z, true);
}
}
DECLARE_COMMAND_OPCODE(off) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsRemove;
+ ZonePtr z = _ctxt.cmd->u._zone;
+
+ if (z) {
+ _ctxt.cmd->u._zone->_flags |= kFlagsRemove;
+ updateGetZone(z, false);
+ }
}
DECLARE_COMMAND_OPCODE(call) {
- callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z);
+ _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);
}
DECLARE_COMMAND_OPCODE(toggle) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags ^= _cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags ^= _ctxt.cmd->u._flags;
} else {
- toggleLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->toggleLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(drop){
- dropItem( _cmdRunCtxt.cmd->u._object );
+ _vm->dropItem( _ctxt.cmd->u._object );
}
@@ -302,70 +317,103 @@ DECLARE_COMMAND_OPCODE(quit) {
DECLARE_COMMAND_OPCODE(move) {
- _char.scheduleWalk(_cmdRunCtxt.cmd->u._move.x, _cmdRunCtxt.cmd->u._move.y);
+ _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y);
}
DECLARE_COMMAND_OPCODE(stop) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsActing;
}
void Parallaction_ns::drawAnimations() {
+ debugC(9, kDebugExec, "Parallaction_ns::drawAnimations()\n");
uint16 layer = 0;
for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) {
- AnimationPtr v18 = *it;
- GfxObj *obj = v18->gfxobj;
+ AnimationPtr anim = *it;
+ GfxObj *obj = anim->gfxobj;
- if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0)) {
+ // Validation is performed here, so that every animation is affected, instead that only the ones
+ // who *own* a script. In fact, some scripts can change values in other animations.
+ // The right way to do this would be to enforce validation when any variable is modified from
+ // a script.
+ anim->validateScriptVars();
- int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1);
- if (v18->_flags & kFlagsNoMasked)
+ if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0)) {
+
+ if (anim->_flags & kFlagsNoMasked)
layer = 3;
else
- layer = _gfx->_backgroundInfo.getLayer(v18->_top + v18->height());
+ layer = _gfx->_backgroundInfo->getLayer(anim->_top + anim->height());
if (obj) {
_gfx->showGfxObj(obj, true);
- obj->frame = frame;
- obj->x = v18->_left;
- obj->y = v18->_top;
- obj->z = v18->_z;
+ obj->frame = anim->_frame;
+ obj->x = anim->_left;
+ obj->y = anim->_top;
+ obj->z = anim->_z;
obj->layer = layer;
}
}
- if (((v18->_flags & kFlagsActive) == 0) && (v18->_flags & kFlagsRemove)) {
- v18->_flags &= ~kFlagsRemove;
- v18->_oldPos.x = -1000;
+ if (((anim->_flags & kFlagsActive) == 0) && (anim->_flags & kFlagsRemove)) {
+ anim->_flags &= ~kFlagsRemove;
+ anim->_oldPos.x = -1000;
}
- if ((v18->_flags & kFlagsActive) && (v18->_flags & kFlagsRemove)) {
- v18->_flags &= ~kFlagsActive;
- v18->_flags |= kFlagsRemove;
+ if ((anim->_flags & kFlagsActive) && (anim->_flags & kFlagsRemove)) {
+ anim->_flags &= ~kFlagsActive;
+ anim->_flags |= kFlagsRemove;
if (obj) {
_gfx->showGfxObj(obj, false);
}
}
}
+ debugC(9, kDebugExec, "Parallaction_ns::drawAnimations done()\n");
+
return;
}
+void ProgramExec::runScript(ProgramPtr script, AnimationPtr a) {
+ debugC(9, kDebugExec, "runScript(Animation = %s)", a->_name);
+
+ _ctxt.ip = script->_ip;
+ _ctxt.anim = a;
+ _ctxt.program = script;
+ _ctxt.suspend = false;
+ _ctxt.modCounter = _modCounter;
+
+ InstructionList::iterator inst;
+ for ( ; (a->_flags & kFlagsActing) ; ) {
+
+ inst = _ctxt.ip;
+ _ctxt.inst = inst;
+ _ctxt.ip++;
+
+ debugC(9, kDebugExec, "inst [%02i] %s\n", (*inst)->_index, _instructionNames[(*inst)->_index - 1]);
+
+ script->_status = kProgramRunning;
+
+ (*_opcodes[(*inst)->_index])();
+
+ if (_ctxt.suspend)
+ break;
-void Parallaction_ns::runScripts() {
- if (_engineFlags & kEnginePauseJobs) {
- return;
}
+ script->_ip = _ctxt.ip;
- debugC(9, kDebugExec, "runScripts");
+}
- static uint16 modCounter = 0;
+void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) {
+ if (_engineFlags & kEnginePauseJobs) {
+ return;
+ }
- for (ProgramList::iterator it = _location._programs.begin(); it != _location._programs.end(); it++) {
+ for (ProgramList::iterator it = first; it != last; it++) {
AnimationPtr a = (*it)->_anim;
@@ -375,116 +423,182 @@ void Parallaction_ns::runScripts() {
if ((a->_flags & kFlagsActing) == 0)
continue;
- InstructionList::iterator inst = (*it)->_ip;
- while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) {
+ runScript(*it, a);
- (*it)->_status = kProgramRunning;
+ if (a->_flags & kFlagsCharacter)
+ a->_z = a->_top + a->height();
+ }
- debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]);
+ _modCounter++;
- _instRunCtxt.inst = inst;
- _instRunCtxt.anim = AnimationPtr(a);
- _instRunCtxt.program = *it;
- _instRunCtxt.modCounter = modCounter;
- _instRunCtxt.suspend = false;
+ return;
+}
+
+void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) {
- (*_instructionOpcodes[(*inst)->_index])();
+ uint32 useFlags = 0;
+ bool useLocalFlags;
- inst = _instRunCtxt.inst; // handles endloop correctly
+ _ctxt.suspend = false;
- if (_instRunCtxt.suspend)
- goto label1;
+ for ( ; first != last; first++) {
+ if (_engineFlags & kEngineQuit)
+ break;
- inst++;
+ CommandPtr cmd = *first;
+
+ if (cmd->_flagsOn & kFlagsGlobal) {
+ useFlags = _commandFlags | kFlagsGlobal;
+ useLocalFlags = false;
+ } else {
+ useFlags = _vm->getLocationFlags();
+ useLocalFlags = true;
}
- (*it)->_ip = ++inst;
+ bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn;
+ bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff;
-label1:
- if (a->_flags & kFlagsCharacter)
- a->_z = a->_top + a->height();
- }
+ debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id, cmd->_flagsOn, cmd->_flagsOff,
+ useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags);
+
+ if (!onMatch || !offMatch) continue;
+
+ _ctxt.z = _execZone;
+ _ctxt.cmd = cmd;
+
+ (*_opcodes[cmd->_id])();
- _char._ani->_z = _char._ani->height() + _char._ani->_top;
- if (_char._ani->gfxobj) {
- _char._ani->gfxobj->z = _char._ani->_z;
+ if (_ctxt.suspend) {
+ createSuspendList(++first, last);
+ return;
+ }
}
- modCounter++;
- return;
}
-
-void Parallaction::runCommands(CommandList& list, ZonePtr z) {
- if (list.size() == 0)
+void CommandExec::run(CommandList& list, ZonePtr z) {
+ if (list.size() == 0) {
+ debugC(3, kDebugExec, "runCommands: nothing to do");
return;
+ }
- debugC(3, kDebugExec, "runCommands");
-
- CommandList::iterator it = list.begin();
- for ( ; it != list.end(); it++) {
+ _execZone = z;
- CommandPtr cmd = *it;
- uint32 v8 = getLocationFlags();
+ debugC(3, kDebugExec, "runCommands starting");
+ runList(list.begin(), list.end());
+ debugC(3, kDebugExec, "runCommands completed");
+}
- if (_engineFlags & kEngineQuit)
- break;
+void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) {
+ if (first == last) {
+ return;
+ }
- if (cmd->_flagsOn & kFlagsGlobal) {
- v8 = _commandFlags | kFlagsGlobal;
- }
+ debugC(3, kDebugExec, "CommandExec::createSuspendList()");
- if ((cmd->_flagsOn & v8) != cmd->_flagsOn) continue;
- if ((cmd->_flagsOff & ~v8) != cmd->_flagsOff) continue;
+ _suspendedCtxt.valid = true;
+ _suspendedCtxt.first = first;
+ _suspendedCtxt.last = last;
+ _suspendedCtxt.zone = _execZone;
+}
-// debugC(3, kDebugExec, "runCommands[%i]: %s (on: %x, off: %x)", cmd->_id, _commandsNamesRes[cmd->_id-1], cmd->_flagsOn, cmd->_flagsOff);
+void CommandExec::cleanSuspendedList() {
+ debugC(3, kDebugExec, "CommandExec::cleanSuspended()");
- _cmdRunCtxt.z = z;
- _cmdRunCtxt.cmd = cmd;
+ _suspendedCtxt.valid = false;
+ _suspendedCtxt.first = _suspendedCtxt.last;
+ _suspendedCtxt.zone = nullZonePtr;
+}
- (*_commandOpcodes[cmd->_id])();
+void CommandExec::runSuspended() {
+ if (_engineFlags & kEngineWalking) {
+ return;
}
- debugC(3, kDebugExec, "runCommands completed");
+ if (_suspendedCtxt.valid) {
+ debugC(3, kDebugExec, "CommandExec::runSuspended()");
- return;
+ _execZone = _suspendedCtxt.zone;
+ runList(_suspendedCtxt.first, _suspendedCtxt.last);
+ cleanSuspendedList();
+ }
+}
+
+CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) {
}
+CommandExec_ns::~CommandExec_ns() {
+
+}
//
// ZONE TYPE: EXAMINE
//
-void Parallaction::displayComment(ExamineData *data) {
+void Parallaction::enterCommentMode(ZonePtr z) {
+ if (!z) {
+ return;
+ }
+
+ _commentZone = z;
+
+ ExamineData *data = _commentZone->u.examine;
+
if (!data->_description) {
return;
}
- int id;
+ // TODO: move this balloons stuff into DialogueManager and BalloonManager
+ if (getGameType() == GType_Nippon) {
+ int id;
+ if (data->_filename) {
+ if (data->_cnv == 0) {
+ data->_cnv = _disk->loadStatic(data->_filename);
+ }
- if (data->_filename) {
- if (data->_cnv == 0) {
- data->_cnv = _disk->loadStatic(data->_filename);
+ _gfx->setHalfbriteMode(true);
+ _balloonMan->setSingleBalloon(data->_description, 0, 90, 0, 0);
+ Common::Rect r;
+ data->_cnv->getRect(0, r);
+ id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2);
+ _gfx->setItemFrame(id, 0);
+ id = _gfx->setItem(_char._head, 100, 152);
+ _gfx->setItemFrame(id, 0);
+ } else {
+ _balloonMan->setSingleBalloon(data->_description, 140, 10, 0, 0);
+ id = _gfx->setItem(_char._talk, 190, 80);
+ _gfx->setItemFrame(id, 0);
}
-
- _gfx->setHalfbriteMode(true);
- _gfx->setSingleBalloon(data->_description, 0, 90, 0, 0);
- Common::Rect r;
- data->_cnv->getRect(0, r);
- id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2);
- _gfx->setItemFrame(id, 0);
- id = _gfx->setItem(_char._head, 100, 152);
- _gfx->setItemFrame(id, 0);
- } else {
- _gfx->setSingleBalloon(data->_description, 140, 10, 0, 0);
- id = _gfx->setItem(_char._talk, 190, 80);
+ } else
+ if (getGameType() == GType_BRA) {
+ _balloonMan->setSingleBalloon(data->_description, 0, 0, 1, 0);
+ int id = _gfx->setItem(_char._talk, 10, 80);
_gfx->setItemFrame(id, 0);
}
_input->_inputMode = Input::kInputModeComment;
}
+void Parallaction::exitCommentMode() {
+ _input->_inputMode = Input::kInputModeGame;
+
+ hideDialogueStuff();
+ _gfx->setHalfbriteMode(false);
+
+ _cmdExec->run(_commentZone->_commands, _commentZone);
+ _commentZone = nullZonePtr;
+}
+
+void Parallaction::runCommentFrame() {
+ if (_input->_inputMode != Input::kInputModeComment) {
+ return;
+ }
+
+ if (_input->getLastButtonEvent() == kMouseLeftUp) {
+ exitCommentMode();
+ }
+}
uint16 Parallaction::runZone(ZonePtr z) {
@@ -496,8 +610,8 @@ uint16 Parallaction::runZone(ZonePtr z) {
switch(subtype) {
case kZoneExamine:
- displayComment(z->u.examine);
- break;
+ enterCommentMode(z);
+ return 0;
case kZoneGet:
if (z->_flags & kFlagsFixed) break;
@@ -518,14 +632,13 @@ uint16 Parallaction::runZone(ZonePtr z) {
break;
case kZoneSpeak:
- runDialogue(z->u.speak);
- break;
-
+ enterDialogueMode(z);
+ return 0;
}
debugC(3, kDebugExec, "runZone completed");
- runCommands(z->_commands, z);
+ _cmdExec->run(z->_commands, z);
return 0;
}
@@ -652,11 +765,34 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) {
}
-void Parallaction_ns::initOpcodes() {
+void CommandExec_ns::init() {
+ Common::Array<const Opcode*> *table = 0;
+
+ SetOpcodeTable(_opcodes);
+ COMMAND_OPCODE(invalid);
+ COMMAND_OPCODE(set);
+ COMMAND_OPCODE(clear);
+ COMMAND_OPCODE(start);
+ COMMAND_OPCODE(speak);
+ COMMAND_OPCODE(get);
+ COMMAND_OPCODE(location);
+ COMMAND_OPCODE(open);
+ COMMAND_OPCODE(close);
+ COMMAND_OPCODE(on);
+ COMMAND_OPCODE(off);
+ COMMAND_OPCODE(call);
+ COMMAND_OPCODE(toggle);
+ COMMAND_OPCODE(drop);
+ COMMAND_OPCODE(quit);
+ COMMAND_OPCODE(move);
+ COMMAND_OPCODE(stop);
+}
+
+void ProgramExec_ns::init() {
Common::Array<const Opcode*> *table = 0;
- SetOpcodeTable(_instructionOpcodes);
+ SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
@@ -666,7 +802,7 @@ void Parallaction_ns::initOpcodes() {
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
- INSTRUCTION_OPCODE(null);
+ INSTRUCTION_OPCODE(show);
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
@@ -678,25 +814,13 @@ void Parallaction_ns::initOpcodes() {
INSTRUCTION_OPCODE(move);
INSTRUCTION_OPCODE(endscript);
- SetOpcodeTable(_commandOpcodes);
- COMMAND_OPCODE(invalid);
- COMMAND_OPCODE(set);
- COMMAND_OPCODE(clear);
- COMMAND_OPCODE(start);
- COMMAND_OPCODE(speak);
- COMMAND_OPCODE(get);
- COMMAND_OPCODE(location);
- COMMAND_OPCODE(open);
- COMMAND_OPCODE(close);
- COMMAND_OPCODE(on);
- COMMAND_OPCODE(off);
- COMMAND_OPCODE(call);
- COMMAND_OPCODE(toggle);
- COMMAND_OPCODE(drop);
- COMMAND_OPCODE(quit);
- COMMAND_OPCODE(move);
- COMMAND_OPCODE(stop);
}
+ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) {
+ _instructionNames = _instructionNamesRes_ns;
+}
+
+ProgramExec_ns::~ProgramExec_ns() {
+}
} // namespace Parallaction
diff --git a/engines/parallaction/font.cpp b/engines/parallaction/font.cpp
index 91848b30a4..e84dad34aa 100644
--- a/engines/parallaction/font.cpp
+++ b/engines/parallaction/font.cpp
@@ -35,6 +35,7 @@ extern byte _amigaTopazFont[];
class BraFont : public Font {
+protected:
byte *_cp;
uint _bufPitch;
@@ -45,15 +46,15 @@ class BraFont : public Font {
uint *_offsets;
byte *_data;
-
- static byte _charMap[];
+ const byte *_charMap;
byte mapChar(byte c) {
- return _charMap[c];
+ return (_charMap == 0) ? c : _charMap[c];
}
public:
- BraFont(Common::ReadStream &stream) {
+ BraFont(Common::ReadStream &stream, const byte *charMap = 0) {
+ _charMap = charMap;
_numGlyphs = stream.readByte();
_height = stream.readUint32BE();
@@ -137,7 +138,7 @@ public:
};
-byte BraFont::_charMap[] = {
+const byte _braDosFullCharMap[256] = {
// 0
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 1
@@ -172,6 +173,111 @@ byte BraFont::_charMap[] = {
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
};
+const byte _braDosDemoComicCharMap[] = {
+// 0
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 1
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 2
+ 0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34,
+// 3
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42,
+// 4
+ 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
+// 5
+ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 6
+ 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+// 7
+ 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 8
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 9
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// A
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// B
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// C
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// D
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// E
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// F
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
+};
+
+const byte _braDosDemoRussiaCharMap[] = {
+// 0
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 1
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 2
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 3
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 4
+ 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
+// 5
+ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 6
+ 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+// 7
+ 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 8
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 9
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// A
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// B
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// C
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// D
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// E
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// F
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
+};
+
+class BraInventoryObjects : public BraFont, public Frames {
+
+public:
+ BraInventoryObjects(Common::ReadStream &stream) : BraFont(stream) {
+ }
+
+ // Frames implementation
+ uint16 getNum() {
+ return _numGlyphs;
+ }
+
+ byte* getData(uint16 index) {
+ assert(index < _numGlyphs);
+ return _data + (_height * _widths[index]) * index;;
+ }
+
+ void getRect(uint16 index, Common::Rect &r) {
+ assert(index < _numGlyphs);
+ r.left = 0;
+ r.top = 0;
+ r.setWidth(_widths[index]);
+ r.setHeight(_height);
+ }
+
+ uint getRawSize(uint16 index) {
+ assert(index < _numGlyphs);
+ return _widths[index] * _height;
+ }
+
+ uint getSize(uint16 index) {
+ assert(index < _numGlyphs);
+ return _widths[index] * _height;
+ }
+
+};
class DosFont : public Font {
@@ -537,7 +643,19 @@ Font *AmigaDisk_ns::createFont(const char *name, Common::SeekableReadStream &str
Font *DosDisk_br::createFont(const char *name, Common::ReadStream &stream) {
// printf("DosDisk_br::createFont(%s)\n", name);
- return new BraFont(stream);
+ Font *font;
+
+ if (_vm->getFeatures() & GF_DEMO) {
+ if (!scumm_stricmp(name, "russia")) {
+ font = new BraFont(stream, _braDosDemoRussiaCharMap);
+ } else {
+ font = new BraFont(stream, _braDosDemoComicCharMap);
+ }
+ } else {
+ font = new BraFont(stream, _braDosFullCharMap);
+ }
+
+ return font;
}
Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &stream) {
@@ -545,6 +663,12 @@ Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &str
return new AmigaFont(stream);
}
+GfxObj* DosDisk_br::createInventoryObjects(Common::SeekableReadStream &stream) {
+ Frames *frames = new BraInventoryObjects(stream);
+ return new GfxObj(0, frames, "inventoryobjects");
+}
+
+
void Parallaction_ns::initFonts() {
if (getPlatform() == Common::kPlatformPC) {
@@ -573,8 +697,8 @@ void Parallaction_br::initFonts() {
// fonts/sonya/18
// fonts/vanya/16
- _menuFont = _disk->loadFont("fonts/natasha/16");
- _dialogueFont = _disk->loadFont("fonts/sonya/18");
+ _menuFont = _disk->loadFont("natasha");
+ _dialogueFont = _disk->loadFont("vanya");
Common::MemoryReadStream stream(_amigaTopazFont, 2600, false);
_labelFont = new AmigaFont(stream);
}
diff --git a/engines/parallaction/gfxbase.cpp b/engines/parallaction/gfxbase.cpp
index 6599a1f81c..1c373dda44 100644
--- a/engines/parallaction/gfxbase.cpp
+++ b/engines/parallaction/gfxbase.cpp
@@ -32,7 +32,7 @@
namespace Parallaction {
-GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : type(objType), _frames(frames), x(0), y(0), z(0), frame(0), layer(3), _flags(0), _keep(true) {
+GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : _frames(frames), _keep(true), x(0), y(0), z(0), _flags(kGfxObjNormal), type(objType), frame(0), layer(3) {
if (name) {
_name = strdup(name);
} else {
@@ -86,93 +86,265 @@ void GfxObj::clearFlags(uint32 flags) {
}
GfxObj* Gfx::loadAnim(const char *name) {
- Frames *frames = _disk->loadFrames(name);
+ Frames* frames = _disk->loadFrames(name);
+ assert(frames);
+
GfxObj *obj = new GfxObj(kGfxObjTypeAnim, frames, name);
assert(obj);
+ // animation Z is not set here, but controlled by game scripts and user interaction.
+ // it is always >=0 and <screen height
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
GfxObj* Gfx::loadGet(const char *name) {
- Frames *frames = _disk->loadStatic(name);
- GfxObj *obj = new GfxObj(kGfxObjTypeGet, frames, name);
+ GfxObj *obj = _disk->loadStatic(name);
assert(obj);
+ obj->z = kGfxObjGetZ; // this preset Z value ensures that get zones are drawn after doors but before animations
+ obj->type = kGfxObjTypeGet;
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
GfxObj* Gfx::loadDoor(const char *name) {
Frames *frames = _disk->loadFrames(name);
+ assert(frames);
+
GfxObj *obj = new GfxObj(kGfxObjTypeDoor, frames, name);
assert(obj);
+ obj->z = kGfxObjDoorZ; // this preset Z value ensures that doors are drawn first
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
-void Gfx::clearGfxObjects() {
- _gfxobjList[0].clear();
- _gfxobjList[1].clear();
- _gfxobjList[2].clear();
+void Gfx::clearGfxObjects(uint filter) {
+
+ GfxObjList::iterator b = _gfxobjList.begin();
+ GfxObjList::iterator e = _gfxobjList.end();
+
+ for ( ; b != e; ) {
+ if (((*b)->_flags & filter) != 0) {
+ b = _gfxobjList.erase(b);
+ } else {
+ b++;
+ }
+ }
+
}
void Gfx::showGfxObj(GfxObj* obj, bool visible) {
- if (!obj || obj->isVisible() == visible) {
+ if (!obj) {
return;
}
if (visible) {
obj->setFlags(kGfxObjVisible);
- _gfxobjList[obj->type].push_back(obj);
} else {
obj->clearFlags(kGfxObjVisible);
- _gfxobjList[obj->type].remove(obj);
}
-
}
-bool compareAnimationZ(const GfxObj* a1, const GfxObj* a2) {
+bool compareZ(const GfxObj* a1, const GfxObj* a2) {
return a1->z < a2->z;
}
void Gfx::sortAnimations() {
- GfxObjList::iterator first = _gfxobjList[kGfxObjTypeAnim].begin();
- GfxObjList::iterator last = _gfxobjList[kGfxObjTypeAnim].end();
+ GfxObjList::iterator first = _gfxobjList.begin();
+ GfxObjList::iterator last = _gfxobjList.end();
- Common::sort(first, last, compareAnimationZ);
+ Common::sort(first, last, compareZ);
}
-void Gfx::drawGfxObjects(Graphics::Surface &surf) {
+
+void Gfx::drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene) {
+ if (!obj->isVisible()) {
+ return;
+ }
Common::Rect rect;
byte *data;
+ uint scrollX = (scene) ? -_varScrollX : 0;
+
+ obj->getRect(obj->frame, rect);
+ rect.translate(obj->x + scrollX, obj->y);
+ data = obj->getData(obj->frame);
+
+ if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) {
+ blt(rect, data, &surf, obj->layer, obj->transparentKey);
+ } else {
+ unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, obj->transparentKey);
+ }
+
+}
+
+
+void Gfx::drawGfxObjects(Graphics::Surface &surf) {
+
sortAnimations();
// TODO: some zones don't appear because of wrong masking (3 or 0?)
- // TODO: Dr.Ki is not visible inside the club
+
+ GfxObjList::iterator b = _gfxobjList.begin();
+ GfxObjList::iterator e = _gfxobjList.end();
+
+ for (; b != e; b++) {
+ drawGfxObject(*b, surf, true);
+ }
+}
- for (uint i = 0; i < 3; i++) {
- GfxObjList::iterator b = _gfxobjList[i].begin();
- GfxObjList::iterator e = _gfxobjList[i].end();
+void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) {
+ byte *dst = (byte*)surf->getBasePtr(x, y);
+ font->setColor(color);
+ font->drawString(dst, surf->w, text);
+}
+
- for (; b != e; b++) {
- GfxObj *obj = *b;
- if (obj->isVisible()) {
- obj->getRect(obj->frame, rect);
- rect.translate(obj->x - _varScrollX, obj->y);
- data = obj->getData(obj->frame);
- if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) {
- blt(rect, data, &surf, obj->layer, 0);
- } else {
- unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, 0);
+#if 0
+void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ byte *d = _unpackedBitmap;
+
+ while (size > 0) {
+
+ uint8 p = *data++;
+ size--;
+ uint8 color = p & 0xF;
+ uint8 repeat = (p & 0xF0) >> 4;
+ if (repeat == 0) {
+ repeat = *data++;
+ size--;
+ }
+
+ memset(d, color, repeat);
+ d += repeat;
+ }
+
+ blt(r, _unpackedBitmap, surf, z, transparentColor);
+}
+#endif
+void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ byte *d = _unpackedBitmap;
+ uint pixelsLeftInLine = r.width();
+
+ while (size > 0) {
+ uint8 p = *data++;
+ size--;
+ uint8 color = p & 0xF;
+ uint8 repeat = (p & 0xF0) >> 4;
+ if (repeat == 0) {
+ repeat = *data++;
+ size--;
+ }
+ if (repeat == 0) {
+ // end of line
+ repeat = pixelsLeftInLine;
+ pixelsLeftInLine = r.width();
+ } else {
+ pixelsLeftInLine -= repeat;
+ }
+
+ memset(d, color, repeat);
+ d += repeat;
+ }
+
+ blt(r, _unpackedBitmap, surf, z, transparentColor);
+}
+
+
+void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ Common::Point dp;
+ Common::Rect q(r);
+
+ Common::Rect clipper(surf->w, surf->h);
+
+ q.clip(clipper);
+ if (!q.isValidRect()) return;
+
+ dp.x = q.left;
+ dp.y = q.top;
+
+ q.translate(-r.left, -r.top);
+
+ byte *s = data + q.left + q.top * r.width();
+ byte *d = (byte*)surf->getBasePtr(dp.x, dp.y);
+
+ uint sPitch = r.width() - q.width();
+ uint dPitch = surf->w - q.width();
+
+
+ if (_varRenderMode == 2) {
+
+ for (uint16 i = 0; i < q.height(); i++) {
+
+ for (uint16 j = 0; j < q.width(); j++) {
+ if (*s != transparentColor) {
+ if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) {
+ byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i);
+ if (z >= v) *d = 5;
+ } else {
+ *d = 5;
+ }
}
+
+ s++;
+ d++;
}
+
+ s += sPitch;
+ d += dPitch;
+ }
+
+ } else {
+ if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) {
+
+ for (uint16 i = 0; i < q.height(); i++) {
+
+ for (uint16 j = 0; j < q.width(); j++) {
+ if (*s != transparentColor) {
+ byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i);
+ if (z >= v) *d = *s;
+ }
+
+ s++;
+ d++;
+ }
+
+ s += sPitch;
+ d += dPitch;
+ }
+
+ } else {
+
+ for (uint16 i = q.top; i < q.bottom; i++) {
+ for (uint16 j = q.left; j < q.right; j++) {
+ if (*s != transparentColor)
+ *d = *s;
+
+ s++;
+ d++;
+ }
+
+ s += sPitch;
+ d += dPitch;
+ }
+
}
}
+
}
+
} // namespace Parallaction
diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp
index 58fb02a750..c19d6ae5e5 100644
--- a/engines/parallaction/graphics.cpp
+++ b/engines/parallaction/graphics.cpp
@@ -33,6 +33,11 @@
namespace Parallaction {
+// this is the size of the receiving buffer for unpacked frames,
+// since BRA uses some insanely big animations.
+#define MAXIMUM_UNPACKED_BITMAP_SIZE 640*401
+
+
void Gfx::registerVar(const Common::String &name, int32 initialValue) {
if (_vars.contains(name)) {
warning("Variable '%s' already registered, ignoring initial value.\n", name.c_str());
@@ -64,10 +69,6 @@ int32 Gfx::getVar(const Common::String &name) {
#define LABEL_TRANSPARENT_COLOR 0xFF
-#define BALLOON_TRANSPARENT_COLOR 2
-
-
-int16 Gfx::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };
void halfbritePixel(int x, int y, int color, void *data) {
byte *buffer = (byte*)data;
@@ -152,6 +153,13 @@ void Palette::setEntry(uint index, int red, int green, int blue) {
_data[index*3+2] = blue & 0xFF;
}
+void Palette::getEntry(uint index, int &red, int &green, int &blue) {
+ assert(index < _colors);
+ red = _data[index*3];
+ green = _data[index*3+1];
+ blue = _data[index*3+2];
+}
+
void Palette::makeGrayscale() {
byte v;
for (uint16 i = 0; i < _colors; i++) {
@@ -238,37 +246,6 @@ void Palette::rotate(uint first, uint last, bool forward) {
}
-#define BALLOON_TAIL_WIDTH 12
-#define BALLOON_TAIL_HEIGHT 10
-
-
-byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = {
- {
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- },
- {
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02
- }
-};
-
void Gfx::setPalette(Palette pal) {
byte sysPal[256*4];
@@ -292,7 +269,7 @@ void Gfx::animatePalette() {
PaletteFxRange *range;
for (uint16 i = 0; i < 4; i++) {
- range = &_backgroundInfo.ranges[i];
+ range = &_backgroundInfo->ranges[i];
if ((range->_flags & 1) == 0) continue; // animated palette
range->_timer += range->_step * 2; // update timer
@@ -337,10 +314,14 @@ void Gfx::setProjectorProgram(int16 *data) {
}
void Gfx::drawInventory() {
-
+/*
if ((_engineFlags & kEngineInventory) == 0) {
return;
}
+*/
+ if (_vm->_input->_inputMode != Input::kInputModeInventory) {
+ return;
+ }
Common::Rect r;
_vm->_inventoryRenderer->getRect(r);
@@ -356,21 +337,19 @@ void Gfx::drawItems() {
Graphics::Surface *surf = g_system->lockScreen();
for (uint i = 0; i < _numItems; i++) {
- blt(_items[i].rect, _items[i].data->getData(_items[i].frame), surf, LAYER_FOREGROUND, _items[i].transparentColor);
+ drawGfxObject(_items[i].data, *surf, false);
}
g_system->unlockScreen();
}
void Gfx::drawBalloons() {
- if (_numBalloons == 0) {
+ if (_balloons.size() == 0) {
return;
}
Graphics::Surface *surf = g_system->lockScreen();
- for (uint i = 0; i < _numBalloons; i++) {
- Common::Rect r(_balloons[i].surface.w, _balloons[i].surface.h);
- r.moveTo(_balloons[i].x, _balloons[i].y);
- blt(r, (byte*)_balloons[i].surface.getBasePtr(0, 0), surf, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR);
+ for (uint i = 0; i < _balloons.size(); i++) {
+ drawGfxObject(_balloons[i], *surf, false);
}
g_system->unlockScreen();
}
@@ -380,29 +359,37 @@ void Gfx::clearScreen() {
}
void Gfx::beginFrame() {
-
- int32 oldBackgroundMode = _varBackgroundMode;
- _varBackgroundMode = getVar("background_mode");
-
- if (oldBackgroundMode != _varBackgroundMode) {
- switch (_varBackgroundMode) {
- case 1:
- _bitmapMask.free();
- break;
- case 2:
- _bitmapMask.create(_backgroundInfo.width, _backgroundInfo.height, 1);
- byte *data = (byte*)_bitmapMask.pixels;
- for (uint y = 0; y < _bitmapMask.h; y++) {
- for (uint x = 0; x < _bitmapMask.w; x++) {
- *data++ = _backgroundInfo.mask.getValue(x, y);
+ _skipBackground = (_backgroundInfo->bg.pixels == 0); // don't render frame if background is missing
+
+ if (!_skipBackground) {
+ int32 oldBackgroundMode = _varBackgroundMode;
+ _varBackgroundMode = getVar("background_mode");
+ if (oldBackgroundMode != _varBackgroundMode) {
+ switch (_varBackgroundMode) {
+ case 1:
+ _bitmapMask.free();
+ break;
+ case 2:
+ _bitmapMask.create(_backgroundInfo->width, _backgroundInfo->height, 1);
+ byte *data = (byte*)_bitmapMask.pixels;
+ for (uint y = 0; y < _bitmapMask.h; y++) {
+ for (uint x = 0; x < _bitmapMask.w; x++) {
+ *data++ = _backgroundInfo->mask.getValue(x, y);
+ }
}
+ break;
}
- break;
}
}
+ _varDrawPathZones = getVar("draw_path_zones");
+ if (_varDrawPathZones == 1 && _vm->getGameType() != GType_BRA) {
+ setVar("draw_path_zones", 0);
+ _varDrawPathZones = 0;
+ warning("Path zones are supported only in Big Red Adventure");
+ }
- if (_vm->_screenWidth >= _backgroundInfo.width) {
+ if (_skipBackground || (_vm->_screenWidth >= _backgroundInfo->width)) {
_varScrollX = 0;
} else {
_varScrollX = getVar("scroll_x");
@@ -427,24 +414,38 @@ int32 Gfx::getRenderMode(const char *type) {
void Gfx::updateScreen() {
- // background may not cover the whole screen, so adjust bulk update size
- uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo.width);
- uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo.height);
-
- byte *backgroundData = 0;
- uint16 backgroundPitch = 0;
- switch (_varBackgroundMode) {
- case 1:
- backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0);
- backgroundPitch = _backgroundInfo.bg.pitch;
- break;
- case 2:
- backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0);
- backgroundPitch = _bitmapMask.pitch;
- break;
+ if (!_skipBackground) {
+ // background may not cover the whole screen, so adjust bulk update size
+ uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo->width);
+ uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo->height);
+
+ byte *backgroundData = 0;
+ uint16 backgroundPitch = 0;
+ switch (_varBackgroundMode) {
+ case 1:
+ backgroundData = (byte*)_backgroundInfo->bg.getBasePtr(_varScrollX, 0);
+ backgroundPitch = _backgroundInfo->bg.pitch;
+ break;
+ case 2:
+ backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0);
+ backgroundPitch = _bitmapMask.pitch;
+ break;
+ }
+ g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->x, _backgroundInfo->y, w, h);
}
- g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h);
+ if (_varDrawPathZones == 1) {
+ Graphics::Surface *surf = g_system->lockScreen();
+ ZoneList::iterator b = _vm->_location._zones.begin();
+ ZoneList::iterator e = _vm->_location._zones.end();
+ for (; b != e; b++) {
+ ZonePtr z = *b;
+ if (z->_type & kZonePath) {
+ surf->frameRect(Common::Rect(z->_left, z->_top, z->_right, z->_bottom), 2);
+ }
+ }
+ g_system->unlockScreen();
+ }
_varRenderMode = _varAnimRenderMode;
@@ -498,17 +499,17 @@ void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask)
Common::Rect r(surf.w, surf.h);
r.moveTo(x, y);
- uint16 z = (mask) ? _backgroundInfo.getLayer(y) : LAYER_FOREGROUND;
- blt(r, (byte*)surf.pixels, &_backgroundInfo.bg, z, 0);
+ uint16 z = (mask) ? _backgroundInfo->getLayer(y) : LAYER_FOREGROUND;
+ blt(r, (byte*)surf.pixels, &_backgroundInfo->bg, z, 0);
}
void Gfx::fillBackground(const Common::Rect& r, byte color) {
- _backgroundInfo.bg.fillRect(r, color);
+ _backgroundInfo->bg.fillRect(r, color);
}
void Gfx::invertBackground(const Common::Rect& r) {
- byte *d = (byte*)_backgroundInfo.bg.getBasePtr(r.left, r.top);
+ byte *d = (byte*)_backgroundInfo->bg.getBasePtr(r.left, r.top);
for (int i = 0; i < r.height(); i++) {
for (int j = 0; j < r.width(); j++) {
@@ -516,146 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) {
d++;
}
- d += (_backgroundInfo.bg.pitch - r.width());
- }
-
-}
-
-// this is the maximum size of an unpacked frame in BRA
-byte _unpackedBitmap[640*401];
-
-#if 0
-void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- byte *d = _unpackedBitmap;
-
- while (size > 0) {
-
- uint8 p = *data++;
- size--;
- uint8 color = p & 0xF;
- uint8 repeat = (p & 0xF0) >> 4;
- if (repeat == 0) {
- repeat = *data++;
- size--;
- }
-
- memset(d, color, repeat);
- d += repeat;
- }
-
- blt(r, _unpackedBitmap, surf, z, transparentColor);
-}
-#endif
-void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- byte *d = _unpackedBitmap;
- uint pixelsLeftInLine = r.width();
-
- while (size > 0) {
- uint8 p = *data++;
- size--;
- uint8 color = p & 0xF;
- uint8 repeat = (p & 0xF0) >> 4;
- if (repeat == 0) {
- repeat = *data++;
- size--;
- }
- if (repeat == 0) {
- // end of line
- repeat = pixelsLeftInLine;
- pixelsLeftInLine = r.width();
- } else {
- pixelsLeftInLine -= repeat;
- }
-
- memset(d, color, repeat);
- d += repeat;
- }
-
- blt(r, _unpackedBitmap, surf, z, transparentColor);
-}
-
-
-void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- Common::Point dp;
- Common::Rect q(r);
-
- Common::Rect clipper(surf->w, surf->h);
-
- q.clip(clipper);
- if (!q.isValidRect()) return;
-
- dp.x = q.left;
- dp.y = q.top;
-
- q.translate(-r.left, -r.top);
-
- byte *s = data + q.left + q.top * r.width();
- byte *d = (byte*)surf->getBasePtr(dp.x, dp.y);
-
- uint sPitch = r.width() - q.width();
- uint dPitch = surf->w - q.width();
-
-
- if (_varRenderMode == 2) {
-
- for (uint16 i = 0; i < q.height(); i++) {
-
- for (uint16 j = 0; j < q.width(); j++) {
- if (*s != transparentColor) {
- if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) {
- byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i);
- if (z >= v) *d = 5;
- } else {
- *d = 5;
- }
- }
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- } else {
- if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) {
-
- for (uint16 i = 0; i < q.height(); i++) {
-
- for (uint16 j = 0; j < q.width(); j++) {
- if (*s != transparentColor) {
- byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i);
- if (z >= v) *d = *s;
- }
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- } else {
-
- for (uint16 i = q.top; i < q.bottom; i++) {
- for (uint16 j = q.left; j < q.right; j++) {
- if (*s != transparentColor)
- *d = *s;
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- }
+ d += (_backgroundInfo->bg.pitch - r.width());
}
}
@@ -669,10 +531,9 @@ void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) {
surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR);
}
-Label *Gfx::renderFloatingLabel(Font *font, char *text) {
+uint Gfx::renderFloatingLabel(Font *font, char *text) {
- Label *label = new Label;
- Graphics::Surface *cnv = &label->_cnv;
+ Graphics::Surface *cnv = new Graphics::Surface;
uint w, h;
@@ -698,80 +559,38 @@ Label *Gfx::renderFloatingLabel(Font *font, char *text) {
drawText(font, cnv, 0, 0, text, 0);
}
- return label;
-}
+ GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel");
+ obj->transparentKey = LABEL_TRANSPARENT_COLOR;
+ obj->layer = LAYER_FOREGROUND;
-uint Gfx::createLabel(Font *font, const char *text, byte color) {
- assert(_numLabels < MAX_NUM_LABELS);
-
- Label *label = new Label;
- Graphics::Surface *cnv = &label->_cnv;
-
- uint w, h;
-
- if (_vm->getPlatform() == Common::kPlatformAmiga) {
- w = font->getStringWidth(text) + 2;
- h = font->height() + 2;
-
- setupLabelSurface(*cnv, w, h);
-
- drawText(font, cnv, 0, 2, text, 0);
- drawText(font, cnv, 2, 0, text, color);
- } else {
- w = font->getStringWidth(text);
- h = font->height();
-
- setupLabelSurface(*cnv, w, h);
-
- drawText(font, cnv, 0, 0, text, color);
- }
-
- uint id = _numLabels;
- _labels[id] = label;
- _numLabels++;
+ uint id = _labels.size();
+ _labels.insert_at(id, obj);
return id;
}
-void Gfx::showLabel(uint id, int16 x, int16 y) {
- assert(id < _numLabels);
- _labels[id]->_visible = true;
+void Gfx::showFloatingLabel(uint label) {
+ assert(label < _labels.size());
- if (x == CENTER_LABEL_HORIZONTAL) {
- x = CLIP<int16>((_vm->_screenWidth - _labels[id]->_cnv.w) / 2, 0, _vm->_screenWidth/2);
- }
-
- if (y == CENTER_LABEL_VERTICAL) {
- y = CLIP<int16>((_vm->_screenHeight - _labels[id]->_cnv.h) / 2, 0, _vm->_screenHeight/2);
- }
+ hideFloatingLabel();
- _labels[id]->_pos.x = x;
- _labels[id]->_pos.y = y;
-}
+ _labels[label]->x = -1000;
+ _labels[label]->y = -1000;
+ _labels[label]->setFlags(kGfxObjVisible);
-void Gfx::hideLabel(uint id) {
- assert(id < _numLabels);
- _labels[id]->_visible = false;
+ _floatingLabel = label;
}
-void Gfx::freeLabels() {
- for (uint i = 0; i < _numLabels; i++) {
- delete _labels[i];
+void Gfx::hideFloatingLabel() {
+ if (_floatingLabel != NO_FLOATING_LABEL) {
+ _labels[_floatingLabel]->clearFlags(kGfxObjVisible);
}
- _numLabels = 0;
+ _floatingLabel = NO_FLOATING_LABEL;
}
-void Gfx::setFloatingLabel(Label *label) {
- _floatingLabel = label;
-
- if (_floatingLabel) {
- _floatingLabel->resetPosition();
- }
-}
-
void Gfx::updateFloatingLabel() {
- if (!_floatingLabel) {
+ if (_floatingLabel == NO_FLOATING_LABEL) {
return;
}
@@ -780,113 +599,115 @@ void Gfx::updateFloatingLabel() {
Common::Point cursor;
_vm->_input->getCursorPos(cursor);
+ Common::Rect r;
+ _labels[_floatingLabel]->getRect(0, r);
+
if (_vm->_input->_activeItem._id != 0) {
- _si = cursor.x + 16 - _floatingLabel->_cnv.w/2;
+ _si = cursor.x + 16 - r.width()/2;
_di = cursor.y + 34;
} else {
- _si = cursor.x + 8 - _floatingLabel->_cnv.w/2;
+ _si = cursor.x + 8 - r.width()/2;
_di = cursor.y + 21;
}
if (_si < 0) _si = 0;
if (_di > 190) _di = 190;
- if (_floatingLabel->_cnv.w + _si > _vm->_screenWidth)
- _si = _vm->_screenWidth - _floatingLabel->_cnv.w;
+ if (r.width() + _si > _vm->_screenWidth)
+ _si = _vm->_screenWidth - r.width();
- _floatingLabel->_pos.x = _si;
- _floatingLabel->_pos.y = _di;
+ _labels[_floatingLabel]->x = _si;
+ _labels[_floatingLabel]->y = _di;
}
-void Gfx::drawLabels() {
- if ((!_floatingLabel) && (_numLabels == 0)) {
- return;
- }
- updateFloatingLabel();
- Graphics::Surface* surf = g_system->lockScreen();
- for (uint i = 0; i < _numLabels; i++) {
- if (_labels[i]->_visible) {
- Common::Rect r(_labels[i]->_cnv.w, _labels[i]->_cnv.h);
- r.moveTo(_labels[i]->_pos);
- blt(r, (byte*)_labels[i]->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR);
- }
- }
- if (_floatingLabel) {
- Common::Rect r(_floatingLabel->_cnv.w, _floatingLabel->_cnv.h);
- r.moveTo(_floatingLabel->_pos);
- blt(r, (byte*)_floatingLabel->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR);
- }
+uint Gfx::createLabel(Font *font, const char *text, byte color) {
+ assert(_labels.size() < MAX_NUM_LABELS);
- g_system->unlockScreen();
-}
+ Graphics::Surface *cnv = new Graphics::Surface;
-Label::Label() {
- resetPosition();
- _visible = false;
-}
+ uint w, h;
-Label::~Label() {
- free();
-}
+ if (_vm->getPlatform() == Common::kPlatformAmiga) {
+ w = font->getStringWidth(text) + 2;
+ h = font->height() + 2;
-void Label::free() {
- _cnv.free();
- resetPosition();
-}
+ setupLabelSurface(*cnv, w, h);
-void Label::resetPosition() {
- _pos.x = -1000;
- _pos.y = -1000;
-}
+ drawText(font, cnv, 0, 2, text, 0);
+ drawText(font, cnv, 2, 0, text, color);
+ } else {
+ w = font->getStringWidth(text);
+ h = font->height();
+ setupLabelSurface(*cnv, w, h);
-void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {
+ drawText(font, cnv, 0, 0, text, color);
+ }
- uint16 lines = 0;
- uint16 w = 0;
- *width = 0;
+ GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label");
+ obj->transparentKey = LABEL_TRANSPARENT_COLOR;
+ obj->layer = LAYER_FOREGROUND;
- uint16 blankWidth = font->getStringWidth(" ");
- uint16 tokenWidth = 0;
+ int id = _labels.size();
- char token[MAX_TOKEN_LEN];
+ _labels.insert_at(id, obj);
- while (strlen(text) != 0) {
+ return id;
+}
- text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
- tokenWidth = font->getStringWidth(token);
+void Gfx::showLabel(uint id, int16 x, int16 y) {
+ assert(id < _labels.size());
+ _labels[id]->setFlags(kGfxObjVisible);
- w += tokenWidth;
+ Common::Rect r;
+ _labels[id]->getRect(0, r);
- if (!scumm_stricmp(token, "%p")) {
- lines++;
- } else {
- if (w > maxwidth) {
- w -= tokenWidth;
- lines++;
- if (w > *width)
- *width = w;
+ if (x == CENTER_LABEL_HORIZONTAL) {
+ x = CLIP<int16>((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2);
+ }
- w = tokenWidth;
- }
- }
+ if (y == CENTER_LABEL_VERTICAL) {
+ y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2);
+ }
- w += blankWidth;
- text = Common::ltrim(text);
+ _labels[id]->x = x;
+ _labels[id]->y = y;
+}
+
+void Gfx::hideLabel(uint id) {
+ assert(id < _labels.size());
+ _labels[id]->clearFlags(kGfxObjVisible);
+}
+
+void Gfx::freeLabels() {
+ for (uint i = 0; i < _labels.size(); i++) {
+ delete _labels[i];
}
+ _labels.clear();
+ _floatingLabel = NO_FLOATING_LABEL;
+}
- if (*width < w) *width = w;
- *width += 10;
+void Gfx::drawLabels() {
+ if (_labels.size() == 0) {
+ return;
+ }
- *height = lines * 10 + 20;
+ updateFloatingLabel();
- return;
+ Graphics::Surface* surf = g_system->lockScreen();
+
+ for (uint i = 0; i < _labels.size(); i++) {
+ drawGfxObject(_labels[i], *surf, false);
+ }
+
+ g_system->unlockScreen();
}
+
void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {
byte *s = (byte*)src.getBasePtr(r.left, r.top);
@@ -903,7 +724,7 @@ void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surf
}
void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) {
- copyRect(r, _backgroundInfo.bg, dst);
+ copyRect(r, _backgroundInfo->bg, dst);
}
@@ -917,17 +738,20 @@ Gfx::Gfx(Parallaction* vm) :
setPalette(_palette);
- _numBalloons = 0;
_numItems = 0;
- _numLabels = 0;
- _floatingLabel = 0;
+ _floatingLabel = NO_FLOATING_LABEL;
_screenX = 0;
_screenY = 0;
+ _backgroundInfo = 0;
+
_halfbrite = false;
_hbCircleRadius = 0;
+ _unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE];
+ assert(_unpackedBitmap);
+
registerVar("background_mode", 1);
_varBackgroundMode = 1;
@@ -937,26 +761,39 @@ Gfx::Gfx(Parallaction* vm) :
registerVar("anim_render_mode", 1);
registerVar("misc_render_mode", 1);
+ registerVar("draw_path_zones", 0);
+
+ if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) {
+ // this loads the backup palette needed by the PC version of BRA (see setBackground()).
+ BackgroundInfo paletteInfo;
+ _disk->loadSlide(paletteInfo, "pointer");
+ _backupPal.clone(paletteInfo.palette);
+ }
+
return;
}
Gfx::~Gfx() {
- freeBackground();
+ delete _backgroundInfo;
+
+ freeLabels();
+
+ delete []_unpackedBitmap;
return;
}
-int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) {
+int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) {
int id = _numItems;
_items[id].data = frames;
- _items[id].x = x;
- _items[id].y = y;
-
- _items[id].transparentColor = transparentColor;
+ _items[id].data->x = x;
+ _items[id].data->y = y;
+ _items[id].data->layer = LAYER_FOREGROUND;
+ _items[id].data->transparentKey = transparentColor;
_numItems++;
@@ -965,223 +802,58 @@ int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) {
void Gfx::setItemFrame(uint item, uint16 f) {
assert(item < _numItems);
- _items[item].frame = f;
- _items[item].data->getRect(f, _items[item].rect);
- _items[item].rect.moveTo(_items[item].x, _items[item].y);
-}
-
-Gfx::Balloon* Gfx::getBalloon(uint id) {
- assert(id < _numBalloons);
- return &_balloons[id];
-}
-
-int Gfx::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) {
- assert(_numBalloons < 5);
-
- int id = _numBalloons;
-
- Gfx::Balloon *balloon = &_balloons[id];
-
- int16 real_h = (winding == -1) ? h : h + 9;
- balloon->surface.create(w, real_h, 1);
- balloon->surface.fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR);
-
- Common::Rect r(w, h);
- balloon->surface.fillRect(r, 0);
- balloon->outerBox = r;
-
- r.grow(-borderThickness);
- balloon->surface.fillRect(r, 1);
- balloon->innerBox = r;
-
- if (winding != -1) {
- // draws tail
- // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill.
- winding = (winding == 0 ? 1 : 0);
- Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT);
- s.moveTo(r.width()/2 - 5, r.bottom - 1);
- blt(s, _resBalloonTail[winding], &balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR);
- }
-
- _numBalloons++;
-
- return id;
-}
-
-int Gfx::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
-
- int16 w, h;
-
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
-
- int id = createBalloon(w+5, h, winding, 1);
- Gfx::Balloon *balloon = &_balloons[id];
-
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-
- balloon->x = x;
- balloon->y = y;
-
- return id;
-}
-
-int Gfx::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
-
- int16 w, h;
-
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
-
- int id = createBalloon(w+5, h, winding, 1);
- Gfx::Balloon *balloon = &_balloons[id];
-
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-
- balloon->x = _dialogueBalloonX[id];
- balloon->y = 10;
-
- if (id > 0) {
- balloon->y += _balloons[id - 1].y + _balloons[id - 1].outerBox.height();
- }
-
-
- return id;
+ _items[item].data->frame = f;
+ _items[item].data->setFlags(kGfxObjVisible);
}
-void Gfx::setBalloonText(uint id, char *text, byte textColor) {
- Gfx::Balloon *balloon = getBalloon(id);
- balloon->surface.fillRect(balloon->innerBox, 1);
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-}
+GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) {
-int Gfx::setLocationBalloon(char *text, bool endGame) {
+ GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text);
- int16 w, h;
+ obj->layer = LAYER_FOREGROUND;
+ obj->frame = 0;
+ obj->setFlags(kGfxObjVisible);
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+ _balloons.push_back(obj);
- int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR);
- Gfx::Balloon *balloon = &_balloons[id];
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, 0, MAX_BALLOON_WIDTH);
-
- balloon->x = 5;
- balloon->y = 5;
-
- return id;
+ return obj;
}
-int Gfx::hitTestDialogueBalloon(int x, int y) {
-
- Common::Point p;
-
- for (uint i = 0; i < _numBalloons; i++) {
- p.x = x - _balloons[i].x;
- p.y = y - _balloons[i].y;
-
- if (_balloons[i].innerBox.contains(p))
- return i;
+void Gfx::destroyBalloons() {
+ for (uint i = 0; i < _balloons.size(); i++) {
+ delete _balloons[i];
}
-
- return -1;
-}
-
-
-void Gfx::freeBalloons() {
- for (uint i = 0; i < _numBalloons; i++) {
- _balloons[i].surface.free();
- }
- _numBalloons = 0;
+ _balloons.clear();
}
void Gfx::freeItems() {
_numItems = 0;
}
-void Gfx::hideDialogueStuff() {
- freeItems();
- freeBalloons();
-}
-
-void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) {
- byte *dst = (byte*)surf->getBasePtr(x, y);
- font->setColor(color);
- font->drawString(dst, surf->w, text);
-}
-
-void Gfx::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) {
-
- uint16 lines = 0;
- uint16 linewidth = 0;
-
- uint16 rx = 10;
- uint16 ry = 4;
-
- uint16 blankWidth = font->getStringWidth(" ");
- uint16 tokenWidth = 0;
-
- char token[MAX_TOKEN_LEN];
-
- if (wrapwidth == -1)
- wrapwidth = _vm->_screenWidth;
-
- while (strlen(text) > 0) {
-
- text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
-
- if (!scumm_stricmp(token, "%p")) {
- lines++;
- rx = 10;
- ry = 4 + lines*10; // y
-
- strcpy(token, "> .......");
- strncpy(token+2, _password, strlen(_password));
- tokenWidth = font->getStringWidth(token);
- } else {
- tokenWidth = font->getStringWidth(token);
-
- linewidth += tokenWidth;
+void Gfx::setBackground(uint type, BackgroundInfo *info) {
+ delete _backgroundInfo;
+ _backgroundInfo = info;
- if (linewidth > wrapwidth) {
- // wrap line
- lines++;
- rx = 10; // x
- ry = 4 + lines*10; // y
- linewidth = tokenWidth;
- }
-
- if (!scumm_stricmp(token, "%s")) {
- sprintf(token, "%d", _score);
+ if (type == kBackgroundLocation) {
+ // The PC version of BRA needs the entries 20-31 of the palette to be constant, but
+ // the background resource files are screwed up. The right colors come from an unused
+ // bitmap (pointer.bmp). Nothing is known about the Amiga version so far.
+ if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) {
+ int r, g, b;
+ for (uint i = 16; i < 32; i++) {
+ _backupPal.getEntry(i, r, g, b);
+ _backgroundInfo->palette.setEntry(i, r, g, b);
}
-
}
- drawText(font, surf, rx, ry, token, color);
-
- rx += tokenWidth + blankWidth;
- linewidth += blankWidth;
-
- text = Common::ltrim(text);
- }
-
-}
-
-void Gfx::freeBackground() {
- _backgroundInfo.free();
-}
-
-void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) {
-
- freeBackground();
-
- if (type == kBackgroundLocation) {
- _disk->loadScenery(_backgroundInfo, name, mask, path);
- setPalette(_backgroundInfo.palette);
- _palette.clone(_backgroundInfo.palette);
+ setPalette(_backgroundInfo->palette);
+ _palette.clone(_backgroundInfo->palette);
} else {
- _disk->loadSlide(_backgroundInfo, name);
- setPalette(_backgroundInfo.palette);
+ for (uint i = 0; i < 6; i++)
+ _backgroundInfo->ranges[i]._flags = 0; // disable palette cycling for slides
+ setPalette(_backgroundInfo->palette);
}
-
}
} // namespace Parallaction
diff --git a/engines/parallaction/graphics.h b/engines/parallaction/graphics.h
index 894e0fd678..23b4569c6a 100644
--- a/engines/parallaction/graphics.h
+++ b/engines/parallaction/graphics.h
@@ -95,6 +95,7 @@ public:
}
~SurfaceToFrames() {
+ _surf->free();
delete _surf;
}
@@ -156,11 +157,11 @@ struct SurfaceToMultiFrames : public Frames {
r.setHeight(_height);
}
uint getRawSize(uint16 index) {
- assert(index == 0);
+ assert(index < _num);
return getSize(index);
}
uint getSize(uint16 index) {
- assert(index == 0);
+ assert(index < _num);
return _width * _height;
}
@@ -260,6 +261,7 @@ public:
void makeBlack();
void setEntries(byte* data, uint first, uint num);
+ void getEntry(uint index, int &red, int &green, int &blue);
void setEntry(uint index, int red, int green, int blue);
void makeGrayscale();
void fadeTo(const Palette& target, uint step);
@@ -325,20 +327,6 @@ public:
#define CENTER_LABEL_HORIZONTAL -1
#define CENTER_LABEL_VERTICAL -1
-struct Label {
- Graphics::Surface _cnv;
-
- Common::Point _pos;
- bool _visible;
-
- Label();
- ~Label();
-
- void free();
- void resetPosition();
-};
-
-
#define MAX_BALLOON_WIDTH 130
@@ -353,25 +341,39 @@ class Disk;
enum {
kGfxObjVisible = 1,
+ kGfxObjNormal = 2,
+ kGfxObjCharacter = 4,
kGfxObjTypeDoor = 0,
kGfxObjTypeGet = 1,
- kGfxObjTypeAnim = 2
+ kGfxObjTypeAnim = 2,
+ kGfxObjTypeLabel = 3,
+ kGfxObjTypeBalloon = 4,
+ kGfxObjTypeCharacter = 8
+};
+
+enum {
+ kGfxObjDoorZ = -200,
+ kGfxObjGetZ = -100
};
class GfxObj {
char *_name;
Frames *_frames;
- uint32 _flags;
bool _keep;
public:
int16 x, y;
- uint16 z;
+
+ int32 z;
+
+ uint32 _flags;
+
uint type;
uint frame;
uint layer;
+ uint transparentKey;
GfxObj(uint type, Frames *frames, const char *name = NULL);
virtual ~GfxObj();
@@ -434,7 +436,7 @@ struct BackgroundInfo {
return LAYER_FOREGROUND;
}
- void free() {
+ ~BackgroundInfo() {
bg.free();
mask.free();
path.free();
@@ -452,49 +454,65 @@ enum {
kBackgroundSlide = 2
};
+
+class BalloonManager {
+public:
+ virtual ~BalloonManager() { }
+
+ virtual void freeBalloons() = 0;
+ virtual int setLocationBalloon(char *text, bool endGame) = 0;
+ virtual int setDialogueBalloon(char *text, uint16 winding, byte textColor) = 0;
+ virtual int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) = 0;
+ virtual void setBalloonText(uint id, char *text, byte textColor) = 0;
+ virtual int hitTestDialogueBalloon(int x, int y) = 0;
+};
+
+
typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> VarMap;
class Gfx {
+protected:
+ Parallaction* _vm;
+
public:
Disk *_disk;
VarMap _vars;
- GfxObjList _gfxobjList[3];
+ GfxObjList _gfxobjList;
GfxObj* loadAnim(const char *name);
GfxObj* loadGet(const char *name);
GfxObj* loadDoor(const char *name);
void drawGfxObjects(Graphics::Surface &surf);
void showGfxObj(GfxObj* obj, bool visible);
- void clearGfxObjects();
+ void clearGfxObjects(uint filter);
void sortAnimations();
+
// labels
- void setFloatingLabel(Label *label);
- Label *renderFloatingLabel(Font *font, char *text);
+ void showFloatingLabel(uint label);
+ void hideFloatingLabel();
+
+ uint renderFloatingLabel(Font *font, char *text);
uint createLabel(Font *font, const char *text, byte color);
void showLabel(uint id, int16 x, int16 y);
void hideLabel(uint id);
void freeLabels();
// dialogue balloons
- int setLocationBalloon(char *text, bool endGame);
- int setDialogueBalloon(char *text, uint16 winding, byte textColor);
- int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
- void setBalloonText(uint id, char *text, byte textColor);
- int hitTestDialogueBalloon(int x, int y);
- void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height);
+ GfxObj* registerBalloon(Frames *frames, const char *text);
+ void destroyBalloons();
// other items
- int setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor = 0);
+ int setItem(GfxObj* obj, uint16 x, uint16 y, byte transparentColor = 0);
void setItemFrame(uint item, uint16 f);
void hideDialogueStuff();
void freeBalloons();
void freeItems();
// background surface
- BackgroundInfo _backgroundInfo;
- void setBackground(uint type, const char* name, const char* mask, const char* path);
+ BackgroundInfo *_backgroundInfo;
+ void setBackground(uint type, BackgroundInfo *info);
void patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask = false);
void grabBackground(const Common::Rect& r, Graphics::Surface &dst);
void fillBackground(const Common::Rect& r, byte color);
@@ -532,52 +550,45 @@ public:
uint _screenX; // scrolling position
uint _screenY;
+ byte *_unpackedBitmap;
+
protected:
- Parallaction* _vm;
bool _halfbrite;
+ bool _skipBackground;
+
Common::Point _hbCirclePos;
int _hbCircleRadius;
+ // BRA specific
+ Palette _backupPal;
+
// frame data stored in programmable variables
int32 _varBackgroundMode; // 1 = normal, 2 = only mask
int32 _varScrollX;
int32 _varAnimRenderMode; // 1 = normal, 2 = flat
int32 _varMiscRenderMode; // 1 = normal, 2 = flat
int32 _varRenderMode;
+ int32 _varDrawPathZones; // 0 = don't draw, 1 = draw
Graphics::Surface _bitmapMask;
int32 getRenderMode(const char *type);
-protected:
- static int16 _dialogueBalloonX[5];
-
- struct Balloon {
- uint16 x;
- uint16 y;
- Common::Rect outerBox;
- Common::Rect innerBox;
- uint16 winding;
- Graphics::Surface surface;
- } _balloons[5];
-
- uint _numBalloons;
+public:
struct Item {
- uint16 x;
- uint16 y;
- uint16 frame;
- Frames *data;
-
- byte transparentColor;
- Common::Rect rect;
+ GfxObj *data;
} _items[14];
uint _numItems;
- #define MAX_NUM_LABELS 5
- Label* _labels[MAX_NUM_LABELS];
- uint _numLabels;
- Label *_floatingLabel;
+ #define MAX_NUM_LABELS 20
+ #define NO_FLOATING_LABEL 1000
+
+ typedef Common::Array<GfxObj*> GfxObjArray;
+ GfxObjArray _labels;
+ GfxObjArray _balloons;
+
+ uint _floatingLabel;
void drawInventory();
void updateFloatingLabel();
@@ -587,13 +598,10 @@ protected:
void copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst);
- int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
- Balloon *getBalloon(uint id);
-
// low level text and patches
void drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color);
- void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
+ void drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene);
void blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor);
void unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor);
};
diff --git a/engines/parallaction/gui.cpp b/engines/parallaction/gui.cpp
new file mode 100644
index 0000000000..2dbe64fcf6
--- /dev/null
+++ b/engines/parallaction/gui.cpp
@@ -0,0 +1,92 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "parallaction/gui.h"
+
+namespace Parallaction {
+
+bool MenuInputHelper::run() {
+ if (_newState == 0) {
+ debugC(3, kDebugExec, "MenuInputHelper has set NULL state");
+ return false;
+ }
+
+ if (_newState != _state) {
+ debugC(3, kDebugExec, "MenuInputHelper changing state to '%s'", _newState->_name.c_str());
+
+ _newState->enter();
+ _state = _newState;
+ }
+
+ _newState = _state->run();
+
+ return true;
+}
+
+MenuInputHelper::~MenuInputHelper() {
+ StateMap::iterator b = _map.begin();
+ for ( ; b != _map.end(); b++) {
+ delete b->_value;
+ }
+ _map.clear();
+}
+
+
+void Parallaction::runGuiFrame() {
+ if (_input->_inputMode != Input::kInputModeMenu) {
+ return;
+ }
+
+ if (!_menuHelper) {
+ error("No menu helper defined!");
+ }
+
+ bool res = _menuHelper->run();
+
+ if (!res) {
+ cleanupGui();
+ _input->_inputMode = Input::kInputModeGame;
+ }
+
+}
+
+void Parallaction::cleanupGui() {
+ delete _menuHelper;
+ _menuHelper = 0;
+}
+
+void Parallaction::setInternLanguage(uint id) {
+ //TODO: assert id!
+
+ _language = id;
+ _disk->setLanguage(id);
+}
+
+uint Parallaction::getInternLanguage() {
+ return _language;
+}
+
+
+} // namespace Parallaction
diff --git a/engines/parallaction/gui.h b/engines/parallaction/gui.h
new file mode 100644
index 0000000000..dc6d1bc71b
--- /dev/null
+++ b/engines/parallaction/gui.h
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef PARALLACTION_GUI_H
+#define PARALLACTION_GUI_H
+
+#include "common/system.h"
+#include "common/hashmap.h"
+
+#include "parallaction/input.h"
+#include "parallaction/parallaction.h"
+#include "parallaction/sound.h"
+
+
+namespace Parallaction {
+
+class MenuInputState;
+
+class MenuInputHelper {
+ typedef Common::HashMap<Common::String, MenuInputState*> StateMap;
+
+ StateMap _map;
+ MenuInputState *_state;
+ MenuInputState *_newState;
+
+public:
+ MenuInputHelper() : _state(0) {
+ }
+
+ ~MenuInputHelper();
+
+ void setState(const Common::String &name) {
+ // bootstrap routine
+ _newState = getState(name);
+ assert(_newState);
+ }
+
+ void addState(const Common::String &name, MenuInputState *state) {
+ _map.setVal(name, state);
+ }
+
+ MenuInputState *getState(const Common::String &name) {
+ return _map[name];
+ }
+
+ bool run();
+};
+
+class MenuInputState {
+
+protected:
+ MenuInputHelper *_helper;
+
+public:
+ MenuInputState(const Common::String &name, MenuInputHelper *helper) : _helper(helper), _name(name) {
+ debugC(3, kDebugExec, "MenuInputState(%s)", name.c_str());
+ _helper->addState(name, this);
+ }
+
+ Common::String _name;
+
+ virtual ~MenuInputState() { }
+
+ virtual MenuInputState* run() = 0;
+ virtual void enter() = 0;
+};
+
+
+} // namespace Parallaction
+
+#endif
diff --git a/engines/parallaction/gui_br.cpp b/engines/parallaction/gui_br.cpp
index c515299a34..3315433762 100644
--- a/engines/parallaction/gui_br.cpp
+++ b/engines/parallaction/gui_br.cpp
@@ -25,184 +25,268 @@
#include "common/system.h"
-
+#include "parallaction/gui.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
-enum MenuOptions {
- kMenuPart0 = 0,
- kMenuPart1 = 1,
- kMenuPart2 = 2,
- kMenuPart3 = 3,
- kMenuPart4 = 4,
- kMenuLoadGame = 5,
- kMenuQuit = 6
-};
-
+class SplashInputState_BR : public MenuInputState {
+protected:
+ Common::String _slideName;
+ uint32 _timeOut;
+ Common::String _nextState;
+ uint32 _startTime;
+ Palette blackPal;
+ Palette pal;
-void Parallaction_br::guiStart() {
+ Parallaction_br *_vm;
+ int _fadeSteps;
- // TODO: load progress value from special save game
- _progress = 3;
+public:
+ SplashInputState_BR(Parallaction_br *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) {
+ }
- int option = guiShowMenu();
- switch (option) {
- case kMenuQuit:
- _engineFlags |= kEngineQuit;
- break;
+ virtual MenuInputState* run() {
+ if (_fadeSteps > 0) {
+ pal.fadeTo(blackPal, 1);
+ _vm->_gfx->setPalette(pal);
+ _fadeSteps--;
+ // TODO: properly implement timers to avoid delay calls
+ _vm->_system->delayMillis(20);
+ return this;
+ }
- case kMenuLoadGame:
- warning("loadgame not yet implemented");
- break;
+ if (_fadeSteps == 0) {
+ _vm->freeBackground();
+ return _helper->getState(_nextState);
+ }
- default:
- _part = option;
- _disk->selectArchive(_partNames[_part]);
- startPart();
+ uint32 curTime = _vm->_system->getMillis();
+ if (curTime - _startTime > _timeOut) {
+ _fadeSteps = 64;
+ pal.clone(_vm->_gfx->_backgroundInfo->palette);
+ }
+ return this;
}
-}
-void Parallaction_br::guiSplash(const char *name) {
+ virtual void enter() {
+ _vm->_gfx->clearScreen();
+ _vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL);
+ _vm->_input->setMouseState(MOUSE_DISABLED);
- _gfx->clearScreen();
- _gfx->setBackground(kBackgroundSlide, name, 0, 0);
- _gfx->_backgroundInfo.x = (_screenWidth - _gfx->_backgroundInfo.width) >> 1;
- _gfx->_backgroundInfo.y = (_screenHeight - _gfx->_backgroundInfo.height) >> 1;
- _gfx->updateScreen();
- _system->delayMillis(600);
+ _startTime = g_system->getMillis();
+ _fadeSteps = -1;
+ }
+};
- Palette blackPal;
- Palette pal(_gfx->_backgroundInfo.palette);
- for (uint i = 0; i < 64; i++) {
- pal.fadeTo(blackPal, 1);
- _gfx->setPalette(pal);
- _gfx->updateScreen();
- _system->delayMillis(20);
+class SplashInputState0_BR : public SplashInputState_BR {
+
+public:
+ SplashInputState0_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro0", helper) {
+ _slideName = "dyna";
+ _timeOut = 600;
+ _nextState = "intro1";
}
+};
-}
+class SplashInputState1_BR : public SplashInputState_BR {
-#define MENUITEMS_X 250
-#define MENUITEMS_Y 200
+public:
+ SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) {
+ _slideName = "core";
+ _timeOut = 600;
+ _nextState = "mainmenu";
+ }
+};
-#define MENUITEM_WIDTH 190
-#define MENUITEM_HEIGHT 18
+class MainMenuInputState_BR : public MenuInputState {
+ Parallaction_br *_vm;
-Frames* Parallaction_br::guiRenderMenuItem(const char *text) {
- // this builds a surface containing two copies of the text.
- // one is in normal color, the other is inverted.
- // the two 'frames' are used to display selected/unselected menu items
+ #define MENUITEMS_X 250
+ #define MENUITEMS_Y 200
- Graphics::Surface *surf = new Graphics::Surface;
- surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1);
+ #define MENUITEM_WIDTH 190
+ #define MENUITEM_HEIGHT 18
- // build first frame to be displayed when item is not selected
- if (getPlatform() == Common::kPlatformPC) {
- _menuFont->setColor(0);
- } else {
- _menuFont->setColor(7);
- }
- _menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text);
+ Frames* renderMenuItem(const char *text) {
+ // this builds a surface containing two copies of the text.
+ // one is in normal color, the other is inverted.
+ // the two 'frames' are used to display selected/unselected menu items
- // build second frame to be displayed when item is selected
- _menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text);
- byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT);
- for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) {
- *s++ ^= 0xD;
- }
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1);
- // wrap the surface into the suitable Frames adapter
- return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
-}
+ // build first frame to be displayed when item is not selected
+ if (_vm->getPlatform() == Common::kPlatformPC) {
+ _vm->_menuFont->setColor(0);
+ } else {
+ _vm->_menuFont->setColor(7);
+ }
+ _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text);
+ // build second frame to be displayed when item is selected
+ _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text);
+ byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT);
+ for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) {
+ *s++ ^= 0xD;
+ }
-int Parallaction_br::guiShowMenu() {
- // TODO: filter menu entries according to progress in game
+ // wrap the surface into the suitable Frames adapter
+ return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
+ }
- #define NUM_MENULINES 7
- Frames *_lines[NUM_MENULINES];
-
- const char *menuStrings[NUM_MENULINES] = {
- "SEE INTRO",
- "NEW GAME",
- "SAVED GAME",
- "EXIT TO DOS",
- "PART 2",
- "PART 3",
- "PART 4"
+ enum MenuOptions {
+ kMenuPart0 = 0,
+ kMenuPart1 = 1,
+ kMenuPart2 = 2,
+ kMenuPart3 = 3,
+ kMenuPart4 = 4,
+ kMenuLoadGame = 5,
+ kMenuQuit = 6
};
- MenuOptions options[NUM_MENULINES] = {
- kMenuPart0,
- kMenuPart1,
- kMenuLoadGame,
- kMenuQuit,
- kMenuPart2,
- kMenuPart3,
- kMenuPart4
- };
+ #define NUM_MENULINES 7
+ GfxObj *_lines[NUM_MENULINES];
- _gfx->clearScreen();
- _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0);
- if (getPlatform() == Common::kPlatformPC) {
- _gfx->_backgroundInfo.x = 20;
- _gfx->_backgroundInfo.y = 50;
- }
+ static const char *_menuStrings[NUM_MENULINES];
+ static const MenuOptions _options[NUM_MENULINES];
- int availItems = 4 + _progress;
+ int _availItems;
+ int _selection;
- // TODO: keep track of and destroy menu item frames/surfaces
+ void cleanup() {
+ _vm->_system->showMouse(false);
+ _vm->hideDialogueStuff();
- int i;
- for (i = 0; i < availItems; i++) {
- _lines[i] = guiRenderMenuItem(menuStrings[i]);
- uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
- _gfx->setItemFrame(id, 0);
+ for (int i = 0; i < _availItems; i++) {
+ delete _lines[i];
+ }
}
- int selectedItem = -1;
+ void performChoice(int selectedItem) {
+ switch (selectedItem) {
+ case kMenuQuit:
+ _engineFlags |= kEngineQuit;
+ break;
- setMousePointer(0);
+ case kMenuLoadGame:
+ warning("loadgame not yet implemented");
+ break;
- uint32 event;
- Common::Point p;
- while (true) {
+ default:
+ _vm->startPart(selectedItem);
+ }
+ }
- _input->readInput();
+public:
+ MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) {
+ }
- event = _input->getLastButtonEvent();
- if ((event == kMouseLeftUp) && selectedItem >= 0)
- break;
+ virtual MenuInputState* run() {
- _input->getCursorPos(p);
+ int event = _vm->_input->getLastButtonEvent();
+ if ((event == kMouseLeftUp) && _selection >= 0) {
+ cleanup();
+ performChoice(_options[_selection]);
+ return 0;
+ }
+
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
if ((p.x > MENUITEMS_X) && (p.x < (MENUITEMS_X+MENUITEM_WIDTH)) && (p.y > MENUITEMS_Y)) {
- selectedItem = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT;
+ _selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT;
- if (!(selectedItem < availItems))
- selectedItem = -1;
+ if (!(_selection < _availItems))
+ _selection = -1;
} else
- selectedItem = -1;
+ _selection = -1;
- for (i = 0; i < availItems; i++) {
- _gfx->setItemFrame(i, selectedItem == i ? 1 : 0);
+ for (int i = 0; i < _availItems; i++) {
+ _vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0);
}
- _gfx->updateScreen();
- _system->delayMillis(20);
- }
- _system->showMouse(false);
- _gfx->hideDialogueStuff();
+ return this;
+ }
- for (i = 0; i < availItems; i++) {
- delete _lines[i];
+ virtual void enter() {
+ _vm->_gfx->clearScreen();
+ int x = 0, y = 0;
+ if (_vm->getPlatform() == Common::kPlatformPC) {
+ x = 20;
+ y = 50;
+ }
+ _vm->showSlide("tbra", x, y);
+
+ // TODO: load progress from savefile
+ int progress = 3;
+ _availItems = 4 + progress;
+
+ // TODO: keep track of and destroy menu item frames/surfaces
+ int i;
+ for (i = 0; i < _availItems; i++) {
+ _lines[i] = new GfxObj(0, renderMenuItem(_menuStrings[i]), "MenuItem");
+ uint id = _vm->_gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
+ _vm->_gfx->setItemFrame(id, 0);
+ }
+ _selection = -1;
+ _vm->setArrowCursor();
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
- return options[selectedItem];
+};
+
+const char *MainMenuInputState_BR::_menuStrings[NUM_MENULINES] = {
+ "SEE INTRO",
+ "NEW GAME",
+ "SAVED GAME",
+ "EXIT TO DOS",
+ "PART 2",
+ "PART 3",
+ "PART 4"
+};
+
+const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_options[NUM_MENULINES] = {
+ kMenuPart0,
+ kMenuPart1,
+ kMenuLoadGame,
+ kMenuQuit,
+ kMenuPart2,
+ kMenuPart3,
+ kMenuPart4
+};
+
+
+
+
+
+
+
+void Parallaction_br::startGui() {
+ _menuHelper = new MenuInputHelper;
+ new SplashInputState0_BR(this, _menuHelper);
+ new SplashInputState1_BR(this, _menuHelper);
+ new MainMenuInputState_BR(this, _menuHelper);
+
+ _menuHelper->setState("intro0");
+ _input->_inputMode = Input::kInputModeMenu;
+
+ do {
+ _input->readInput();
+ if (!_menuHelper->run()) break;
+ _gfx->beginFrame();
+ _gfx->updateScreen();
+ } while (true);
+
+ delete _menuHelper;
+ _menuHelper = 0;
+
+ _input->_inputMode = Input::kInputModeGame;
}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp
index 1d4d44fa46..815c27bd1c 100644
--- a/engines/parallaction/gui_ns.cpp
+++ b/engines/parallaction/gui_ns.cpp
@@ -24,7 +24,9 @@
*/
#include "common/system.h"
+#include "common/hashmap.h"
+#include "parallaction/gui.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
@@ -32,311 +34,567 @@
namespace Parallaction {
-const char *introMsg1[] = {
- "INSERISCI IL CODICE",
- "ENTREZ CODE",
- "ENTER CODE",
- "GIB DEN KODE EIN"
-};
+class SplashInputState_NS : public MenuInputState {
+protected:
+ Common::String _slideName;
+ uint32 _timeOut;
+ Common::String _nextState;
+ uint32 _startTime;
-const char *introMsg2[] = {
- "CODICE ERRATO",
- "CODE ERRONE",
- "WRONG CODE",
- "GIB DEN KODE EIN"
-};
+ Parallaction_ns *_vm;
-const char *introMsg3[] = {
- "PRESS LEFT MOUSE BUTTON",
- "TO SEE INTRO",
- "PRESS RIGHT MOUSE BUTTON",
- "TO START"
-};
+public:
+ SplashInputState_NS(Parallaction_ns *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) {
+ }
-const char *newGameMsg[] = {
- "NUOVO GIOCO",
- "NEUF JEU",
- "NEW GAME",
- "NEUES SPIEL"
+ virtual MenuInputState* run() {
+ uint32 curTime = g_system->getMillis();
+ if (curTime - _startTime > _timeOut) {
+ _vm->freeBackground();
+ return _helper->getState(_nextState);
+ }
+ return this;
+ }
+
+ virtual void enter() {
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ _vm->showSlide(_slideName.c_str());
+ _startTime = g_system->getMillis();
+ }
};
-const char *loadGameMsg[] = {
- "GIOCO SALVATO",
- "JEU SAUVE'",
- "SAVED GAME",
- "SPIEL GESPEICHERT"
+class SplashInputState0_NS : public SplashInputState_NS {
+
+public:
+ SplashInputState0_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro0", helper) {
+ _slideName = "intro";
+ _timeOut = 2000;
+ _nextState = "intro1";
+ }
};
+class SplashInputState1_NS : public SplashInputState_NS {
-#define BLOCK_WIDTH 16
-#define BLOCK_HEIGHT 24
+public:
+ SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) {
+ _slideName = "minintro";
+ _timeOut = 2000;
+ _nextState = "chooselanguage";
+ }
+};
-#define BLOCK_X 112
-#define BLOCK_Y 130
-#define BLOCK_SELECTION_X (BLOCK_X-1)
-#define BLOCK_SELECTION_Y (BLOCK_Y-1)
+class ChooseLanguageInputState_NS : public MenuInputState {
+ #define BLOCK_WIDTH 16
+ #define BLOCK_HEIGHT 24
-#define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
-#define BLOCK_Y_OFFSET 9
+ #define BLOCK_X 112
+ #define BLOCK_Y 130
-// destination slots for code blocks
-//
-#define SLOT_X 61
-#define SLOT_Y 64
-#define SLOT_WIDTH (BLOCK_WIDTH+2)
+ #define BLOCK_SELECTION_X (BLOCK_X-1)
+ #define BLOCK_SELECTION_Y (BLOCK_Y-1)
-#define PASSWORD_LEN 6
+ #define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
+ #define BLOCK_Y_OFFSET 9
-#define CHAR_DINO 0
-#define CHAR_DONNA 1
-#define CHAR_DOUGH 2
+ // destination slots for code blocks
+ //
+ #define SLOT_X 61
+ #define SLOT_Y 64
+ #define SLOT_WIDTH (BLOCK_WIDTH+2)
-static const uint16 _amigaKeys[][PASSWORD_LEN] = {
- { 5, 3, 6, 2, 2, 7 }, // dino
- { 0, 3, 6, 2, 2, 6 }, // donna
- { 1, 3 ,7, 2, 4, 6 } // dough
-};
+ int _language;
+ bool _allowChoice;
+ Common::String _nextState;
-static const uint16 _pcKeys[][PASSWORD_LEN] = {
- { 5, 3, 6, 1, 4, 7 }, // dino
- { 0, 2, 8, 5, 5, 1 }, // donna
- { 1, 7 ,7, 2, 2, 6 } // dough
-};
+ static const Common::Rect _dosLanguageSelectBlocks[4];
+ static const Common::Rect _amigaLanguageSelectBlocks[4];
+ const Common::Rect *_blocks;
-static const char *_charStartLocation[] = {
- "test.dino",
- "test.donna",
- "test.dough"
-};
+ Parallaction_ns *_vm;
-enum {
- NEW_GAME,
- LOAD_GAME
-};
+public:
+ ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) {
+ _allowChoice = false;
+ _nextState = "selectgame";
-enum {
- START_DEMO,
- START_INTRO,
- GAME_LOADED,
- SELECT_CHARACTER
-};
+ if (_vm->getPlatform() == Common::kPlatformAmiga) {
+ if (!(_vm->getFeatures() & GF_LANG_MULT)) {
+ if (_vm->getFeatures() & GF_DEMO) {
+ _language = 1; // Amiga Demo supports English
+ _nextState = "startdemo";
+ return;
+ } else {
+ _language = 0; // The only other non multi-lingual version just supports Italian
+ return;
+ }
+ }
-void Parallaction_ns::guiStart() {
+ _blocks = _amigaLanguageSelectBlocks;
+ } else {
+ _blocks = _dosLanguageSelectBlocks;
+ }
- _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ _language = -1;
+ _allowChoice = true;
+ }
- guiSplash();
+ virtual MenuInputState* run() {
+ if (!_allowChoice) {
+ _vm->setInternLanguage(_language);
+ return _helper->getState(_nextState);
+ }
- _language = guiChooseLanguage();
- _disk->setLanguage(_language);
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- int event;
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
- if (getFeatures() & GF_DEMO) {
- event = START_DEMO;
- } else {
- if (guiSelectGame() == NEW_GAME) {
- event = guiNewGame();
- } else {
- event = loadGame() ? GAME_LOADED : START_INTRO;
+ for (uint16 i = 0; i < 4; i++) {
+ if (_blocks[i].contains(p)) {
+ _vm->setInternLanguage(i);
+ _vm->beep();
+ _vm->_gfx->freeLabels();
+ return _helper->getState(_nextState);
+ }
}
+
+ return this;
}
- switch (event) {
- case START_DEMO:
- strcpy(_location._name, "fognedemo.dough");
- break;
+ virtual void enter() {
+ if (!_allowChoice) {
+ return;
+ }
- case START_INTRO:
- strcpy(_location._name, "fogne.dough");
- break;
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
- case GAME_LOADED:
- // nothing to do here
- return;
+ // user can choose language in this version
+ _vm->showSlide("lingua");
- case SELECT_CHARACTER:
- selectStartLocation();
- break;
+ uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1);
+ _vm->_gfx->showLabel(id, 60, 30);
+ _vm->setArrowCursor();
}
+};
- return;
-}
+const Common::Rect ChooseLanguageInputState_NS::_dosLanguageSelectBlocks[4] = {
+ Common::Rect( 80, 110, 128, 180 ), // Italian
+ Common::Rect( 129, 85, 177, 155 ), // French
+ Common::Rect( 178, 60, 226, 130 ), // English
+ Common::Rect( 227, 35, 275, 105 ) // German
+};
-void Parallaction_ns::selectStartLocation() {
- int character = guiSelectCharacter();
- if (character == -1)
- error("invalid character selected from menu screen");
+const Common::Rect ChooseLanguageInputState_NS::_amigaLanguageSelectBlocks[4] = {
+ Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version
+ Common::Rect( 129, 85, 177, 155 ), // French
+ Common::Rect( 178, 60, 226, 130 ), // English
+ Common::Rect( 227, 35, 275, 105 ) // German
+};
- scheduleLocationSwitch(_charStartLocation[character]);
-}
+class SelectGameInputState_NS : public MenuInputState {
+ int _choice, _oldChoice;
+ Common::String _nextState[2];
-void Parallaction_ns::guiSplash() {
+ uint _labels[2];
- showSlide("intro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
+ Parallaction_ns *_vm;
- showSlide("minintro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
-}
+ static const char *newGameMsg[4];
+ static const char *loadGameMsg[4];
-int Parallaction_ns::guiNewGame() {
+public:
+ SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) {
+ _choice = 0;
+ _oldChoice = -1;
- const char **v14 = introMsg3;
+ _nextState[0] = "newgame";
+ _nextState[1] = "loadgame";
+ }
- _disk->selectArchive("disk1");
- setBackground("test", NULL, NULL);
+ virtual MenuInputState *run() {
+ int event = _vm->_input->getLastButtonEvent();
- _gfx->updateScreen();
+ if (event == kMouseLeftUp) {
+ _vm->_gfx->freeLabels();
+ return _helper->getState(_nextState[_choice]);
+ }
- uint id[4];
- id[0] = _gfx->createLabel(_menuFont, v14[0], 1);
- id[1] = _gfx->createLabel(_menuFont, v14[1], 1);
- id[2] = _gfx->createLabel(_menuFont, v14[2], 1);
- id[3] = _gfx->createLabel(_menuFont, v14[3], 1);
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70);
- _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100);
- _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120);
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+ _choice = (p.x > 160) ? 1 : 0;
- _input->showCursor(false);
+ if (_choice != _oldChoice) {
+ if (_oldChoice != -1)
+ _vm->_gfx->hideLabel(_labels[_oldChoice]);
- _gfx->updateScreen();
+ if (_choice != -1)
+ _vm->_gfx->showLabel(_labels[_choice], 60, 30);
- _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp);
- uint32 event = _input->getLastButtonEvent();
+ _oldChoice = _choice;
+ }
- _input->showCursor(true);
+ return this;
+ }
- _gfx->freeLabels();
+ virtual void enter() {
+ _vm->showSlide("restore");
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
- if (event != kMouseRightUp) {
- return START_INTRO;
+ _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1);
+ _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1);
}
- return SELECT_CHARACTER;
-}
+};
-static const Common::Rect _dosLanguageSelectBlocks[4] = {
- Common::Rect( 80, 110, 128, 180 ), // Italian
- Common::Rect( 129, 85, 177, 155 ), // French
- Common::Rect( 178, 60, 226, 130 ), // English
- Common::Rect( 227, 35, 275, 105 ) // German
+const char *SelectGameInputState_NS::newGameMsg[4] = {
+ "NUOVO GIOCO",
+ "NEUF JEU",
+ "NEW GAME",
+ "NEUES SPIEL"
};
-static const Common::Rect _amigaLanguageSelectBlocks[4] = {
- Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version
- Common::Rect( 129, 85, 177, 155 ), // French
- Common::Rect( 178, 60, 226, 130 ), // English
- Common::Rect( 227, 35, 275, 105 ) // German
+const char *SelectGameInputState_NS::loadGameMsg[4] = {
+ "GIOCO SALVATO",
+ "JEU SAUVE'",
+ "SAVED GAME",
+ "SPIEL GESPEICHERT"
};
-uint16 Parallaction_ns::guiChooseLanguage() {
- const Common::Rect *blocks;
+class LoadGameInputState_NS : public MenuInputState {
+ bool _result;
+ Parallaction_ns *_vm;
+
+public:
+ LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { }
+
+ virtual MenuInputState* run() {
+ if (!_result) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ }
+ return 0;
+ }
+
+ virtual void enter() {
+ _result = _vm->loadGame();
+ }
+};
+
+
- if (getPlatform() == Common::kPlatformAmiga) {
- if (!(getFeatures() & GF_LANG_MULT)) {
- if (getFeatures() & GF_DEMO) {
- return 1; // Amiga Demo supports English
- } else {
- return 0; // The only other non multi-lingual version just supports Italian
+class NewGameInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+
+ static const char *introMsg3[4];
+
+public:
+ NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) {
+ }
+
+ virtual MenuInputState* run() {
+ int event = _vm->_input->getLastButtonEvent();
+
+ if (event == kMouseLeftUp || event == kMouseRightUp) {
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+ _vm->_gfx->freeLabels();
+
+ if (event == kMouseLeftUp) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ return 0;
}
+
+ return _helper->getState("selectcharacter");
}
- blocks = _amigaLanguageSelectBlocks;
- } else {
- blocks = _dosLanguageSelectBlocks;
+ return this;
+ }
+
+ virtual void enter() {
+ _vm->_disk->selectArchive("disk1");
+ _vm->setBackground("test", NULL, NULL);
+ _vm->_input->setMouseState(MOUSE_ENABLED_HIDE);
+
+ uint id[4];
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[0], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[1], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[2], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[3], 1);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70);
+ _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100);
+ _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120);
+ }
+};
+
+const char *NewGameInputState_NS::introMsg3[4] = {
+ "PRESS LEFT MOUSE BUTTON",
+ "TO SEE INTRO",
+ "PRESS RIGHT MOUSE BUTTON",
+ "TO START"
+};
+
+
+
+class StartDemoInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+
+public:
+ StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) {
}
- // user can choose language in dos version
- showSlide("lingua");
+ virtual MenuInputState* run() {
+ _vm->scheduleLocationSwitch("fognedemo.dough");
+ return 0;
+ }
- uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1);
- _gfx->showLabel(id, 60, 30);
+ virtual void enter() {
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ }
+};
- setArrowCursor();
+class SelectCharacterInputState_NS : public MenuInputState {
- Common::Point p;
+ #define PASSWORD_LEN 6
- int selection = -1;
- while (selection == -1) {
- _input->waitUntilLeftClick();
- _input->getCursorPos(p);
- for (uint16 i = 0; i < 4; i++) {
- if (blocks[i].contains(p)) {
+ #define CHAR_DINO 0
+ #define CHAR_DONNA 1
+ #define CHAR_DOUGH 2
+
+ static const Common::Rect codeSelectBlocks[9];
+ static const Common::Rect codeTrueBlocks[9];
+
+ Parallaction_ns *_vm;
+
+ int guiGetSelectedBlock(const Common::Point &p) {
+
+ int selection = -1;
+
+ for (uint16 i = 0; i < 9; i++) {
+ if (codeSelectBlocks[i].contains(p)) {
selection = i;
break;
}
}
+
+ if ((selection != -1) && (_vm->getPlatform() == Common::kPlatformAmiga)) {
+ _vm->_gfx->invertBackground(codeTrueBlocks[selection]);
+ _vm->_gfx->updateScreen();
+ _vm->beep();
+ g_system->delayMillis(100);
+ _vm->_gfx->invertBackground(codeTrueBlocks[selection]);
+ _vm->_gfx->updateScreen();
+ }
+
+ return selection;
}
- beep();
+ byte _points[3];
+ bool _fail;
+ const uint16 (*_keys)[PASSWORD_LEN];
+ Graphics::Surface _block;
+ Graphics::Surface _emptySlots;
- _gfx->freeLabels();
+ uint _labels[2];
+ uint _len;
+ uint32 _startTime;
- return selection;
-}
+ enum {
+ CHOICE,
+ FAIL,
+ SUCCESS,
+ DELAY
+ };
+ uint _state;
+ static const char *introMsg1[4];
+ static const char *introMsg2[4];
-uint16 Parallaction_ns::guiSelectGame() {
-// printf("selectGame()\n");
+ static const uint16 _amigaKeys[3][PASSWORD_LEN];
+ static const uint16 _pcKeys[3][PASSWORD_LEN];
+ static const char *_charStartLocation[3];
- showSlide("restore");
- uint16 _si = 0;
- uint16 _di = 3;
+public:
+ SelectCharacterInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectcharacter", helper), _vm(vm) {
+ _keys = (_vm->getPlatform() == Common::kPlatformAmiga && (_vm->getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys;
+ _block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1);
+ }
- uint id0, id1;
- id0 = _gfx->createLabel(_introFont, loadGameMsg[_language], 1);
- id1 = _gfx->createLabel(_introFont, newGameMsg[_language], 1);
+ ~SelectCharacterInputState_NS() {
+ _block.free();
+ _emptySlots.free();
+ }
- Common::Point p;
+ void cleanup() {
+ _points[0] = _points[1] = _points[2] = 0;
+ _vm->_gfx->hideLabel(_labels[1]);
+ _vm->_gfx->showLabel(_labels[0], 60, 30);
+ _fail = false;
+ _len = 0;
+ }
- _input->readInput();
- uint32 event = _input->getLastButtonEvent();
+ void delay() {
+ if (g_system->getMillis() - _startTime < 2000) {
+ return;
+ }
+ cleanup();
+ _state = CHOICE;
+ }
- while (event != kMouseLeftUp) {
+ void choice() {
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return;
+ }
- _input->readInput();
- _input->getCursorPos(p);
- event = _input->getLastButtonEvent();
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+ int _si = guiGetSelectedBlock(p);
- _si = (p.x > 160) ? 1 : 0;
+ if (_si != -1) {
+ _vm->_gfx->grabBackground(codeTrueBlocks[_si], _block);
+ _vm->_gfx->patchBackground(_block, _len * SLOT_WIDTH + SLOT_X, SLOT_Y, false);
- if (_si != _di) {
- if (_si != 0) {
- // load a game
- _gfx->hideLabel(id1);
- _gfx->showLabel(id0, 60, 30);
- } else {
- // new game
- _gfx->hideLabel(id0);
- _gfx->showLabel(id1, 60, 30);
+ if (_keys[0][_len] != _si && _keys[1][_len] != _si && _keys[2][_len] != _si) {
+ _fail = true;
}
- _di = _si;
+
+ // build user preference
+ _points[0] += (_keys[0][_len] == _si);
+ _points[1] += (_keys[1][_len] == _si);
+ _points[2] += (_keys[2][_len] == _si);
+
+ _len++;
+ }
+
+ if (_len == PASSWORD_LEN) {
+ _state = _fail ? FAIL : SUCCESS;
}
+ }
- _gfx->updateScreen();
- g_system->delayMillis(30);
+ void fail() {
+ _vm->_gfx->patchBackground(_emptySlots, SLOT_X, SLOT_Y, false);
+ _vm->_gfx->hideLabel(_labels[0]);
+ _vm->_gfx->showLabel(_labels[1], 60, 30);
+ _startTime = g_system->getMillis();
+ _state = DELAY;
}
- _gfx->freeLabels();
+ void success() {
+ _vm->_gfx->freeLabels();
+ _vm->_gfx->setBlackPalette();
+ _emptySlots.free();
+
+ // actually select character
+ int character = -1;
+ if (_points[0] >= _points[1] && _points[0] >= _points[2]) {
+ character = CHAR_DINO;
+ } else
+ if (_points[1] >= _points[0] && _points[1] >= _points[2]) {
+ character = CHAR_DONNA;
+ } else
+ if (_points[2] >= _points[0] && _points[2] >= _points[1]) {
+ character = CHAR_DOUGH;
+ } else {
+ error("If you read this, either your CPU or transivity is broken (we believe the former).");
+ }
- return _si ? LOAD_GAME : NEW_GAME;
-}
+ _vm->_inTestResult = false;
+ _vm->cleanupGame();
+ _vm->scheduleLocationSwitch(_charStartLocation[character]);
+ }
+
+ virtual MenuInputState* run() {
+ MenuInputState* nextState = this;
+
+ switch (_state) {
+ case DELAY:
+ delay();
+ break;
+
+ case CHOICE:
+ choice();
+ break;
+
+ case FAIL:
+ fail();
+ break;
+
+ case SUCCESS:
+ success();
+ nextState = 0;
+ break;
+
+ default:
+ error("unknown state in SelectCharacterInputState");
+ }
+
+ return nextState;
+ }
+
+ virtual void enter() {
+ _vm->_soundMan->stopMusic();
+ _vm->_disk->selectArchive((_vm->getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ _vm->showSlide("password");
-static const Common::Rect codeSelectBlocks[9] = {
+ _emptySlots.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1);
+ Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT);
+ _vm->_gfx->grabBackground(rect, _emptySlots);
+
+ _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, introMsg1[_vm->getInternLanguage()], 1);
+ _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, introMsg2[_vm->getInternLanguage()], 1);
+
+ cleanup();
+
+ _vm->setArrowCursor();
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+ _state = CHOICE;
+ }
+};
+
+const char *SelectCharacterInputState_NS::introMsg1[4] = {
+ "INSERISCI IL CODICE",
+ "ENTREZ CODE",
+ "ENTER CODE",
+ "GIB DEN KODE EIN"
+};
+
+const char *SelectCharacterInputState_NS::introMsg2[4] = {
+ "CODICE ERRATO",
+ "CODE ERRONE",
+ "WRONG CODE",
+ "GIB DEN KODE EIN"
+};
+
+const uint16 SelectCharacterInputState_NS::_amigaKeys[][PASSWORD_LEN] = {
+ { 5, 3, 6, 2, 2, 7 }, // dino
+ { 0, 3, 6, 2, 2, 6 }, // donna
+ { 1, 3 ,7, 2, 4, 6 } // dough
+};
+
+const uint16 SelectCharacterInputState_NS::_pcKeys[][PASSWORD_LEN] = {
+ { 5, 3, 6, 1, 4, 7 }, // dino
+ { 0, 2, 8, 5, 5, 1 }, // donna
+ { 1, 7 ,7, 2, 2, 6 } // dough
+};
+
+const char *SelectCharacterInputState_NS::_charStartLocation[] = {
+ "test.dino",
+ "test.donna",
+ "test.dough"
+};
+
+
+const Common::Rect SelectCharacterInputState_NS::codeSelectBlocks[9] = {
Common::Rect( 111, 129, 127, 153 ), // na
Common::Rect( 128, 120, 144, 144 ), // wa
Common::Rect( 145, 111, 161, 135 ), // ra
@@ -348,7 +606,7 @@ static const Common::Rect codeSelectBlocks[9] = {
Common::Rect( 247, 57, 263, 81 ) // ka
};
-static const Common::Rect codeTrueBlocks[9] = {
+const Common::Rect SelectCharacterInputState_NS::codeTrueBlocks[9] = {
Common::Rect( 112, 130, 128, 154 ),
Common::Rect( 129, 121, 145, 145 ),
Common::Rect( 146, 112, 162, 136 ),
@@ -361,146 +619,219 @@ static const Common::Rect codeTrueBlocks[9] = {
};
-int Parallaction_ns::guiGetSelectedBlock(const Common::Point &p) {
+class ShowCreditsInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ int _current;
+ uint32 _startTime;
- int selection = -1;
+ struct Credit {
+ const char *_role;
+ const char *_name;
+ };
- for (uint16 i = 0; i < 9; i++) {
- if (codeSelectBlocks[i].contains(p)) {
- selection = i;
- break;
- }
+ static const Credit _credits[6];
+
+public:
+ ShowCreditsInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("showcredits", helper), _vm(vm) {
}
- if ((selection != -1) && (getPlatform() == Common::kPlatformAmiga)) {
- _gfx->invertBackground(codeTrueBlocks[selection]);
- _gfx->updateScreen();
- beep();
- g_system->delayMillis(100);
- _gfx->invertBackground(codeTrueBlocks[selection]);
- _gfx->updateScreen();
+ void drawCurrentLabel() {
+ uint id[2];
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._role, 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._name, 1);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
}
- return selection;
-}
+ virtual MenuInputState* run() {
+ if (_current == -1) {
+ _startTime = g_system->getMillis();
+ _current = 0;
+ drawCurrentLabel();
+ return this;
+ }
-//
-// character selection and protection
-//
-int Parallaction_ns::guiSelectCharacter() {
- debugC(1, kDebugMenu, "Parallaction_ns::guiselectCharacter()");
+ int event = _vm->_input->getLastButtonEvent();
+ uint32 curTime = g_system->getMillis();
+ if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) {
+ _current++;
+ _startTime = curTime;
+ _vm->_gfx->freeLabels();
- setArrowCursor();
- _soundMan->stopMusic();
+ if (_current == 6) {
+ return _helper->getState("endintro");
+ }
- _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ drawCurrentLabel();
+ }
- showSlide("password");
+ return this;
+ }
+ virtual void enter() {
+ _current = -1;
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ }
+};
- const uint16 (*keys)[PASSWORD_LEN] = (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys;
- uint16 _di = 0;
- byte points[3] = { 0, 0, 0 };
+const ShowCreditsInputState_NS::Credit ShowCreditsInputState_NS::_credits[6] = {
+ {"Music and Sound Effects", "MARCO CAPRELLI"},
+ {"PC Version", "RICCARDO BALLARINO"},
+ {"Project Manager", "LOVRANO CANEPA"},
+ {"Production", "BRUNO BOZ"},
+ {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"},
+ {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"}
+};
- bool fail;
+class EndIntroInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ bool _isDemo;
- uint id[2];
- id[0] = _gfx->createLabel(_introFont, introMsg1[_language], 1);
- id[1] = _gfx->createLabel(_introFont, introMsg2[_language], 1);
+public:
+ EndIntroInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endintro", helper), _vm(vm) {
+ _isDemo = (_vm->getFeatures() & GF_DEMO) != 0;
+ }
- Graphics::Surface v14;
- v14.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1);
- Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT);
- _gfx->grabBackground(rect, v14);
+ virtual MenuInputState* run() {
- Graphics::Surface block;
- block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1);
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- Common::Point p;
+ if (_isDemo) {
+ _engineFlags |= kEngineQuit;
+ return 0;
+ }
- while (true) {
+ _vm->_gfx->freeLabels();
+ return _helper->getState("selectcharacter");
+ }
- points[0] = 0;
- points[1] = 0;
- points[2] = 0;
- fail = false;
+ virtual void enter() {
+ _vm->_soundMan->stopMusic();
+ _vm->_input->setMouseState(MOUSE_DISABLED);
- _gfx->hideLabel(id[1]);
- _gfx->showLabel(id[0], 60, 30);
+ if (!_isDemo) {
+ int label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1);
+ _vm->_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 80);
+ }
+ }
+};
- _di = 0;
- while (_di < PASSWORD_LEN) {
- _input->waitUntilLeftClick();
- _input->getCursorPos(p);
+class EndPartInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ bool _allPartsComplete;
- int _si = guiGetSelectedBlock(p);
+ // part completion messages
+ static const char *endMsg0[4];
+ static const char *endMsg1[4];
+ static const char *endMsg2[4];
+ static const char *endMsg3[4];
+ // game completion messages
+ static const char *endMsg4[4];
+ static const char *endMsg5[4];
+ static const char *endMsg6[4];
+ static const char *endMsg7[4];
- if (_si != -1) {
- _gfx->grabBackground(codeTrueBlocks[_si], block);
- _gfx->patchBackground(block, _di * SLOT_WIDTH + SLOT_X, SLOT_Y, false);
- if (keys[0][_di] == _si) {
- points[0]++;
- } else
- if (keys[1][_di] == _si) {
- points[1]++;
- } else
- if (keys[2][_di] == _si) {
- points[2]++;
- } else {
- fail = true;
- }
+public:
+ EndPartInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endpart", helper), _vm(vm) {
+ }
- // build user preference
- points[0] += (keys[0][_di] == _si);
- points[1] += (keys[1][_di] == _si);
- points[2] += (keys[2][_di] == _si);
+ virtual MenuInputState* run() {
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- _di++;
- }
+ _vm->_gfx->freeLabels();
+ if (_allPartsComplete) {
+ _vm->scheduleLocationSwitch("estgrotta.drki");
+ return 0;
}
- if (!fail) {
- break;
+ return _helper->getState("selectcharacter");
+ }
+
+ virtual void enter() {
+ _allPartsComplete = _vm->allPartsComplete();
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+
+ uint id[4];
+ if (_allPartsComplete) {
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg4[_language], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg5[_language], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg6[_language], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg7[_language], 1);
+ } else {
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg0[_language], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg1[_language], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg2[_language], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg3[_language], 1);
}
- _gfx->patchBackground(v14, SLOT_X, SLOT_Y, false);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
+ _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130);
+ _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160);
+ }
+};
- _gfx->hideLabel(id[0]);
- _gfx->showLabel(id[1], 60, 30);
+// part completion messages
+const char *EndPartInputState_NS::endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
+const char *EndPartInputState_NS::endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"};
+const char *EndPartInputState_NS::endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"};
+const char *EndPartInputState_NS::endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"};
+// game completion messages
+const char *EndPartInputState_NS::endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
+const char *EndPartInputState_NS::endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"};
+const char *EndPartInputState_NS::endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"};
+const char *EndPartInputState_NS::endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"};
+
+void Parallaction_ns::startGui() {
+ _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
- _gfx->updateScreen();
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- g_system->delayMillis(2000);
- }
+ new SelectGameInputState_NS(this, _menuHelper);
+ new LoadGameInputState_NS(this, _menuHelper);
+ new NewGameInputState_NS(this, _menuHelper);
+ new StartDemoInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ new ChooseLanguageInputState_NS(this, _menuHelper);
+ new SplashInputState1_NS(this, _menuHelper);
+ new SplashInputState0_NS(this, _menuHelper);
+ _menuHelper->setState("intro0");
- _gfx->freeLabels();
+ _input->_inputMode = Input::kInputModeMenu;
+}
- _gfx->setBlackPalette();
- _gfx->updateScreen();
+void Parallaction_ns::startCreditSequence() {
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- v14.free();
+ new ShowCreditsInputState_NS(this, _menuHelper);
+ new EndIntroInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ _menuHelper->setState("showcredits");
+ _input->_inputMode = Input::kInputModeMenu;
+}
- // actually select character
+void Parallaction_ns::startEndPartSequence() {
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- int character = -1;
- if (points[0] >= points[1] && points[0] >= points[2]) {
- character = CHAR_DINO;
- } else
- if (points[1] >= points[0] && points[1] >= points[2]) {
- character = CHAR_DONNA;
- } else
- if (points[2] >= points[0] && points[2] >= points[1]) {
- character = CHAR_DOUGH;
- } else {
- error("If you read this, either your CPU or transivity is broken (we believe the former).");
- }
+ new EndPartInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ _menuHelper->setState("endpart");
- return character;
+ _input->_inputMode = Input::kInputModeMenu;
}
diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp
index 28d0ad888d..287618e803 100644
--- a/engines/parallaction/input.cpp
+++ b/engines/parallaction/input.cpp
@@ -36,23 +36,23 @@ namespace Parallaction {
// loops which could possibly be merged into this one with some effort in changing
// caller code, i.e. adding condition checks.
//
-uint16 Input::readInput() {
+void Input::readInput() {
Common::Event e;
- uint16 KeyDown = 0;
_mouseButtons = kMouseNone;
+ _hasKeyPressEvent = false;
Common::EventManager *eventMan = _vm->_system->getEventManager();
while (eventMan->pollEvent(e)) {
switch (e.type) {
case Common::EVENT_KEYDOWN:
+ _hasKeyPressEvent = true;
+ _keyPressed = e.kbd;
+
if (e.kbd.flags == Common::KBD_CTRL && e.kbd.keycode == 'd')
_vm->_debugger->attach();
- if (_vm->getFeatures() & GF_DEMO) break;
- if (e.kbd.keycode == Common::KEYCODE_l) KeyDown = kEvLoadGame;
- if (e.kbd.keycode == Common::KEYCODE_s) KeyDown = kEvSaveGame;
break;
case Common::EVENT_LBUTTONDOWN:
@@ -80,11 +80,8 @@ uint16 Input::readInput() {
break;
case Common::EVENT_QUIT:
- // TODO: don't quit() here, just have caller routines to check
- // on kEngineQuit and exit gracefully to allow the engine to shut down
_engineFlags |= kEngineQuit;
- _vm->_system->quit();
- break;
+ return;
default:
break;
@@ -96,10 +93,15 @@ uint16 Input::readInput() {
if (_vm->_debugger->isAttached())
_vm->_debugger->onFrame();
- return KeyDown;
+ return;
}
+bool Input::getLastKeyDown(uint16 &ascii) {
+ ascii = _keyPressed.ascii;
+ return (_hasKeyPressEvent);
+}
+
// FIXME: see comment for readInput()
void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {
@@ -124,64 +126,36 @@ void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {
}
-// FIXME: see comment for readInput()
-void Input::waitUntilLeftClick() {
-
- do {
- readInput();
- _vm->_gfx->updateScreen();
- _vm->_system->delayMillis(30);
- } while (_mouseButtons != kMouseLeftUp);
-
- return;
-}
-
void Input::updateGameInput() {
- int16 keyDown = readInput();
-
- debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)",
- !_mouseHidden,
- (_engineFlags & kEngineBlockInput) == 0,
- (_engineFlags & kEngineWalking) == 0,
- (_engineFlags & kEngineChangeLocation) == 0
- );
+ readInput();
- if ((_mouseHidden) ||
- (_engineFlags & kEngineBlockInput) ||
+ if (!isMouseEnabled() ||
(_engineFlags & kEngineWalking) ||
(_engineFlags & kEngineChangeLocation)) {
+ debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)",
+ isMouseEnabled(),
+ (_engineFlags & kEngineWalking) == 0,
+ (_engineFlags & kEngineChangeLocation) == 0
+ );
+
return;
}
- if (keyDown == kEvQuitGame) {
- _inputData._event = kEvQuitGame;
- } else
- if (keyDown == kEvSaveGame) {
- _inputData._event = kEvSaveGame;
- } else
- if (keyDown == kEvLoadGame) {
- _inputData._event = kEvLoadGame;
- } else {
+ if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) {
+ if (_keyPressed.keycode == Common::KEYCODE_l) _inputData._event = kEvLoadGame;
+ if (_keyPressed.keycode == Common::KEYCODE_s) _inputData._event = kEvSaveGame;
+ }
+
+ if (_inputData._event == kEvNone) {
_inputData._mousePos = _mousePos;
- _inputData._event = kEvNone;
- if (!translateGameInput()) {
- translateInventoryInput();
- }
+ translateGameInput();
}
}
-void Input::updateCommentInput() {
- waitUntilLeftClick();
-
- _vm->_gfx->hideDialogueStuff();
- _vm->_gfx->setHalfbriteMode(false);
-
- _inputMode = kInputModeGame;
-}
InputData* Input::updateInput() {
@@ -189,84 +163,109 @@ InputData* Input::updateInput() {
switch (_inputMode) {
case kInputModeComment:
- updateCommentInput();
+ case kInputModeDialogue:
+ case kInputModeMenu:
+ readInput();
break;
case kInputModeGame:
updateGameInput();
break;
+
+ case kInputModeInventory:
+ readInput();
+ updateInventoryInput();
+ break;
}
return &_inputData;
}
+void Input::trackMouse(ZonePtr z) {
+ if ((z != _hoverZone) && (_hoverZone)) {
+ stopHovering();
+ return;
+ }
+
+ if (!z) {
+ return;
+ }
+
+ if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) {
+ _hoverZone = z;
+ _vm->_gfx->showFloatingLabel(_hoverZone->_label);
+ return;
+ }
+}
+
+void Input::stopHovering() {
+ _hoverZone = nullZonePtr;
+ _vm->_gfx->hideFloatingLabel();
+}
+
+void Input::takeAction(ZonePtr z) {
+ stopHovering();
+ _vm->pauseJobs();
+ _vm->runZone(z);
+ _vm->resumeJobs();
+}
+
+void Input::walkTo(const Common::Point &dest) {
+ stopHovering();
+ _vm->setArrowCursor();
+ _vm->_char.scheduleWalk(dest.x, dest.y);
+}
+
bool Input::translateGameInput() {
- if ((_engineFlags & kEnginePauseJobs) || (_engineFlags & kEngineInventory)) {
+ if (_engineFlags & kEnginePauseJobs) {
return false;
}
- if (_actionAfterWalk) {
+ if (_hasDelayedAction) {
// if walking is over, then take programmed action
- _inputData._event = kEvAction;
- _actionAfterWalk = false;
+ takeAction(_delayedActionZone);
+ _hasDelayedAction = false;
+ _delayedActionZone = nullZonePtr;
return true;
}
if (_mouseButtons == kMouseRightDown) {
// right button down shows inventory
-
- if (_vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y) && (_activeItem._id != 0)) {
- _activeItem._index = (_activeItem._id >> 16) & 0xFFFF;
- _engineFlags |= kEngineDragging;
- }
-
- _inputData._event = kEvOpenInventory;
- _transCurrentHoverItem = -1;
+ enterInventoryMode();
return true;
}
// test if mouse is hovering on an interactive zone for the currently selected inventory item
ZonePtr z = _vm->hitZone(_activeItem._id, _mousePos.x, _mousePos.y);
+ Common::Point dest(_mousePos);
if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((_engineFlags & kEngineWalking) == 0)) && ((!z) || ((z->_type & 0xFFFF) != kZoneCommand))) {
- _inputData._event = kEvWalk;
+ walkTo(dest);
return true;
}
- if ((z != _hoverZone) && (_hoverZone)) {
- _hoverZone = nullZonePtr;
- _inputData._event = kEvExitZone;
- return true;
- }
-
- if (!z) {
- _inputData._event = kEvNone;
- return true;
- }
-
- if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) {
- _hoverZone = z;
- _inputData._event = kEvEnterZone;
- _inputData._label = z->_label;
- return true;
- }
+ trackMouse(z);
+ if (!z) {
+ return true;
+ }
if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || ((z->_type & 0xFFFF) == kZoneCommand))) {
_inputData._zone = z;
if (z->_flags & kFlagsNoWalk) {
// character doesn't need to walk to take specified action
- _inputData._event = kEvAction;
-
+ takeAction(z);
} else {
// action delayed: if Zone defined a moveto position the character is programmed to move there,
// else it will move to the mouse position
- _inputData._event = kEvWalk;
- _actionAfterWalk = true;
+ _delayedActionZone = z;
+ _hasDelayedAction = true;
if (z->_moveTo.y != 0) {
- _inputData._mousePos = z->_moveTo;
+ dest = z->_moveTo;
}
+
+ walkTo(dest);
}
_vm->beep();
@@ -275,58 +274,103 @@ bool Input::translateGameInput() {
}
return true;
-
}
-bool Input::translateInventoryInput() {
- if ((_engineFlags & kEngineInventory) == 0) {
- return false;
+void Input::enterInventoryMode() {
+ bool hitCharacter = _vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y);
+
+ if (hitCharacter) {
+ if (_activeItem._id != 0) {
+ _activeItem._index = (_activeItem._id >> 16) & 0xFFFF;
+ _engineFlags |= kEngineDragging;
+ } else {
+ _vm->setArrowCursor();
+ }
}
- // in inventory
- int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ stopHovering();
+ _vm->pauseJobs();
+ _vm->openInventory();
- if (_mouseButtons == kMouseRightUp) {
- // right up hides inventory
+ _transCurrentHoverItem = -1;
- _inputData._event = kEvCloseInventory;
- _inputData._inventoryIndex = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
- _vm->highlightInventoryItem(-1); // disable
+ _inputMode = kInputModeInventory;
+}
- if ((_engineFlags & kEngineDragging) == 0) {
- return true;
- }
+void Input::exitInventoryMode() {
+ // right up hides inventory
+
+ int pos = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ _vm->highlightInventoryItem(-1); // disable
+
+ if ((_engineFlags & kEngineDragging)) {
_engineFlags &= ~kEngineDragging;
- ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(_inputData._inventoryIndex));
+ ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos));
if (z) {
_vm->dropItem(z->u.merge->_obj1);
_vm->dropItem(z->u.merge->_obj2);
_vm->addInventoryItem(z->u.merge->_obj3);
- _vm->runCommands(z->_commands);
+ _vm->_cmdExec->run(z->_commands);
}
- return true;
}
- if (_si == _transCurrentHoverItem) {
- _inputData._event = kEvNone;
+ _vm->closeInventory();
+ if (pos == -1) {
+ _vm->setArrowCursor();
+ } else {
+ const InventoryItem *item = _vm->getInventoryItem(pos);
+ if (item->_index != 0) {
+ _activeItem._id = item->_id;
+ _vm->setInventoryCursor(item->_index);
+ }
+ }
+ _vm->resumeJobs();
+
+ _inputMode = kInputModeGame;
+}
+
+bool Input::updateInventoryInput() {
+ if (_mouseButtons == kMouseRightUp) {
+ exitInventoryMode();
return true;
}
- _transCurrentHoverItem = _si;
- _inputData._event = kEvHoverInventory;
- _inputData._inventoryIndex = _si;
+ int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ if (_si != _transCurrentHoverItem) {
+ _transCurrentHoverItem = _si;
+ _vm->highlightInventoryItem(_si); // enable
+ }
+
return true;
}
-void Input::showCursor(bool visible) {
- _mouseHidden = !visible;
- _vm->_system->showMouse(visible);
+void Input::setMouseState(MouseTriState state) {
+ assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED);
+ _mouseState = state;
+
+ switch (_mouseState) {
+ case MOUSE_ENABLED_HIDE:
+ case MOUSE_DISABLED:
+ _vm->_system->showMouse(false);
+ break;
+
+ case MOUSE_ENABLED_SHOW:
+ _vm->_system->showMouse(true);
+ break;
+ }
+}
+
+MouseTriState Input::getMouseState() {
+ return _mouseState;
}
+bool Input::isMouseEnabled() {
+ return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE);
+}
} // namespace Parallaction
diff --git a/engines/parallaction/input.h b/engines/parallaction/input.h
index 46dbb08c4e..c1e912db74 100644
--- a/engines/parallaction/input.h
+++ b/engines/parallaction/input.h
@@ -26,6 +26,8 @@
#ifndef PARALLACTION_INPUT_H
#define PARALLACTION_INPUT_H
+#include "common/keyboard.h"
+
#include "parallaction/objects.h"
#include "parallaction/inventory.h"
@@ -44,52 +46,68 @@ struct InputData {
Common::Point _mousePos;
int16 _inventoryIndex;
ZonePtr _zone;
- Label* _label;
+ uint _label;
+};
+
+enum MouseTriState {
+ MOUSE_ENABLED_SHOW,
+ MOUSE_ENABLED_HIDE,
+ MOUSE_DISABLED
};
class Input {
void updateGameInput();
- void updateCommentInput();
// input-only
InputData _inputData;
- bool _actionAfterWalk; // actived when the character needs to move before taking an action
- // these two could/should be merged as they carry on the same duty in two member functions,
- // respectively processInput and translateInput
+
+ bool _hasKeyPressEvent;
+ Common::KeyState _keyPressed;
+
+ bool _hasDelayedAction; // actived when the character needs to move before taking an action
+ ZonePtr _delayedActionZone;
+
int16 _transCurrentHoverItem;
InputData *translateInput();
bool translateGameInput();
- bool translateInventoryInput();
+ bool updateInventoryInput();
+ void takeAction(ZonePtr z);
+ void walkTo(const Common::Point &dest);
Parallaction *_vm;
Common::Point _mousePos;
uint16 _mouseButtons;
- bool _mouseHidden;
ZonePtr _hoverZone;
+ void enterInventoryMode();
+ void exitInventoryMode();
+
public:
enum {
kInputModeGame = 0,
- kInputModeComment = 1
+ kInputModeComment = 1,
+ kInputModeDialogue = 2,
+ kInputModeInventory = 3,
+ kInputModeMenu = 4
};
Input(Parallaction *vm) : _vm(vm) {
_transCurrentHoverItem = 0;
- _actionAfterWalk = false; // actived when the character needs to move before taking an action
- _mouseHidden = false;
+ _hasDelayedAction = false; // actived when the character needs to move before taking an action
+ _mouseState = MOUSE_DISABLED;
_activeItem._index = 0;
_activeItem._id = 0;
_mouseButtons = 0;
+ _delayedActionZone = nullZonePtr;
}
virtual ~Input() { }
- void showCursor(bool visible);
void getCursorPos(Common::Point& p) {
p = _mousePos;
}
@@ -97,16 +115,20 @@ public:
int _inputMode;
InventoryItem _activeItem;
- uint16 readInput();
+ void readInput();
InputData* updateInput();
- void waitUntilLeftClick();
+ void trackMouse(ZonePtr z);
void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1);
uint32 getLastButtonEvent() { return _mouseButtons; }
+ bool getLastKeyDown(uint16 &ascii);
- void stopHovering() {
- _hoverZone = nullZonePtr;
- }
+ void stopHovering();
+
+ MouseTriState _mouseState;
+ void setMouseState(MouseTriState state);
+ MouseTriState getMouseState();
+ bool isMouseEnabled();
};
} // namespace Parallaction
diff --git a/engines/parallaction/inventory.cpp b/engines/parallaction/inventory.cpp
index 58848196d7..7b92974205 100644
--- a/engines/parallaction/inventory.cpp
+++ b/engines/parallaction/inventory.cpp
@@ -30,23 +30,58 @@
namespace Parallaction {
-//
-// inventory is a grid made of (at most) 30 cells, 24x24 pixels each,
-// arranged in 6 lines
-//
-// inventory items are stored in cnv files in a 32x24 grid
-// but only 24x24 pixels are actually copied to graphic memory
-//
+
+/*
+#define INVENTORYITEM_PITCH 32
+#define INVENTORYITEM_WIDTH 24
+#define INVENTORYITEM_HEIGHT 24
#define INVENTORY_MAX_ITEMS 30
-#define INVENTORY_FIRST_ITEM 4 // first four entries are used up by verbs
#define INVENTORY_ITEMS_PER_LINE 5
#define INVENTORY_LINES 6
#define INVENTORY_WIDTH (INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
#define INVENTORY_HEIGHT (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
-
+*/
+
+InventoryItem _verbs_NS[] = {
+ { 1, kZoneDoor },
+ { 3, kZoneExamine },
+ { 2, kZoneGet },
+ { 4, kZoneSpeak },
+ { 0, 0 }
+};
+
+InventoryItem _verbs_BR[] = {
+ { 1, kZoneBox },
+ { 2, kZoneGet },
+ { 3, kZoneExamine },
+ { 4, kZoneSpeak },
+ { 0, 0 }
+};
+
+InventoryProperties _invProps_NS = {
+ 32, // INVENTORYITEM_PITCH
+ 24, // INVENTORYITEM_WIDTH
+ 24, // INVENTORYITEM_HEIGHT
+ 30, // INVENTORY_MAX_ITEMS
+ 5, // INVENTORY_ITEMS_PER_LINE
+ 6, // INVENTORY_LINES
+ 5 * 24, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
+ 6 * 24 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
+};
+
+InventoryProperties _invProps_BR = {
+ 51, // INVENTORYITEM_PITCH
+ 51, // INVENTORYITEM_WIDTH
+ 51, // INVENTORYITEM_HEIGHT
+ 48, // INVENTORY_MAX_ITEMS
+ 6, // INVENTORY_ITEMS_PER_LINE
+ 8, // INVENTORY_LINES
+ 6 * 51, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
+ 8 * 51 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
+};
int16 Parallaction::getHoverInventoryItem(int16 x, int16 y) {
return _inventoryRenderer->hitTest(Common::Point(x,y));
@@ -91,8 +126,19 @@ int16 Parallaction::getInventoryItemIndex(int16 pos) {
}
void Parallaction::initInventory() {
- _inventory = new Inventory(INVENTORY_MAX_ITEMS);
- _inventoryRenderer = new InventoryRenderer(this);
+ InventoryProperties *props;
+ InventoryItem *verbs;
+
+ if (getGameType() == GType_Nippon) {
+ props = &_invProps_NS;
+ verbs = _verbs_NS;
+ } else {
+ props = &_invProps_BR;
+ verbs = _verbs_BR;
+ }
+
+ _inventory = new Inventory(props, verbs);
+ _inventoryRenderer = new InventoryRenderer(this, props);
_inventoryRenderer->bindInventory(_inventory);
}
@@ -119,8 +165,8 @@ void Parallaction::closeInventory() {
-InventoryRenderer::InventoryRenderer(Parallaction *vm) : _vm(vm) {
- _surf.create(INVENTORY_WIDTH, INVENTORY_HEIGHT, 1);
+InventoryRenderer::InventoryRenderer(Parallaction *vm, InventoryProperties *props) : _vm(vm), _props(props) {
+ _surf.create(_props->_width, _props->_height, 1);
}
InventoryRenderer::~InventoryRenderer() {
@@ -131,15 +177,13 @@ void InventoryRenderer::showInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
- _engineFlags |= kEngineInventory;
-
uint16 lines = getNumLines();
Common::Point p;
_vm->_input->getCursorPos(p);
- _pos.x = CLIP(p.x - (INVENTORY_WIDTH / 2), 0, (int)(_vm->_screenWidth - INVENTORY_WIDTH));
- _pos.y = CLIP(p.y - 2 - (lines * INVENTORYITEM_HEIGHT), 0, (int)(_vm->_screenHeight - lines * INVENTORYITEM_HEIGHT));
+ _pos.x = CLIP((int)(p.x - (_props->_width / 2)), 0, (int)(_vm->_screenWidth - _props->_width));
+ _pos.y = CLIP((int)(p.y - 2 - (lines * _props->_itemHeight)), 0, (int)(_vm->_screenHeight - lines * _props->_itemHeight));
refresh();
}
@@ -147,13 +191,11 @@ void InventoryRenderer::showInventory() {
void InventoryRenderer::hideInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
-
- _engineFlags &= ~kEngineInventory;
}
void InventoryRenderer::getRect(Common::Rect& r) const {
- r.setWidth(INVENTORY_WIDTH);
- r.setHeight(INVENTORYITEM_HEIGHT * getNumLines());
+ r.setWidth(_props->_width);
+ r.setHeight(_props->_itemHeight * getNumLines());
r.moveTo(_pos);
}
@@ -163,35 +205,36 @@ ItemPosition InventoryRenderer::hitTest(const Common::Point &p) const {
if (!r.contains(p))
return -1;
- return ((p.x - _pos.x) / INVENTORYITEM_WIDTH) + (INVENTORY_ITEMS_PER_LINE * ((p.y - _pos.y) / INVENTORYITEM_HEIGHT));
+ return ((p.x - _pos.x) / _props->_itemWidth) + (_props->_itemsPerLine * ((p.y - _pos.y) / _props->_itemHeight));
}
-
void InventoryRenderer::drawItem(ItemPosition pos, ItemName name) {
-
Common::Rect r;
getItemRect(pos, r);
+ byte* d = (byte*)_surf.getBasePtr(r.left, r.top);
+ drawItem(name, d, _surf.pitch);
+}
- // FIXME: this will end up in a general blit function
-
+void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) {
byte* s = _vm->_char._objs->getData(name);
- byte* d = (byte*)_surf.getBasePtr(r.left, r.top);
- for (uint32 i = 0; i < INVENTORYITEM_HEIGHT; i++) {
- memcpy(d, s, INVENTORYITEM_WIDTH);
+ byte* d = buffer;
+ for (uint i = 0; i < _props->_itemHeight; i++) {
+ memcpy(d, s, _props->_itemWidth);
- d += INVENTORY_WIDTH;
- s += INVENTORYITEM_PITCH;
+ s += _props->_itemPitch;
+ d += pitch;
}
}
+
int16 InventoryRenderer::getNumLines() const {
int16 num = _inv->getNumItems();
- return (num / INVENTORY_ITEMS_PER_LINE) + ((num % INVENTORY_ITEMS_PER_LINE) > 0 ? 1 : 0);
+ return (num / _props->_itemsPerLine) + ((num % _props->_itemsPerLine) > 0 ? 1 : 0);
}
void InventoryRenderer::refresh() {
- for (uint16 i = 0; i < INVENTORY_MAX_ITEMS; i++) {
+ for (uint16 i = 0; i < _props->_maxItems; i++) {
ItemName name = _inv->getItemName(i);
drawItem(i, name);
}
@@ -212,25 +255,24 @@ void InventoryRenderer::highlightItem(ItemPosition pos, byte color) {
void InventoryRenderer::getItemRect(ItemPosition pos, Common::Rect &r) {
- r.setHeight(INVENTORYITEM_HEIGHT);
- r.setWidth(INVENTORYITEM_WIDTH);
+ r.setHeight(_props->_itemHeight);
+ r.setWidth(_props->_itemWidth);
- uint16 line = pos / INVENTORY_ITEMS_PER_LINE;
- uint16 col = pos % INVENTORY_ITEMS_PER_LINE;
+ uint16 line = pos / _props->_itemsPerLine;
+ uint16 col = pos % _props->_itemsPerLine;
- r.moveTo(col * INVENTORYITEM_WIDTH, line * INVENTORYITEM_HEIGHT);
+ r.moveTo(col * _props->_itemWidth, line * _props->_itemHeight);
}
+Inventory::Inventory(InventoryProperties *props, InventoryItem *verbs) : _numItems(0), _props(props) {
+ _items = (InventoryItem*)calloc(_props->_maxItems, sizeof(InventoryItem));
-
-Inventory::Inventory(uint16 maxItems) : _maxItems(maxItems), _numItems(0) {
- _items = (InventoryItem*)calloc(_maxItems, sizeof(InventoryItem));
-
- addItem(1, kZoneDoor);
- addItem(3, kZoneExamine);
- addItem(2, kZoneGet);
- addItem(4, kZoneSpeak);
+ int i = 0;
+ for ( ; verbs[i]._id; i++) {
+ addItem(verbs[i]._id, verbs[i]._index);
+ }
+ _numVerbs = i;
}
@@ -241,7 +283,7 @@ Inventory::~Inventory() {
ItemPosition Inventory::addItem(ItemName name, uint32 value) {
debugC(1, kDebugInventory, "addItem(%i, %i)", name, value);
- if (_numItems == INVENTORY_MAX_ITEMS) {
+ if (_numItems == _props->_maxItems) {
debugC(3, kDebugInventory, "addItem: inventory is full");
return -1;
}
@@ -300,9 +342,9 @@ void Inventory::removeItem(ItemName name) {
void Inventory::clear(bool keepVerbs) {
debugC(1, kDebugInventory, "clearInventory()");
- uint first = (keepVerbs ? INVENTORY_FIRST_ITEM : 0);
+ uint first = (keepVerbs ? _numVerbs : 0);
- for (uint16 slot = first; slot < _maxItems; slot++) {
+ for (uint16 slot = first; slot < _numVerbs; slot++) {
_items[slot]._id = 0;
_items[slot]._index = 0;
}
@@ -312,7 +354,7 @@ void Inventory::clear(bool keepVerbs) {
ItemName Inventory::getItemName(ItemPosition pos) const {
- return (pos >= 0 && pos < INVENTORY_MAX_ITEMS) ? _items[pos]._index : 0;
+ return (pos >= 0 && pos < _props->_maxItems) ? _items[pos]._index : 0;
}
const InventoryItem* Inventory::getItem(ItemPosition pos) const {
diff --git a/engines/parallaction/inventory.h b/engines/parallaction/inventory.h
index 8c32c09219..f041627810 100644
--- a/engines/parallaction/inventory.h
+++ b/engines/parallaction/inventory.h
@@ -38,9 +38,19 @@ struct InventoryItem {
uint16 _index; // index to frame in objs file
};
-#define INVENTORYITEM_PITCH 32
-#define INVENTORYITEM_WIDTH 24
-#define INVENTORYITEM_HEIGHT 24
+struct InventoryProperties {
+ uint _itemPitch;
+ uint _itemWidth;
+ uint _itemHeight;
+
+ int _maxItems;
+
+ int _itemsPerLine;
+ int _maxLines;
+
+ int _width;
+ int _height;
+};
#define MAKE_INVENTORY_ID(x) (((x) & 0xFFFF) << 16)
@@ -50,12 +60,14 @@ typedef uint16 ItemName;
class Inventory {
protected:
+ uint16 _numVerbs;
+
InventoryItem *_items;
- uint16 _maxItems;
uint16 _numItems;
+ InventoryProperties *_props;
public:
- Inventory(uint16 maxItems);
+ Inventory(InventoryProperties *props, InventoryItem *verbs);
virtual ~Inventory();
ItemPosition addItem(ItemName name, uint32 value);
@@ -75,6 +87,8 @@ public:
class InventoryRenderer {
Parallaction *_vm;
+ InventoryProperties *_props;
+
Inventory *_inv;
Common::Point _pos;
@@ -87,7 +101,7 @@ protected:
void refresh();
public:
- InventoryRenderer(Parallaction *vm);
+ InventoryRenderer(Parallaction *vm, InventoryProperties *props);
virtual ~InventoryRenderer();
void bindInventory(Inventory *inv) { _inv = inv; }
@@ -97,6 +111,7 @@ public:
ItemPosition hitTest(const Common::Point &p) const;
void highlightItem(ItemPosition pos, byte color);
+ void drawItem(ItemName name, byte *buffer, uint pitch);
byte* getData() const { return (byte*)_surf.pixels; }
diff --git a/engines/parallaction/module.mk b/engines/parallaction/module.mk
index 2478b4b2e1..9d44422541 100644
--- a/engines/parallaction/module.mk
+++ b/engines/parallaction/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/parallaction
MODULE_OBJS := \
+ balloons.o \
callables_br.o \
callables_ns.o \
debug.o \
@@ -13,6 +14,7 @@ MODULE_OBJS := \
font.o \
gfxbase.o \
graphics.o \
+ gui.o \
gui_br.o \
gui_ns.o \
input.o \
diff --git a/engines/parallaction/objects.cpp b/engines/parallaction/objects.cpp
index cac31911f4..c387484de7 100644
--- a/engines/parallaction/objects.cpp
+++ b/engines/parallaction/objects.cpp
@@ -54,19 +54,20 @@ Animation::Animation() {
Animation::~Animation() {
free(_scriptName);
+ gfxobj->release();
}
uint16 Animation::width() const {
if (!gfxobj) return 0;
Common::Rect r;
- gfxobj->getRect(0, r);
+ gfxobj->getRect(_frame, r);
return r.width();
}
uint16 Animation::height() const {
if (!gfxobj) return 0;
Common::Rect r;
- gfxobj->getRect(0, r);
+ gfxobj->getRect(_frame, r);
return r.height();
}
@@ -80,6 +81,12 @@ byte* Animation::getFrameData(uint32 index) const {
return gfxobj->getData(index);
}
+void Animation::validateScriptVars() {
+ // this is used to clip values of _frame, _left and _top
+ // which can be screwed up by buggy scripts.
+
+ _frame = CLIP(_frame, (int16)0, (int16)(getFrameNum() - 1));
+}
#define NUM_LOCALS 10
char _localNames[NUM_LOCALS][10];
@@ -182,7 +189,8 @@ Zone::~Zone() {
break;
}
- delete _label;
+
+ free(_linkedName);
}
void Zone::getRect(Common::Rect& r) const {
@@ -207,6 +215,16 @@ uint16 Zone::height() const {
return _bottom - _top;
}
+Dialogue::Dialogue() {
+ memset(_questions, 0, sizeof(_questions));
+}
+
+Dialogue::~Dialogue() {
+ for (int i = 0; i < NUM_QUESTIONS; i++) {
+ delete _questions[i];
+ }
+}
+
Answer::Answer() {
_text = NULL;
_mood = 0;
diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h
index c2c2c154b5..a3bf757bdb 100644
--- a/engines/parallaction/objects.h
+++ b/engines/parallaction/objects.h
@@ -54,6 +54,7 @@ typedef Common::SharedPtr<Instruction> InstructionPtr;
typedef Common::List<InstructionPtr> InstructionList;
extern InstructionPtr nullInstructionPtr;
+typedef Common::List<Common::Point> PointList;
enum ZoneTypes {
kZoneExamine = 1, // zone displays comment if activated
@@ -67,7 +68,11 @@ enum ZoneTypes {
kZoneNone = 0x100, // used to prevent parsing on peculiar Animations
kZoneTrap = 0x200, // zone activated when character enters
kZoneYou = 0x400, // marks the character
- kZoneCommand = 0x800
+ kZoneCommand = 0x800,
+
+ // BRA specific
+ kZonePath = 0x1000, // defines nodes for assisting walk calculation routines
+ kZoneBox = 0x2000
};
@@ -89,6 +94,7 @@ enum ZoneFlags {
kFlagsYourself = 0x1000,
kFlagsScaled = 0x2000,
kFlagsSelfuse = 0x4000,
+ kFlagsIsAnimation = 0x1000000, // BRA: used in walk code (trap check), to tell is a Zone is an Animation
kFlagsAnimLinked = 0x2000000
};
@@ -181,6 +187,9 @@ struct Question {
struct Dialogue {
Question *_questions[NUM_QUESTIONS];
+
+ Dialogue();
+ ~Dialogue();
};
struct GetData { // size = 24
@@ -206,7 +215,7 @@ struct SpeakData { // size = 36
}
};
struct ExamineData { // size = 28
- Frames *_cnv;
+ GfxObj *_cnv;
uint16 _opBase; // unused
uint16 field_12; // unused
char* _description;
@@ -253,6 +262,15 @@ struct MergeData { // size = 12
_obj1 = _obj2 = _obj3 = 0;
}
};
+#define MAX_WALKPOINT_LISTS 20
+struct PathData {
+ int _numLists;
+ PointList _lists[MAX_WALKPOINT_LISTS];
+
+ PathData() {
+ _numLists = 0;
+ }
+};
struct TypeData {
GetData *get;
@@ -261,6 +279,8 @@ struct TypeData {
DoorData *door;
HearData *hear;
MergeData *merge;
+ // BRA specific field
+ PathData *path;
TypeData() {
get = NULL;
@@ -269,6 +289,7 @@ struct TypeData {
door = NULL;
hear = NULL;
merge = NULL;
+ path = NULL;
}
};
@@ -284,7 +305,7 @@ struct Zone {
int16 _bottom;
uint32 _type;
uint32 _flags;
- Label *_label;
+ uint _label;
uint16 field_2C; // unused
uint16 field_2E; // unused
TypeData u;
@@ -429,6 +450,8 @@ struct Animation : public Zone {
virtual uint16 height() const;
uint16 getFrameNum() const;
byte* getFrameData(uint32 index) const;
+
+ void validateScriptVars();
};
class Table {
diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp
index d66b1af1f1..bb306c3299 100644
--- a/engines/parallaction/parallaction.cpp
+++ b/engines/parallaction/parallaction.cpp
@@ -85,20 +85,28 @@ Parallaction::Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gam
Parallaction::~Parallaction() {
delete _debugger;
-
delete _globalTable;
-
delete _callableNames;
- delete _localFlagNames;
+ delete _cmdExec;
+ delete _programExec;
+ _gfx->clearGfxObjects(kGfxObjCharacter | kGfxObjNormal);
+ hideDialogueStuff();
+ delete _balloonMan;
freeLocation();
freeCharacter();
destroyInventory();
+ cleanupGui();
+
+ delete _comboArrow;
+
+ delete _localFlagNames;
delete _gfx;
delete _soundMan;
delete _disk;
+ delete _input;
}
@@ -132,18 +140,24 @@ int Parallaction::init() {
_debugger = new Debugger(this);
- return 0;
-}
-
+ _menuHelper = 0;
+ setupBalloonManager();
+ return 0;
+}
+void Parallaction::clearSet(OpcodeSet &opcodes) {
+ for (Common::Array<const Opcode*>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
+ delete *i;
+ opcodes.clear();
+}
void Parallaction::updateView() {
- if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) {
+ if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) {
return;
}
@@ -153,6 +167,11 @@ void Parallaction::updateView() {
}
+void Parallaction::hideDialogueStuff() {
+ _gfx->freeItems();
+ _balloonMan->freeBalloons();
+}
+
void Parallaction::freeCharacter() {
debugC(1, kDebugExec, "freeCharacter()");
@@ -160,6 +179,8 @@ void Parallaction::freeCharacter() {
delete _objectsNames;
_objectsNames = 0;
+ _gfx->clearGfxObjects(kGfxObjCharacter);
+
_char.free();
return;
@@ -189,6 +210,9 @@ AnimationPtr Parallaction::findAnimation(const char *name) {
}
void Parallaction::freeAnimations() {
+ for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) {
+ (*it)->_commands.clear(); // See comment for freeZones(), about circular references.
+ }
_location._animations.clear();
return;
}
@@ -237,10 +261,9 @@ void Parallaction::freeLocation() {
_localFlagNames->clear();
- _location._walkNodes.clear();
+ _location._walkPoints.clear();
- _gfx->clearGfxObjects();
- freeBackground();
+ _gfx->clearGfxObjects(kGfxObjNormal);
_location._programs.clear();
freeZones();
@@ -260,76 +283,32 @@ void Parallaction::freeLocation() {
void Parallaction::freeBackground() {
- _gfx->freeBackground();
_pathBuffer = 0;
}
void Parallaction::setBackground(const char* name, const char* mask, const char* path) {
- _gfx->setBackground(kBackgroundLocation, name, mask, path);
- _pathBuffer = &_gfx->_backgroundInfo.path;
+ BackgroundInfo *info = new BackgroundInfo;
+ _disk->loadScenery(*info, name, mask, path);
+
+ _gfx->setBackground(kBackgroundLocation, info);
+ _pathBuffer = &info->path;
return;
}
void Parallaction::showLocationComment(const char *text, bool end) {
- _gfx->setLocationBalloon(const_cast<char*>(text), end);
+ _balloonMan->setLocationBalloon(const_cast<char*>(text), end);
}
void Parallaction::processInput(InputData *data) {
+ if (!data) {
+ return;
+ }
switch (data->_event) {
- case kEvEnterZone:
- debugC(2, kDebugInput, "processInput: kEvEnterZone");
- _gfx->setFloatingLabel(data->_label);
- break;
-
- case kEvExitZone:
- debugC(2, kDebugInput, "processInput: kEvExitZone");
- _gfx->setFloatingLabel(0);
- break;
-
- case kEvAction:
- debugC(2, kDebugInput, "processInput: kEvAction");
- _input->stopHovering();
- pauseJobs();
- runZone(data->_zone);
- resumeJobs();
- break;
-
- case kEvOpenInventory:
- _input->stopHovering();
- _gfx->setFloatingLabel(0);
- if (hitZone(kZoneYou, data->_mousePos.x, data->_mousePos.y) == 0) {
- setArrowCursor();
- }
- pauseJobs();
- openInventory();
- break;
-
- case kEvCloseInventory: // closes inventory and possibly select item
- closeInventory();
- setInventoryCursor(data->_inventoryIndex);
- resumeJobs();
- break;
-
- case kEvHoverInventory:
- highlightInventoryItem(data->_inventoryIndex); // enable
- break;
-
- case kEvWalk:
- debugC(2, kDebugInput, "processInput: kEvWalk");
- _input->stopHovering();
- setArrowCursor();
- _char.scheduleWalk(data->_mousePos.x, data->_mousePos.y);
- break;
-
- case kEvQuitGame:
- _engineFlags |= kEngineQuit;
- break;
-
case kEvSaveGame:
_input->stopHovering();
saveGame();
@@ -350,28 +329,39 @@ void Parallaction::processInput(InputData *data) {
void Parallaction::runGame() {
InputData *data = _input->updateInput();
- if (data->_event != kEvNone) {
+ if (_engineFlags & kEngineQuit)
+ return;
+
+ runGuiFrame();
+ runDialogueFrame();
+ runCommentFrame();
+
+ if (_input->_inputMode == Input::kInputModeGame) {
processInput(data);
- }
+ runPendingZones();
- runPendingZones();
+ if (_engineFlags & kEngineQuit)
+ return;
- if (_engineFlags & kEngineChangeLocation) {
- changeLocation(_location._name);
+ if (_engineFlags & kEngineChangeLocation) {
+ changeLocation(_location._name);
+ }
}
-
_gfx->beginFrame();
if (_input->_inputMode == Input::kInputModeGame) {
- runScripts();
- walk();
+ _programExec->runScripts(_location._programs.begin(), _location._programs.end());
+ _char._ani->_z = _char._ani->height() + _char._ani->_top;
+ if (_char._ani->gfxobj) {
+ _char._ani->gfxobj->z = _char._ani->_z;
+ }
+ _char._walker->walk();
drawAnimations();
}
// change this to endFrame?
updateView();
-
}
@@ -400,14 +390,13 @@ void Parallaction::doLocationEnterTransition() {
pal.makeGrayscale();
_gfx->setPalette(pal);
- runScripts();
+ _programExec->runScripts(_location._programs.begin(), _location._programs.end());
drawAnimations();
-
+ showLocationComment(_location._comment, false);
_gfx->updateScreen();
- showLocationComment(_location._comment, false);
- _input->waitUntilLeftClick();
- _gfx->freeBalloons();
+ _input->waitForButtonEvent(kMouseLeftUp);
+ _balloonMan->freeBalloons();
// fades maximum intensity palette towards approximation of main palette
for (uint16 _si = 0; _si<6; _si++) {
@@ -467,6 +456,9 @@ void Parallaction::freeZones() {
debugC(2, kDebugExec, "freeZones preserving zone '%s'", z->_name);
it++;
} else {
+ (*it)->_commands.clear(); // Since commands may reference zones, and both commands and zones are kept stored into
+ // SharedPtr's, we need to kill commands explicitly to destroy any potential circular
+ // reference.
it = _location._zones.erase(it);
}
}
@@ -475,16 +467,47 @@ void Parallaction::freeZones() {
}
+enum {
+ WALK_LEFT = 0,
+ WALK_RIGHT = 1,
+ WALK_DOWN = 2,
+ WALK_UP = 3
+};
+
+struct WalkFrames {
+ int16 stillFrame[4];
+ int16 firstWalkFrame[4];
+ int16 numWalkFrames[4];
+ int16 frameRepeat[4];
+};
+
+WalkFrames _char20WalkFrames = {
+ { 0, 7, 14, 17 },
+ { 1, 8, 15, 18 },
+ { 6, 6, 2, 2 },
+ { 2, 2, 4, 4 }
+};
+
+WalkFrames _char24WalkFrames = {
+ { 0, 9, 18, 21 },
+ { 1, 10, 19, 22 },
+ { 8, 8, 2, 2 },
+ { 2, 2, 4, 4 }
+};
+
const char Character::_prefixMini[] = "mini";
const char Character::_suffixTras[] = "tras";
const char Character::_empty[] = "\0";
-Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(_ani) {
+Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation) {
_talk = NULL;
_head = NULL;
_objs = NULL;
+ _direction = WALK_DOWN;
+ _step = 0;
+
_dummy = false;
_ani->_left = 150;
@@ -496,24 +519,61 @@ Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(
_ani->_flags = kFlagsActive | kFlagsNoName;
_ani->_type = kZoneYou;
strncpy(_ani->_name, "yourself", ZONENAME_LENGTH);
+
+ // TODO: move creation into Parallaction. Needs to make Character a pointer first.
+ if (_vm->getGameType() == GType_Nippon) {
+ _builder = new PathBuilder_NS(this);
+ _walker = new PathWalker_NS(this);
+ } else {
+ _builder = new PathBuilder_BR(this);
+ _walker = new PathWalker_BR(this);
+ }
+}
+
+Character::~Character() {
+ delete _builder;
+ _builder = 0;
+
+ delete _walker;
+ _walker = 0;
+
+ free();
}
void Character::getFoot(Common::Point &foot) {
- foot.x = _ani->_left + _ani->width() / 2;
- foot.y = _ani->_top + _ani->height();
+ Common::Rect rect;
+ _ani->gfxobj->getRect(_ani->_frame, rect);
+
+ foot.x = _ani->_left + (rect.left + rect.width() / 2);
+ foot.y = _ani->_top + (rect.top + rect.height());
}
void Character::setFoot(const Common::Point &foot) {
- _ani->_left = foot.x - _ani->width() / 2;
- _ani->_top = foot.y - _ani->height();
+ Common::Rect rect;
+ _ani->gfxobj->getRect(_ani->_frame, rect);
+
+ _ani->_left = foot.x - (rect.left + rect.width() / 2);
+ _ani->_top = foot.y - (rect.top + rect.height());
+}
+
+#if 0
+void dumpPath(const PointList &list, const char* text) {
+ for (PointList::iterator it = list.begin(); it != list.end(); it++)
+ printf("node (%i, %i)\n", it->x, it->y);
+
+ return;
}
+#endif
void Character::scheduleWalk(int16 x, int16 y) {
if ((_ani->_flags & kFlagsRemove) || (_ani->_flags & kFlagsActive) == 0) {
return;
}
- _walkPath = _builder.buildPath(x, y);
+ _builder->buildPath(x, y);
+#if 0
+ dumpPath(_walkPath, _name);
+#endif
_engineFlags |= kEngineWalking;
}
@@ -522,11 +582,12 @@ void Character::free() {
delete _talk;
delete _head;
delete _objs;
+ delete _ani->gfxobj;
- _ani->gfxobj = NULL;
_talk = NULL;
_head = NULL;
_objs = NULL;
+ _ani->gfxobj = NULL;
return;
}
@@ -548,10 +609,14 @@ void Character::setName(const char *name) {
const char *end = begin + strlen(name);
_prefix = _empty;
+ _suffix = _empty;
_dummy = IS_DUMMY_CHARACTER(name);
if (!_dummy) {
+ if (!strstr(name, "donna")) {
+ _engineFlags &= ~kEngineTransformedDonna;
+ } else
if (_engineFlags & kEngineTransformedDonna) {
_suffix = _suffixTras;
} else {
@@ -560,8 +625,6 @@ void Character::setName(const char *name) {
_engineFlags |= kEngineTransformedDonna;
_suffix = _suffixTras;
end = s;
- } else {
- _suffix = _empty;
}
}
if (IS_MINI_CHARACTER(name)) {
@@ -597,9 +660,35 @@ void Parallaction::beep() {
}
void Parallaction::scheduleLocationSwitch(const char *location) {
+ debugC(9, kDebugExec, "scheduleLocationSwitch(%s)\n", location);
strcpy(_location._name, location);
_engineFlags |= kEngineChangeLocation;
}
+
+
+
+void Character::updateDirection(const Common::Point& pos, const Common::Point& to) {
+
+ Common::Point dist(to.x - pos.x, to.y - pos.y);
+ WalkFrames *frames = (_ani->getFrameNum() == 20) ? &_char20WalkFrames : &_char24WalkFrames;
+
+ _step++;
+
+ if (dist.x == 0 && dist.y == 0) {
+ _ani->_frame = frames->stillFrame[_direction];
+ return;
+ }
+
+ if (dist.x < 0)
+ dist.x = -dist.x;
+ if (dist.y < 0)
+ dist.y = -dist.y;
+
+ _direction = (dist.x > dist.y) ? ((to.x > pos.x) ? WALK_LEFT : WALK_RIGHT) : ((to.y > pos.y) ? WALK_DOWN : WALK_UP);
+ _ani->_frame = frames->firstWalkFrame[_direction] + (_step / frames->frameRepeat[_direction]) % frames->numWalkFrames[_direction];
+}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h
index 6e5957d3cd..e5c5221414 100644
--- a/engines/parallaction/parallaction.h
+++ b/engines/parallaction/parallaction.h
@@ -33,6 +33,7 @@
#include "engines/engine.h"
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/inventory.h"
#include "parallaction/parser.h"
@@ -100,10 +101,8 @@ enum {
enum EngineFlags {
kEngineQuit = (1 << 0),
kEnginePauseJobs = (1 << 1),
- kEngineInventory = (1 << 2),
kEngineWalking = (1 << 3),
kEngineChangeLocation = (1 << 4),
- kEngineBlockInput = (1 << 5),
kEngineDragging = (1 << 6),
kEngineTransformedDonna = (1 << 7),
@@ -113,14 +112,6 @@ enum EngineFlags {
enum {
kEvNone = 0,
- kEvEnterZone = 1,
- kEvExitZone = 2,
- kEvAction = 3,
- kEvOpenInventory = 4,
- kEvCloseInventory = 5,
- kEvHoverInventory = 6,
- kEvWalk = 7,
- kEvQuitGame = 1000,
kEvSaveGame = 2000,
kEvLoadGame = 4000
};
@@ -164,6 +155,8 @@ class Debugger;
class Gfx;
class SoundMan;
class Input;
+class DialogueManager;
+class MenuInputHelper;
struct Location {
@@ -184,7 +177,7 @@ struct Location {
char _soundFile[50];
// NS specific
- WalkNodeList _walkNodes;
+ PointList _walkPoints;
char _slideText[2][MAX_TOKEN_LEN];
// BRA specific
@@ -202,13 +195,16 @@ struct Character {
AnimationPtr _ani;
- Frames *_head;
- Frames *_talk;
- Frames *_objs;
- PathBuilder _builder;
- WalkNodeList *_walkPath;
+ GfxObj *_head;
+ GfxObj *_talk;
+ GfxObj *_objs;
+ PathBuilder *_builder;
+ PathWalker *_walker;
+ PointList _walkPath;
Character(Parallaction *vm);
+ ~Character();
+
void getFoot(Common::Point &foot);
void setFoot(const Common::Point &foot);
void scheduleWalk(int16 x, int16 y);
@@ -228,18 +224,19 @@ protected:
static const char _suffixTras[];
static const char _empty[];
+ int16 _direction, _step;
+
public:
void setName(const char *name);
const char *getName() const;
const char *getBaseName() const;
const char *getFullName() const;
bool dummy() const;
-};
+ void updateDirection(const Common::Point& pos, const Common::Point& to);
+};
-#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op()
-#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op()
#define NUM_LOCATIONS 120
@@ -259,41 +256,16 @@ public:
Input *_input;
- OpcodeSet _commandOpcodes;
-
- struct ParallactionStruct1 {
- CommandPtr cmd;
- ZonePtr z;
- } _cmdRunCtxt;
-
- OpcodeSet _instructionOpcodes;
-
- struct ParallactionStruct2 {
- AnimationPtr anim;
- ProgramPtr program;
- InstructionList::iterator inst;
- uint16 modCounter;
- bool suspend;
- } _instRunCtxt;
-
void processInput(InputData* data);
void pauseJobs();
void resumeJobs();
- void finalizeWalk(WalkNodeList *list);
- int16 selectWalkFrame(const Common::Point& pos, const WalkNodePtr from);
- void clipMove(Common::Point& pos, const WalkNodePtr from);
-
ZonePtr findZone(const char *name);
ZonePtr hitZone(uint32 type, uint16 x, uint16 y);
uint16 runZone(ZonePtr z);
void freeZones();
- void runDialogue(SpeakData*);
-
- void runCommands(CommandList& list, ZonePtr z = nullZonePtr);
-
AnimationPtr findAnimation(const char *name);
void freeAnimations();
@@ -327,6 +299,8 @@ public:
Gfx* _gfx;
Disk* _disk;
+ CommandExec* _cmdExec;
+ ProgramExec* _programExec;
Character _char;
void setLocationFlags(uint32 flags);
@@ -351,6 +325,7 @@ public:
Common::RandomSource _rnd;
Debugger *_debugger;
+ Frames *_comboArrow;
protected: // data
@@ -367,10 +342,8 @@ protected: // members
void runGame();
void updateView();
- void scheduleLocationSwitch(const char *location);
void doLocationEnterTransition();
virtual void changeLocation(char *location) = 0;
- virtual void changeCharacter(const char *name) = 0;
virtual void runPendingZones() = 0;
void allocateLocationSlot(const char *name);
void finalizeLocationParsing();
@@ -379,28 +352,33 @@ protected: // members
void displayComment(ExamineData *data);
- uint16 checkDoor();
-
void freeCharacter();
int16 pickupItem(ZonePtr z);
+ void clearSet(OpcodeSet &opcodes);
+
+
public:
+ void scheduleLocationSwitch(const char *location);
+ virtual void changeCharacter(const char *name) = 0;
+
virtual void callFunction(uint index, void* parm) { }
virtual void setArrowCursor() = 0;
- virtual void setInventoryCursor(int pos) = 0;
+ virtual void setInventoryCursor(ItemName name) = 0;
virtual void parseLocation(const char* name) = 0;
void updateDoor(ZonePtr z);
- virtual void runScripts() = 0;
- virtual void walk() = 0;
virtual void drawAnimations() = 0;
void beep();
+ ZonePtr _zoneTrap;
+ PathBuilder* getPathBuilder(Character *ch);
+
public:
// const char **_zoneFlagNamesRes;
// const char **_zoneTypeNamesRes;
@@ -425,6 +403,27 @@ public:
Inventory *_inventory;
InventoryRenderer *_inventoryRenderer;
+ BalloonManager *_balloonMan;
+
+ void setupBalloonManager();
+
+ void hideDialogueStuff();
+ DialogueManager *_dialogueMan;
+ void enterDialogueMode(ZonePtr z);
+ void exitDialogueMode();
+ void runDialogueFrame();
+
+ MenuInputHelper *_menuHelper;
+ void runGuiFrame();
+ void cleanupGui();
+
+ ZonePtr _commentZone;
+ void enterCommentMode(ZonePtr z);
+ void exitCommentMode();
+ void runCommentFrame();
+
+ void setInternLanguage(uint id);
+ uint getInternLanguage();
};
@@ -483,12 +482,18 @@ public:
typedef void (Parallaction_ns::*Callable)(void*);
virtual void callFunction(uint index, void* parm);
- void setMousePointer(uint32 value);
bool loadGame();
bool saveGame();
void switchBackground(const char* background, const char* mask);
+ void showSlide(const char *name, int x = 0, int y = 0);
+ void setArrowCursor();
+
+ // TODO: this should be private!!!!!!!
+ bool _inTestResult;
+ void cleanupGame();
+ bool allPartsComplete();
private:
LocationParser_ns *_locationParser;
@@ -500,17 +505,14 @@ private:
Common::String genSaveFileName(uint slot, bool oldStyle = false);
Common::InSaveFile *getInSaveFile(uint slot);
Common::OutSaveFile *getOutSaveFile(uint slot);
- bool allPartsComplete();
void setPartComplete(const Character& character);
private:
void changeLocation(char *location);
void changeCharacter(const char *name);
void runPendingZones();
- void cleanupGame();
- void setArrowCursor();
- void setInventoryCursor(int pos);
+ void setInventoryCursor(ItemName name);
void doLoadGame(uint16 slot);
@@ -520,11 +522,9 @@ private:
void initResources();
void initCursors();
- void initParsers();
static byte _resMouseArrow[256];
byte *_mouseArrow;
- Frames *_mouseComposedArrow;
static const Callable _dosCallables[25];
static const Callable _amigaCallables[25];
@@ -580,60 +580,16 @@ private:
const Callable *_callables;
protected:
- void runScripts();
- void walk();
void drawAnimations();
void parseLocation(const char *filename);
void loadProgram(AnimationPtr a, const char *filename);
- void initOpcodes();
-
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
-
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
-
void selectStartLocation();
- void guiStart();
- int guiSelectCharacter();
- void guiSplash();
- int guiNewGame();
- uint16 guiChooseLanguage();
- uint16 guiSelectGame();
- int guiGetSelectedBlock(const Common::Point &p);
-
- void showSlide(const char *name);
+ void startGui();
+ void startCreditSequence();
+ void startEndPartSequence();
};
@@ -655,6 +611,9 @@ public:
typedef void (Parallaction_br::*Callable)(void*);
virtual void callFunction(uint index, void* parm);
void changeCharacter(const char *name);
+ void setupSubtitles(char *s, char *s2, int y);
+ void clearSubtitles();
+
public:
Table *_countersNames;
@@ -674,7 +633,8 @@ public:
int32 _counters[32];
uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES];
-
+ void startPart(uint part);
+ void setArrowCursor();
private:
LocationParser_br *_locationParser;
ProgramParser_br *_programParser;
@@ -682,20 +642,15 @@ private:
void initResources();
void initFonts();
void freeFonts();
- void initOpcodes();
- void initParsers();
- void setArrowCursor();
- void setInventoryCursor(int pos);
+ void setInventoryCursor(ItemName name);
void changeLocation(char *location);
void runPendingZones();
void initPart();
void freePart();
- void startPart();
- void setMousePointer(int16 index);
void initCursors();
Frames *_dinoCursor;
@@ -706,10 +661,7 @@ private:
static const char *_partNames[];
- void guiStart();
- int guiShowMenu();
- void guiSplash(const char *name);
- Frames* guiRenderMenuItem(const char *text);
+ void startGui();
static const Callable _dosCallables[6];
@@ -725,68 +677,6 @@ private:
void parseLocation(const char* name);
void loadProgram(AnimationPtr a, const char *filename);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(character);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(add);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(let);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(music);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(give);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(text);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(part);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave);
-
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
-
- void setupSubtitles(char *s, char *s2, int y);
- void clearSubtitles();
#if 0
void jobWaitRemoveLabelJob(void *parm, Job *job);
void jobPauseSfx(void *parm, Job *job);
diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp
index 0f5cc2a0c4..761c8d1b74 100644
--- a/engines/parallaction/parallaction_br.cpp
+++ b/engines/parallaction/parallaction_br.cpp
@@ -32,6 +32,27 @@
namespace Parallaction {
+struct MouseComboProperties {
+ int _xOffset;
+ int _yOffset;
+ int _width;
+ int _height;
+};
+/*
+// TODO: improve NS's handling of normal cursor before merging cursor code.
+MouseComboProperties _mouseComboProps_NS = {
+ 7, // combo x offset (the icon from the inventory will be rendered from here)
+ 7, // combo y offset (ditto)
+ 32, // combo (arrow + icon) width
+ 32 // combo (arrow + icon) height
+};
+*/
+MouseComboProperties _mouseComboProps_BR = {
+ 8, // combo x offset (the icon from the inventory will be rendered from here)
+ 8, // combo y offset (ditto)
+ 68, // combo (arrow + icon) width
+ 68 // combo (arrow + icon) height
+};
const char *Parallaction_br::_partNames[] = {
"PART0",
@@ -56,7 +77,11 @@ int Parallaction_br::init() {
if (getGameType() == GType_BRA) {
if (getPlatform() == Common::kPlatformPC) {
- _disk = new DosDisk_br(this);
+ if (getFeatures() & GF_DEMO) {
+ _disk = new DosDemo_br(this);
+ } else {
+ _disk = new DosDisk_br(this);
+ }
_disk->setLanguage(2); // NOTE: language is now hardcoded to English. Original used command-line parameters.
_soundMan = new DummySoundMan(this);
} else {
@@ -72,14 +97,21 @@ int Parallaction_br::init() {
initResources();
initFonts();
initCursors();
- initOpcodes();
_locationParser = new LocationParser_br(this);
_locationParser->init();
_programParser = new ProgramParser_br(this);
_programParser->init();
+ _cmdExec = new CommandExec_br(this);
+ _cmdExec->init();
+ _programExec = new ProgramExec_br(this);
+ _programExec->init();
+
_part = -1;
+ _subtitle[0] = -1;
+ _subtitle[1] = -1;
+
Parallaction::init();
return 0;
@@ -92,6 +124,7 @@ Parallaction_br::~Parallaction_br() {
delete _dougCursor;
delete _donnaCursor;
+ delete _mouseArrow;
}
void Parallaction_br::callFunction(uint index, void* parm) {
@@ -102,13 +135,14 @@ void Parallaction_br::callFunction(uint index, void* parm) {
int Parallaction_br::go() {
- guiSplash("dyna");
- guiSplash("core");
+ if (getFeatures() & GF_DEMO) {
+ startPart(1);
+ } else {
+ startGui();
+ }
while ((_engineFlags & kEngineQuit) == 0) {
- guiStart();
-
// initCharacter();
_input->_inputMode = Input::kInputModeGame;
@@ -142,6 +176,12 @@ void Parallaction_br::initCursors() {
_dougCursor = _disk->loadPointer("pointer2");
_donnaCursor = _disk->loadPointer("pointer3");
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, 1);
+ _comboArrow = new SurfaceToFrames(surf);
+
+ // TODO: choose the pointer depending on the active character
+ // For now, we pick Donna's
_mouseArrow = _donnaCursor;
} else {
// TODO: Where are the Amiga cursors?
@@ -149,19 +189,6 @@ void Parallaction_br::initCursors() {
}
-void Parallaction_br::setMousePointer(int16 index) {
- // FIXME: Where are the Amiga cursors?
- if (getPlatform() == Common::kPlatformAmiga)
- return;
-
- Common::Rect r;
- _mouseArrow->getRect(0, r);
-
- _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0);
- _system->showMouse(true);
-
-}
-
void Parallaction_br::initPart() {
memset(_counters, 0, ARRAYSIZE(_counters));
@@ -170,7 +197,12 @@ void Parallaction_br::initPart() {
_objectsNames = _disk->loadTable("objects");
_countersNames = _disk->loadTable("counters");
-// _disk->loadObjects("icone.ico");
+ // TODO: maybe handle this into Disk
+ if (getPlatform() == Common::kPlatformPC) {
+ _char._objs = _disk->loadObjects("icone.ico");
+ } else {
+ _char._objs = _disk->loadObjects("icons.ico");
+ }
}
@@ -185,11 +217,17 @@ void Parallaction_br::freePart() {
_countersNames = 0;
}
-void Parallaction_br::startPart() {
+void Parallaction_br::startPart(uint part) {
+ _part = part;
+ _disk->selectArchive(_partNames[_part]);
initPart();
- strcpy(_location._name, partFirstLocation[_part]);
+ if (getFeatures() & GF_DEMO) {
+ strcpy(_location._name, "camalb");
+ } else {
+ strcpy(_location._name, partFirstLocation[_part]);
+ }
parseLocation("common");
changeLocation(_location._name);
@@ -199,16 +237,26 @@ void Parallaction_br::startPart() {
void Parallaction_br::runPendingZones() {
ZonePtr z;
+ _cmdExec->runSuspended();
+
if (_activeZone) {
z = _activeZone; // speak Zone or sound
_activeZone = nullZonePtr;
- runZone(z); // FIXME: BRA doesn't handle sound yet
+ if ((z->_type & 0xFFFF) == kZoneSpeak) {
+ enterDialogueMode(z);
+ } else {
+ runZone(z); // FIXME: BRA doesn't handle sound yet
+ }
}
if (_activeZone2) {
z = _activeZone2; // speak Zone or sound
_activeZone2 = nullZonePtr;
- runZone(z);
+ if ((z->_type & 0xFFFF) == kZoneSpeak) {
+ enterDialogueMode(z);
+ } else {
+ runZone(z); // FIXME: BRA doesn't handle sound yet
+ }
}
}
@@ -218,21 +266,35 @@ void Parallaction_br::changeLocation(char *location) {
// free open location stuff
clearSubtitles();
freeBackground();
- _gfx->clearGfxObjects();
+ _gfx->clearGfxObjects(kGfxObjNormal);
+ _gfx->freeLabels();
+ _subtitle[0] = _subtitle[1] = -1;
+
_location._programs.clear();
+
+ _location._animations.remove(_char._ani);
+
freeZones();
freeAnimations();
+
+ _location._animations.push_front(_char._ani);
+
// free(_location._comment);
// _location._comment = 0;
-// _location._commands.clear();
-// _location._aCommands.clear();
-
+ _location._commands.clear();
+ _location._aCommands.clear();
// load new location
parseLocation(location);
- runCommands(_location._commands);
+
+ // kFlagsRemove is cleared because the character defaults to visible on new locations
+ // script command can hide the character, anyway, so that's why the flag is cleared
+ // before _location._commands are executed
+ _char._ani->_flags &= ~kFlagsRemove;
+
+ _cmdExec->run(_location._commands);
// doLocationEnterTransition();
- runCommands(_location._aCommands);
+ _cmdExec->run(_location._aCommands);
_engineFlags &= ~kEngineChangeLocation;
}
@@ -282,25 +344,45 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) {
void Parallaction_br::changeCharacter(const char *name) {
const char *charName = _char.getName();
- if (!scumm_stricmp(charName, name)) {
- return;
+
+ if (scumm_stricmp(charName, name)) {
+ debugC(1, kDebugExec, "changeCharacter(%s)", name);
+
+ _char.setName(name);
+ _char._ani->gfxobj = _gfx->loadAnim(name);
+ _char._ani->gfxobj->setFlags(kGfxObjCharacter);
+ _char._ani->gfxobj->clearFlags(kGfxObjNormal);
+ _char._talk = _disk->loadTalk(name);
}
- _char.setName(name);
- _char._talk = _disk->loadTalk(name);
+ _char._ani->_flags |= kFlagsActive;
}
void Parallaction_br::setArrowCursor() {
+ // FIXME: Where are the Amiga cursors?
+ if (getPlatform() == Common::kPlatformAmiga)
+ return;
+ Common::Rect r;
+ _mouseArrow->getRect(0, r);
+ _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0);
+ _system->showMouse(true);
+ _input->_activeItem._id = 0;
}
-void Parallaction_br::setInventoryCursor(int pos) {
-
+void Parallaction_br::setInventoryCursor(ItemName name) {
+ assert(name > 0);
+ byte *src = _mouseArrow->getData(0);
+ byte *dst = _comboArrow->getData(0);
+ memcpy(dst, src, _comboArrow->getSize(0));
+ // FIXME: destination offseting is not clear
+ _inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width);
+ _system->setMouseCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0);
}
} // namespace Parallaction
diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp
index 2cca3a6a4a..e81492e655 100644
--- a/engines/parallaction/parallaction_ns.cpp
+++ b/engines/parallaction/parallaction_ns.cpp
@@ -34,6 +34,7 @@
namespace Parallaction {
+
#define MOUSEARROW_WIDTH 16
#define MOUSEARROW_HEIGHT 16
@@ -135,18 +136,24 @@ int Parallaction_ns::init() {
initResources();
initFonts();
initCursors();
- initOpcodes();
_locationParser = new LocationParser_ns(this);
_locationParser->init();
_programParser = new ProgramParser_ns(this);
_programParser->init();
+ _cmdExec = new CommandExec_ns(this);
+ _cmdExec->init();
+ _programExec = new ProgramExec_ns(this);
+ _programExec->init();
+
_introSarcData1 = 0;
_introSarcData2 = 1;
_introSarcData3 = 200;
num_foglie = 0;
+ _inTestResult = false;
+
_location._animations.push_front(_char._ani);
Parallaction::init();
@@ -157,7 +164,8 @@ int Parallaction_ns::init() {
Parallaction_ns::~Parallaction_ns() {
freeFonts();
- delete _mouseComposedArrow;
+ delete _locationParser;
+ delete _programParser;
_location._animations.remove(_char._ani);
@@ -174,7 +182,7 @@ void Parallaction_ns::freeFonts() {
}
void Parallaction_ns::initCursors() {
- _mouseComposedArrow = _disk->loadPointer("pointer");
+ _comboArrow = _disk->loadPointer("pointer");
_mouseArrow = _resMouseArrow;
}
@@ -183,40 +191,20 @@ void Parallaction_ns::setArrowCursor() {
debugC(1, kDebugInput, "setting mouse cursor to arrow");
// this stuff is needed to avoid artifacts with labels and selected items when switching cursors
- _gfx->setFloatingLabel(0);
+ _input->stopHovering();
_input->_activeItem._id = 0;
_system->setMouseCursor(_mouseArrow, MOUSEARROW_WIDTH, MOUSEARROW_HEIGHT, 0, 0, 0);
- _system->showMouse(true);
-
}
-void Parallaction_ns::setInventoryCursor(int pos) {
-
- if (pos == -1)
- return;
+void Parallaction_ns::setInventoryCursor(ItemName name) {
+ assert(name > 0);
- const InventoryItem *item = getInventoryItem(pos);
- if (item->_index == 0)
- return;
-
- _input->_activeItem._id = item->_id;
-
- byte *v8 = _mouseComposedArrow->getData(0);
+ byte *v8 = _comboArrow->getData(0);
// FIXME: destination offseting is not clear
- byte* s = _char._objs->getData(item->_index);
- byte* d = v8 + 7 + MOUSECOMBO_WIDTH * 7;
-
- for (uint i = 0; i < INVENTORYITEM_HEIGHT; i++) {
- memcpy(d, s, INVENTORYITEM_WIDTH);
-
- s += INVENTORYITEM_PITCH;
- d += MOUSECOMBO_WIDTH;
- }
-
+ _inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH + 7, MOUSECOMBO_WIDTH);
_system->setMouseCursor(v8, MOUSECOMBO_WIDTH, MOUSECOMBO_HEIGHT, 0, 0, 0);
-
}
@@ -232,11 +220,8 @@ int Parallaction_ns::go() {
_globalTable = _disk->loadTable("global");
- guiStart();
+ startGui();
- changeLocation(_location._name);
-
- _input->_inputMode = Input::kInputModeGame;
while ((_engineFlags & kEngineQuit) == 0) {
runGame();
}
@@ -268,8 +253,14 @@ void Parallaction_ns::switchBackground(const char* background, const char* mask)
}
-void Parallaction_ns::showSlide(const char *name) {
- _gfx->setBackground(kBackgroundSlide, name, 0, 0);
+void Parallaction_ns::showSlide(const char *name, int x, int y) {
+ BackgroundInfo *info = new BackgroundInfo;
+ _disk->loadSlide(*info, name);
+
+ info->x = (x == CENTER_LABEL_HORIZONTAL) ? ((_vm->_screenWidth - info->width) >> 1) : x;
+ info->y = (y == CENTER_LABEL_VERTICAL) ? ((_vm->_screenHeight - info->height) >> 1) : y;
+
+ _gfx->setBackground(kBackgroundSlide, info);
}
void Parallaction_ns::runPendingZones() {
@@ -286,16 +277,19 @@ void Parallaction_ns::runPendingZones() {
void Parallaction_ns::changeLocation(char *location) {
debugC(1, kDebugExec, "changeLocation(%s)", location);
+ MouseTriState oldMouseState = _input->getMouseState();
+ _input->setMouseState(MOUSE_DISABLED);
+
_soundMan->playLocationMusic(location);
- _gfx->setFloatingLabel(0);
+ _input->stopHovering();
_gfx->freeLabels();
- _input->stopHovering();
- if (_engineFlags & kEngineBlockInput) {
- setArrowCursor();
- }
+ _zoneTrap = nullZonePtr;
+ setArrowCursor();
+
+ _gfx->showGfxObj(_char._ani->gfxobj, false);
_location._animations.remove(_char._ani);
freeLocation();
@@ -307,7 +301,9 @@ void Parallaction_ns::changeLocation(char *location) {
showSlide(locname.slide());
uint id = _gfx->createLabel(_menuFont, _location._slideText[0], 1);
_gfx->showLabel(id, CENTER_LABEL_HORIZONTAL, 14);
- _input->waitUntilLeftClick();
+ _gfx->updateScreen();
+
+ _input->waitForButtonEvent(kMouseLeftUp);
_gfx->freeLabels();
freeBackground();
}
@@ -317,6 +313,7 @@ void Parallaction_ns::changeLocation(char *location) {
}
_location._animations.push_front(_char._ani);
+ _gfx->showGfxObj(_char._ani->gfxobj, true);
strcpy(_saveData1, locname.location());
parseLocation(_saveData1);
@@ -341,19 +338,18 @@ void Parallaction_ns::changeLocation(char *location) {
// and acommands are executed, so that it can be set again if needed.
_engineFlags &= ~kEngineChangeLocation;
- runCommands(_location._commands);
+ _cmdExec->run(_location._commands);
doLocationEnterTransition();
- runCommands(_location._aCommands);
+ _cmdExec->run(_location._aCommands);
if (_location._hasSound)
_soundMan->playSfx(_location._soundFile, 0, true);
- debugC(1, kDebugExec, "changeLocation() done");
-
- return;
+ _input->setMouseState(oldMouseState);
+ debugC(1, kDebugExec, "changeLocation() done");
}
@@ -401,6 +397,8 @@ void Parallaction_ns::changeCharacter(const char *name) {
Common::String oldArchive = _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
_char._ani->gfxobj = _gfx->loadAnim(_char.getFullName());
+ _char._ani->gfxobj->setFlags(kGfxObjCharacter);
+ _char._ani->gfxobj->clearFlags(kGfxObjNormal);
if (!_char.dummy()) {
if (getPlatform() == Common::kPlatformAmiga) {
diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp
index f9de6eb4af..6de0a7d7f5 100644
--- a/engines/parallaction/parser.cpp
+++ b/engines/parallaction/parser.cpp
@@ -30,8 +30,7 @@ namespace Parallaction {
char _tokens[20][MAX_TOKEN_LEN];
-Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {
-}
+Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}
Script::~Script() {
if (_disposeSource)
diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h
index d488cf9b58..79e6cf6640 100644
--- a/engines/parallaction/parser.h
+++ b/engines/parallaction/parser.h
@@ -128,6 +128,7 @@ protected:
// BRA specific
int numZones;
+ BackgroundInfo *info;
char *bgName;
char *maskName;
char *pathName;
@@ -171,7 +172,7 @@ protected:
DECLARE_UNQUALIFIED_COMMAND_PARSER(animation);
DECLARE_UNQUALIFIED_COMMAND_PARSER(zone);
DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
- DECLARE_UNQUALIFIED_COMMAND_PARSER(drop);
+ DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject);
DECLARE_UNQUALIFIED_COMMAND_PARSER(call);
DECLARE_UNQUALIFIED_COMMAND_PARSER(simple);
DECLARE_UNQUALIFIED_COMMAND_PARSER(move);
@@ -192,8 +193,8 @@ protected:
Question *parseQuestion();
void parseZone(ZoneList &list, char *name);
- void parseZoneTypeBlock(ZonePtr z);
- void parseWalkNodes(WalkNodeList &list);
+ virtual void parseZoneTypeBlock(ZonePtr z);
+ void parsePointList(PointList &list);
void parseAnimation(AnimationList &list, char *name);
void parseCommands(CommandList&);
void parseCommandFlags();
@@ -221,13 +222,14 @@ public:
virtual void init();
virtual ~LocationParser_ns() {
+ delete _parser;
delete _commandsNames;
delete _locationStmt;
+ delete _locationZoneStmt;
+ delete _locationAnimStmt;
delete _zoneTypeNames;
delete _zoneFlagNames;
- delete _parser;
-
clearSet(_commandParsers);
clearSet(_locationAnimParsers);
clearSet(_locationZoneParsers);
@@ -283,6 +285,9 @@ protected:
DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);
+ virtual void parseZoneTypeBlock(ZonePtr z);
+ void parsePathData(ZonePtr z);
+
public:
LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm) {
}
@@ -306,6 +311,7 @@ protected:
Parser *_parser;
Parallaction_ns *_vm;
+
Script *_script;
ProgramPtr _program;
@@ -356,7 +362,9 @@ public:
virtual void init();
virtual ~ProgramParser_ns() {
+ delete _parser;
delete _instructionNames;
+
clearSet(_instructionParsers);
}
diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp
index 51da7eb396..3b446805d7 100644
--- a/engines/parallaction/parser_br.cpp
+++ b/engines/parallaction/parser_br.cpp
@@ -390,7 +390,7 @@ DECLARE_LOCATION_PARSER(flags) {
if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {
// only for 1st visit
- _vm->clearLocationFlags(kFlagsAll);
+ _vm->clearLocationFlags((uint32)kFlagsAll);
int _si = 1;
do {
@@ -442,7 +442,7 @@ DECLARE_LOCATION_PARSER(redundant) {
DECLARE_LOCATION_PARSER(character) {
debugC(7, kDebugParser, "LOCATION_PARSER(character) ");
- ctxt.characterName = strdup(_tokens[0]);
+ ctxt.characterName = strdup(_tokens[1]);
}
@@ -464,9 +464,9 @@ DECLARE_LOCATION_PARSER(mask) {
debugC(7, kDebugParser, "LOCATION_PARSER(mask) ");
ctxt.maskName = strdup(_tokens[1]);
- _vm->_gfx->_backgroundInfo.layers[0] = atoi(_tokens[2]);
- _vm->_gfx->_backgroundInfo.layers[1] = atoi(_tokens[3]);
- _vm->_gfx->_backgroundInfo.layers[2] = atoi(_tokens[4]);
+ ctxt.info->layers[0] = atoi(_tokens[2]);
+ ctxt.info->layers[1] = atoi(_tokens[3]);
+ ctxt.info->layers[2] = atoi(_tokens[4]);
}
@@ -750,6 +750,67 @@ DECLARE_ZONE_PARSER(type) {
_parser->popTables();
}
+void LocationParser_br::parsePathData(ZonePtr z) {
+
+ PathData *data = new PathData;
+
+ do {
+
+ if (!scumm_stricmp("zone", _tokens[0])) {
+ int id = atoi(_tokens[1]);
+ parsePointList(data->_lists[id]);
+ data->_numLists++;
+ }
+
+ _script->readLineToken(true);
+ } while (scumm_stricmp("endzone", _tokens[0]));
+
+ z->u.path = data;
+}
+
+void LocationParser_br::parseZoneTypeBlock(ZonePtr z) {
+ debugC(7, kDebugParser, "parseZoneTypeBlock(name: %s, type: %x)", z->_name, z->_type);
+
+ switch (z->_type & 0xFFFF) {
+ case kZoneExamine: // examine Zone alloc
+ parseExamineData(z);
+ break;
+
+ case kZoneDoor: // door Zone alloc
+ parseDoorData(z);
+ break;
+
+ case kZoneGet: // get Zone alloc
+ parseGetData(z);
+ break;
+
+ case kZoneMerge: // merge Zone alloc
+ parseMergeData(z);
+ break;
+
+ case kZoneHear: // hear Zone alloc
+ parseHearData(z);
+ break;
+
+ case kZoneSpeak: // speak Zone alloc
+ parseSpeakData(z);
+ break;
+
+ // BRA specific zone
+ case kZonePath:
+ parsePathData(z);
+ break;
+
+ default:
+ // eats up 'ENDZONE' line for unprocessed zone types
+ _script->readLineToken(true);
+ break;
+ }
+
+ debugC(7, kDebugParser, "parseZoneTypeBlock() done");
+
+ return;
+}
DECLARE_ANIM_PARSER(file) {
debugC(7, kDebugParser, "ANIM_PARSER(file) ");
@@ -983,7 +1044,7 @@ void LocationParser_br::init() {
COMMAND_PARSER(zone); // off
COMMAND_PARSER(call);
COMMAND_PARSER(flags); // toggle
- COMMAND_PARSER(drop);
+ COMMAND_PARSER(invObject); // drop
COMMAND_PARSER(simple); // quit
COMMAND_PARSER(move);
COMMAND_PARSER(zone); // stop
@@ -991,7 +1052,7 @@ void LocationParser_br::init() {
COMMAND_PARSER(string); // followme
COMMAND_PARSER(simple); // onmouse
COMMAND_PARSER(simple); // offmouse
- COMMAND_PARSER(drop); // add
+ COMMAND_PARSER(invObject); // add
COMMAND_PARSER(zone); // leave
COMMAND_PARSER(math); // inc
COMMAND_PARSER(math); // dec
@@ -1114,11 +1175,14 @@ void LocationParser_br::parse(Script *script) {
ctxt.maskName = 0;
ctxt.pathName = 0;
ctxt.characterName = 0;
+ ctxt.info = new BackgroundInfo;
LocationParser_ns::parse(script);
- _vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName);
- _vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path;
+ _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName);
+ _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info);
+ _vm->_pathBuffer = &ctxt.info->path;
+
if (ctxt.characterName) {
_vm->changeCharacter(ctxt.characterName);
diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp
index 2c4601c938..ad0f714fdc 100644
--- a/engines/parallaction/parser_ns.cpp
+++ b/engines/parallaction/parser_ns.cpp
@@ -299,6 +299,7 @@ void LocationParser_ns::parseAnimation(AnimationList &list, char *name) {
AnimationPtr a(new Animation);
strncpy(a->_name, name, ZONENAME_LENGTH);
+ a->_flags |= kFlagsIsAnimation;
list.push_front(AnimationPtr(a));
@@ -658,7 +659,7 @@ DECLARE_COMMAND_PARSER(location) {
}
-DECLARE_COMMAND_PARSER(drop) {
+DECLARE_COMMAND_PARSER(invObject) {
debugC(7, kDebugParser, "COMMAND_PARSER(drop) ");
createCommand(_parser->_lookup);
@@ -1011,7 +1012,7 @@ DECLARE_LOCATION_PARSER(disk) {
DECLARE_LOCATION_PARSER(nodes) {
debugC(7, kDebugParser, "LOCATION_PARSER(nodes) ");
- parseWalkNodes(_vm->_location._walkNodes);
+ parsePointList(_vm->_location._walkPoints);
}
@@ -1059,7 +1060,7 @@ DECLARE_LOCATION_PARSER(flags) {
if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {
// only for 1st visit
- _vm->clearLocationFlags(kFlagsAll);
+ _vm->clearLocationFlags((uint32)kFlagsAll);
int _si = 1;
do {
@@ -1124,26 +1125,20 @@ void LocationParser_ns::parse(Script *script) {
resolveCommandForwards();
}
-void LocationParser_ns::parseWalkNodes(WalkNodeList &list) {
- debugC(5, kDebugParser, "parseWalkNodes()");
+void LocationParser_ns::parsePointList(PointList &list) {
+ debugC(5, kDebugParser, "parsePointList()");
_script->readLineToken(true);
while (scumm_stricmp(_tokens[0], "ENDNODES")) {
if (!scumm_stricmp(_tokens[0], "COORD")) {
-
- WalkNodePtr v4(new WalkNode(
- atoi(_tokens[1]),
- atoi(_tokens[2])
- ));
-
- list.push_front(v4);
+ list.push_front(Common::Point(atoi(_tokens[1]), atoi(_tokens[2])));
}
_script->readLineToken(true);
}
- debugC(5, kDebugParser, "parseWalkNodes() done");
+ debugC(5, kDebugParser, "parsePointList() done");
return;
}
@@ -1203,7 +1198,7 @@ void LocationParser_ns::init() {
COMMAND_PARSER(zone); // off
COMMAND_PARSER(call); // call
COMMAND_PARSER(flags); // toggle
- COMMAND_PARSER(drop); // drop
+ COMMAND_PARSER(invObject); // drop
COMMAND_PARSER(simple); // quit
COMMAND_PARSER(move); // move
COMMAND_PARSER(zone); // stop
diff --git a/engines/parallaction/sound.cpp b/engines/parallaction/sound.cpp
index dd74e8f7aa..df6867a90c 100644
--- a/engines/parallaction/sound.cpp
+++ b/engines/parallaction/sound.cpp
@@ -175,6 +175,7 @@ void MidiPlayer::close() {
_mutex.lock();
_driver->setTimerCallback(NULL, NULL);
_driver->close();
+ delete _driver;
_driver = 0;
_parser->setMidiDriver(NULL);
delete _parser;
@@ -249,6 +250,9 @@ void DosSoundMan::stopMusic() {
}
void DosSoundMan::playCharacterMusic(const char *character) {
+ if (character == NULL) {
+ return;
+ }
if (!scumm_stricmp(_vm->_location._name, "night") ||
!scumm_stricmp(_vm->_location._name, "intsushi")) {
diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp
index 0a8ded9e29..bf8f423fd5 100644
--- a/engines/parallaction/walk.cpp
+++ b/engines/parallaction/walk.cpp
@@ -27,33 +27,43 @@
namespace Parallaction {
-static uint16 _doorData1 = 1000;
-static ZonePtr _zoneTrap;
-static uint16 walkData1 = 0;
-static uint16 walkData2 = 0; // next walk frame
+#define IS_PATH_CLEAR(x,y) _vm->_pathBuffer->getValue((x), (y))
inline byte PathBuffer::getValue(uint16 x, uint16 y) {
byte m = data[(x >> 3) + y * internalWidth];
- uint n = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7));
- return ((1 << n) & m) >> n;
+ uint bit = 0;
+ switch (_vm->getGameType()) {
+ case GType_Nippon:
+ bit = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7));
+ break;
+
+ case GType_BRA:
+ // Amiga and PC versions pack the path bits the same way in BRA
+ bit = 7 - (x & 7);
+ break;
+
+ default:
+ error("path mask not yet implemented for this game type");
+ }
+ return ((1 << bit) & m) >> bit;
}
// adjusts position towards nearest walkable point
//
-void PathBuilder::correctPathPoint(Common::Point &to) {
+void PathBuilder_NS::correctPathPoint(Common::Point &to) {
- if (_vm->_pathBuffer->getValue(to.x, to.y)) return;
+ if (IS_PATH_CLEAR(to.x, to.y)) return;
int16 right = to.x;
int16 left = to.x;
do {
right++;
- } while ((_vm->_pathBuffer->getValue(right, to.y) == 0) && (right < _vm->_pathBuffer->w));
+ } while (!IS_PATH_CLEAR(right, to.y) && (right < _vm->_pathBuffer->w));
do {
left--;
- } while ((_vm->_pathBuffer->getValue(left, to.y) == 0) && (left > 0));
+ } while (!IS_PATH_CLEAR(left, to.y) && (left > 0));
right = (right == _vm->_pathBuffer->w) ? 1000 : right - to.x;
left = (left == 0) ? 1000 : to.x - left;
@@ -62,10 +72,10 @@ void PathBuilder::correctPathPoint(Common::Point &to) {
int16 bottom = to.y;
do {
top--;
- } while ((_vm->_pathBuffer->getValue(to.x, top) == 0) && (top > 0));
+ } while (!IS_PATH_CLEAR(to.x, top) && (top > 0));
do {
bottom++;
- } while ((_vm->_pathBuffer->getValue(to.x, bottom) == 0) && (bottom < _vm->_pathBuffer->h));
+ } while (!IS_PATH_CLEAR(to.x, bottom) && (bottom < _vm->_pathBuffer->h));
top = (top == 0) ? 1000 : to.y - top;
bottom = (bottom == _vm->_pathBuffer->h) ? 1000 : bottom - to.y;
@@ -90,7 +100,7 @@ void PathBuilder::correctPathPoint(Common::Point &to) {
}
-uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& stop) {
+uint32 PathBuilder_NS::buildSubPath(const Common::Point& pos, const Common::Point& stop) {
uint32 v28 = 0;
uint32 v2C = 0;
@@ -103,16 +113,15 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&
while (true) {
- WalkNodeList::iterator nearest = _vm->_location._walkNodes.end();
- WalkNodeList::iterator locNode = _vm->_location._walkNodes.begin();
+ PointList::iterator nearest = _vm->_location._walkPoints.end();
+ PointList::iterator locNode = _vm->_location._walkPoints.begin();
// scans location path nodes searching for the nearest Node
// which can't be farther than the target position
// otherwise no _closest_node is selected
- while (locNode != _vm->_location._walkNodes.end()) {
+ while (locNode != _vm->_location._walkPoints.end()) {
- Common::Point v8;
- (*locNode)->getPoint(v8);
+ Common::Point v8 = *locNode;
v2C = v8.sqrDist(stop);
v28 = v8.sqrDist(v20);
@@ -124,80 +133,59 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&
locNode++;
}
- if (nearest == _vm->_location._walkNodes.end()) break;
+ if (nearest == _vm->_location._walkPoints.end()) break;
- (*nearest)->getPoint(v20);
+ v20 = *nearest;
v34 = v30 = v20.sqrDist(stop);
- _subPath.push_back(WalkNodePtr(new WalkNode(**nearest)));
+ _subPath.push_back(*nearest);
}
return v34;
}
-#if 0
-void printNodes(WalkNodeList *list, const char* text) {
- printf("%s\n-------------------\n", text);
- for (WalkNodeList::iterator it = list->begin(); it != list->end(); it++)
- printf("node [%p] (%i, %i)\n", *it, (*it)->_x, (*it)->_y);
- return;
-}
-#endif
//
// x, y: mouse click (foot) coordinates
//
-WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) {
+void PathBuilder_NS::buildPath(uint16 x, uint16 y) {
debugC(1, kDebugWalk, "PathBuilder::buildPath to (%i, %i)", x, y);
+ _ch->_walkPath.clear();
+
Common::Point to(x, y);
correctPathPoint(to);
debugC(1, kDebugWalk, "found closest path point at (%i, %i)", to.x, to.y);
- WalkNodePtr v48(new WalkNode(to.x, to.y));
- WalkNodePtr v44 = v48;
+ Common::Point v48(to);
+ Common::Point v44(to);
- uint16 v38 = walkFunc1(to.x, to.y, v44);
+ uint16 v38 = walkFunc1(to, v44);
if (v38 == 1) {
// destination directly reachable
debugC(1, kDebugWalk, "direct move to (%i, %i)", to.x, to.y);
-
- _list = new WalkNodeList;
- _list->push_back(v48);
- return _list;
+ _ch->_walkPath.push_back(v48);
+ return;
}
// path is obstructed: look for alternative
- _list = new WalkNodeList;
- _list->push_back(v48);
-#if 0
- printNodes(_list, "start");
-#endif
-
- Common::Point stop(v48->_x, v48->_y);
+ _ch->_walkPath.push_back(v48);
Common::Point pos;
- _vm->_char.getFoot(pos);
+ _ch->getFoot(pos);
- uint32 v34 = buildSubPath(pos, stop);
+ uint32 v34 = buildSubPath(pos, v48);
if (v38 != 0 && v34 > v38) {
// no alternative path (gap?)
- _list->clear();
- _list->push_back(v44);
- return _list;
+ _ch->_walkPath.clear();
+ _ch->_walkPath.push_back(v44);
+ return;
}
- _list->insert(_list->begin(), _subPath.begin(), _subPath.end());
-#if 0
- printNodes(_list, "first segment");
-#endif
+ _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end());
- (*_list->begin())->getPoint(stop);
- buildSubPath(pos, stop);
- _list->insert(_list->begin(), _subPath.begin(), _subPath.end());
-#if 0
- printNodes(_list, "complete");
-#endif
+ buildSubPath(pos, *_ch->_walkPath.begin());
+ _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end());
- return _list;
+ return;
}
@@ -208,23 +196,23 @@ WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) {
// 1 : Point reachable in a straight line
// other values: square distance to target (point not reachable in a straight line)
//
-uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
+uint16 PathBuilder_NS::walkFunc1(const Common::Point &to, Common::Point& node) {
- Common::Point arg(x, y);
+ Common::Point arg(to);
- Common::Point v4(0, 0);
+ Common::Point v4;
Common::Point foot;
- _vm->_char.getFoot(foot);
+ _ch->getFoot(foot);
Common::Point v8(foot);
while (foot != arg) {
- if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) != 0) foot.x++;
- if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) != 0) foot.x--;
- if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) != 0) foot.y++;
- if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) != 0) foot.y--;
+ if (foot.x < to.x && IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++;
+ if (foot.x > to.x && IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--;
+ if (foot.y < to.y && IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++;
+ if (foot.y > to.y && IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg) {
@@ -234,10 +222,10 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
while (foot != arg) {
- if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) == 0) foot.x++;
- if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) == 0) foot.x--;
- if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) == 0) foot.y++;
- if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) == 0) foot.y--;
+ if (foot.x < to.x && !IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++;
+ if (foot.x > to.x && !IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--;
+ if (foot.y < to.y && !IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++;
+ if (foot.y > to.y && !IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg)
return 0;
@@ -245,10 +233,8 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
v8 = foot;
}
- Node->_x = v4.x;
- Node->_y = v4.y;
-
- return (x - v4.x) * (x - v4.x) + (y - v4.y) * (y - v4.y);
+ node = v4;
+ return v4.sqrDist(to);
}
v8 = foot;
@@ -259,190 +245,390 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
return 1;
}
-void Parallaction::clipMove(Common::Point& pos, const WalkNodePtr from) {
+void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) {
- if ((pos.x < from->_x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) {
- pos.x = (pos.x + 2 < from->_x) ? pos.x + 2 : from->_x;
+ if ((pos.x < to.x) && (pos.x < _vm->_pathBuffer->w) && IS_PATH_CLEAR(pos.x + 2, pos.y)) {
+ pos.x = (pos.x + 2 < to.x) ? pos.x + 2 : to.x;
}
- if ((pos.x > from->_x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) {
- pos.x = (pos.x - 2 > from->_x) ? pos.x - 2 : from->_x;
+ if ((pos.x > to.x) && (pos.x > 0) && IS_PATH_CLEAR(pos.x - 2, pos.y)) {
+ pos.x = (pos.x - 2 > to.x) ? pos.x - 2 : to.x;
}
- if ((pos.y < from->_y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) {
- pos.y = (pos.y + 2 <= from->_y) ? pos.y + 2 : from->_y;
+ if ((pos.y < to.y) && (pos.y < _vm->_pathBuffer->h) && IS_PATH_CLEAR(pos.x, pos.y + 2)) {
+ pos.y = (pos.y + 2 <= to.y) ? pos.y + 2 : to.y;
}
- if ((pos.y > from->_y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) {
- pos.y = (pos.y - 2 >= from->_y) ? pos.y - 2 :from->_y;
+ if ((pos.y > to.y) && (pos.y > 0) && IS_PATH_CLEAR(pos.x, pos.y - 2)) {
+ pos.y = (pos.y - 2 >= to.y) ? pos.y - 2 : to.y;
}
return;
}
-int16 Parallaction::selectWalkFrame(const Common::Point& pos, const WalkNodePtr from) {
- Common::Point dist(from->_x - pos.x, from->_y - pos.y);
+void PathWalker_NS::checkDoor(const Common::Point &foot) {
- if (dist.x < 0)
- dist.x = -dist.x;
- if (dist.y < 0)
- dist.y = -dist.y;
+ ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
+ if (z) {
+ if ((z->_flags & kFlagsClosed) == 0) {
+ _vm->_location._startPosition = z->u.door->_startPos;
+ _vm->_location._startFrame = z->u.door->_startFrame;
+ _vm->scheduleLocationSwitch(z->u.door->_location);
+ _vm->_zoneTrap = nullZonePtr;
+ } else {
+ _vm->_cmdExec->run(z->_commands, z);
+ }
+ }
- walkData1++;
+ z = _vm->hitZone(kZoneTrap, foot.x, foot.y);
+ if (z) {
+ _vm->setLocationFlags(kFlagsEnter);
+ _vm->_cmdExec->run(z->_commands, z);
+ _vm->clearLocationFlags(kFlagsEnter);
+ _vm->_zoneTrap = z;
+ } else
+ if (_vm->_zoneTrap) {
+ _vm->setLocationFlags(kFlagsExit);
+ _vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap);
+ _vm->clearLocationFlags(kFlagsExit);
+ _vm->_zoneTrap = nullZonePtr;
+ }
- // walk frame selection
- int16 v16;
- if (_char._ani->getFrameNum() == 20) {
+}
- if (dist.x > dist.y) {
- walkData2 = (from->_x > pos.x) ? 0 : 7;
- walkData1 %= 12;
- v16 = walkData1 / 2;
- } else {
- walkData2 = (from->_y > pos.y) ? 14 : 17;
- walkData1 %= 8;
- v16 = walkData1 / 4;
+
+void PathWalker_NS::finalizeWalk() {
+ _engineFlags &= ~kEngineWalking;
+
+ Common::Point foot;
+ _ch->getFoot(foot);
+ checkDoor(foot);
+
+ _ch->_walkPath.clear();
+}
+
+void PathWalker_NS::walk() {
+ if ((_engineFlags & kEngineWalking) == 0) {
+ return;
+ }
+
+ Common::Point curPos;
+ _ch->getFoot(curPos);
+
+ // update target, if previous was reached
+ PointList::iterator it = _ch->_walkPath.begin();
+ if (it != _ch->_walkPath.end()) {
+ if (*it == curPos) {
+ debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it).x, (*it).y);
+ it = _ch->_walkPath.erase(it);
}
+ }
+ // advance character towards the target
+ Common::Point targetPos;
+ if (it == _ch->_walkPath.end()) {
+ debugC(1, kDebugWalk, "walk reached last node");
+ finalizeWalk();
+ targetPos = curPos;
} else {
+ // targetPos is saved to help setting character direction
+ targetPos = *it;
- if (dist.x > dist.y) {
- walkData2 = (from->_x > pos.x) ? 0 : 9;
- walkData1 %= 16;
- v16 = walkData1 / 2;
- } else {
- walkData2 = (from->_y > pos.y) ? 18 : 21;
- walkData1 %= 8;
- v16 = walkData1 / 4;
- }
+ Common::Point newPos(curPos);
+ clipMove(newPos, targetPos);
+ _ch->setFoot(newPos);
+ if (newPos == curPos) {
+ debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
+ finalizeWalk();
+ targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected
+ }
}
- return v16;
+ // targetPos is used to select the direction (and the walkFrame) of a character,
+ // since it doesn't cause the sudden changes in orientation that newPos would.
+ // Since newPos is 'adjusted' according to walkable areas, an imaginary line drawn
+ // from curPos to newPos is prone to abrutply change in direction, thus making the
+ // code select 'too different' frames when walking diagonally against obstacles,
+ // and yielding an annoying shaking effect in the character.
+ _ch->updateDirection(curPos, targetPos);
}
-uint16 Parallaction::checkDoor() {
-// printf("checkDoor()...");
- if (_currentLocationIndex != _doorData1) {
- _doorData1 = _currentLocationIndex;
- _zoneTrap = nullZonePtr;
- }
- _engineFlags &= ~kEngineWalking;
+PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) {
+}
- Common::Point foot;
- _char.getFoot(foot);
- ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y);
+bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) {
- if (z) {
+ Common::Point copy(from);
+ Common::Point p(copy);
- if ((z->_flags & kFlagsClosed) == 0) {
- _location._startPosition = z->u.door->_startPos;
- _location._startFrame = z->u.door->_startFrame;
+ while (p != to) {
- scheduleLocationSwitch(z->u.door->_location);
- _zoneTrap = nullZonePtr;
+ if (p.x < to.x && IS_PATH_CLEAR(p.x + 1, p.y)) p.x++;
+ if (p.x > to.x && IS_PATH_CLEAR(p.x - 1, p.y)) p.x--;
+ if (p.y < to.y && IS_PATH_CLEAR(p.x, p.y + 1)) p.y++;
+ if (p.y > to.y && IS_PATH_CLEAR(p.x, p.y - 1)) p.y--;
- } else {
- runCommands(z->_commands, z);
+ if (p == copy && p != to) {
+ return false;
}
+
+ copy = p;
}
- _char.getFoot(foot);
- z = hitZone(kZoneTrap, foot.x, foot.y);
+ return true;
+}
- if (z) {
- setLocationFlags(kFlagsEnter);
- runCommands(z->_commands, z);
- clearLocationFlags(kFlagsEnter);
- _zoneTrap = z;
- } else
- if (_zoneTrap) {
- setLocationFlags(kFlagsExit);
- runCommands(_zoneTrap->_commands, _zoneTrap);
- clearLocationFlags(kFlagsExit);
- _zoneTrap = nullZonePtr;
+void PathBuilder_BR::buildPath(uint16 x, uint16 y) {
+ Common::Point foot;
+ _ch->getFoot(foot);
+
+ debugC(1, kDebugWalk, "buildPath: from (%i, %i) to (%i, %i)", foot.x, foot.y, x, y);
+ _ch->_walkPath.clear();
+
+ // look for easy path first
+ Common::Point dest(x, y);
+ if (directPathExists(foot, dest)) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: direct path found");
+ return;
+ }
+
+ // look for short circuit cases
+ ZonePtr z0 = _vm->hitZone(kZonePath, x, y);
+ if (!z0) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: corner case 0");
+ return;
+ }
+ ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y);
+ if (!z1 || z1 == z0) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: corner case 1");
+ return;
+ }
+
+ // build complex path
+ int id = atoi(z0->_name);
+
+ if (z1->u.path->_lists[id].empty()) {
+ _ch->_walkPath.clear();
+ debugC(3, kDebugWalk, "buildPath: no path");
+ return;
}
-// printf("done\n");
+ PointList::iterator b = z1->u.path->_lists[id].begin();
+ PointList::iterator e = z1->u.path->_lists[id].end();
+ for ( ; b != e; b++) {
+ _ch->_walkPath.push_front(*b);
+ }
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: complex path");
- _char._ani->_frame = walkData2;
- return _char._ani->_frame;
+ return;
}
+PathBuilder_BR::PathBuilder_BR(Character *ch) : PathBuilder(ch) {
+}
+
+void PathWalker_BR::finalizeWalk() {
+ _engineFlags &= ~kEngineWalking;
+ _first = true;
+ _fieldC = 1;
+
+ Common::Point foot;
+ _ch->getFoot(foot);
+
+ ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
+ if (z && ((z->_flags & kFlagsClosed) == 0)) {
+ _vm->_location._startPosition = z->u.door->_startPos; // foot pos
+ _vm->_location._startFrame = z->u.door->_startFrame;
+
+#if 0
+ // TODO: implement working follower. Must find out a location in which the code is
+ // used and which is stable enough.
+ _followerFootInit.x = -1;
+ if (_follower && z->u.door->startPos2.x != -1) {
+ _followerFootInit.x = z->u.door->startPos2.x; // foot pos
+ _followerFootInit.y = z->u.door->startPos2.y; // foot pos
+ }
+ _followerFootInit.z = -1;
+ if (_follower && z->u.door->startPos2.z != -1) {
+ _followerFootInit.z = z->u.door->startPos2.z; // foot pos
+ }
+#endif
+
+ _vm->scheduleLocationSwitch(z->u.door->_location);
+ _vm->_cmdExec->run(z->_commands, z);
+ }
+
+#if 0
+ // TODO: Input::walkTo must be extended to support destination frame in addition to coordinates
+ // TODO: the frame argument must be passed to PathWalker through PathBuilder, so probably
+ // a merge between the two Path managers is the right solution
+ if (_engineFlags & FINAL_WALK_FRAME) { // this flag is set in readInput()
+ _engineFlags &= ~FINAL_WALK_FRAME;
+ _char.ani->_frame = _moveToF; // from readInput()...
+ } else {
+ _char.ani->_frame = _dirFrame; // from walk()
+ }
+ _char.setFoot(foot);
+#endif
+
+ _ch->_ani->_frame = _dirFrame; // temporary solution
+
+#if 0
+ // TODO: support scrolling ;)
+ if (foot.x > _gfx->hscroll + 600) _gfx->scrollRight(78);
+ if (foot.x < _gfx->hscroll + 40) _gfx->scrollLeft(78);
+ if (foot.y > 350) _gfx->scrollDown(100);
+ if (foot.y < 80) _gfx->scrollUp(100);
+#endif
-void Parallaction::finalizeWalk(WalkNodeList *list) {
- checkDoor();
- delete list;
+ return;
}
-void Parallaction_ns::walk() {
+
+void PathWalker_BR::walk() {
if ((_engineFlags & kEngineWalking) == 0) {
return;
}
- WalkNodeList *list = _char._walkPath;
+#if 0
+ // TODO: support delays in walking. This requires extending Input::walkIo().
+ if (ch._walkDelay > 0) {
+ ch._walkDelay--;
+ if (ch._walkDelay == 0 && _ch._ani->_scriptName) {
+ // stop script and reset
+ _ch._ani->_flags &= ~kFlagsActing;
+ Script *script = findScript(_ch._ani->_scriptName);
+ script->_nextCommand = script->firstCommand;
+ }
+ return;
+ }
+#endif
- _char._ani->_oldPos.x = _char._ani->_left;
- _char._ani->_oldPos.y = _char._ani->_top;
+ GfxObj *obj = _ch->_ani->gfxobj;
- Common::Point pos;
- _char.getFoot(pos);
+ Common::Rect rect;
+ obj->getRect(_ch->_ani->_frame, rect);
- WalkNodeList::iterator it = list->begin();
+ uint scale;
+ if (rect.bottom > _vm->_location._zeta0) {
+ scale = 100;
+ } else
+ if (rect.bottom < _vm->_location._zeta1) {
+ scale = _vm->_location._zeta2;
+ } else {
+ scale = _vm->_location._zeta2 + ((rect.bottom - _vm->_location._zeta1) * (100 - _vm->_location._zeta2)) / (_vm->_location._zeta0 - _vm->_location._zeta1);
+ }
+ int xStep = (scale * 16) / 100 + 1;
+ int yStep = (scale * 10) / 100 + 1;
+
+ debugC(9, kDebugWalk, "calculated step: (%i, %i)\n", xStep, yStep);
+
+ if (_fieldC == 0) {
+ _ch->_walkPath.erase(_ch->_walkPath.begin());
- if (it != list->end()) {
- if ((*it)->_x == pos.x && (*it)->_y == pos.y) {
- debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y);
- it = list->erase(it);
+ if (_ch->_walkPath.empty()) {
+ finalizeWalk();
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 0\n");
+ return;
+ } else {
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, moving to next node\n");
}
}
- if (it == list->end()) {
- debugC(1, kDebugWalk, "walk reached last node");
-// j->_finished = 1;
- finalizeWalk(list);
- return;
- }
- _char._walkPath = list;
- // selectWalkFrame must be performed before position is changed by clipMove
- int16 v16 = selectWalkFrame(pos, *it);
- clipMove(pos, *it);
+ _ch->getFoot(_startFoot);
- _char.setFoot(pos);
+ _fieldC = 0;
+ _step++;
+ _step %= 8;
- Common::Point newpos(_char._ani->_left, _char._ani->_top);
+ int walkFrame = _step;
+ _dirFrame = 0;
+ Common::Point newpos(_startFoot), delta;
- if (newpos == _char._ani->_oldPos) {
- debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
-// j->_finished = 1;
- finalizeWalk(list);
- } else {
- _char._ani->_frame = v16 + walkData2 + 1;
+ Common::Point p(*_ch->_walkPath.begin());
+
+ if (_startFoot.y < p.y && _startFoot.y < 400 && IS_PATH_CLEAR(_startFoot.x, yStep + _startFoot.y)) {
+ if (yStep + _startFoot.y <= p.y) {
+ _fieldC = 1;
+ delta.y = yStep;
+ newpos.y = yStep + _startFoot.y;
+ } else {
+ delta.y = p.y - _startFoot.y;
+ newpos.y = p.y;
+ }
+ _dirFrame = 9;
+ } else
+ if (_startFoot.y > p.y && _startFoot.y > 0 && IS_PATH_CLEAR(_startFoot.x, _startFoot.y - yStep)) {
+ if (_startFoot.y - yStep >= p.y) {
+ _fieldC = 1;
+ delta.y = yStep;
+ newpos.y = _startFoot.y - yStep;
+ } else {
+ delta.y = _startFoot.y - p.y;
+ newpos.y = p.y;
+ }
+ _dirFrame = 0;
}
- return;
-}
+ if (_startFoot.x < p.x && _startFoot.x < 640 && IS_PATH_CLEAR(_startFoot.x + xStep, _startFoot.y)) {
+ if (_startFoot.x + xStep <= p.x) {
+ _fieldC = 1;
+ delta.x = xStep;
+ newpos.x = xStep + _startFoot.x;
+ } else {
+ delta.x = p.x - _startFoot.x;
+ newpos.x = p.x;
+ }
+ if (delta.y < delta.x) {
+ _dirFrame = 18; // right
+ }
+ } else
+ if (_startFoot.x > p.x && _startFoot.x > 0 && IS_PATH_CLEAR(_startFoot.x - xStep, _startFoot.y)) {
+ if (_startFoot.x - xStep >= p.x) {
+ _fieldC = 1;
+ delta.x = xStep;
+ newpos.x = _startFoot.x - xStep;
+ } else {
+ delta.x = _startFoot.x - p.x;
+ newpos.x = p.x;
+ }
+ if (delta.y < delta.x) {
+ _dirFrame = 27; // left
+ }
+ }
+ debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i \n", _startFoot.x, _startFoot.y, p.x, p.y, delta.x, delta.y);
-WalkNode::WalkNode() : _x(0), _y(0) {
-}
+ if (_fieldC) {
+ debugC(9, kDebugWalk, "PathWalker_BR::walk, foot moved from (%i, %i) to (%i, %i)\n", _startFoot.x, _startFoot.y, newpos.x, newpos.y);
+ _ch->_ani->_frame = walkFrame + _dirFrame + 1;
+ _startFoot.x = newpos.x;
+ _startFoot.y = newpos.y;
+ _ch->setFoot(_startFoot);
+ _ch->_ani->_z = newpos.y;
+ }
-WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) {
-}
+ if (_fieldC || !_ch->_walkPath.empty()) {
+// checkTrap();
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n");
+ return;
+ }
-WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) {
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n");
+ finalizeWalk();
+ return;
}
-void WalkNode::getPoint(Common::Point &p) const {
- p.x = _x;
- p.y = _y;
-}
+PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) {
-PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) {
}
diff --git a/engines/parallaction/walk.h b/engines/parallaction/walk.h
index 788a6e1375..8d21e5ebbd 100644
--- a/engines/parallaction/walk.h
+++ b/engines/parallaction/walk.h
@@ -29,43 +29,89 @@
#include "common/ptr.h"
#include "common/list.h"
+#include "parallaction/objects.h"
+
+
namespace Parallaction {
-struct Animation;
+struct Character;
+
+class PathBuilder {
-struct WalkNode {
- int16 _x;
- int16 _y;
+protected:
+ Character *_ch;
public:
- WalkNode();
- WalkNode(int16 x, int16 y);
- WalkNode(const WalkNode& w);
+ PathBuilder(Character *ch) : _ch(ch) { }
+ virtual ~PathBuilder() { }
- void getPoint(Common::Point &p) const;
+ virtual void buildPath(uint16 x, uint16 y) = 0;
};
-typedef Common::SharedPtr<WalkNode> WalkNodePtr;
-typedef Common::List<WalkNodePtr> WalkNodeList;
+class PathBuilder_NS : public PathBuilder {
-class PathBuilder {
-
- AnimationPtr _anim;
-
- WalkNodeList *_list;
- WalkNodeList _subPath;
+ PointList *_list;
+ PointList _subPath;
void correctPathPoint(Common::Point &to);
uint32 buildSubPath(const Common::Point& pos, const Common::Point& stop);
- uint16 walkFunc1(int16 x, int16 y, WalkNodePtr Node);
+ uint16 walkFunc1(const Common::Point &to, Common::Point& node);
public:
- PathBuilder(AnimationPtr anim);
- WalkNodeList* buildPath(uint16 x, uint16 y);
+ PathBuilder_NS(Character *ch);
+ void buildPath(uint16 x, uint16 y);
+};
+
+class PathBuilder_BR : public PathBuilder {
+
+ bool directPathExists(const Common::Point &from, const Common::Point &to);
+
+public:
+ PathBuilder_BR(Character *ch);
+ void buildPath(uint16 x, uint16 y);
+};
+
+class PathWalker {
+protected:
+ Character *_ch;
+public:
+ PathWalker(Character *ch) : _ch(ch) { }
+ virtual ~PathWalker() { }
+ virtual void walk() = 0;
};
+class PathWalker_NS : public PathWalker {
+
+
+ void finalizeWalk();
+ void clipMove(Common::Point& pos, const Common::Point& to);
+ void checkDoor(const Common::Point &foot);
+
+public:
+ PathWalker_NS(Character *ch) : PathWalker(ch) { }
+ void walk();
+};
+
+
+class PathWalker_BR : public PathWalker {
+
+
+ int _walkDelay;
+ int _fieldC;
+ Common::Point _startFoot;
+ bool _first;
+ int _step;
+
+ int _dirFrame;
+
+ void finalizeWalk();
+
+public:
+ PathWalker_BR(Character *ch);
+ void walk();
+};
}
diff --git a/engines/queen/graphics.cpp b/engines/queen/graphics.cpp
index f863f7663c..6d0a11ccfe 100644
--- a/engines/queen/graphics.cpp
+++ b/engines/queen/graphics.cpp
@@ -1175,15 +1175,8 @@ BamScene::BamScene(QueenEngine *vm)
}
void BamScene::playSfx() {
- // Don't try to play all the sounds. This is only necessary for the
- // fight bam, in which the number of 'sfx bam frames' is too much
- // important / too much closer. The original game does not have
- // this problem since its playSfx() function returns immediately
- // if a sound is already being played.
- if (_lastSoundIndex == 0 || _index - _lastSoundIndex >= SFX_SKIP) {
- _vm->sound()->playSfx(_vm->logic()->currentRoomSfx());
- _lastSoundIndex = _index;
- }
+ _vm->sound()->playSfx(_vm->logic()->currentRoomSfx());
+ _lastSoundIndex = _index;
}
void BamScene::prepareAnimation() {
diff --git a/engines/queen/graphics.h b/engines/queen/graphics.h
index 6f00111635..7eadf9a191 100644
--- a/engines/queen/graphics.h
+++ b/engines/queen/graphics.h
@@ -248,10 +248,6 @@ public:
F_REQ_STOP = 2
};
- enum {
- SFX_SKIP = 8
- };
-
uint16 _flag, _index;
private:
diff --git a/engines/queen/input.cpp b/engines/queen/input.cpp
index 146e95bcef..9f03c341c9 100644
--- a/engines/queen/input.cpp
+++ b/engines/queen/input.cpp
@@ -27,6 +27,7 @@
#include "common/events.h"
#include "common/system.h"
+#include "queen/queen.h"
#include "queen/input.h"
namespace Queen {
@@ -51,12 +52,12 @@ const Verb Input::_verbKeys[] = {
VERB_USE
};
-Input::Input(Common::Language language, OSystem *system) :
+Input::Input(Common::Language language, OSystem *system, QueenEngine *vm) :
_system(system), _eventMan(system->getEventManager()), _fastMode(false),
_keyVerb(VERB_NONE), _cutawayRunning(false), _canQuit(false),
_cutawayQuit(false), _dialogueRunning(false), _talkQuit(false),
_quickSave(false), _quickLoad(false), _debugger(false), _inKey(Common::KEYCODE_INVALID),
- _mouseButton(0), _idleTime(0) {
+ _mouseButton(0), _idleTime(0) , _vm(vm) {
switch (language) {
case Common::EN_ANY:
@@ -119,8 +120,8 @@ void Input::delay(uint amount) {
break;
case Common::EVENT_QUIT:
- _system->quit();
- break;
+ _vm->quitGame();
+ return;
default:
break;
diff --git a/engines/queen/input.h b/engines/queen/input.h
index 86092aeed6..43a57729c6 100644
--- a/engines/queen/input.h
+++ b/engines/queen/input.h
@@ -49,7 +49,7 @@ public:
MOUSE_RBUTTON = 2
};
- Input(Common::Language language, OSystem *system);
+ Input(Common::Language language, OSystem *system, QueenEngine *vm);
void delay(uint amount);
@@ -99,6 +99,8 @@ private:
Common::EventManager *_eventMan;
+ QueenEngine *_vm;
+
//! some cutaways require update() run faster
bool _fastMode;
diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp
index bfbcfa4e59..0327fb74b8 100644
--- a/engines/queen/journal.cpp
+++ b/engines/queen/journal.cpp
@@ -85,8 +85,8 @@ void Journal::use() {
handleMouseWheel(1);
break;
case Common::EVENT_QUIT:
- _system->quit();
- break;
+ _vm->quitGame();
+ return;
default:
break;
}
diff --git a/engines/queen/queen.cpp b/engines/queen/queen.cpp
index d1a1247c46..c95e44b477 100644
--- a/engines/queen/queen.cpp
+++ b/engines/queen/queen.cpp
@@ -418,7 +418,7 @@ int QueenEngine::init() {
_display = new Display(this, _system);
_graphics = new Graphics(this);
_grid = new Grid(this);
- _input = new Input(_resource->getLanguage(), _system);
+ _input = new Input(_resource->getLanguage(), _system, this);
if (_resource->isDemo()) {
_logic = new LogicDemo(this);
diff --git a/engines/queen/resource.cpp b/engines/queen/resource.cpp
index 5a8db74e3b..b3bd663baf 100644
--- a/engines/queen/resource.cpp
+++ b/engines/queen/resource.cpp
@@ -106,7 +106,7 @@ ResourceEntry *Resource::resourceEntry(const char *filename) const {
re = &_resourceTable[cur];
break;
}
- } while (cur++ < _resourceEntries);
+ } while (++cur < _resourceEntries);
#endif
return re;
}
diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp
index d70fe7209d..27cf1bf6a2 100644
--- a/engines/queen/sound.cpp
+++ b/engines/queen/sound.cpp
@@ -35,6 +35,7 @@
#include "queen/queen.h"
#include "queen/resource.h"
+#include "sound/audiostream.h"
#include "sound/flac.h"
#include "sound/mididrv.h"
#include "sound/mp3.h"
@@ -45,6 +46,42 @@
namespace Queen {
+// The sounds in the PC versions are all played at 11840 Hz. Unfortunately, we
+// did not know that at the time, so there are plenty of compressed versions
+// which claim that they should be played at 11025 Hz. This "wrapper" class
+// works around that.
+
+class AudioStreamWrapper : public Audio::AudioStream {
+protected:
+ Audio::AudioStream *_stream;
+
+public:
+ AudioStreamWrapper(Audio::AudioStream *stream) {
+ _stream = stream;
+ }
+ ~AudioStreamWrapper() {
+ delete _stream;
+ }
+ int readBuffer(int16 *buffer, const int numSamples) {
+ return _stream->readBuffer(buffer, numSamples);
+ }
+ bool isStereo() const {
+ return _stream->isStereo();
+ }
+ bool endOfData() const {
+ return _stream->endOfData();
+ }
+ bool endOfStream() {
+ return _stream->endOfStream();
+ }
+ int getRate() const {
+ return 11840;
+ }
+ int32 getTotalPlayTime() {
+ return _stream->getTotalPlayTime();
+ }
+};
+
class SilentSound : public PCSound {
public:
SilentSound(Audio::Mixer *mixer, QueenEngine *vm) : PCSound(mixer, vm) {}
@@ -69,7 +106,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeMP3Stream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeMP3Stream(tmp, true)));
}
};
#endif
@@ -82,7 +119,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeVorbisStream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeVorbisStream(tmp, true)));
}
};
#endif
@@ -95,7 +132,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeFlacStream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeFlacStream(tmp, true)));
}
};
#endif // #ifdef USE_FLAC
@@ -229,11 +266,6 @@ void PCSound::setVolume(int vol) {
_music->setVolume(vol);
}
-void PCSound::waitFinished(bool isSpeech) {
- while (_mixer->isSoundHandleActive(isSpeech ? _speechHandle : _sfxHandle))
- _vm->input()->delay(10);
-}
-
void PCSound::playSound(const char *base, bool isSpeech) {
char name[13];
strcpy(name, base);
@@ -243,7 +275,13 @@ void PCSound::playSound(const char *base, bool isSpeech) {
name[i] = '0';
}
strcat(name, ".SB");
- waitFinished(isSpeech);
+ if (isSpeech) {
+ while (_mixer->isSoundHandleActive(_speechHandle)) {
+ _vm->input()->delay(10);
+ }
+ } else {
+ _mixer->stopHandle(_sfxHandle);
+ }
uint32 size;
Common::File *f = _vm->resource()->findSound(name, &size);
if (f) {
@@ -255,6 +293,8 @@ void PCSound::playSound(const char *base, bool isSpeech) {
}
void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
+ // In order to simplify the code, we don't parse the .sb header but hard-code the
+ // values. Refer to tracker item #1876741 for details on the format/fields.
int headerSize;
f->seek(2, SEEK_CUR);
uint16 version = f->readUint16LE();
@@ -276,7 +316,7 @@ void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *so
if (sound) {
f->read(sound, size);
byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE;
- _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11025, flags);
+ _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11840, flags);
}
}
diff --git a/engines/queen/sound.h b/engines/queen/sound.h
index c2c1481cc6..331034f746 100644
--- a/engines/queen/sound.h
+++ b/engines/queen/sound.h
@@ -143,7 +143,6 @@ public:
void setVolume(int vol);
protected:
- void waitFinished(bool isSpeech);
void playSound(const char *base, bool isSpeech);
virtual void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) = 0;
diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp
index 3a1e510529..9fffb0f8bf 100644
--- a/engines/saga/animation.cpp
+++ b/engines/saga/animation.cpp
@@ -55,6 +55,7 @@ Anim::Anim(SagaEngine *vm) : _vm(vm) {
Anim::~Anim(void) {
reset();
+ freeCutawayList();
}
void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) {
diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp
index e936117894..482b3a4c82 100644
--- a/engines/saga/font.cpp
+++ b/engines/saga/font.cpp
@@ -63,6 +63,8 @@ Font::~Font(void) {
free(_fonts[i]);
}
+
+ free(_fonts);
}
@@ -238,6 +240,13 @@ void Font::createOutline(FontData *font) {
}
}
+int Font::translateChar(int charId) {
+ if (charId <= 127)
+ return charId; // normal character
+ else
+ return _charMap[charId - 128]; // extended character
+}
+
// Returns the horizontal length in pixels of the graphical representation
// of at most 'count' characters of the string 'text', taking
// into account any formatting options specified by 'flags'.
@@ -257,7 +266,7 @@ int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffe
for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {
ch = *txt & 0xFFU;
// Translate character
- ch = _charMap[ch];
+ ch = translateChar(ch);
assert(ch < FONT_CHARCOUNT);
width += font->normal.fontCharEntry[ch].tracking;
}
@@ -336,11 +345,11 @@ void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, siz
// Don't do any special font mapping for the Italian fan
// translation of ITE
if (_vm->getLanguage() != Common::IT_ITA)
- c_code = _charMap[c_code];
+ c_code = translateChar(c_code);
}
} else if (_fontMapping == 1) {
// Force font mapping
- c_code = _charMap[c_code];
+ c_code = translateChar(c_code);
} else {
// In all other cases, ignore font mapping
}
diff --git a/engines/saga/font.h b/engines/saga/font.h
index 6b930ddca0..76c0f06725 100644
--- a/engines/saga/font.h
+++ b/engines/saga/font.h
@@ -158,6 +158,7 @@ class Font {
};
Font::FontId knownFont2FontIdx(KnownFont font);
+ int translateChar(int charId);
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags);
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags);
@@ -196,7 +197,7 @@ class Font {
return byteLength;
}
- static const int _charMap[256];
+ static const int _charMap[128];
SagaEngine *_vm;
bool _initialized;
diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp
index 6246cb71da..6abaeea151 100644
--- a/engines/saga/font_map.cpp
+++ b/engines/saga/font_map.cpp
@@ -32,135 +32,8 @@
namespace Saga {
-const int Font::_charMap[256] = {
- 0, // 0
- 1, // 1
- 2, // 2
- 3, // 3
- 4, // 4
- 5, // 5
- 6, // 6
- 7, // 7
- 8, // 8
- 9, // 9
- 10, // 10
- 11, // 11
- 12, // 12
- 13, // 13
- 14, // 14
- 15, // 15
- 16, // 16
- 17, // 17
- 18, // 18
- 19, // 19
- 20, // 20
- 21, // 21
- 22, // 22
- 23, // 23
- 24, // 24
- 25, // 25
- 26, // 26
- 27, // 27
- 28, // 28
- 29, // 29
- 30, // 30
- 31, // 31
- 32, // 32
- 33, // 33
- 34, // 34
- 35, // 35
- 36, // 36
- 37, // 37
- 38, // 38
- 39, // 39
- 40, // 40
- 41, // 41
- 42, // 42
- 43, // 43
- 44, // 44
- 45, // 45
- 46, // 46
- 47, // 47
- 48, // 48
- 49, // 49
- 50, // 50
- 51, // 51
- 52, // 52
- 53, // 53
- 54, // 54
- 55, // 55
- 56, // 56
- 57, // 57
- 58, // 58
- 59, // 59
- 60, // 60
- 61, // 61
- 62, // 62
- 63, // 63
- 64, // 64
- 65, // 65
- 66, // 66
- 67, // 67
- 68, // 68
- 69, // 69
- 70, // 70
- 71, // 71
- 72, // 72
- 73, // 73
- 74, // 74
- 75, // 75
- 76, // 76
- 77, // 77
- 78, // 78
- 79, // 79
- 80, // 80
- 81, // 81
- 82, // 82
- 83, // 83
- 84, // 84
- 85, // 85
- 86, // 86
- 87, // 87
- 88, // 88
- 89, // 89
- 90, // 90
- 91, // 91
- 92, // 92
- 93, // 93
- 94, // 94
- 95, // 95
- 96, // 96
- 97, // 97
- 98, // 98
- 99, // 99
- 100, // 100
- 101, // 101
- 102, // 102
- 103, // 103
- 104, // 104
- 105, // 105
- 106, // 106
- 107, // 107
- 108, // 108
- 109, // 109
- 110, // 110
- 111, // 111
- 112, // 112
- 113, // 113
- 114, // 114
- 115, // 115
- 116, // 116
- 117, // 117
- 118, // 118
- 119, // 119
- 120, // 120
- 121, // 121
- 122, // 122
- 123, // 123
- 124, // 124
- 125, // 125
- 126, // 126
- 127, // 127
+const int Font::_charMap[128] = {
+ // Characters 0 - 127 are mapped directly to ISO 8859-1
199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA
252, // 129 LATIN SMALL LETTER U WITH DIAERESIS
233, // 130 LATIN SMALL LETTER E WITH ACUTE
diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp
index 7380570a99..1d048baaad 100644
--- a/engines/saga/interface.cpp
+++ b/engines/saga/interface.cpp
@@ -334,7 +334,21 @@ Interface::Interface(SagaEngine *vm) : _vm(vm) {
Interface::~Interface(void) {
free(_inventory);
+ free(_mainPanel.image);
+ free(_conversePanel.image);
+ free(_optionPanel.image);
+ free(_quitPanel.image);
+ free(_loadPanel.image);
+ free(_savePanel.image);
+
_mainPanel.sprites.freeMem();
+ _conversePanel.sprites.freeMem();
+ _optionPanel.sprites.freeMem();
+ _quitPanel.sprites.freeMem();
+ _loadPanel.sprites.freeMem();
+ _savePanel.sprites.freeMem();
+ _protectPanel.sprites.freeMem();
+
_defPortraits.freeMem();
_scenePortraits.freeMem();
}
diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp
index 5f1d0157d5..6614f4098f 100644
--- a/engines/saga/introproc_ihnm.cpp
+++ b/engines/saga/introproc_ihnm.cpp
@@ -132,6 +132,8 @@ void Scene::IHNMLoadCutaways() {
// Load the cutaways for the title screens
_vm->_anim->loadCutawayList(resourcePointer, resourceLength);
+
+ free(resourcePointer);
}
bool Scene::checkKey() {
diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp
index b7d4f4f1bd..e150caeca5 100644
--- a/engines/saga/rscfile.cpp
+++ b/engines/saga/rscfile.cpp
@@ -769,6 +769,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {
_vm->_sprite->_mainSprites.freeMem();
_vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites);
+
_vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID);
_vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength);
@@ -806,6 +807,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {
// The IHNM demo has a fixed music track and doesn't load a song table
_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
_vm->_music->play(3, MUSIC_LOOP);
+ free(resourcePointer);
}
int voiceLUTResourceID = 0;
diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp
index 40eb32b276..fafbd02cec 100644
--- a/engines/saga/saga.cpp
+++ b/engines/saga/saga.cpp
@@ -79,6 +79,7 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc)
_scene = NULL;
_isoMap = NULL;
_gfx = NULL;
+ _driver = NULL;
_console = NULL;
_render = NULL;
_music = NULL;
@@ -133,6 +134,7 @@ SagaEngine::~SagaEngine() {
delete _render;
delete _music;
delete _sound;
+ delete _driver;
delete _gfx;
delete _console;
@@ -188,11 +190,11 @@ int SagaEngine::init() {
bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
bool adlib = (midiDriver == MD_ADLIB);
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ _driver = MidiDriver::createMidi(midiDriver);
if (native_mt32)
- driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
- _music = new Music(this, _mixer, driver, _musicVolume);
+ _music = new Music(this, _mixer, _driver, _musicVolume);
_music->setNativeMT32(native_mt32);
_music->setAdlib(adlib);
diff --git a/engines/saga/saga.h b/engines/saga/saga.h
index 4a5fae7ddb..6b6eb6b3fb 100644
--- a/engines/saga/saga.h
+++ b/engines/saga/saga.h
@@ -29,6 +29,7 @@
#include "engines/engine.h"
#include "common/stream.h"
+#include "sound/mididrv.h"
#include "saga/gfx.h"
#include "saga/list.h"
@@ -531,6 +532,7 @@ public:
SndRes *_sndRes;
Sound *_sound;
Music *_music;
+ MidiDriver *_driver;
Anim *_anim;
Render *_render;
IsoMap *_isoMap;
diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp
index 7664af314f..088be34c72 100644
--- a/engines/saga/script.cpp
+++ b/engines/saga/script.cpp
@@ -150,6 +150,7 @@ Script::~Script() {
debug(8, "Shutting down scripting subsystem.");
_mainStrings.freeMem();
+ _globalVoiceLUT.freeMem();
freeModules();
free(_modules);
diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp
index e9d002819c..be4f2a423d 100644
--- a/engines/saga/sprite.cpp
+++ b/engines/saga/sprite.cpp
@@ -74,6 +74,9 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
Sprite::~Sprite(void) {
debug(8, "Shutting down sprite subsystem...");
_mainSprites.freeMem();
+ _inventorySprites.freeMem();
+ _arrowSprites.freeMem();
+ _saveReminderSprites.freeMem();
free(_decodeBuf);
}
diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp
index 8f3175f098..5a45fb7da9 100644
--- a/engines/scumm/charset.cpp
+++ b/engines/scumm/charset.cpp
@@ -56,7 +56,7 @@ void ScummEngine::loadCJKFont() {
_2byteWidth = 16;
_2byteHeight = 16;
// use FM-TOWNS font rom, since game files don't have kanji font resources
- if (fp.open("fmt_fnt.rom", Common::File::kFileReadMode)) {
+ if (fp.open("fmt_fnt.rom")) {
_useCJKMode = true;
debug(2, "Loading FM-TOWNS Kanji rom");
_2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar];
diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp
index 9f9115e207..23af1f9672 100644
--- a/engines/scumm/debugger.cpp
+++ b/engines/scumm/debugger.cpp
@@ -298,7 +298,7 @@ bool ScummDebugger::Cmd_ImportRes(int argc, const char** argv) {
// FIXME add bounds check
if (!strncmp(argv[1], "scr", 3)) {
- file.open(argv[2], Common::File::kFileReadMode);
+ file.open(argv[2]);
if (file.isOpen() == false) {
DebugPrintf("Could not open file %s\n", argv[2]);
return true;
diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp
index 9359c6610c..68d3010199 100644
--- a/engines/scumm/detection.cpp
+++ b/engines/scumm/detection.cpp
@@ -492,7 +492,7 @@ static bool testGame(const GameSettings *g, const DescMap &fileMD5Map, const Com
// Note that GF_OLD_BUNDLE is true if and only if GF_OLD256 is false.
// Candidates: maniac enhanced, zak enhanced, indy3ega, loom
- if (g->version != 2 && g->version != 3 || (g->features & GF_OLD256))
+ if ((g->version != 2 && g->version != 3) || (g->features & GF_OLD256))
return false;
/* We distinguish the games by the presence/absence of
@@ -949,7 +949,7 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const {
sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
SaveStateList saveList;
- for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++) {
+ for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Obtain the last 2 digits of the filename, since they correspond to the save slot
int slotNum = atoi(file->c_str() + file->size() - 2);
diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp
index 6d1cf1bbd8..e4e2b2b620 100644
--- a/engines/scumm/dialogs.cpp
+++ b/engines/scumm/dialogs.cpp
@@ -297,7 +297,7 @@ void SaveLoadChooser::handleCommand(CommandSender *sender, uint32 cmd, uint32 da
break;
case GUI::kListSelectionChangedCmd: {
if (_gfxWidget) {
- updateInfos();
+ updateInfos(true);
}
if (_saveMode) {
@@ -350,7 +350,7 @@ void SaveLoadChooser::reflowLayout() {
_fillR = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillR");
_fillG = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillG");
_fillB = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillB");
- updateInfos();
+ updateInfos(false);
} else {
_container->setFlags(GUI::WIDGET_INVISIBLE);
_gfxWidget->setFlags(GUI::WIDGET_INVISIBLE);
@@ -362,7 +362,7 @@ void SaveLoadChooser::reflowLayout() {
Dialog::reflowLayout();
}
-void SaveLoadChooser::updateInfos() {
+void SaveLoadChooser::updateInfos(bool redraw) {
int selItem = _list->getSelected();
Graphics::Surface *thumb;
thumb = _vm->loadThumbnailFromSlot(_saveMode ? selItem + 1 : selItem);
@@ -376,7 +376,8 @@ void SaveLoadChooser::updateInfos() {
}
delete thumb;
- _gfxWidget->draw();
+ if (redraw)
+ _gfxWidget->draw();
InfoStuff infos;
memset(&infos, 0, sizeof(InfoStuff));
@@ -386,12 +387,14 @@ void SaveLoadChooser::updateInfos() {
(infos.date >> 24) & 0xFF, (infos.date >> 16) & 0xFF,
infos.date & 0xFFFF);
_date->setLabel(buffer);
- _date->draw();
+ if (redraw)
+ _date->draw();
snprintf(buffer, 32, "Time: %.2d:%.2d",
(infos.time >> 8) & 0xFF, infos.time & 0xFF);
_time->setLabel(buffer);
- _time->draw();
+ if (redraw)
+ _time->draw();
int minutes = infos.playtime / 60;
int hours = minutes / 60;
@@ -400,19 +403,23 @@ void SaveLoadChooser::updateInfos() {
snprintf(buffer, 32, "Playtime: %.2d:%.2d",
hours & 0xFF, minutes & 0xFF);
_playtime->setLabel(buffer);
- _playtime->draw();
+ if (redraw)
+ _playtime->draw();
} else {
snprintf(buffer, 32, "No date saved");
_date->setLabel(buffer);
- _date->draw();
+ if (redraw)
+ _date->draw();
snprintf(buffer, 32, "No time saved");
_time->setLabel(buffer);
- _time->draw();
+ if (redraw)
+ _time->draw();
snprintf(buffer, 32, "No playtime saved");
_playtime->setLabel(buffer);
- _playtime->draw();
+ if (redraw)
+ _playtime->draw();
}
}
diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h
index 7c99a0ebcc..0d04d8faea 100644
--- a/engines/scumm/dialogs.h
+++ b/engines/scumm/dialogs.h
@@ -69,7 +69,7 @@ protected:
uint8 _fillR, _fillG, _fillB;
- void updateInfos();
+ void updateInfos(bool redraw);
public:
SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode, ScummEngine *engine);
~SaveLoadChooser();
diff --git a/engines/scumm/file.cpp b/engines/scumm/file.cpp
index bc5fc38225..bf13308a0c 100644
--- a/engines/scumm/file.cpp
+++ b/engines/scumm/file.cpp
@@ -58,8 +58,8 @@ void ScummFile::resetSubfile() {
seek(0, SEEK_SET);
}
-bool ScummFile::open(const Common::String &filename, AccessMode mode) {
- if (File::open(filename, mode)) {
+bool ScummFile::open(const Common::String &filename) {
+ if (File::open(filename)) {
resetSubfile();
return true;
} else {
@@ -187,11 +187,6 @@ uint32 ScummFile::read(void *dataPtr, uint32 dataSize) {
return realLen;
}
-uint32 ScummFile::write(const void *, uint32) {
- error("ScummFile does not support writing!");
- return 0;
-}
-
#pragma mark -
#pragma mark --- ScummDiskImage ---
#pragma mark -
@@ -250,11 +245,6 @@ ScummDiskImage::ScummDiskImage(const char *disk1, const char *disk2, GameSetting
}
}
-uint32 ScummDiskImage::write(const void *, uint32) {
- error("ScummDiskImage does not support writing!");
- return 0;
-}
-
void ScummDiskImage::setEnc(byte enc) {
_stream->setEnc(enc);
}
@@ -300,7 +290,7 @@ bool ScummDiskImage::openDisk(char num) {
return true;
}
-bool ScummDiskImage::open(const Common::String &filename, AccessMode mode) {
+bool ScummDiskImage::open(const Common::String &filename) {
uint16 signature;
// check signature
diff --git a/engines/scumm/file.h b/engines/scumm/file.h
index 7064654f89..a2695cac59 100644
--- a/engines/scumm/file.h
+++ b/engines/scumm/file.h
@@ -36,7 +36,7 @@ class BaseScummFile : public Common::File {
public:
virtual void setEnc(byte value) = 0;
- virtual bool open(const Common::String &filename, AccessMode mode = kFileReadMode) = 0;
+ virtual bool open(const Common::String &filename) = 0;
virtual bool openSubFile(const Common::String &filename) = 0;
virtual bool eof() = 0;
@@ -44,7 +44,6 @@ public:
virtual uint32 size() = 0;
virtual void seek(int32 offs, int whence = SEEK_SET) = 0;
virtual uint32 read(void *dataPtr, uint32 dataSize) = 0;
- virtual uint32 write(const void *dataPtr, uint32 dataSize) = 0;
};
class ScummFile : public BaseScummFile {
@@ -59,7 +58,7 @@ public:
void setSubfileRange(uint32 start, uint32 len);
void resetSubfile();
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
bool eof();
@@ -67,7 +66,6 @@ public:
uint32 size();
void seek(int32 offs, int whence = SEEK_SET);
uint32 read(void *dataPtr, uint32 dataSize);
- uint32 write(const void *dataPtr, uint32 dataSize);
};
class ScummDiskImage : public BaseScummFile {
@@ -104,7 +102,7 @@ public:
ScummDiskImage(const char *disk1, const char *disk2, GameSettings game);
void setEnc(byte value);
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
void close();
@@ -113,7 +111,6 @@ public:
uint32 size() { return _stream->size(); }
void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }
uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); }
- uint32 write(const void *dataPtr, uint32 dataSize);
};
} // End of namespace Scumm
diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp
index 95f5eec4ea..8325436f87 100644
--- a/engines/scumm/file_nes.cpp
+++ b/engines/scumm/file_nes.cpp
@@ -62,11 +62,6 @@ struct ScummNESFile::Resource {
ScummNESFile::ScummNESFile() : _stream(0), _buf(0), _ROMset(kROMsetNum) {
}
-uint32 ScummNESFile::write(const void *, uint32) {
- error("ScummNESFile does not support writing!");
- return 0;
-}
-
void ScummNESFile::setEnc(byte enc) {
_stream->setEnc(enc);
}
@@ -1234,7 +1229,7 @@ bool ScummNESFile::generateIndex() {
return true;
}
-bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
+bool ScummNESFile::open(const Common::String &filename) {
if (_ROMset == kROMsetNum) {
char md5str[32+1];
@@ -1267,9 +1262,8 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
}
}
- if (File::open(filename, mode)) {
- if (_stream)
- delete _stream;
+ if (File::open(filename)) {
+ delete _stream;
_stream = 0;
free(_buf);
@@ -1282,8 +1276,7 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
}
void ScummNESFile::close() {
- if (_stream)
- delete _stream;
+ delete _stream;
_stream = 0;
free(_buf);
diff --git a/engines/scumm/file_nes.h b/engines/scumm/file_nes.h
index d601c2c496..4d2d6de275 100644
--- a/engines/scumm/file_nes.h
+++ b/engines/scumm/file_nes.h
@@ -64,7 +64,7 @@ public:
ScummNESFile();
void setEnc(byte value);
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
void close();
@@ -73,7 +73,6 @@ public:
uint32 size() { return _stream->size(); }
void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }
uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); }
- uint32 write(const void *dataPtr, uint32 dataSize);
};
} // End of namespace Scumm
diff --git a/engines/scumm/gfxARM.s b/engines/scumm/gfxARM.s
index cd3e5c7dad..83aaa78927 100644
--- a/engines/scumm/gfxARM.s
+++ b/engines/scumm/gfxARM.s
@@ -59,7 +59,7 @@ asmDrawStripToScreen:
CMP r1,#4 @ If width<4
BLT end @ return
- @ Width &= ~4 ? What's that about then? Width &= ~3 I could have
+ @ Width &= ~4 ? What''s that about then? Width &= ~3 I could have
@ understood...
BIC r1,r1,#4
diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp
index 33e6748860..f8fb1efca2 100644
--- a/engines/scumm/he/resource_he.cpp
+++ b/engines/scumm/he/resource_he.cpp
@@ -522,12 +522,13 @@ int Win32ResExtractor::do_resources_recurs(WinLibrary *fi, WinResource *base,
/* get a list of all resources at this level */
wr = list_resources(fi, base, &rescnt);
- if (wr == NULL)
+ if (wr == NULL) {
if (size != 0)
return size;
else
return 0;
-
+ }
+
/* process each resource listed */
for (c = 0 ; c < rescnt ; c++) {
/* (over)write the corresponding WinResource holder with the current */
diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp
index 4d5ec668a0..9429f8d086 100644
--- a/engines/scumm/he/script_v60he.cpp
+++ b/engines/scumm/he/script_v60he.cpp
@@ -1012,7 +1012,7 @@ void ScummEngine_v60he::o60_openFile() {
_hInFileTable[slot] = _saveFileMan->openForLoading(filename);
if (_hInFileTable[slot] == 0) {
Common::File *f = new Common::File();
- f->open(filename, Common::File::kFileReadMode);
+ f->open(filename);
if (!f->isOpen())
delete f;
else
diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp
index df3d857642..6c3d0023d8 100644
--- a/engines/scumm/he/script_v72he.cpp
+++ b/engines/scumm/he/script_v72he.cpp
@@ -1692,7 +1692,7 @@ void ScummEngine_v72he::o72_openFile() {
_hInFileTable[slot] = _saveFileMan->openForLoading(filename);
if (_hInFileTable[slot] == 0) {
Common::File *f = new Common::File();
- f->open(filename, Common::File::kFileReadMode);
+ f->open(filename);
if (!f->isOpen())
delete f;
else
diff --git a/engines/scumm/he/script_v80he.cpp b/engines/scumm/he/script_v80he.cpp
index 393e1d3a8f..39ec715d94 100644
--- a/engines/scumm/he/script_v80he.cpp
+++ b/engines/scumm/he/script_v80he.cpp
@@ -409,7 +409,7 @@ void ScummEngine_v80he::o80_getFileSize() {
Common::SeekableReadStream *f = _saveFileMan->openForLoading((const char *)filename);
if (!f) {
Common::File *file = new Common::File();
- file->open((const char *)filename, Common::File::kFileReadMode);
+ file->open((const char *)filename);
if (!file->isOpen())
delete f;
else
diff --git a/engines/scumm/he/wiz_he.cpp b/engines/scumm/he/wiz_he.cpp
index df472307eb..f514449bff 100644
--- a/engines/scumm/he/wiz_he.cpp
+++ b/engines/scumm/he/wiz_he.cpp
@@ -1881,7 +1881,7 @@ void Wiz::processWizImage(const WizParameters *params) {
memcpy(filename, params->filename, 260);
_vm->convertFilePath(filename);
- if (f.open((const char *)filename, Common::File::kFileReadMode)) {
+ if (f.open((const char *)filename)) {
uint32 id = f.readUint32BE();
if (id == MKID_BE('AWIZ') || id == MKID_BE('MULT')) {
uint32 size = f.readUint32BE();
@@ -1911,7 +1911,7 @@ void Wiz::processWizImage(const WizParameters *params) {
break;
case 4:
if (params->processFlags & kWPFUseFile) {
- Common::File f;
+ Common::DumpFile f;
switch (params->fileWriteMode) {
case 2:
@@ -1924,7 +1924,7 @@ void Wiz::processWizImage(const WizParameters *params) {
memcpy(filename, params->filename, 260);
_vm->convertFilePath(filename);
- if (!f.open((const char *)filename, Common::File::kFileWriteMode)) {
+ if (!f.open((const char *)filename)) {
debug(0, "Unable to open for write '%s'", filename);
_vm->VAR(119) = -3;
} else {
diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp
index fa50eca604..d3359fa33e 100644
--- a/engines/scumm/imuse_digi/dimuse.cpp
+++ b/engines/scumm/imuse_digi/dimuse.cpp
@@ -57,8 +57,8 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps)
for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
_track[l] = new Track;
assert(_track[l]);
+ memset(_track[l], 0, sizeof(Track));
_track[l]->trackId = l;
- _track[l]->used = false;
}
_vm->_timer->installTimerProc(timer_handler, 1000000 / _callbackFps, this);
diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
index 1511b9aefc..b18b0ba70f 100644
--- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
+++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
@@ -102,10 +102,10 @@ void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::File *file, SoundDesc *sound,
int32 version = file->readUint32BE();
if (version != 3) {
if (version == 2) {
- warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2.");
- warning("Suggested to recompress with latest tool from daily builds.");
+ warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2");
+ warning("Suggested to recompress with latest tool from daily builds");
} else
- error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d.", version);
+ error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d", version);
}
sound->bits = file->readUint32BE();
sound->freq = file->readUint32BE();
diff --git a/engines/scumm/imuse_digi/dimuse_track.h b/engines/scumm/imuse_digi/dimuse_track.h
index 33147128cb..2d4c673cf6 100644
--- a/engines/scumm/imuse_digi/dimuse_track.h
+++ b/engines/scumm/imuse_digi/dimuse_track.h
@@ -85,13 +85,15 @@ struct Track {
int getPan() const { return (pan != 64) ? 2 * pan - 127 : 0; }
int getVol() const { return vol / 1000; }
Audio::Mixer::SoundType getType() const {
- Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType;
+ Audio::Mixer::SoundType type;
if (volGroupId == 1)
type = Audio::Mixer::kSpeechSoundType;
else if (volGroupId == 2)
type = Audio::Mixer::kSFXSoundType;
else if (volGroupId == 3)
type = Audio::Mixer::kMusicSoundType;
+ else
+ error("Track::getType(): invalid sound type");
return type;
}
};
diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp
index acdc2bc222..6bd62c1761 100644
--- a/engines/scumm/resource.cpp
+++ b/engines/scumm/resource.cpp
@@ -1299,7 +1299,7 @@ void ScummEngine::allocateArrays() {
void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int length) {
char buf[256];
- Common::File out;
+ Common::DumpFile out;
uint32 size;
if (length >= 0)
@@ -1313,7 +1313,7 @@ void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int le
sprintf(buf, "dumps/%s%d.dmp", tag, idx);
- out.open(buf, Common::File::kFileWriteMode);
+ out.open(buf);
if (out.isOpen() == false)
return;
out.write(ptr, size);
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 36b82519e9..f9e4eb415c 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -411,15 +411,15 @@ void ScummEngine::listSavegames(bool *marks, int num) {
char prefix[256];
char slot[3];
int slotNum;
- Common::StringList filenames;
+ Common::StringList files;
makeSavegameName(prefix, 99, false);
prefix[strlen(prefix)-2] = '*';
prefix[strlen(prefix)-1] = 0;
memset(marks, false, num * sizeof(bool)); //assume no savegames for this title
- filenames = _saveFileMan->listSavefiles(prefix);
+ files = _saveFileMan->listSavefiles(prefix);
- for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++){
+ for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {
//Obtain the last 2 digits of the filename, since they correspond to the save slot
slot[0] = file->c_str()[file->size()-2];
slot[1] = file->c_str()[file->size()-1];
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 62d777aa33..ce8f0a4d9a 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Mon Jun 02 08:37:50 2008
+ This file was generated by the md5table tool on Mon Jul 28 00:13:01 2008
DO NOT EDIT MANUALLY!
*/
@@ -75,7 +75,7 @@ static const MD5Table md5table[] = {
{ "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformWindows },
{ "179879b6e35c1ead0d93aab26db0951b", "fbear", "HE 70", "", 13381, Common::EN_ANY, Common::kPlatformWindows },
{ "17b5d5e6af4ae89d62631641d66d5a05", "indy3", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformPC },
- { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES },
+ { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES },
{ "17fa250eb72dae2dad511ba79c0b6b0a", "tentacle", "", "Demo", -1, Common::FR_FRA, Common::kPlatformPC },
{ "182344899c2e2998fca0bebcd82aa81a", "atlantis", "", "CD", 12035, Common::EN_ANY, Common::kPlatformPC },
{ "183d7464902d40d00800e8ee1f04117c", "maniac", "V2", "V2", 1988, Common::DE_DEU, Common::kPlatformPC },
@@ -149,7 +149,7 @@ static const MD5Table md5table[] = {
{ "37ff1b308999c4cca7319edfcc1280a0", "puttputt", "HE 70", "Demo", 8269, Common::EN_ANY, Common::kPlatformWindows },
{ "3824e60cdf639d22f6df92a03dc4b131", "fbear", "HE 61", "", 7732, Common::EN_ANY, Common::kPlatformPC },
{ "387a544b8b10b26912d8413bab63a853", "monkey2", "", "Demo", -1, Common::EN_ANY, Common::kPlatformPC },
- { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES },
+ { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES },
{ "3938ee1aa4433fca9d9308c9891172b1", "zak", "FM-TOWNS", "Demo", -1, Common::EN_ANY, Common::kPlatformFMTowns },
{ "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformPC },
{ "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::FR_FRA, Common::kPlatformAmiga },
@@ -357,7 +357,7 @@ static const MD5Table md5table[] = {
{ "90e2f0af4f779629695c6394a65bb702", "spyfox2", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
{ "910e31cffb28226bd68c569668a0d6b4", "monkey", "EGA", "EGA", -1, Common::ES_ESP, Common::kPlatformPC },
{ "91469353f7be1b122fa88d23480a1320", "zak", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformAmiga },
- { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES },
+ { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES },
{ "927a764615c7fcdd72f591355e089d8c", "monkey", "No Adlib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST },
{ "92b078d9d6d9d751da9c26b8b3075779", "tentacle", "", "Floppy", -1, Common::FR_FRA, Common::kPlatformPC },
{ "92e7727e67f5cd979d8a1070e4eb8cb3", "puttzoo", "HE 98.5", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown },
@@ -503,7 +503,7 @@ static const MD5Table md5table[] = {
{ "d7b247c26bf1f01f8f7daf142be84de3", "balloon", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "d831f7c048574dd9d5d85db2a1468099", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 },
{ "d8323015ecb8b10bf53474f6e6b0ae33", "dig", "", "", 16304, Common::UNK_LANG, Common::kPlatformUnknown },
- { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES },
+ { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES },
{ "d917f311a448e3cc7239c31bddb00dd2", "samnmax", "", "CD", 9080, Common::EN_ANY, Common::kPlatformUnknown },
{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformPC },
{ "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns },
diff --git a/engines/scumm/smush/codec47ARM.s b/engines/scumm/smush/codec47ARM.s
index 81bfdb2d22..73341c117f 100644
--- a/engines/scumm/smush/codec47ARM.s
+++ b/engines/scumm/smush/codec47ARM.s
@@ -80,7 +80,7 @@ level1codeFD:
LDRB r9,[r8,#384] @ r9 = l = tmp_ptr[384]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
ADD r12,r8,#384 @ r12= &tmp_ptr[384]
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level1codeFD_over1
level1codeFD_loop1:
@@ -94,7 +94,7 @@ level1codeFD_over1:
LDRB r9,[r12,#1] @ r9 = l = tmp_ptr[385]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
SUB r12,r12,#256 @ r12= &tmp_ptr[128] (256 = 384-128)
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level1codeFD_over2
level1codeFD_loop2:
@@ -219,7 +219,7 @@ level2codeFD:
LDRB r9,[r8,#96] @ r9 = l = tmp_ptr[96]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
ADD r12,r8,#32 @ r12 = tmp_ptr + 32
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level2codeFD_over1
level2codeFD_loop1:
@@ -232,7 +232,7 @@ level2codeFD_loop1:
level2codeFD_over1:
LDRB r9,[r12,#65] @ r9 = l = tmp_ptr[97] (65 = 97-32)
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
MOVEQ PC,R14
level2codeFD_loop2:
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index fdd0598378..7500b16c87 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -89,6 +89,7 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer)
Sound::~Sound() {
stopCDTimer();
+ AudioCD.destroy();
delete _sfxFile;
}
diff --git a/engines/sky/disk.cpp b/engines/sky/disk.cpp
index a2f7d57cb0..a30276f8be 100644
--- a/engines/sky/disk.cpp
+++ b/engines/sky/disk.cpp
@@ -326,14 +326,14 @@ void Disk::fnFlushBuffers(void) {
void Disk::dumpFile(uint16 fileNr) {
char buf[128];
- Common::File out;
+ Common::DumpFile out;
byte* filePtr;
filePtr = loadFile(fileNr);
sprintf(buf, "dumps/file-%d.dmp", fileNr);
if (!Common::File::exists(buf)) {
- if (out.open(buf, Common::File::kFileWriteMode))
+ if (out.open(buf))
out.write(filePtr, _lastLoadedFileSize);
}
free(filePtr);
diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp
index 7c2b262d82..4434f4cd68 100644
--- a/engines/sky/music/adlibmusic.cpp
+++ b/engines/sky/music/adlibmusic.cpp
@@ -47,6 +47,7 @@ AdlibMusic::AdlibMusic(Audio::Mixer *pMixer, Disk *pDisk)
AdlibMusic::~AdlibMusic(void) {
+ OPLDestroy(_opl);
_mixer->stopHandle(_soundHandle);
}
diff --git a/engines/sword1/resman.cpp b/engines/sword1/resman.cpp
index d54e290b09..adb84eee83 100644
--- a/engines/sword1/resman.cpp
+++ b/engines/sword1/resman.cpp
@@ -212,8 +212,8 @@ void *ResMan::openFetchRes(uint32 id) {
void ResMan::dumpRes(uint32 id) {
char outn[30];
sprintf(outn, "DUMP%08X.BIN", id);
- Common::File outf;
- if (outf.open(outn, Common::File::kFileWriteMode)) {
+ Common::DumpFile outf;
+ if (outf.open(outn)) {
resOpen(id);
MemHandle *memHandle = resHandle(id);
outf.write(memHandle->data, memHandle->size);
diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp
index fd72ba8d52..3b5a09578b 100644
--- a/engines/sword2/music.cpp
+++ b/engines/sword2/music.cpp
@@ -52,9 +52,11 @@ namespace Sword2 {
static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);
static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
- debug(3, "Playing %s from CD %d", base, cd);
+ bool alreadyOpen;
if (!fh->file.isOpen()) {
+ alreadyOpen = false;
+
struct {
const char *ext;
int mode;
@@ -75,16 +77,14 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
char filename[20];
for (int i = 0; i < ARRAYSIZE(file_types); i++) {
- Common::File f;
-
sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
- if (f.open(filename)) {
+ if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
sprintf(filename, "%s.%s", base, file_types[i].ext);
- if (f.open(filename)) {
+ if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
@@ -105,7 +105,8 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
fh->idxTab = NULL;
}
}
- }
+ } else
+ alreadyOpen = true;
uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3;
@@ -134,7 +135,13 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
*numSamples = len;
if (!pos || !len) {
- fh->file.close();
+ // We couldn't find the sound. Possibly as a result of a bad
+ // installation (e.g. using the music file from CD 2 as the
+ // first music file). Don't close the file if it was already
+ // open though, because something is playing from it.
+ warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id);
+ if (!alreadyOpen)
+ fh->file.close();
return NULL;
}
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
index d6b8025cda..8cddddff78 100644
--- a/engines/sword2/resman.cpp
+++ b/engines/sword2/resman.cpp
@@ -234,7 +234,6 @@ bool ResourceManager::init() {
/**
* Returns the address of a resource. Loads if not in memory. Retains a count.
*/
-
byte *ResourceManager::openResource(uint32 res, bool dump) {
assert(res < _totalResFiles);
@@ -287,7 +286,6 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {
if (dump) {
char buf[256];
const char *tag;
- Common::File out;
switch (fetchType(_resList[res].ptr)) {
case ANIMATION_FILE:
@@ -337,7 +335,8 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {
sprintf(buf, "dumps/%s-%d.dmp", tag, res);
if (!Common::File::exists(buf)) {
- if (out.open(buf, Common::File::kFileWriteMode))
+ Common::DumpFile out;
+ if (out.open(buf))
out.write(_resList[res].ptr, len);
}
}
diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h
index 70bae6f851..b89ef8f12b 100644
--- a/engines/sword2/sound.h
+++ b/engines/sword2/sound.h
@@ -106,7 +106,7 @@ private:
void refill();
inline bool eosIntern() const {
- return _pos >= _bufferEnd;
+ return !_file->isOpen() || _pos >= _bufferEnd;
}
public:
diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp
new file mode 100644
index 0000000000..c2f01added
--- /dev/null
+++ b/engines/tinsel/actors.cpp
@@ -0,0 +1,897 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles things to do with actors, delegates much moving actor stuff.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/events.h"
+#include "tinsel/film.h" // for FREEL
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h" // INV_NOICON
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h" // for POBJECT
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** actor struct - one per actor */
+struct ACTOR_STRUC {
+ int32 masking; //!< type of actor masking
+ SCNHANDLE hActorId; //!< handle actor ID string index
+ SCNHANDLE hActorCode; //!< handle to actor script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeadActorId = 0; // The lead actor
+
+static int NumActors = 0; // The total number of actors in the game
+
+struct ACTORINFO {
+ bool alive; // TRUE == alive
+ bool hidden; // TRUE == hidden
+ bool completed; // TRUE == script played out
+
+ int x, y, z;
+
+ int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS
+ SCNHANDLE actorCode; // The actor's script
+
+ const FREEL *presReel; // the present reel
+ int presRnum; // the present reel number
+ SCNHANDLE presFilm; // the film that reel belongs to
+ OBJECT *presObj; // reference for position information
+ int presX, presY;
+
+ bool tagged; // actor tagged?
+ SCNHANDLE hTag; // handle to tag text
+ int tType; // e.g. TAG_Q1TO3
+
+ bool escOn;
+ int escEv;
+
+ COLORREF tColour; // Text colour
+
+ SCNHANDLE playFilm; // revert to this after talks
+ SCNHANDLE talkFilm; // this be deleted in the future!
+ SCNHANDLE latestFilm; // the last film ordered
+ bool talking;
+
+ int steps;
+
+};
+
+static ACTORINFO *actorInfo = 0;
+
+static COLORREF defaultColour = 0; // Text colour
+
+static bool bActorsOn = false;
+
+static int ti = 0;
+
+/**
+ * Called once at start-up time, and again at restart time.
+ * Registers the total number of actors in the game.
+ * @param num Chunk Id
+ */
+void RegisterActors(int num) {
+ if (actorInfo == NULL) {
+ // Store the total number of actors in the game
+ NumActors = num;
+
+ // Check we can save so many
+ assert(NumActors <= MAX_SAVED_ALIVES);
+
+ // Allocate RAM for actorInfo
+ // FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks,
+ // as this makes the save/load code simpler
+ actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO));
+
+ // make sure memory allocated
+ if (actorInfo == NULL) {
+ error("Cannot allocate memory for actors");
+ }
+ } else {
+ // Check the total number of actors is still the same
+ assert(num == NumActors);
+
+ memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO));
+ }
+
+ // All actors start off alive.
+ while (num--)
+ actorInfo[num].alive = true;
+}
+
+void FreeActors() {
+ if (actorInfo) {
+ free(actorInfo);
+ actorInfo = NULL;
+ }
+}
+
+/**
+ * Called from dec_lead(), i.e. normally once at start of master script.
+ * @param leadID Lead Id
+ */
+void setleadid(int leadID) {
+ LeadActorId = leadID;
+ actorInfo[leadID-1].mtype = ACT_MASK;
+}
+
+/**
+ * No comment.
+ */
+int LeadId(void) {
+ return LeadActorId;
+}
+
+struct ATP_INIT {
+ int id; // Actor number
+ USER_EVENT event; // Event
+ BUTEVENT bev; // Causal mouse event
+};
+
+/**
+ * Runs actor's glitter code.
+ */
+static void ActorTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ATP_INIT *atp = (ATP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click
+
+ // Run the Glitter code
+ assert(actorInfo[atp->id - 1].actorCode); // no code to run
+
+ _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[atp->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+
+//---------------------------------------------------------------------------
+
+struct RATP_INIT {
+ INT_CONTEXT *pic;
+ int id; // Actor number
+};
+
+static void ActorRestoredProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ RATP_INIT *r = (RATP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = RestoreInterpretContext(r->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[r->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+void RestoreActorProcess(int id, INT_CONTEXT *pic) {
+ RATP_INIT r = { pic, id };
+
+ g_scheduler->createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
+}
+
+/**
+ * Starts up process to runs actor's glitter code.
+ * @param ano Actor Id
+ * @param event Event structure
+ * @param be ButEvent
+ */
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be) {
+ ATP_INIT atp;
+
+ // Only if there is Glitter code associated with this actor.
+ if (actorInfo[ano - 1].actorCode) {
+ atp.id = ano;
+ atp.event = event;
+ atp.bev = be;
+ g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
+ }
+}
+
+/**
+ * Called at the start of each scene for each actor with a code block.
+ * @param as Actor structure
+ * @param bRunScript Flag for whether to run actor's script for the scene
+ */
+void StartActor(const ACTOR_STRUC *as, bool bRunScript) {
+ SCNHANDLE hActorId = FROM_LE_32(as->hActorId);
+
+ // Zero-out many things
+ actorInfo[hActorId - 1].hidden = false;
+ actorInfo[hActorId - 1].completed = false;
+ actorInfo[hActorId - 1].x = 0;
+ actorInfo[hActorId - 1].y = 0;
+ actorInfo[hActorId - 1].presReel = NULL;
+ actorInfo[hActorId - 1].presFilm = 0;
+ actorInfo[hActorId - 1].presObj = NULL;
+
+ // Store current scene's parameters for this actor
+ actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking);
+ actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode);
+
+ // Run actor's script for this scene
+ if (bRunScript) {
+ if (bActorsOn)
+ actorInfo[hActorId - 1].alive = true;
+
+ if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode))
+ actorEvent(hActorId, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Called at the start of each scene. Start each actor with a code block.
+ * @param ah Scene handle
+ * @param numActors Number of actors
+ * @param bRunScript Flag for whether to run actor scene scripts
+ */
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) {
+ int i;
+
+ // Only actors with code blocks got (x, y) re-initialised, so...
+ for (i = 0; i < NumActors; i++) {
+ actorInfo[i].x = actorInfo[i].y = 0;
+ actorInfo[i].mtype = 0;
+ }
+
+ const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah);
+ for (i = 0; i < numActors; i++, as++) {
+ StartActor(as, bRunScript);
+ }
+}
+
+/**
+ * Called between scenes, zeroises all actors.
+ */
+void DropActors(void) {
+ for (int i = 0; i < NumActors; i++) {
+ actorInfo[i].actorCode = 0; // No script
+ actorInfo[i].presReel = NULL; // No reel running
+ actorInfo[i].presFilm = 0; // ditto
+ actorInfo[i].presObj = NULL; // No object
+ actorInfo[i].x = 0; // No position
+ actorInfo[i].y = 0; // ditto
+
+ actorInfo[i].talkFilm = 0;
+ actorInfo[i].latestFilm = 0;
+ actorInfo[i].playFilm = 0;
+ actorInfo[i].talking = false;
+ }
+}
+
+/**
+ * Kill actors.
+ * @param ano Actor Id
+ */
+void DisableActor(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].alive = false; // Record as dead
+ actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0;
+
+ // Kill off moving actor properly
+ pActor = GetMover(ano);
+ if (pActor)
+ KillMActor(pActor);
+}
+
+/**
+ * Enable actors.
+ * @param ano Actor Id
+ */
+void EnableActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Re-incarnate only if it's dead, or it's script ran to completion
+ if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) {
+ actorInfo[ano - 1].alive = true;
+ actorInfo[ano - 1].hidden = false;
+ actorInfo[ano - 1].completed = false;
+
+ // Re-run actor's script for this scene
+ if (actorInfo[ano-1].actorCode)
+ actorEvent(ano, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Returns the aliveness (to coin a word) of the actor.
+ * @param ano Actor Id
+ */
+bool actorAlive(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].alive;
+}
+
+/**
+ * Define an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = true;
+ actorInfo[ano-1].hTag = tagtext;
+ actorInfo[ano-1].tType = tp;
+}
+
+/**
+ * Undefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void UnTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = false;
+}
+
+/**
+ * Redefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void ReTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano-1].hTag)
+ actorInfo[ano-1].tagged = true;
+}
+
+/**
+ * Returns a tagged actor's tag type. e.g. TAG_Q1TO3
+ * @param ano Actor Id
+ */
+int TagType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano-1].tType;
+}
+
+/**
+ * Returns handle to tagged actor's tag text
+ * @param ano Actor Id
+ */
+SCNHANDLE GetActorTag(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].hTag;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+void FirstTaggedActor(void) {
+ ti = 0;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+int NextTaggedActor(void) {
+ PMACTOR pActor;
+ bool hid;
+
+ do {
+ if (actorInfo[ti].tagged) {
+ pActor = GetMover(ti+1);
+ if (pActor)
+ hid = getMActorHideState(pActor);
+ else
+ hid = actorInfo[ti].hidden;
+
+ if (!hid) {
+ return ++ti;
+ }
+ }
+ } while (++ti < NumActors);
+
+ return 0;
+}
+
+/**
+ * Returns the masking type of the actor.
+ * @param ano Actor Id
+ */
+int32 actorMaskType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].mtype;
+}
+
+/**
+ * Store/Return the currently stored co-ordinates of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x X position
+ * @param y Y position
+ */
+void storeActorPos(int ano, int x, int y) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].x = x;
+ actorInfo[ano - 1].y = y;
+}
+
+void storeActorSteps(int ano, int steps) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].steps = steps;
+}
+
+int getActorSteps(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].steps;
+}
+
+void storeActorZpos(int ano, int z) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].z = z;
+}
+
+
+void GetActorPos(int ano, int *x, int *y) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorPosition(pActor, x, y);
+ else {
+ *x = actorInfo[ano - 1].x;
+ *y = actorInfo[ano - 1].y;
+ }
+}
+
+/**
+ * Returns the position of the mid-top of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x Output x
+ * @param y Output y
+ */
+void GetActorMidTop(int ano, int *x, int *y) {
+ // Not used in JAPAN version
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorMidTopPosition(pActor, x, y);
+ else if (actorInfo[ano - 1].presObj) {
+ *x = (MultiLeftmost(actorInfo[ano - 1].presObj)
+ + MultiRightmost(actorInfo[ano - 1].presObj)) / 2;
+ *y = MultiHighest(actorInfo[ano - 1].presObj);
+ } else
+ GetActorPos(ano, x, y); // The best we can do!
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorLeft(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLeftmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorRight(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiRightmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorTop(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiHighest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ */
+int GetActorBottom(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLowest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Set actor hidden status to true.
+ * For a moving actor, actually hide it.
+ * @param ano Actor Id
+ */
+void HideActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor)
+ hideMActor(pActor, 0);
+ else
+ actorInfo[ano - 1].hidden = true;
+}
+
+/**
+ * Hide an actor if it's a moving actor.
+ * @param ano Actor Id
+ * @param sf sf
+ */
+bool HideMovingActor(int ano, int sf) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor) {
+ hideMActor(pActor, sf);
+ return true;
+ } else {
+ if (actorInfo[ano - 1].presObj != NULL)
+ MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object
+ return false;
+ }
+}
+
+/**
+ * Unhide an actor if it's a moving actor.
+ * @param ano Actor Id
+ */
+void unHideMovingActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ unhideMActor(pActor);
+}
+
+/**
+ * Called after a moving actor had been replaced by an splay().
+ * Moves the actor to where the splay() left it, and continues the
+ * actor's walk (if any) from the new co-ordinates.
+ */
+void restoreMovement(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y)
+ return;
+
+ pActor->objx = actorInfo[ano - 1].x;
+ pActor->objy = actorInfo[ano - 1].y;
+
+ if (pActor->actorObj)
+ SSetActorDest(pActor);
+}
+
+/**
+ * More properly should be called:
+ * 'store_actor_reel_and/or_film_and/or_object()'
+ */
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ pActor = GetMover(ano);
+
+ // Only store the reel and film for a moving actor if NOT called from MActorProcess()
+ // (MActorProcess() calls with reel=film=NULL, pobj not NULL)
+ if (!pActor
+ || !(reel == NULL && film == 0 && pobj != NULL)) {
+ actorInfo[ano - 1].presReel = reel; // Store reel
+ actorInfo[ano - 1].presRnum = reelnum; // Store reel number
+ actorInfo[ano - 1].presFilm = film; // Store film
+ actorInfo[ano - 1].presX = x;
+ actorInfo[ano - 1].presY = y;
+ }
+
+ // Only store the object for a moving actor if called from MActorProcess()
+ if (!pActor) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ } else if (reel == NULL && film == 0 && pobj != NULL) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ }
+}
+
+/**
+ * Return the present reel/film of the actor.
+ */
+const FREEL *actorReel(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].presReel; // the present reel
+}
+
+/***************************************************************************/
+
+void setActorPlayFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].playFilm = film;
+}
+
+SCNHANDLE getActorPlayFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].playFilm;
+}
+
+void setActorTalkFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talkFilm = film;
+}
+
+SCNHANDLE getActorTalkFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talkFilm;
+}
+
+void setActorTalking(int ano, bool tf) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talking = tf;;
+}
+
+bool isActorTalking(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talking;
+}
+
+void setActorLatestFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].latestFilm = film;
+ actorInfo[ano - 1].steps = 0;
+}
+
+SCNHANDLE getActorLatestFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].latestFilm;
+}
+
+/***************************************************************************/
+
+void updateActorEsc(int ano, bool escOn, int escEvent) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].escOn = escOn;
+ actorInfo[ano - 1].escEv = escEvent;
+}
+
+bool actorEsc(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escOn;
+}
+
+int actorEev(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escEv;
+}
+
+/**
+ * Guess what these do.
+ */
+int AsetZPos(OBJECT *pObj, int y, int32 z) {
+ int zPos;
+
+ z += z ? -1 : 0;
+
+ zPos = y + (z << 10);
+ MultiSetZPosition(pObj, zPos);
+ return zPos;
+}
+
+/**
+ * Guess what these do.
+ */
+void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) {
+ if (!pActor->aHidden)
+ AsetZPos(pActor->actorObj, y, zFactor);
+}
+
+/**
+ * Stores actor's attributes.
+ * Currently only the speech colours.
+ */
+void storeActorAttr(int ano, int r1, int g1, int b1) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ if (ano == -1)
+ defaultColour = RGB(r1, g1, b1);
+ else
+ actorInfo[ano - 1].tColour = RGB(r1, g1, b1);
+}
+
+/**
+ * Get the actor's stored speech colour.
+ * @param ano Actor Id
+ */
+COLORREF getActorTcol(int ano) {
+ // Not used in JAPAN version
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano - 1].tColour)
+ return actorInfo[ano - 1].tColour;
+ else
+ return defaultColour;
+}
+
+/**
+ * Store relevant information pertaining to currently existing actors.
+ */
+int SaveActors(SAVED_ACTOR *sActorInfo) {
+ int i, j;
+
+ for (i = 0, j = 0; i < NumActors; i++) {
+ if (actorInfo[i].presObj != NULL) {
+ assert(j < MAX_SAVED_ACTORS); // Saving too many actors
+
+// sActorInfo[j].hidden = actorInfo[i].hidden;
+ sActorInfo[j].bAlive = actorInfo[i].alive;
+// sActorInfo[j].x = (short)actorInfo[i].x;
+// sActorInfo[j].y = (short)actorInfo[i].y;
+ sActorInfo[j].z = (short)actorInfo[i].z;
+// sActorInfo[j].presReel = actorInfo[i].presReel;
+ sActorInfo[j].presRnum = (short)actorInfo[i].presRnum;
+ sActorInfo[j].presFilm = actorInfo[i].presFilm;
+ sActorInfo[j].presX = (short)actorInfo[i].presX;
+ sActorInfo[j].presY = (short)actorInfo[i].presY;
+ sActorInfo[j].actorID = (short)(i+1);
+ j++;
+ }
+ }
+
+ return j;
+}
+
+void setactorson(void) {
+ bActorsOn = true;
+}
+
+void ActorsLife(int ano, bool bAlive) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ actorInfo[ano-1].alive = bAlive;
+}
+
+
+void syncAllActorsAlive(Serializer &s) {
+ for (int i = 0; i < MAX_SAVED_ALIVES; i++) {
+ s.syncAsByte(actorInfo[i].alive);
+ s.syncAsByte(actorInfo[i].tagged);
+ s.syncAsByte(actorInfo[i].tType);
+ s.syncAsUint32LE(actorInfo[i].hTag);
+ }
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h
new file mode 100644
index 0000000000..91f54519d5
--- /dev/null
+++ b/engines/tinsel/actors.h
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Prototypes of actor functions
+ */
+
+#ifndef TINSEL_ACTOR_H // prevent multiple includes
+#define TINSEL_ACTOR_H
+
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/events.h" // for USER_EVENT
+#include "tinsel/palette.h" // for COLORREF
+
+namespace Tinsel {
+
+struct FREEL;
+struct INT_CONTEXT;
+struct MACTOR;
+struct OBJECT;
+
+
+/*----------------------------------------------------------------------*/
+
+void RegisterActors(int num);
+void FreeActors(void);
+void setleadid(int rid);
+int LeadId(void);
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript);
+void DropActors(void); // No actor reels running
+void DisableActor(int actor);
+void EnableActor(int actor);
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp);
+void UnTagActor(int ano);
+void ReTagActor(int ano);
+int TagType(int ano);
+bool actorAlive(int ano);
+int32 actorMaskType(int ano);
+void GetActorPos(int ano, int *x, int *y);
+void SetActorPos(int ano, int x, int y);
+void GetActorMidTop(int ano, int *x, int *y);
+int GetActorLeft(int ano);
+int GetActorRight(int ano);
+int GetActorTop(int ano);
+int GetActorBottom(int ano);
+void HideActor(int ano);
+bool HideMovingActor(int id, int sf);
+void unHideMovingActor(int id);
+void restoreMovement(int id);
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y);
+const FREEL *actorReel(int ano);
+SCNHANDLE actorFilm(int ano);
+
+void setActorPlayFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorPlayFilm(int ano);
+void setActorTalkFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorTalkFilm(int ano);
+void setActorTalking(int ano, bool tf);
+bool isActorTalking(int ano);
+void setActorLatestFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorLatestFilm(int ano);
+
+void updateActorEsc(int ano, bool escOn, int escEv);
+bool actorEsc(int ano);
+int actorEev(int ano);
+void storeActorPos(int ano, int x, int y);
+void storeActorSteps(int ano, int steps);
+int getActorSteps(int ano);
+void storeActorZpos(int ano, int z);
+SCNHANDLE GetActorTag(int ano);
+void FirstTaggedActor(void);
+int NextTaggedActor(void);
+int AsetZPos(OBJECT *pObj, int y, int32 zFactor);
+void MAsetZPos(MACTOR *pActor, int y, int32 zFactor);
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be);
+
+void storeActorAttr(int ano, int r1, int g1, int b1);
+COLORREF getActorTcol(int ano);
+
+void setactorson(void);
+
+void ActorsLife(int id, bool bAlive);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_ACTOR {
+ short actorID;
+ short z;
+ bool bAlive;
+ SCNHANDLE presFilm; //!< the film that reel belongs to
+ short presRnum; //!< the present reel number
+ short presX, presY;
+};
+
+int SaveActors(SAVED_ACTOR *sActorInfo);
+
+
+void RestoreActorProcess(int id, INT_CONTEXT *pic);
+
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_ACTOR_H */
diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp
new file mode 100644
index 0000000000..95d834d88a
--- /dev/null
+++ b/engines/tinsel/anim.cpp
@@ -0,0 +1,404 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle object animation.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+/** Animation script commands */
+enum {
+ ANI_END = 0, //!< end of animation script
+ ANI_JUMP = 1, //!< animation script jump
+ ANI_HFLIP = 2, //!< flip animated object horizontally
+ ANI_VFLIP = 3, //!< flip animated object vertically
+ ANI_HVFLIP = 4, //!< flip animated object in both directions
+ ANI_ADJUSTX = 5, //!< adjust animated object x animation point
+ ANI_ADJUSTY = 6, //!< adjust animated object y animation point
+ ANI_ADJUSTXY = 7, //!< adjust animated object x & y animation points
+ ANI_NOSLEEP = 8, //!< do not sleep for this frame
+ ANI_CALL = 9, //!< call routine
+ ANI_HIDE = 10 //!< hide animated object
+};
+
+/** animation script command possibilities */
+union ANI_SCRIPT {
+ int32 op; //!< treat as an opcode or operand
+ uint32 hFrame; //!< treat as a animation frame handle
+};
+
+/**
+ * Advance to next frame routine.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE DoNextFrame(ANIM *pAnim) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+
+ // move to next opcode
+ pAnim->scriptIndex++;
+
+ // indicate script has finished
+ return ScriptFinished;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // indicate not to sleep
+ return ScriptNoSleep;
+
+ case ANI_CALL: // call routine
+
+ // move to function address
+ pAnim->scriptIndex++;
+
+ // make function call
+
+ // REMOVED BUGGY CODE
+ // pFunc is a function pointer that's part of a union and is assumed to be 32-bits.
+ // There is no known place where a function pointer is stored inside the animation
+ // scripts, something which wouldn't have worked anyway. Having played through the
+ // entire game, there hasn't been any occurence of this case, so just error out here
+ // in case we missed something (highly unlikely though)
+ error("ANI_CALL opcode encountered! Please report this error to the ScummVM team");
+ //(*pAni[pAnim->scriptIndex].pFunc)(pAnim);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ MultiHideObject(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+
+ default: // must be an actual animation frame handle
+
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+ }
+ }
+}
+
+/**
+ * Init a ANIM structure for single stepping through a animation script.
+ * @param pAnim Animation data structure
+ * @param pAniObj Object to animate
+ * @param hNewScript Script of multipart frames
+ * @param aniSpeed Sets speed of animation in frames
+ */
+void InitStepAnimScript(ANIM *pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) {
+ OBJECT *pObj; // multi-object list iterator
+
+ pAnim->aniDelta = 1; // will animate on next call to NextAnimRate
+ pAnim->pObject = pAniObj; // set object to animate
+ pAnim->hScript = hNewScript; // set animation script
+ pAnim->scriptIndex = 0; // start of script
+ pAnim->aniRate = aniSpeed; // set speed of animation
+
+ // reset flip flags for the object - let the script do the flipping
+ for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) {
+ AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV),
+ pObj->hImg);
+ }
+}
+
+/**
+ * Execute the next command in a animation script.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE StepAnimScript(ANIM *pAnim) {
+ SCRIPTSTATE state;
+
+ if (--pAnim->aniDelta == 0) {
+ // re-init animation delta counter
+ pAnim->aniDelta = pAnim->aniRate;
+
+ // move to next frame
+ while ((state = DoNextFrame(pAnim)) == ScriptNoSleep)
+ ;
+
+ return state;
+ }
+
+ // indicate calling task should sleep
+ return ScriptSleep;
+}
+
+/**
+ * Skip the specified number of frames.
+ * @param pAnim Animation data structure
+ * @param numFrames Number of frames to skip
+ */
+void SkipFrames(ANIM *pAnim, int numFrames) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ if (numFrames <= 0)
+ // do nothing
+ return;
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+ // going off the end is probably a error
+ error("SkipFrames(): formally 'assert(0)!'");
+ break;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_CALL: // call routine
+
+ // skip function address
+ pAnim->scriptIndex += 2;
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ default: // must be an actual animation frame handle
+
+ // one less frame
+ if (numFrames-- > 0) {
+ // next opcode
+ pAnim->scriptIndex++;
+ } else {
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // we have skipped to the correct place
+ return;
+ }
+ break;
+ }
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h
new file mode 100644
index 0000000000..5b25292a6b
--- /dev/null
+++ b/engines/tinsel/anim.h
@@ -0,0 +1,71 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object animation definitions
+ */
+
+#ifndef TINSEL_ANIM_H // prevent multiple includes
+#define TINSEL_ANIM_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct OBJECT;
+
+/** animation structure */
+struct ANIM {
+ int aniRate; //!< animation speed
+ int aniDelta; //!< animation speed delta counter
+ OBJECT *pObject; //!< object to animate (assumed to be multi-part)
+ uint32 hScript; //!< animation script handle
+ int scriptIndex; //!< current position in animation script
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Anim Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/** states for DoNextFrame */
+enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep};
+
+SCRIPTSTATE DoNextFrame( // Execute the next animation frame of a animation script
+ ANIM *pAnim); // animation data structure
+
+void InitStepAnimScript( // Init a ANIM struct for single stepping through a animation script
+ ANIM *pAnim, // animation data structure
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewScript, // handle to script of multipart frames
+ int aniSpeed); // sets speed of animation in frames
+
+SCRIPTSTATE StepAnimScript( // Execute the next command in a animation script
+ ANIM *pAnim); // animation data structure
+
+void SkipFrames( // Skip the specified number of frames
+ ANIM *pAnim, // animation data structure
+ int numFrames); // number of frames to skip
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_ANIM_H
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
new file mode 100644
index 0000000000..91d21b4e0b
--- /dev/null
+++ b/engines/tinsel/background.cpp
@@ -0,0 +1,232 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Background handling code.
+ */
+
+#include "tinsel/background.h"
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h"
+#include "tinsel/sched.h" // process sheduler defs
+#include "tinsel/object.h"
+#include "tinsel/pid.h" // process identifiers
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+// current background
+BACKGND *pCurBgnd = NULL;
+
+/**
+ * Called to initialise a background.
+ * @param pBgnd Pointer to data struct for current background
+ */
+
+void InitBackground(BACKGND *pBgnd) {
+ int i; // playfield counter
+ PLAYFIELD *pPlayfield; // pointer to current playfield
+
+ // set current background
+ pCurBgnd = pBgnd;
+
+ // init background sky colour
+ SetBgndColour(pBgnd->rgbSkyColour);
+
+ // start of playfield array
+ pPlayfield = pBgnd->fieldArray;
+
+ // for each background playfield
+ for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) {
+ // init playfield pos
+ pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x);
+ pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y);
+
+ // no scrolling
+ pPlayfield->fieldXvel = intToFrac(0);
+ pPlayfield->fieldYvel = intToFrac(0);
+
+ // clear playfield display list
+ pPlayfield->pDispList = NULL;
+
+ // clear playfield moved flag
+ pPlayfield->bMoved = false;
+ }
+}
+
+/**
+ * Sets the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param newXpos New x position
+ * @param newYpos New y position
+ */
+
+void PlayfieldSetPos(int which, int newXpos, int newYpos) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // set new integer position
+ pPlayfield->fieldX = intToFrac(newXpos);
+ pPlayfield->fieldY = intToFrac(newYpos);
+
+ // set moved flag
+ pPlayfield->bMoved = true;
+}
+
+/**
+ * Returns the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param pXpos Returns current x position
+ * @param pYpos Returns current y position
+ */
+
+void PlayfieldGetPos(int which, int *pXpos, int *pYpos) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // get current integer position
+ *pXpos = fracToInt(pPlayfield->fieldX);
+ *pYpos = fracToInt(pPlayfield->fieldY);
+}
+
+/**
+ * Returns the display list for the specified playfield.
+ * @param which Which playfield
+ */
+
+OBJECT *GetPlayfieldList(int which) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // return the display list pointer for this playfield
+ return (OBJECT *)&pPlayfield->pDispList;
+}
+
+/**
+ * Draws all the playfield object lists for the current background.
+ * The playfield velocity is added to the playfield position in order
+ * to scroll each playfield before it is drawn.
+ */
+
+void DrawBackgnd(void) {
+ int i; // playfield counter
+ PLAYFIELD *pPlay; // playfield pointer
+ int prevX, prevY; // save interger part of position
+ Common::Point ptWin; // window top left
+
+ if (pCurBgnd == NULL)
+ return; // no current background
+
+ // scroll each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // save integer part of position
+ prevX = fracToInt(pPlay->fieldX);
+ prevY = fracToInt(pPlay->fieldY);
+
+ // update scrolling
+ pPlay->fieldX += pPlay->fieldXvel;
+ pPlay->fieldY += pPlay->fieldYvel;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ // set the moved flag if the playfield has moved
+ if (prevX != ptWin.x || prevY != ptWin.y)
+ pPlay->bMoved = true;
+
+ // sort the display list for this background - just in case somebody has changed object Z positions
+ SortObjectList((OBJECT *)&pPlay->pDispList);
+
+ // generate clipping rects for all objects that have moved etc.
+ FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin,
+ &pPlay->rcClip, false, pPlay->bMoved);
+
+ // clear playfield moved flag
+ pPlay->bMoved = false;
+ }
+
+ // merge the clipping rectangles
+ MergeClipRect();
+
+ // redraw all playfields within the clipping rectangles
+ const RectList &clipRects = GetClipRects();
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ // clear the clip rectangle on the virtual screen
+ // for each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ Common::Rect rcPlayClip; // clip rect for this playfield
+
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
+ // redraw all objects within this clipping rect
+ UpdateClipRect((OBJECT *)&pPlay->pDispList,
+ &ptWin, &rcPlayClip);
+ }
+ }
+
+ // transfer any new palettes to the video DAC
+ PalettesToVideoDAC();
+
+ // update the screen within the clipping rectangles
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ UpdateScreenRect(*r);
+ }
+
+ // delete all the clipping rectangles
+ ResetClipRect();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h
new file mode 100644
index 0000000000..7b8d099446
--- /dev/null
+++ b/engines/tinsel/background.h
@@ -0,0 +1,107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used for handling backgrounds
+ */
+
+#ifndef TINSEL_BACKGND_H // prevent multiple includes
+#define TINSEL_BACKGND_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/palette.h" // palette definitions
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+
+/** Scrolling padding. Needed because scroll process does not normally run on every frame */
+enum {
+ SCROLLX_PAD = 64,
+ SCROLLY_PAD = 64
+};
+
+/** When module BLK_INFO list is this long, switch from a binary to linear search */
+#define LINEAR_SEARCH 5
+
+/** background playfield structure - a playfield is a container for modules */
+struct PLAYFIELD {
+ OBJECT *pDispList; //!< object display list for this playfield
+ frac_t fieldX; //!< current world x position of playfield
+ frac_t fieldY; //!< current world y position of playfield
+ frac_t fieldXvel; //!< current x velocity of playfield
+ frac_t fieldYvel; //!< current y velocity of playfield
+ Common::Rect rcClip; //!< clip rectangle for this playfield
+ bool bMoved; //!< set when playfield has moved
+};
+
+/** multi-playfield background structure - a backgnd is a container of playfields */
+struct BACKGND {
+ COLORREF rgbSkyColour; //!< background sky colour
+ Common::Point ptInitWorld; //!< initial world position
+ Common::Rect rcScrollLimits; //!< scroll limits
+ int refreshRate; //!< background update process refresh rate
+ frac_t *pXscrollTable; //!< pointer to x direction scroll table for this background
+ frac_t *pYscrollTable; //!< pointer to y direction scroll table for this background
+ int numPlayfields; //!< number of playfields for this background
+ PLAYFIELD *fieldArray; //!< pointer to array of all playfields for this background
+ bool bAutoErase; //!< when set - screen is cleared before anything is plotted (unused)
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Background Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void InitBackground( // called to initialise a background
+ BACKGND *pBgnd); // pointer to data struct for current background
+
+void StopBgndScrolling(void); // Stops all background playfields from scrolling
+
+void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int newXpos, // new x position
+ int newYpos); // new y position
+
+void PlayfieldGetPos( // Returns the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int *pXpos, // returns current x position
+ int *pYpos); // returns current y position
+
+OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield
+ int which); // which playfield
+
+void KillPlayfieldList( // Kills all the objects on the display list for the specified playfield
+ int which); // which playfield
+
+void DrawBackgnd(void); // Draws all playfields for the current background
+
+void RedrawBackgnd(void); // Completely redraws all the playfield object lists for the current background
+
+SCNHANDLE BackPal(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_BACKGND_H
diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp
new file mode 100644
index 0000000000..9c1e5f1540
--- /dev/null
+++ b/engines/tinsel/bg.cpp
@@ -0,0 +1,189 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays the background film of a scene.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h" // CONTROL_STARTOFF
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // For control()
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static SCNHANDLE BackPalette = 0; // Background's palette
+static OBJECT *pBG = 0; // The main picture's object.
+static int BGspeed = 0;
+static SCNHANDLE BgroundHandle = 0; // Current scene handle - stored in case of Save_Scene()
+static bool DoFadeIn = false;
+static ANIM thisAnim; // used by BGmainProcess()
+
+/**
+ * BackPal
+ */
+SCNHANDLE BackPal(void) {
+ return BackPalette;
+}
+
+/**
+ * SetDoFadeIn
+*/
+void SetDoFadeIn(bool tf) {
+ DoFadeIn = tf;
+}
+
+/**
+ * Called before scene change.
+ */
+void DropBackground(void) {
+ pBG = NULL; // No background
+ BackPalette = 0; // No background palette
+}
+
+/**
+ * Return the width of the current background.
+ */
+int BackgroundWidth(void) {
+ assert(pBG);
+ return MultiRightmost(pBG) + 1;
+}
+
+/**
+ * Return the height of the current background.
+ */
+int BackgroundHeight(void) {
+ assert(pBG);
+ return MultiLowest(pBG) + 1;
+}
+
+/**
+ * Run main animation that comprises the scene background.
+ */
+static void BGmainProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+
+ // get the stuff copied to process when it was created
+ pfr = (const FREEL *)param;
+
+ if (pBG == NULL) {
+ /*** At start of scene ***/
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+
+ // Initialise and insert the object, and initialise its script.
+ pBG = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG);
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+
+ if (DoFadeIn) {
+ FadeInFast(NULL);
+ DoFadeIn = false;
+ }
+
+ while (StepAnimScript(&thisAnim) != ScriptFinished)
+ CORO_SLEEP(1);
+
+ error("Background animation has finished!");
+ } else {
+ // New background during scene
+
+ // Just re-initialise the script.
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+ StepAnimScript(&thisAnim);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * setBackPal()
+ */
+void setBackPal(SCNHANDLE hPal) {
+ BackPalette = hPal;
+
+ fettleFontPal(BackPalette);
+ CreateTranslucentPalette(BackPalette);
+}
+
+void ChangePalette(SCNHANDLE hPal) {
+ SwapPalette(FindPalette(BackPalette), hPal);
+
+ setBackPal(hPal);
+}
+
+/**
+ * Given the scene background film, extracts the palette handle for
+ * everything else's use, then starts a display process for each reel
+ * in the film.
+ * @param bfilm Scene background film
+ */
+void startupBackground(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+ IMAGE *pim;
+
+ BgroundHandle = bfilm; // Save handle in case of Save_Scene()
+
+ pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm);
+ setBackPal(FROM_LE_32(pim->hImgPal));
+
+ // Extract the film speed
+ BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate);
+
+ if (pBG == NULL)
+ control(CONTROL_STARTOFF); // New feature - start scene with control off
+
+ // Start display process for each reel in the film
+ assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn
+ g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL));
+}
+
+/**
+ * Return the current scene handle.
+ */
+SCNHANDLE GetBgroundHandle(void) {
+ return BgroundHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp
new file mode 100644
index 0000000000..b67ae7b17f
--- /dev/null
+++ b/engines/tinsel/cliprect.cpp
@@ -0,0 +1,313 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the clipping rectangle code.
+ */
+
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // normal object drawing
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+
+namespace Tinsel {
+
+/** list of all clip rectangles */
+static RectList s_rectList;
+
+/**
+ * Resets the clipping rectangle allocator.
+ */
+void ResetClipRect(void) {
+ s_rectList.clear();
+}
+
+/**
+ * Allocate a clipping rectangle from the free list.
+ * @param pClip clip rectangle dimensions to allocate
+ */
+void AddClipRect(const Common::Rect &pClip) {
+ s_rectList.push_back(pClip);
+}
+
+const RectList &GetClipRects() {
+ return s_rectList;
+}
+
+/**
+ * Creates the intersection of two rectangles.
+ * Returns True if there is a intersection.
+ * @param pDest Pointer to destination rectangle that is to receive the intersection
+ * @param pSrc1 Pointer to a source rectangle
+ * @param pSrc2 Pointer to a source rectangle
+ */
+bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = MAX(pSrc1.left, pSrc2.left);
+ pDest.top = MAX(pSrc1.top, pSrc2.top);
+ pDest.right = MIN(pSrc1.right, pSrc2.right);
+ pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
+
+ return !pDest.isEmpty();
+}
+
+/**
+ * Creates the union of two rectangles.
+ * Returns True if there is a union.
+ * @param pDest destination rectangle that is to receive the new union
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = MIN(pSrc1.left, pSrc2.left);
+ pDest.top = MIN(pSrc1.top, pSrc2.top);
+ pDest.right = MAX(pSrc1.right, pSrc2.right);
+ pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom);
+
+ return !pDest.isEmpty();
+}
+
+/**
+ * Check if the two rectangles are next to each other.
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ Common::Rect pDest;
+
+ pDest.left = MAX(pSrc1.left, pSrc2.left);
+ pDest.top = MAX(pSrc1.top, pSrc2.top);
+ pDest.right = MIN(pSrc1.right, pSrc2.right);
+ pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
+
+ return pDest.isValidRect();
+}
+
+/**
+ * Adds velocities and creates clipping rectangles for all the
+ * objects that have moved on the specified object list.
+ * @param pObjList Playfield display list to draw
+ * @param pWin Playfield window top left position
+ * @param pClip Playfield clipping rectangle
+ * @param bNoVelocity When reset, objects pos is updated with velocity
+ * @param bScrolled) When set, playfield has scrolled
+ */
+void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) {
+ OBJECT *pObj; // object list traversal pointer
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (!bNoVelocity) {
+ // we want to add velocities to objects position
+
+ if (bScrolled) {
+ // this playfield has scrolled
+
+ // indicate change
+ pObj->flags |= DMA_CHANGED;
+ }
+ }
+
+ if ((pObj->flags & DMA_CHANGED) || // object changed
+ HasPalMoved(pObj->pPal)) { // or palette moved
+ // object has changed in some way
+
+ Common::Rect rcClip; // objects clipped bounding rectangle
+ Common::Rect rcObj; // objects bounding rectangle
+
+ // calc intersection of objects previous bounding rectangle
+ // NOTE: previous position is in screen co-ords
+ if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) {
+ // previous position is within clipping rect
+ AddClipRect(rcClip);
+ }
+
+ // calc objects current bounding rectangle
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ rcObj.left = fracToInt(pObj->xPos);
+ rcObj.top = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ rcObj.left = fracToInt(pObj->xPos) - pWin->x;
+ rcObj.top = fracToInt(pObj->yPos) - pWin->y;
+ }
+ rcObj.right = rcObj.left + pObj->width;
+ rcObj.bottom = rcObj.top + pObj->height;
+
+ // calc intersection of object with clipping rect
+ if (IntersectRectangle(rcClip, rcObj, *pClip)) {
+ // current position is within clipping rect
+ AddClipRect(rcClip);
+
+ // update previous position
+ pObj->rcPrev = rcClip;
+ } else {
+ // clear previous position
+ pObj->rcPrev = Common::Rect();
+ }
+
+ // clear changed flag
+ pObj->flags &= ~DMA_CHANGED;
+ }
+ }
+}
+
+/**
+ * Merges any clipping rectangles that overlap to try and reduce
+ * the total number of clip rectangles.
+ */
+void MergeClipRect() {
+ if (s_rectList.size() <= 1)
+ return;
+
+ RectList::iterator rOuter, rInner;
+
+ for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != s_rectList.end()) {
+
+ if (LooseIntersectRectangle(*rOuter, *rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ UnionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ s_rectList.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+}
+
+/**
+ * Redraws all objects within this clipping rectangle.
+ * @param pObjList Object list to draw
+ * @param pWin Window top left position
+ * @param pClip Pointer to clip rectangle
+ */
+void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) {
+ int x, y, right, bottom; // object corners
+ int hclip, vclip; // total size of object clipping
+ DRAWOBJECT currentObj; // filled in to draw the current object in list
+ OBJECT *pObj; // object list iterator
+
+ // Initialise the fields of the drawing object to empty
+ memset(&currentObj, 0, sizeof(DRAWOBJECT));
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ x = fracToInt(pObj->xPos);
+ y = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ x = fracToInt(pObj->xPos) - pWin->x;
+ y = fracToInt(pObj->yPos) - pWin->y;
+ }
+
+ // calc object right
+ right = x + pObj->width;
+ if (right < 0)
+ // totally clipped if negative
+ continue;
+
+ // calc object bottom
+ bottom = y + pObj->height;
+ if (bottom < 0)
+ // totally clipped if negative
+ continue;
+
+ // bottom clip = low right y - clip low right y
+ currentObj.botClip = bottom - pClip->bottom;
+ if (currentObj.botClip < 0) {
+ // negative - object is not clipped
+ currentObj.botClip = 0;
+ }
+
+ // right clip = low right x - clip low right x
+ currentObj.rightClip = right - pClip->right;
+ if (currentObj.rightClip < 0) {
+ // negative - object is not clipped
+ currentObj.rightClip = 0;
+ }
+
+ // top clip = clip top left y - top left y
+ currentObj.topClip = pClip->top - y;
+ if (currentObj.topClip < 0) {
+ // negative - object is not clipped
+ currentObj.topClip = 0;
+ } else { // clipped - adjust start position to top of clip rect
+ y = pClip->top;
+ }
+
+ // left clip = clip top left x - top left x
+ currentObj.leftClip = pClip->left - x;
+ if (currentObj.leftClip < 0) {
+ // negative - object is not clipped
+ currentObj.leftClip = 0;
+ }
+ else
+ // NOTE: This else statement is disabled in tinsel v1
+ { // clipped - adjust start position to left of clip rect
+ x = pClip->left;
+ }
+
+ // calc object total horizontal clipping
+ hclip = currentObj.leftClip + currentObj.rightClip;
+
+ // calc object total vertical clipping
+ vclip = currentObj.topClip + currentObj.botClip;
+
+ if (hclip + vclip != 0) {
+ // object is clipped in some way
+
+ if (pObj->width <= hclip)
+ // object totally clipped horizontally - ignore
+ continue;
+
+ if (pObj->height <= vclip)
+ // object totally clipped vertically - ignore
+ continue;
+
+ // set clip bit in objects flags
+ currentObj.flags = pObj->flags | DMA_CLIP;
+ } else { // object is not clipped - copy flags
+ currentObj.flags = pObj->flags;
+ }
+
+ // copy objects properties to local object
+ currentObj.width = pObj->width;
+ currentObj.height = pObj->height;
+ currentObj.xPos = (short)x;
+ currentObj.yPos = (short)y;
+ currentObj.pPal = pObj->pPal;
+ currentObj.constant = pObj->constant;
+ currentObj.hBits = pObj->hBits;
+
+ // draw the object
+ DrawObject(&currentObj);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h
new file mode 100644
index 0000000000..28a66c312c
--- /dev/null
+++ b/engines/tinsel/cliprect.h
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CLIPRECT_H // prevent multiple includes
+#define TINSEL_CLIPRECT_H
+
+#include "common/list.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+typedef Common::List<Common::Rect> RectList;
+
+/*----------------------------------------------------------------------*\
+|* Clip Rect Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetClipRect(); // Resets the clipping rectangle allocator
+
+void AddClipRect( // Allocate a clipping rectangle from the free list
+ const Common::Rect &pClip); // clip rectangle dimensions to allocate
+
+const RectList &GetClipRects();
+
+bool IntersectRectangle( // Creates the intersection of two rectangles
+ Common::Rect &pDest, // pointer to destination rectangle that is to receive the intersection
+ const Common::Rect &pSrc1, // pointer to a source rectangle
+ const Common::Rect &pSrc2); // pointer to a source rectangle
+
+bool UnionRectangle( // Creates the union of two rectangles
+ Common::Rect &pDest, // destination rectangle that is to receive the new union
+ const Common::Rect &pSrc1, // a source rectangle
+ const Common::Rect &pSrc2); // a source rectangle
+
+void FindMovingObjects( // Creates clipping rectangles for all the objects that have moved on the specified object list
+ OBJECT *pObjList, // playfield display list to draw
+ Common::Point *pWin, // playfield window top left position
+ Common::Rect *pClip, // playfield clipping rectangle
+ bool bVelocity, // when set, objects pos is updated with velocity
+ bool bScrolled); // when set, playfield has scrolled
+
+void MergeClipRect(); // Merges any clipping rectangles that overlap
+
+void UpdateClipRect( // Redraws all objects within this clipping rectangle
+ OBJECT *pObjList, // object list to draw
+ Common::Point *pWin, // window top left position
+ Common::Rect *pClip); // pointer to clip rectangle
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CLIPRECT_H
diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp
new file mode 100644
index 0000000000..4c143f1b8d
--- /dev/null
+++ b/engines/tinsel/config.cpp
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains configuration functionality
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+#include "common/file.h"
+#include "common/config-manager.h"
+
+#include "sound/mixer.h"
+
+namespace Tinsel {
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+int dclickSpeed = DOUBLE_CLICK_TIME;
+int volMidi = MAXMIDIVOL;
+int volSound = MAXSAMPVOL;
+int volVoice = MAXSAMPVOL;
+int speedText = DEFTEXTSPEED;
+int bSubtitles = false;
+int bSwapButtons = 0;
+LANGUAGE language = TXT_ENGLISH;
+int bAmerica = 0;
+
+
+// Shouldn't really be here, but time is short...
+bool bNoBlocking;
+
+/**
+ * WriteConfig()
+ */
+
+void WriteConfig(void) {
+ ConfMan.setInt("dclick_speed", dclickSpeed);
+ ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL);
+ ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("talkspeed", (speedText * 255) / 100);
+ ConfMan.setBool("subtitles", bSubtitles);
+ //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0);
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+}
+
+/*---------------------------------------------------------------------*\
+| ReadConfig() |
+|-----------------------------------------------------------------------|
+|
+\*---------------------------------------------------------------------*/
+void ReadConfig(void) {
+ if (ConfMan.hasKey("dclick_speed"))
+ dclickSpeed = ConfMan.getInt("dclick_speed");
+
+ volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume;
+ volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+ volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+
+ if (ConfMan.hasKey("talkspeed"))
+ speedText = (ConfMan.getInt("talkspeed") * 100) / 255;
+ if (ConfMan.hasKey("subtitles"))
+ bSubtitles = ConfMan.getBool("subtitles");
+
+ // FIXME: If JAPAN is set, subtitles are forced OFF in the original engine
+
+ //bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false;
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+
+// The flags here control how many country flags are displayed in one of the option dialogs.
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ language = ConfigData.language;
+ #ifdef USE_3FLAGS
+ if (language == TXT_ENGLISH || language == TXT_ITALIAN) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+ #ifdef USE_4FLAGS
+ if (language == TXT_ENGLISH) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+#else
+ language = TXT_ENGLISH;
+#endif
+}
+
+bool isJapanMode() {
+#ifdef JAPAN
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h
new file mode 100644
index 0000000000..73cc411cb6
--- /dev/null
+++ b/engines/tinsel/config.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_CONFIG_H
+#define TINSEL_CONFIG_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// None of these defined -> 1 language, in ENGLISH.TXT
+//#define USE_5FLAGS 1 // All 5 flags
+//#define USE_4FLAGS 1 // French, German, Italian, Spanish
+//#define USE_3FLAGS 1 // French, German, Spanish
+
+// The Hebrew version appears to the software as being English
+// but it needs to have subtitles on...
+//#define HEBREW 1
+
+//#define JAPAN 1
+
+
+// double click timer initial value
+#define DOUBLE_CLICK_TIME 6 // 6 @ 18Hz = .33 sec
+
+#define DEFTEXTSPEED 0
+
+
+extern int dclickSpeed;
+extern int volMidi;
+extern int volSound;
+extern int volVoice;
+extern int speedText;
+extern int bSubtitles;
+extern int bSwapButtons;
+extern LANGUAGE language;
+extern int bAmerica;
+
+void WriteConfig(void);
+void ReadConfig(void);
+
+extern bool isJapanMode();
+
+
+// Shouldn't really be here, but time is short...
+extern bool bNoBlocking;
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h
new file mode 100644
index 0000000000..e0292735bb
--- /dev/null
+++ b/engines/tinsel/coroutine.h
@@ -0,0 +1,147 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_COROUTINE_H
+#define TINSEL_COROUTINE_H
+
+#include "common/scummsys.h"
+
+namespace Tinsel {
+
+/*
+ * The following is loosely based on an article by Simon Tatham:
+ * <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>.
+ * However, many improvements and tweaks have been made, in particular
+ * by taking advantage of C++ features not available in C.
+ *
+ * Why is this code here? Well, the Tinsel engine apparently used
+ * setjmp/longjmp based coroutines as a core tool from the start, and
+ * so they are deeply ingrained into the whole code base. When we
+ * started to get Tinsel ready for ScummVM, we had to deal with that.
+ * It soon got clear that we could not simply rewrite the code to work
+ * without some form of coroutines. While possible in principle, it
+ * would have meant a major restructuring of the entire code base, a
+ * rather daunting task. Also, it would have very likely introduced
+ * tons of regressons.
+ *
+ * So instead of getting rid of the coroutines, we chose to implement
+ * them in an alternate way, using Simon Tatham's trick as described
+ * above. While the trick is dirty, the result seems to be clear enough,
+ * we hope; plus, it allowed us to stay relatively close to the
+ * original structure of the code, which made it easier to avoid
+ * regressions, and will be helpful in the future when comparing things
+ * against the original code base.
+ */
+
+
+/**
+ * The core of any coroutine context which captures the 'state' of a coroutine.
+ * Private use only
+ */
+struct CoroBaseContext {
+ int _line;
+ int _sleep;
+ CoroBaseContext *_subctx;
+ CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {}
+ ~CoroBaseContext() { delete _subctx; }
+};
+
+typedef CoroBaseContext *CoroContext;
+
+
+/**
+ * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
+ * The interesting part is the destructor, which kills the context being held,
+ * but ONLY if the _sleep val of that context is zero. This way, a coroutine
+ * can just 'return' w/o having to worry about freeing the allocated context
+ * (in Simon Tatham's original code, one had to use a special macro to
+ * return from a coroutine).
+ */
+class CoroContextHolder {
+ CoroContext &_ctx;
+public:
+ CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {}
+ ~CoroContextHolder() {
+ if (_ctx && _ctx->_sleep == 0) {
+ delete _ctx;
+ _ctx = 0;
+ }
+ }
+};
+
+
+#define CORO_PARAM CoroContext &coroParam
+
+#define CORO_SUBCTX coroParam->_subctx
+
+
+#define CORO_BEGIN_CONTEXT struct CoroContextTag : CoroBaseContext { int DUMMY
+#define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam
+
+#define CORO_BEGIN_CODE(x) \
+ if (!x) {coroParam = x = new CoroContextTag();}\
+ assert(coroParam);\
+ assert(coroParam->_sleep >= 0);\
+ coroParam->_sleep = 0;\
+ CoroContextHolder tmpHolder(coroParam);\
+ switch(coroParam->_line) { case 0:;
+
+#define CORO_END_CODE \
+ }
+
+#define CORO_SLEEP(delay) \
+ do {\
+ coroParam->_line = __LINE__;\
+ coroParam->_sleep = delay;\
+ return; case __LINE__:;\
+ } while (0)
+
+/** Stop the currently running coroutine */
+#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0)
+
+/** Invoke another coroutine */
+#define CORO_INVOKE_ARGS(subCoro, ARGS) \
+ do {\
+ coroParam->_line = __LINE__;\
+ coroParam->_subctx = 0;\
+ do {\
+ subCoro ARGS;\
+ if (!coroParam->_subctx) break;\
+ coroParam->_sleep = coroParam->_subctx->_sleep;\
+ return; case __LINE__:;\
+ } while(1);\
+ } while (0)
+
+#define CORO_INVOKE_0(subCoroutine) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
+#define CORO_INVOKE_1(subCoroutine, a0) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
+#define CORO_INVOKE_2(subCoroutine, a0,a1) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_COROUTINE_H
diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp
new file mode 100644
index 0000000000..b95662cbfe
--- /dev/null
+++ b/engines/tinsel/cursor.cpp
@@ -0,0 +1,647 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Cursor and cursor trails.
+ */
+
+#include "tinsel/cursor.h"
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // For EventsManager class
+#include "tinsel/film.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // resetidletime()
+#include "tinsel/tinsel.h" // For engine access
+
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define ITERATION_BASE FRAC_ONE
+#define ITER_ACCELLERATION (10L << (FRAC_BITS - 4))
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static OBJECT *McurObj = 0; // Main cursor object
+static OBJECT *AcurObj = 0; // Auxiliary cursor object
+
+static ANIM McurAnim = {0,0,0,0,0}; // Main cursor animation structure
+static ANIM AcurAnim = {0,0,0,0,0}; // Auxiliary cursor animation structure
+
+static bool bHiddenCursor = false; // Set when cursor is hidden
+static bool bTempNoTrailers = false; // Set when cursor trails are hidden
+
+static bool bFrozenCursor = false; // Set when cursor position is frozen
+
+static frac_t IterationSize = 0;
+
+static SCNHANDLE CursorHandle = 0; // Handle to cursor reel data
+
+static int numTrails = 0;
+static int nextTrail = 0;
+
+static bool bWhoa = false; // Set by DropCursor() at the end of a scene
+ // - causes cursor processes to do nothing
+ // Reset when main cursor has re-initialised
+
+static bool restart = false; // When main cursor has been bWhoa-ed, it waits
+ // for this to be set to true.
+
+static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets
+
+
+
+#define MAX_TRAILERS 10
+
+static struct {
+
+ ANIM trailAnim; // Animation structure
+ OBJECT *trailObj; // This trailer's object
+
+} ntrailData [MAX_TRAILERS];
+
+static int lastCursorX = 0, lastCursorY = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void MoveCursor(void);
+
+/**
+ * Initialise and insert a cursor trail object, set its Z-pos, and hide
+ * it. Also initialise its animation script.
+ */
+static void InitCurTrailObj(int i, int x, int y) {
+ const FREEL *pfr; // pointer to reel
+ IMAGE *pim; // pointer to image
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ const FILM *pfilm;
+
+ if (!numTrails)
+ return;
+
+ // Get rid of old object
+ if (ntrailData[i].trailObj != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+
+ pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // No background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Initialise and insert the object, set its Z-pos, and hide it
+ ntrailData[i].trailObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL);
+ MultiSetAniXY(ntrailData[i].trailObj, x, y);
+
+ // Initialise the animation script
+ InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&ntrailData[i].trailAnim);
+}
+
+/**
+ * Get the cursor position from the mouse driver.
+ */
+static bool GetDriverPosition(int *x, int *y) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ *x = ptMouse.x;
+ *y = ptMouse.y;
+
+ return(*x >= 0 && *x <= SCREEN_WIDTH-1 &&
+ *y >= 0 && *y <= SCREEN_HEIGHT-1);
+}
+
+/**
+ * Move the cursor relative to current position.
+ */
+void AdjustCursorXY(int deltaX, int deltaY) {
+ int x, y;
+
+ if (deltaX || deltaY) {
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY));
+ }
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to an absolute position.
+ */
+void SetCursorXY(int newx, int newy) {
+ int x, y;
+ int Loffset, Toffset; // Screen offset
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ newx -= Loffset;
+ newy -= Toffset;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to a screen position.
+ */
+void SetCursorScreenXY(int newx, int newy) {
+ int x, y;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position in (x,y).
+ * Returns false if there is no cursor object.
+ */
+bool GetCursorXYNoWait(int *x, int *y, bool absolute) {
+ if (McurObj == NULL) {
+ *x = *y = 0;
+ return false;
+ }
+
+ GetAniPosition(McurObj, x, y);
+
+ if (absolute) {
+ int Loffset, Toffset; // Screen offset
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *x += Loffset;
+ *y += Toffset;
+ }
+
+ return true;
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position.
+ * If called while there is no cursor object, the calling process ends
+ * up waiting until there is.
+ */
+void GetCursorXY(int *x, int *y, bool absolute) {
+ //while (McurObj == NULL)
+ // ProcessSleepSelf();
+ assert(McurObj);
+ GetCursorXYNoWait(x, y, absolute);
+}
+
+/**
+ * Re-initialise the main cursor to use the main cursor reel.
+ * Called from TINLIB.C to restore cursor after hiding it.
+ * Called from INVENTRY.C to restore cursor after customising it.
+ */
+void RestoreMainCursor(void) {
+ const FILM *pfilm;
+
+ if (McurObj != NULL) {
+ pfilm = (const FILM *)LockMem(CursorHandle);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&McurAnim);
+ }
+ bHiddenCursor = false;
+ bFrozenCursor = false;
+}
+
+/**
+ * Called from INVENTRY.C to customise the main cursor.
+ */
+void SetTempCursor(SCNHANDLE pScript) {
+ if (McurObj != NULL)
+ InitStepAnimScript(&McurAnim, McurObj, pScript, 2);
+}
+
+/**
+ * Hide the cursor.
+ */
+void DwHideCursor(void) {
+ int i;
+
+ bHiddenCursor = true;
+
+ if (McurObj)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * Unhide the cursor.
+ */
+void UnHideCursor(void) {
+ bHiddenCursor = false;
+}
+
+/**
+ * Freeze the cursor.
+ */
+void FreezeCursor(void) {
+ bFrozenCursor = true;
+}
+
+/**
+ * HideCursorTrails
+ */
+void HideCursorTrails(void) {
+ int i;
+
+ bTempNoTrailers = true;
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * UnHideCursorTrails
+ */
+void UnHideCursorTrails(void) {
+ bTempNoTrailers = false;
+}
+
+/**
+ * Get pointer to image from a film reel. And the rest.
+ */
+IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) {
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+ if (ppmi)
+ *ppmi = pmi;
+
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ return (IMAGE *)LockMem(READ_LE_UINT32(pFrame));
+}
+
+/**
+ * Get pointer to image from a film. And the rest.
+ */
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+
+ pfilm = (const FILM *)LockMem(hFilm);
+ if (ppfilm)
+ *ppfilm = pfilm;
+
+ pfr = &pfilm->reels[reel];
+ if (ppfr)
+ *ppfr = pfr;
+
+ return GetImageFromReel(pfr, ppmi);
+}
+
+/**
+ * Delete auxillary cursor. Restore animation offsets in the image.
+ */
+void DelAuxCursor(void) {
+ if (AcurObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+ AcurObj = NULL;
+ }
+}
+
+/**
+ * Set auxillary cursor.
+ * Save animation offsets from the image if required.
+ */
+void SetAuxCursor(SCNHANDLE hFilm) {
+ IMAGE *pim; // Pointer to auxillary cursor's image
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ const FILM *pfilm;
+ int x, y; // Cursor position
+
+ DelAuxCursor(); // Get rid of previous
+
+ GetCursorXY(&x, &y, false); // Note: also waits for cursor to appear
+
+ pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal()); // Poke in the background palette
+
+ ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - ((int16) FROM_LE_16(pim->anioffX)));
+ ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - ((int16) FROM_LE_16(pim->anioffY)));
+
+ // Initialise and insert the auxillary cursor object
+ AcurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+
+ // Initialise the animation and set its position
+ InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ MultiSetAniXY(AcurObj, x - ACoX, y - ACoY);
+ MultiSetZPosition(AcurObj, Z_ACURSOR);
+
+ if (bHiddenCursor)
+ MultiHideObject(AcurObj);
+}
+
+/**
+ * MoveCursor
+ */
+static void MoveCursor(void) {
+ int startX, startY;
+ Common::Point ptMouse;
+ frac_t newX, newY;
+ unsigned dir;
+
+ // get cursors start animation position
+ GetCursorXYNoWait(&startX, &startY, false);
+
+ // get mouse drivers current position
+ ptMouse = _vm->getMousePosition();
+
+ // convert to fixed point
+ newX = intToFrac(ptMouse.x);
+ newY = intToFrac(ptMouse.y);
+
+ // modify mouse driver position depending on cursor keys
+ if ((dir = _vm->getKeyDirection()) != 0) {
+ if (dir & MSK_LEFT)
+ newX -= IterationSize;
+
+ if (dir & MSK_RIGHT)
+ newX += IterationSize;
+
+ if (dir & MSK_UP)
+ newY -= IterationSize;
+
+ if (dir & MSK_DOWN)
+ newY += IterationSize;
+
+ IterationSize += ITER_ACCELLERATION;
+
+ // set new mouse driver position
+ _vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY)));
+ } else
+
+ IterationSize = ITERATION_BASE;
+
+ // get new mouse driver position - could have been modified
+ ptMouse = _vm->getMousePosition();
+
+ if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) {
+ resetUserEventTime();
+
+ if (!bTempNoTrailers && !bHiddenCursor) {
+ InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY);
+ if (nextTrail == numTrails)
+ nextTrail = 0;
+ }
+ }
+
+ // adjust cursor to new mouse position
+ if (McurObj)
+ MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y);
+ if (AcurObj != NULL)
+ MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY);
+
+ if (InventoryActive() && McurObj) {
+ // Notify the inventory
+ Xmovement(ptMouse.x - startX);
+ Ymovement(ptMouse.y - startY);
+ }
+
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+}
+
+/**
+ * Initialise cursor object.
+ */
+static void InitCurObj(void) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ IMAGE *pim;
+
+ pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+
+ AcurObj = NULL; // No auxillary cursor
+
+ McurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+}
+
+/**
+ * Initialise the cursor position.
+ */
+static void InitCurPos(void) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+
+ MultiSetZPosition(McurObj, Z_CURSOR);
+ MoveCursor();
+ MultiHideObject(McurObj);
+
+ IterationSize = ITERATION_BASE;
+}
+
+/**
+ * CursorStoppedCheck
+ */
+static void CursorStoppedCheck(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // If scene is closing down
+ if (bWhoa) {
+ // ...wait for next scene start-up
+ while (!restart)
+ CORO_SLEEP(1);
+
+ // Re-initialise
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ // Re-start the cursor trails
+ restart = false; // set all bits
+ bWhoa = false;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * The main cursor process.
+ */
+void CursorProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (!CursorHandle || !BackPal())
+ CORO_SLEEP(1);
+
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ bWhoa = false;
+ restart = false;
+
+ while (1) {
+ // allow rescheduling
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+
+ // Step the animation script(s)
+ StepAnimScript(&McurAnim);
+ if (AcurObj != NULL)
+ StepAnimScript(&AcurAnim);
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+ }
+
+ // Move the cursor as appropriate
+ if (!bFrozenCursor)
+ MoveCursor();
+
+ // If the cursor should be hidden...
+ if (bHiddenCursor) {
+ // ...hide the cursor object(s)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL)
+ MultiHideObject(ntrailData[i].trailObj);
+ }
+
+ // Wait 'til cursor is again required.
+ while (bHiddenCursor) {
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+ }
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Called from dec_cursor() Glitter function.
+ * Register the handle to cursor reel data.
+ */
+void DwInitCursor(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+
+ CursorHandle = bfilm;
+
+ pfilm = (const FILM *)LockMem(CursorHandle);
+ numTrails = FROM_LE_32(pfilm->numreels) - 1;
+
+ assert(numTrails <= MAX_TRAILERS);
+}
+
+/**
+ * DropCursor is called when a scene is closing down.
+ */
+void DropCursor(void) {
+ AcurObj = NULL; // No auxillary cursor
+ McurObj = NULL; // No cursor object (imminently deleted elsewhere)
+ bHiddenCursor = false; // Not hidden in next scene
+ bTempNoTrailers = false; // Trailers not hidden in next scene
+ bWhoa = true; // Suspend cursor processes
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * RestartCursor is called when a new scene is starting up.
+ */
+void RestartCursor(void) {
+ restart = true; // Get the main cursor to re-initialise
+}
+
+/**
+ * Called when restarting the game, ensures correct re-start with NULL
+ * pointers etc.
+ */
+void RebootCursor(void) {
+ McurObj = AcurObj = NULL;
+ for (int i = 0; i < MAX_TRAILERS; i++)
+ ntrailData[i].trailObj = NULL;
+
+ bHiddenCursor = bTempNoTrailers = bFrozenCursor = false;
+
+ CursorHandle = 0;
+
+ bWhoa = false;
+ restart = false;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h
new file mode 100644
index 0000000000..15349dda26
--- /dev/null
+++ b/engines/tinsel/cursor.h
@@ -0,0 +1,56 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CURSOR_H // prevent multiple includes
+#define TINSEL_CURSOR_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+void AdjustCursorXY(int deltaX, int deltaY);
+void SetCursorXY(int x, int y);
+void SetCursorScreenXY(int newx, int newy);
+void GetCursorXY(int *x, int *y, bool absolute);
+bool GetCursorXYNoWait(int *x, int *y, bool absolute);
+
+void RestoreMainCursor(void);
+void SetTempCursor(SCNHANDLE pScript);
+void DwHideCursor(void);
+void UnHideCursor(void);
+void FreezeCursor(void);
+void HideCursorTrails(void);
+void UnHideCursorTrails(void);
+void DelAuxCursor(void);
+void SetAuxCursor(SCNHANDLE hFilm);
+void DwInitCursor(SCNHANDLE bfilm);
+void DropCursor(void);
+void RestartCursor(void);
+void RebootCursor(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CURSOR_H
diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp
new file mode 100644
index 0000000000..dc37e6a9a1
--- /dev/null
+++ b/engines/tinsel/debugger.cpp
@@ -0,0 +1,162 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "tinsel/tinsel.h"
+#include "tinsel/debugger.h"
+#include "tinsel/inventory.h"
+#include "tinsel/pcode.h"
+#include "tinsel/scene.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+#include "tinsel/font.h"
+#include "tinsel/strres.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In PDISPLAY.CPP
+extern void TogglePathDisplay(void);
+// In tinsel.cpp
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+// In scene.cpp
+extern SCNHANDLE GetSceneHandle(void);
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+//static
+int strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp;
+ sscanf(s, "%xh", &tmp);
+ return (int)tmp;
+}
+
+//----------------- CONSOLE CLASS ---------------------
+
+Console::Console() : GUI::Debugger() {
+ DCmd_Register("item", WRAP_METHOD(Console, cmd_item));
+ DCmd_Register("scene", WRAP_METHOD(Console, cmd_scene));
+ DCmd_Register("music", WRAP_METHOD(Console, cmd_music));
+ DCmd_Register("sound", WRAP_METHOD(Console, cmd_sound));
+ DCmd_Register("string", WRAP_METHOD(Console, cmd_string));
+}
+
+Console::~Console() {
+}
+
+bool Console::cmd_item(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s item_number\n", argv[0]);
+ DebugPrintf("Sets the currently active 'held' item\n");
+ return true;
+ }
+
+ HoldItem(INV_NOICON);
+ HoldItem(strToInt(argv[1]));
+ return false;
+}
+
+bool Console::cmd_scene(int argc, const char **argv) {
+ if (argc < 1 || argc > 3) {
+ DebugPrintf("%s [scene_number [entry number]]\n", argv[0]);
+ DebugPrintf("If no parameters are given, prints the current scene.\n");
+ DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n");
+ return true;
+ }
+
+ if (argc == 1) {
+ DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT);
+ return true;
+ }
+
+ uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT;
+ int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1;
+
+ SetNewScene(sceneNumber, entryNumber, TRANS_CUT);
+ return false;
+}
+
+bool Console::cmd_music(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]);
+ DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n");
+ DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n");
+ return true;
+ }
+
+ int param = strToInt(argv[1]);
+ if (param == 0) {
+ DebugPrintf("Track number/offset can't be 0!\n", argv[0]);
+ } else if (param > 0) {
+ // Track provided
+ PlayMidiSequence(GetTrackOffset(param - 1), false);
+ } else if (param < 0) {
+ // Offset provided
+ param = param * -1;
+ PlayMidiSequence(param, false);
+ }
+ return true;
+}
+
+bool Console::cmd_sound(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Plays the sound with the given ID\n");
+ return true;
+ }
+
+ int id = strToInt(argv[1]);
+ if (_vm->_sound->sampleExists(id))
+ _vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType);
+ else
+ DebugPrintf("Sample %d does not exist!\n", id);
+
+ return true;
+}
+
+bool Console::cmd_string(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Prints the string with the given ID\n");
+ return true;
+ }
+
+ char tmp[TBUFSZ];
+ int id = strToInt(argv[1]);
+ LoadStringRes(id, tmp, TBUFSZ);
+ DebugPrintf("%s\n", tmp);
+
+ return true;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h
new file mode 100644
index 0000000000..219bc71224
--- /dev/null
+++ b/engines/tinsel/debugger.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DEBUGGER_H
+#define TINSEL_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace Tinsel {
+
+class TinselEngine;
+
+class Console: public GUI::Debugger {
+protected:
+ bool cmd_item(int argc, const char **argv);
+ bool cmd_scene(int argc, const char **argv);
+ bool cmd_music(int argc, const char **argv);
+ bool cmd_sound(int argc, const char **argv);
+ bool cmd_string(int argc, const char **argv);
+public:
+ Console();
+ virtual ~Console(void);
+};
+
+} // End of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp
new file mode 100644
index 0000000000..a638dde2c5
--- /dev/null
+++ b/engines/tinsel/detection.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/advancedDetector.h"
+#include "common/file.h"
+
+#include "tinsel/tinsel.h"
+
+
+namespace Tinsel {
+
+struct TinselGameDescription {
+ Common::ADGameDescription desc;
+
+ int gameID;
+ int gameType;
+ uint32 features;
+ uint16 version;
+};
+
+uint32 TinselEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+uint32 TinselEngine::getFeatures() const {
+ return _gameDescription->features;
+}
+
+Common::Language TinselEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform TinselEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+uint16 TinselEngine::getVersion() const {
+ return _gameDescription->version;
+}
+
+}
+
+static const PlainGameDescriptor tinselGames[] = {
+ {"tinsel", "Tinsel engine game"},
+ {"dw", "Discworld"},
+ {"dw2", "Discworld 2: Mortality Bytes!"},
+ {0, 0}
+};
+
+
+namespace Tinsel {
+
+static const TinselGameDescription gameDescriptions[] = {
+
+ // Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas
+ // versions with *.scn files tinsel v2 (7/5/1995)
+ // Update: this is not entirely true, there were some versions released
+ // with *.gra files and used tinsel v2
+
+ {
+ { // This version has *.gra files but uses tinsel v2
+ "dw",
+ "Floppy",
+ AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_FLOPPY,
+ TINSEL_V2,
+ },
+
+ { // English CD v1. This version has *.gra files but uses tinsel v2
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V2,
+ },
+
+ { // English CD v2
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V2,
+ },
+
+#if 0
+ { // English Saturn CD
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V2,
+ },
+#endif
+
+ { // Demo from http://www.adventure-treff.de/specials/dl_demos.php
+ {
+ "dw",
+ "Demo",
+ AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192),
+ //AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GID_DW1,
+ 0,
+ GF_DEMO,
+ TINSEL_V1,
+ },
+
+ { // German CD re-release "Neon Edition"
+ // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT
+ {
+ "dw",
+ "CD",
+ AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V2,
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0, 0, 0 }
+};
+
+/**
+ * The fallback game descriptor used by the Tinsel engine's fallbackDetector.
+ * Contents of this struct are to be overwritten by the fallbackDetector.
+ */
+static TinselGameDescription g_fallbackDesc = {
+ {
+ "",
+ "",
+ AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor
+ Common::UNK_LANG,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+} // End of namespace Tinsel
+
+static const Common::ADParams detectionParams = {
+ // Pointer to ADGameDescription or its superset structure
+ (const byte *)Tinsel::gameDescriptions,
+ // Size of that superset structure
+ sizeof(Tinsel::TinselGameDescription),
+ // Number of bytes to compute MD5 sum for
+ 5000,
+ // List of all engine targets
+ tinselGames,
+ // Structure for autoupgrading obsolete targets
+ 0,
+ // Name of single gameid (optional)
+ "tinsel",
+ // List of files for file-based fallback detection (optional)
+ 0,
+ // Flags
+ 0
+};
+
+class TinselMetaEngine : public Common::AdvancedMetaEngine {
+public:
+ TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {}
+
+ virtual const char *getName() const {
+ return "Tinsel Engine";
+ }
+
+ virtual const char *getCopyright() const {
+ return "Tinsel Engine";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const;
+
+ const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const;
+
+};
+
+bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const {
+ const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc;
+ if (gd) {
+ *engine = new Tinsel::TinselEngine(syst, gd);
+ }
+ return gd != 0;
+}
+
+const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const {
+ // Set the default values for the fallback descriptor's ADGameDescription part.
+ Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG;
+ Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC;
+ Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS;
+
+ // Set default values for the fallback descriptor's TinselGameDescription part.
+ Tinsel::g_fallbackDesc.gameID = 0;
+ Tinsel::g_fallbackDesc.features = 0;
+ Tinsel::g_fallbackDesc.version = 0;
+
+ //return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc;
+ return NULL;
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(TINSEL)
+ REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#endif
diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h
new file mode 100644
index 0000000000..d14dd43fa2
--- /dev/null
+++ b/engines/tinsel/dw.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DW_H
+#define TINSEL_DW_H
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+/** scene handle data type */
+typedef uint32 SCNHANDLE;
+
+/** polygon handle */
+typedef int HPOLYGON;
+
+
+#define EOS_CHAR '\0' // string terminator
+#define LF_CHAR '\x0a' // line feed
+
+// file names
+#define SAMPLE_FILE "english.smp" // all samples
+#define SAMPLE_INDEX "english.idx" // sample index filename
+#define MIDI_FILE "midi.dat" // all MIDI sequences
+#define INDEX_FILENAME "index" // name of index file
+
+#define SCNHANDLE_SHIFT 23 // amount to shift scene handles by
+#define NO_SCNHANDLES 300 // number of memory handles for scenes
+#define MASTER_SCNHANDLE (0 << SCNHANDLE_SHIFT) // master scene memory handle
+
+// the minimum value a integer number can have
+#define MIN_INT (1 << (8*sizeof(int) - 1))
+#define MIN_INT16 (-32767)
+
+// the maximum value a integer number can have
+#define MAX_INT (~MIN_INT)
+
+// TODO: v1->v2 scene files
+#ifdef FILE_SPLIT
+// each scene is split into 2 files
+#define INV_OBJ_SCNHANDLE (2 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#else
+#define INV_OBJ_SCNHANDLE (1 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#endif
+
+
+#define FIELD_WORLD 0
+#define FIELD_STATUS 1
+
+
+
+
+// We don't set the Z position for print and talk text
+// i.e. it gets a Z position of 0
+
+#define Z_INV_BRECT 10 // Inventory background rectangle
+#define Z_INV_MFRAME 15 // Inventory window frame
+#define Z_INV_HTEXT 15 // Inventory heading text
+#define Z_INV_ICONS 16 // Icons in inventory
+#define Z_INV_ITEXT 995 // Icon text
+
+#define Z_INV_RFRAME 22 // Re-sizing frame
+
+#define Z_CURSOR 1000 // Cursor
+#define Z_CURSORTRAIL 999 // Cursor trails
+#define Z_ACURSOR 990 // Auxillary cursor
+
+#define Z_TAG_TEXT 995 // In front of auxillary cursor
+
+#define Z_MDGROOVE 20
+#define Z_MDSLIDER 21
+
+#define Z_TOPPLAY 100
+
+#define Z_TOPW_TEXT Z_TAG_TEXT
+
+// Started a collection of assorted maximum numbers here:
+#define MAX_MOVERS 6 // Moving actors using path system
+#define MAX_SAVED_ACTORS 32 // Saved 'Normal' actors
+#define MAX_SAVED_ALIVES 512 // Saves actors'lives
+
+// Legal non-existant entrance number for LoadScene()
+#define NO_ENTRY_NUM (-3458) // Magic unlikely number
+
+
+#define SAMPLETIMEOUT (15*ONE_SECOND)
+
+// Language for the resource strings
+enum LANGUAGE {
+ TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN,
+ TXT_ITALIAN, TXT_SPANISH
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_DW_H
diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp
new file mode 100644
index 0000000000..91645da71b
--- /dev/null
+++ b/engines/tinsel/effect.cpp
@@ -0,0 +1,134 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Handles effect polygons.
+//
+// EffectPolyProcess() monitors triggering of effect code (i.e. a moving
+// actor entering an effect polygon).
+// EffectProcess() runs the appropriate effect code.
+//
+// NOTE: Currently will only run one effect process at a time, i.e.
+// effect polygons will not currently nest. It won't be very difficult
+// to fix this if required.
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/pid.h"
+#include "tinsel/pcode.h" // LEAD_ACTOR
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+
+
+namespace Tinsel {
+
+struct EP_INIT {
+ HPOLYGON hEpoly;
+ PMACTOR pActor;
+ int index;
+};
+
+/**
+ * Runs an effect polygon's Glitter code with ENTER event, waits for the
+ * actor to leave that polygon. Then runs the polygon's Glitter code
+ * with LEAVE event.
+ */
+static void EffectProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ EP_INIT *to = (EP_INIT *)param; // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int x, y; // Lead actor position
+
+ // Run effect poly enter script
+ effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID);
+
+ do {
+ CORO_SLEEP(1);
+ GetMActorPosition(to->pActor, &x, &y);
+ } while (InPolygon(x, y, EFFECT) == to->hEpoly);
+
+ // Run effect poly leave script
+ effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID);
+
+ SetMAinEffectPoly(to->index, false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * If the actor was not already in an effect polygon, checks to see if
+ * it has just entered one. If it has, a process is started up to run
+ * the polygon's Glitter code.
+ */
+static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) {
+ HPOLYGON hPoly;
+ EP_INIT epi;
+
+ // If just entered an effect polygon, the effect should be triggered.
+ if (!IsMAinEffectPoly(index)) {
+ hPoly = InPolygon(x, y, EFFECT);
+ if (hPoly != NOPOLY) {
+ //Just entered effect polygon
+ SetMAinEffectPoly(index, true);
+
+ epi.hEpoly = hPoly;
+ epi.pActor = pActor;
+ epi.index = index;
+ g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi));
+ }
+ }
+}
+
+/**
+ * Just calls FettleEffectPolys() every clock tick.
+ */
+void EffectPolyProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (1) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ PMACTOR pActor = GetLiveMover(i);
+ if (pActor != NULL) {
+ int x, y;
+ GetMActorPosition(pActor, &x, &y);
+ FettleEffectPolys(x, y, i, pActor);
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp
new file mode 100644
index 0000000000..bf9f428fd4
--- /dev/null
+++ b/engines/tinsel/events.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Main purpose is to process user events.
+ * Also provides a couple of utility functions.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/handle.h" // For LockMem()
+#include "tinsel/inventory.h"
+#include "tinsel/move.h" // For walking lead actor
+#include "tinsel/pcode.h" // For Interpret()
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h" // For walking lead actor
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h" // For DontScrollCursor()
+#include "tinsel/timers.h" // DwGetCurrentTime()
+#include "tinsel/tinlib.h" // For control()
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in PDISPLAY.C
+extern int GetTaggedActor(void);
+extern HPOLYGON GetTaggedPoly(void);
+
+
+//----------------- EXTERNAL GLOBAL DATA ---------------------
+
+extern bool bEnableF1;
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int userEvents = 0; // Whenever a button or a key comes in
+static uint32 lastUserEvent = 0; // Time it hapenned
+static int butEvents = 0; // Single or double, left or right. Or escape key.
+static int escEvents = 0; // Escape key
+
+
+static int eCount = 0;
+
+/**
+ * Gets called before each schedule, only 1 user action per schedule
+ * is allowed.
+ */
+void ResetEcount(void) {
+ eCount = 0;
+}
+
+
+void IncUserEvents(void) {
+ userEvents++;
+ lastUserEvent = DwGetCurrentTime();
+}
+
+/**
+ * If this is a single click, wait to check it's not the first half of a
+ * double click.
+ * If this is a double click, the process from the waiting single click
+ * gets killed.
+ */
+void AllowDclick(CORO_PARAM, BUTEVENT be) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (be == BE_SLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ CORO_KILL_SELF();
+
+ break;
+
+ } else if (be == BE_DLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Take control from player, if the player has it.
+ * Return TRUE if control taken, FALSE if not.
+ */
+
+bool GetControl(int param) {
+ if (TestToken(TOKEN_CONTROL)) {
+ control(param);
+ return true;
+ } else
+ return false;
+}
+
+struct TP_INIT {
+ HPOLYGON hPoly; // Polygon
+ USER_EVENT event; // Trigerring event
+ BUTEVENT bev; // To allow for double clicks
+ bool take_control; // Set if control should be taken
+ // while code is running.
+ int actor;
+};
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+static void PolyTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ TP_INIT *to = (TP_INIT *)param; // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click
+
+ // Control may have gone off during AllowDclick()
+ if (!TestToken(TOKEN_CONTROL)
+ && (to->event == WALKTO || to->event == ACTION || to->event == LOOK))
+ CORO_KILL_SELF();
+
+ // Take control, if requested
+ if (to->take_control)
+ _ctx->took_control = GetControl(CONTROL_OFF);
+ else
+ _ctx->took_control = false;
+
+ // Hide conversation if appropriate
+ if (to->event == CONVERSE)
+ convHide(true);
+
+ // Run the code
+ _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ // Restore conv window if applicable
+ if (to->event == CONVERSE)
+ convHide(false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) {
+ TP_INIT to = { hPoly, event, be, tc, 0 };
+
+ g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) {
+ TP_INIT to = { hPoly, event, BE_NONE, false, actor };
+
+ g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+//-----------------------------------------------------------------------
+
+struct WP_INIT {
+ int x; // } Where to walk to
+ int y; // }
+};
+
+/**
+ * Perform a walk directly initiated by a click.
+ */
+static void WalkProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PMACTOR pActor;
+ CORO_END_CONTEXT(_ctx);
+
+ WP_INIT *to = (WP_INIT *)param; // get the co-ordinates - copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pActor = GetMover(LEAD_ACTOR);
+ if (_ctx->pActor->MActorState == NORM_MACTOR) {
+ assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path
+
+ GetToken(TOKEN_LEAD);
+ SetActorDest(_ctx->pActor, to->x, to->y, false, 0);
+ DontScrollCursor();
+
+ while (MAmoving(_ctx->pActor))
+ CORO_SLEEP(1);
+
+ FreeToken(TOKEN_LEAD);
+ }
+
+ CORO_END_CODE;
+}
+
+void walkto(int x, int y) {
+ WP_INIT to = { x, y };
+
+ g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
+}
+
+/**
+ * Run appropriate actor or polygon glitter code.
+ * If none, and it's a WALKTO event, do a walk.
+ */
+static void User_Event(USER_EVENT uEvent, BUTEVENT be) {
+ int actor;
+ int aniX, aniY;
+ HPOLYGON hPoly;
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ return;
+
+ if ((actor = GetTaggedActor()) != 0)
+ actorEvent(actor, uEvent, be);
+ else if ((hPoly = GetTaggedPoly()) != NOPOLY)
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ else {
+ GetCursorXY(&aniX, &aniY, true);
+
+ // There could be a poly involved which has no tag.
+ if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY
+ || (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) {
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ } else if (uEvent == WALKTO)
+ walkto(aniX, aniY);
+ }
+}
+
+
+/**
+ * ProcessButEvent
+ */
+void ProcessButEvent(BUTEVENT be) {
+ IncUserEvents();
+
+ if (bSwapButtons) {
+ switch (be) {
+ case BE_SLEFT:
+ be = BE_SRIGHT;
+ break;
+ case BE_DLEFT:
+ be = BE_DRIGHT;
+ break;
+ case BE_SRIGHT:
+ be = BE_SLEFT;
+ break;
+ case BE_DRIGHT:
+ be = BE_DLEFT;
+ break;
+ case BE_LDSTART:
+ be = BE_RDSTART;
+ break;
+ case BE_LDEND:
+ be = BE_RDEND;
+ break;
+ case BE_RDSTART:
+ be = BE_LDSTART;
+ break;
+ case BE_RDEND:
+ be = BE_LDEND;
+ break;
+ default:
+ break;
+ }
+ }
+
+// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT)
+ if (be == BE_SLEFT || be == BE_SRIGHT)
+ butEvents++;
+
+ if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND)
+ return;
+
+ if (InventoryActive()) {
+ ButtonToInventory(be);
+ } else {
+ switch (be) {
+ case BE_SLEFT:
+ User_Event(WALKTO, BE_SLEFT);
+ break;
+
+ case BE_DLEFT:
+ User_Event(ACTION, BE_DLEFT);
+ break;
+
+ case BE_SRIGHT:
+ User_Event(LOOK, BE_SRIGHT);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * ProcessKeyEvent
+ */
+
+void ProcessKeyEvent(KEYEVENT ke) {
+ // This stuff to allow F1 key during startup.
+ if (bEnableF1 && ke == OPTION_KEY)
+ control(CONTROL_ON);
+ else
+ IncUserEvents();
+
+ if (ke == ESC_KEY) {
+ escEvents++;
+ butEvents++; // Yes, I do mean this
+ }
+
+ // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning
+ if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND)
+ return;
+
+ switch (ke) {
+ case QUIT_KEY:
+ PopUpConf(QUIT);
+ break;
+
+ case OPTION_KEY:
+ PopUpConf(OPTION);
+ break;
+
+ case SAVE_KEY:
+ PopUpConf(SAVE);
+ break;
+
+ case LOAD_KEY:
+ PopUpConf(LOAD);
+ break;
+
+ case WALKTO_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SLEFT);
+ else
+ User_Event(WALKTO, BE_NONE);
+ break;
+
+ case ACTION_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_DLEFT);
+ else
+ User_Event(ACTION, BE_NONE);
+ break;
+
+ case LOOK_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SRIGHT);
+ else
+ User_Event(LOOK, BE_NONE);
+ break;
+
+ case ESC_KEY:
+ case PGUP_KEY:
+ case PGDN_KEY:
+ case HOME_KEY:
+ case END_KEY:
+ if (InventoryActive())
+ KeyToInventory(ke);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * For ESCapable Glitter sequences
+ */
+
+int GetEscEvents(void) {
+ return escEvents;
+}
+
+/**
+ * For cutting short talk()s etc.
+ */
+
+int GetLeftEvents(void) {
+ return butEvents;
+}
+
+/**
+ * For waitkey() Glitter function
+ */
+
+int getUserEvents(void) {
+ return userEvents;
+}
+
+uint32 getUserEventTime(void) {
+ return DwGetCurrentTime() - lastUserEvent;
+}
+
+void resetUserEventTime(void) {
+ lastUserEvent = DwGetCurrentTime();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h
new file mode 100644
index 0000000000..bc49d68717
--- /dev/null
+++ b/engines/tinsel/events.h
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * User events processing and utility functions
+ */
+
+#ifndef TINSEL_EVENTS_H
+#define TINSEL_EVENTS_H
+
+#include "tinsel/dw.h"
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+enum BUTEVENT {
+ BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT,
+ BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND,
+ BE_UNKNOWN
+};
+
+
+enum KEYEVENT {
+ ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY,
+ PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY,
+ WALKTO_KEY, ACTION_KEY, LOOK_KEY,
+ NOEVENT_KEY
+};
+
+
+/**
+ * Reasons for running Glitter code.
+ * Do not re-order these as equivalent CONSTs are defined in the master
+ * scene Glitter source file for testing against the event() library function.
+ */
+enum USER_EVENT {
+ POINTED, WALKTO, ACTION, LOOK,
+ ENTER, LEAVE, STARTUP, CONVERSE,
+ UNPOINT, PUTDOWN,
+ NOEVENT
+};
+
+
+void AllowDclick(CORO_PARAM, BUTEVENT be);
+bool GetControl(int param);
+
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc);
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor);
+
+void ProcessButEvent(BUTEVENT be);
+void ProcessKeyEvent(KEYEVENT ke);
+
+
+int GetEscEvents(void);
+int GetLeftEvents(void);
+int getUserEvents(void);
+
+uint32 getUserEventTime(void);
+void resetUserEventTime(void);
+
+void ResetEcount(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_EVENTS_H */
diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp
new file mode 100644
index 0000000000..0018727ccb
--- /dev/null
+++ b/engines/tinsel/faders.cpp
@@ -0,0 +1,175 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Fader and Flasher processes.
+ */
+
+#include "tinsel/pid.h" // list of all process IDs
+#include "tinsel/sched.h" // scheduler defs
+#include "tinsel/faders.h" // fader defs
+#include "tinsel/handle.h"
+#include "tinsel/palette.h" // Palette Manager defs
+
+namespace Tinsel {
+
+/** structure used by the "FadeProcess" process */
+struct FADE {
+ const long *pColourMultTable; // list of fixed point colour multipliers - terminated with negative entry
+ PALQ *pPalQ; // palette queue entry to fade
+};
+
+// fixed point fade multiplier tables
+//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1};
+const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1};
+//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1};
+const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1};
+
+/**
+ * Scale 'colour' by the fixed point colour multiplier 'colourMult'
+ * @param colour Colour to scale
+ * @param colourMult Fixed point multiplier
+ */
+static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) {
+ // apply multiplier to RGB components
+ uint32 red = ((GetRValue(colour) * colourMult) << 8) >> 24;
+ uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24;
+ uint32 blue = ((GetBValue(colour) * colourMult) << 8) >> 24;
+
+ // return new colour
+ return RGB(red, green, blue);
+}
+
+/**
+ * Applies the fixed point multiplier 'mult' to all colours in
+ * 'pOrig' to produce 'pNew'. Each colour in the palette will be
+ * multiplied by 'mult'.
+ * @param pNew Pointer to new palette
+ * @param pOrig Pointer to original palette
+ * @param numColours Number of colours in the above palettes
+ * @param mult Fixed point multiplier
+ */
+static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) {
+ for (int i = 0; i < numColours; i++, pNew++, pOrig++) {
+ // apply multiplier to RGB components
+ *pNew = ScaleColour(*pOrig, mult);
+ }
+}
+
+/**
+ * Process to fade one palette.
+ * A pointer to a 'FADE' structure must be passed to this process when
+ * it is created.
+ */
+static void FadeProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ COLORREF fadeRGB[MAX_COLOURS]; // local copy of palette
+ const long *pColMult; // pointer to colour multiplier table
+ PALETTE *pPalette; // pointer to palette
+ CORO_END_CONTEXT(_ctx);
+
+ // get the fade data structure - copied to process when it was created
+ FADE *pFade = (FADE *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get pointer to palette - reduce pointer indirection a bit
+ _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal);
+
+ for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) {
+ // go through all multipliers in table - until a negative entry
+
+ // fade palette using next multiplier
+ FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB,
+ FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult);
+
+ // send new palette to video DAC
+ UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB);
+
+ // allow time for video DAC to be updated
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Generic palette fader/unfader. Creates a 'FadeProcess' process
+ * for each palette that is to fade.
+ * @param multTable Fixed point colour multiplier table
+ * @param noFadeTable List of palettes not to fade
+ */
+static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) {
+ PALQ *pPal; // palette manager iterator
+
+ // create a process for each palette in the palette queue
+ for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) {
+ bool bFade = true;
+ // assume we want to fade this palette
+
+ // is palette in the list of palettes not to fade
+ if (noFadeTable != NULL) {
+ // there is a list of palettes not to fade
+ for (int i = 0; noFadeTable[i] != 0; i++) {
+ if (pPal->hPal == noFadeTable[i]) {
+ // palette is in the list - dont fade it
+ bFade = false;
+
+ // leave loop prematurely
+ break;
+ }
+ }
+ }
+
+ if (bFade) {
+ FADE fade;
+
+ // fill in FADE struct
+ fade.pColourMultTable = multTable;
+ fade.pPalQ = pPal;
+
+ // create a fader process for this palette
+ g_scheduler->createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE));
+ }
+ }
+}
+
+/**
+ * Fades a list of palettes down to black.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeOutFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadeout, noFadeTable);
+}
+
+/**
+ * Fades a list of palettes from black to their current colours.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeInFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadein, noFadeTable);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h
new file mode 100644
index 0000000000..1e9336fae8
--- /dev/null
+++ b/engines/tinsel/faders.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the fader and flasher processes
+ */
+
+#ifndef TINSEL_FADERS_H // prevent multiple includes
+#define TINSEL_FADERS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+enum {
+ /**
+ * Number of iterations in a fade out.
+ * Must match which FadeOut() is in use.
+ */
+ COUNTOUT_COUNT = 6
+};
+
+/*----------------------------------------------------------------------*\
+|* Fader Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+// usefull palette faders - they all need a list of palettes that
+// should not be faded. This parameter can be
+// NULL - fade all palettes.
+
+void FadeOutFast(SCNHANDLE noFadeTable[]);
+void FadeInFast(SCNHANDLE noFadeTable[]);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FADERS_H
diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h
new file mode 100644
index 0000000000..c8bf4604bc
--- /dev/null
+++ b/engines/tinsel/film.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FILM_H // prevent multiple includes
+#define TINSEL_FILM_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct FREEL {
+ SCNHANDLE mobj;
+ SCNHANDLE script;
+} PACKED_STRUCT;
+
+struct FILM {
+ int32 frate;
+ int32 numreels;
+ FREEL reels[1];
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp
new file mode 100644
index 0000000000..620298867e
--- /dev/null
+++ b/engines/tinsel/font.cpp
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static char tBuffer[TBUFSZ];
+
+static SCNHANDLE hTagFont = 0, hTalkFont = 0;
+
+
+/**
+ * Return address of tBuffer
+ */
+char *tBufferAddr() {
+ return tBuffer;
+}
+
+/**
+ * Return hTagFont handle.
+ */
+SCNHANDLE hTagFontHandle() {
+ return hTagFont;
+}
+
+/**
+ * Return hTalkFont handle.
+ */
+SCNHANDLE hTalkFontHandle() {
+ return hTalkFont;
+}
+
+/**
+ * Called from dec_tagfont() Glitter function. Store the tag font handle.
+ */
+void TagFontHandle(SCNHANDLE hf) {
+ hTagFont = hf; // Store the font handle
+}
+
+/**
+ * Called from dec_talkfont() Glitter function.
+ * Store the talk font handle.
+ */
+void TalkFontHandle(SCNHANDLE hf) {
+ hTalkFont = hf; // Store the font handle
+}
+
+/**
+ * Poke the background palette into character 0's images.
+ */
+void fettleFontPal(SCNHANDLE fontPal) {
+ const FONT *pFont;
+ IMAGE *pImg;
+
+ assert(fontPal);
+ assert(hTagFont); // Tag font not declared
+ assert(hTalkFont); // Talk font not declared
+
+ pFont = (const FONT *)LockMem(hTagFont);
+ pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+
+ pFont = (const FONT *)LockMem(hTalkFont);
+ pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h
new file mode 100644
index 0000000000..b75c36191c
--- /dev/null
+++ b/engines/tinsel/font.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FONT_H // prevent multiple includes
+#define TINSEL_FONT_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// A temporary buffer for extracting text into is defined in font.c
+// Accessed using tBufferAddr(), this is how big it is:
+#define TBUFSZ 512
+
+
+char *tBufferAddr(void);
+SCNHANDLE hTagFontHandle(void);
+SCNHANDLE hTalkFontHandle(void);
+
+void TagFontHandle(SCNHANDLE hf);
+void TalkFontHandle(SCNHANDLE hf);
+void fettleFontPal(SCNHANDLE fontPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FONT_H
diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp
new file mode 100644
index 0000000000..cd0937d944
--- /dev/null
+++ b/engines/tinsel/graphics.cpp
@@ -0,0 +1,440 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+// Defines used in graphic drawing
+#define CHARPTR_OFFSET 16
+#define CHAR_WIDTH 4
+#define CHAR_HEIGHT 4
+
+extern uint8 transPalette[MAX_COLOURS];
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+/**
+ * Straight rendering with transparency support
+ */
+
+static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // Set up the offset between destination blocks
+ int rightClip = applyClipping ? pObj->rightClip : 0;
+ Common::Rect boxBounds;
+
+ if (applyClipping) {
+ // Adjust the height down to skip any bottom clipping
+ pObj->height -= pObj->botClip;
+
+ // Make adjustment for the top clipping row
+ srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
+ pObj->height -= pObj->topClip;
+ pObj->topClip %= 4;
+ }
+
+ // Vertical loop
+ while (pObj->height > 0) {
+ // Get the start of the next line output
+ uint8 *tempDest = destP;
+
+ // Get the line width, and figure out which row range within the 4 row high blocks
+ // will be displayed if clipping is to be taken into account
+ int width = pObj->width;
+
+ if (!applyClipping) {
+ // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
+ boxBounds.top = 0;
+ boxBounds.bottom = 3;
+ boxBounds.left = 0;
+ } else {
+ // Handle any possible clipping at the top of the char block.
+ // We already handled topClip partially at the beginning of this function.
+ // Hence the only non-zero values it can assume at this point are 1,2,3,
+ // and that only during the very first iteration (i.e. when the top char
+ // block is drawn only partially). In particular, we set topClip to zero,
+ // as all following blocks are not to be top clipped.
+ boxBounds.top = pObj->topClip;
+ pObj->topClip = 0;
+
+ boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
+
+ // Handle any possible clipping at the start of the line
+ boxBounds.left = pObj->leftClip;
+ if (boxBounds.left >= 4) {
+ srcP += sizeof(uint16) * (boxBounds.left >> 2);
+ width -= boxBounds.left & 0xfffc;
+ boxBounds.left %= 4;
+ }
+
+ width -= boxBounds.left;
+ }
+
+ // Horizontal loop
+ while (width > rightClip) {
+ boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
+ assert(boxBounds.bottom >= boxBounds.top);
+ assert(boxBounds.right >= boxBounds.left);
+
+ int16 indexVal = READ_LE_UINT16(srcP);
+ srcP += sizeof(uint16);
+
+ if (indexVal >= 0) {
+ // Draw a 4x4 block based on the opcode as in index into the block list
+ const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
+ Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
+ }
+
+ } else {
+ // Draw a 4x4 block with transparency support
+ indexVal &= 0x7fff;
+
+ // If index is zero, then skip drawing the block completely
+ if (indexVal > 0) {
+ // Use the index along with the object's translation offset
+ const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
+
+ // Loop through each pixel - only draw a pixel if it's non-zero
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
+ p += boxBounds.left;
+ for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
+ if (*p)
+ *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
+ }
+ p += 3 - boxBounds.right;
+ }
+ }
+ }
+
+ tempDest += boxBounds.right - boxBounds.left + 1;
+ width -= 3 - boxBounds.left + 1;
+
+ // None of the remaining horizontal blocks should be left clipped
+ boxBounds.left = 0;
+ }
+
+ // If there is any width remaining, there must be a right edge clipping
+ if (width >= 0)
+ srcP += sizeof(uint16) * ((width + 3) >> 2);
+
+ // Move to next line line
+ pObj->height -= boxBounds.bottom - boxBounds.top + 1;
+ destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Fill the destination area with a constant colour
+ */
+
+static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ Common::set_to(destP, destP + pObj->width, pObj->constant);
+
+ --pObj->height;
+ destP += SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Translates the destination surface within the object's bounds using the transparency
+ * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
+ */
+
+static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Set up the offset between destination lines
+ int lineOffset = SCREEN_WIDTH - pObj->width;
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ for (int i = 0; i < pObj->width; ++i, ++destP)
+ *destP = transPalette[*destP];
+
+ --pObj->height;
+ destP += lineOffset;
+ }
+}
+
+
+#if 0
+// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method
+// from the v1 source. It may be needed to be included later on to support v1 gfx files
+
+/**
+ * Straight rendering with transparency support
+ * Possibly only used in the Discworld Demo
+ */
+
+static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // FIXME: If this method is used for the demo, it still needs to be made Endian safe
+
+ // Set up the offset between destination lines
+ pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0);
+
+ // Top clipped line handling
+ while (applyClipping && (pObj->topClip > 0)) {
+ // Loop through discarding the data for the line
+ int width = pObj->width;
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ --pObj->topClip;
+ }
+
+ // Loop for the required number of rows
+ while (pObj->height > 0) {
+
+ int width = pObj->width;
+
+ // Handling for left edge clipping - this basically involves dumping data until we reach
+ // the part of the line to be displayed
+ int clipLeft = pObj->leftClip;
+ while (applyClipping && (clipLeft > 0)) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy a specified number of bytes
+ // Make adjustments for past the clipping width
+ int remainder = 4 - (opcodeOrLen % 4);
+ srcP += MIN(clipLeft, opcodeOrLen);
+ opcodeOrLen -= MIN(clipLeft, opcodeOrLen);
+ clipLeft -= MIN(clipLeft, opcodeOrLen);
+ width -= opcodeOrLen;
+
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip) {
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ if (opcodeOrLen > 0)
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+
+ } else {
+ // Output a run length number of bytes
+ // Get data for byte value and run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ // Make adjustments for past the clipping width
+ runLength -= MIN(clipLeft, runLength);
+ clipLeft -= MIN(clipLeft, runLength);
+ width -= runLength;
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip)
+ runLength -= (pObj->rightClip - width);
+
+ if (runLength > 0) {
+ // Displayable part starts partway through the slice
+ if (colourVal != 0)
+ Common::set_to(destP, destP + runLength, colourVal);
+ destP += runLength;
+ }
+ }
+
+ if (width < pObj->rightClip)
+ width = 0;
+ }
+
+ // Handling for the visible part of the line
+ int endWidth = applyClipping ? pObj->rightClip : 0;
+ while (width > endWidth) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy the specified number of bytes
+ int remainder = 4 - (opcodeOrLen % 4);
+
+ if (width < endWidth) {
+ // Shorten run length by right clipping
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+ srcP += opcodeOrLen + remainder;
+ destP += opcodeOrLen;
+ width -= opcodeOrLen;
+
+ } else {
+ // Handle a given run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ if (width < endWidth)
+ // Shorten run length by right clipping
+ runLength -= (pObj->rightClip - width);
+
+ // Only set pixels if colourVal non-zero (0 signifies transparency)
+ if (colourVal != 0)
+ // Fill out a run length of a specified colour
+ Common::set_to(destP, destP + runLength, colourVal);
+
+ destP += runLength;
+ width -= runLength;
+ }
+ }
+
+ // If right edge clipping is being applied, then width may still be non-zero - in
+ // that case all remaining line data until the end of the line must be ignored
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ destP += pObj->lineoffset;
+ }
+}
+#endif
+
+//----------------- MAIN FUNCTIONS ---------------------
+
+/**
+ * Clears both the screen surface buffer and screen to the specified value
+ */
+void ClearScreen() {
+ void *pDest = _vm->screen().getBasePtr(0, 0);
+ memset(pDest, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
+ g_system->clearScreen();
+ g_system->updateScreen();
+}
+
+/**
+ * Updates the screen surface within the following rectangle
+ */
+void UpdateScreenRect(const Common::Rect &pClip) {
+ byte *pDest = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top);
+ g_system->copyRectToScreen(pDest, _vm->screen().pitch, pClip.left, pClip.top, pClip.width(), pClip.height());
+ g_system->updateScreen();
+}
+
+/**
+ * Draws the specified object onto the screen surface buffer
+ */
+void DrawObject(DRAWOBJECT *pObj) {
+ uint8 *srcPtr = NULL;
+ uint8 *destPtr;
+
+ if ((pObj->width <= 0) || (pObj->height <= 0))
+ // Empty image, so return immediately
+ return;
+
+ // If writing constant data, don't bother locking the data pointer and reading src details
+ if ((pObj->flags & DMA_CONST) == 0) {
+ byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000);
+
+ srcPtr = p + (pObj->hBits & 0x7FFFFF);
+ pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
+ pObj->transOffset = READ_LE_UINT32(p + 0x14);
+ }
+
+ // Get destination starting point
+ destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
+
+ // Handle various draw types
+ uint8 typeId = pObj->flags & 0xff;
+ switch (typeId) {
+ case 0x01:
+ case 0x08:
+ case 0x41:
+ case 0x48:
+ WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
+ break;
+
+ case 0x04:
+ case 0x44:
+ // ClpWrtConst with/without clipping
+ WrtConst(pObj,destPtr, typeId == 0x44);
+ break;
+
+ case 0x84:
+ case 0xC4:
+ // WrtTrans with/without clipping
+ WrtTrans(pObj, destPtr, typeId == 0xC4);
+ break;
+
+ default:
+ // NoOp
+ error("Unknown drawing type %d", typeId);
+ break;
+ }
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h
new file mode 100644
index 0000000000..85299d4873
--- /dev/null
+++ b/engines/tinsel/graphics.h
@@ -0,0 +1,78 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#ifndef TINSEL_GRAPHICS_H // prevent multiple includes
+#define TINSEL_GRAPHICS_H
+
+#include "tinsel/dw.h"
+
+#include "common/rect.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+
+#define SCREEN_WIDTH 320 // PC screen dimensions
+#define SCREEN_HEIGHT 200
+#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x
+#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y
+
+/** draw object structure - only used when drawing objects */
+struct DRAWOBJECT {
+ char *charBase; // character set base address
+ int transOffset; // transparent character offset
+ int flags; // object flags - see above for list
+ PALQ *pPal; // objects palette Q position
+ int constant; // which colour in palette for monochrome objects
+ int width; // width of object
+ int height; // height of object
+ SCNHANDLE hBits; // image bitmap handle
+ int lineoffset; // offset to next line
+ int leftClip; // amount to clip off object left
+ int rightClip; // amount to clip off object right
+ int topClip; // amount to clip off object top
+ int botClip; // amount to clip off object bottom
+ short xPos; // x position of object
+ short yPos; // y position of object
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ClearScreen();
+void DrawObject(DRAWOBJECT *pObj);
+
+// called to update a rectangle on the video screen from a video page
+void UpdateScreenRect(const Common::Rect &pClip);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp
new file mode 100644
index 0000000000..11623516ec
--- /dev/null
+++ b/engines/tinsel/handle.cpp
@@ -0,0 +1,366 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code
+ */
+
+#define BODGE
+
+#include "common/file.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/scn.h" // name of "index" file
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // heap memory manager
+
+
+// these are included only so the relevant structs can be used in convertLEStructToNative()
+#include "tinsel/anim.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/film.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/text.h"
+#include "tinsel/scene.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+bool bLockedScene = 0;
+#endif
+
+
+//----------------- LOCAL DEFINES --------------------
+
+struct MEMHANDLE {
+ char szName[12]; //!< 00 - file name of graphics file
+ int32 filesize; //!< 12 - file size and flags
+ MEM_NODE *pNode; //!< 16 - memory node for the graphics
+};
+
+
+/** memory allocation flags - stored in the top bits of the filesize field */
+enum {
+ fPreload = 0x01000000L, //!< preload memory
+ fDiscard = 0x02000000L, //!< discard memory
+ fSound = 0x04000000L, //!< sound data
+ fGraphic = 0x08000000L, //!< graphic data
+ fCompressed = 0x10000000L, //!< compressed data
+ fLoaded = 0x20000000L //!< set when file data has been loaded
+};
+#define FSIZE_MASK 0x00FFFFFFL //!< mask to isolate the filesize
+#define MALLOC_MASK 0xFF000000L //!< mask to isolate the memory allocation flags
+#define OFFSETMASK 0x007fffffL //!< get offset of address
+//#define HANDLEMASK 0xFF800000L //!< get handle of address
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// handle table gets loaded from index file at runtime
+static MEMHANDLE *handleTable = 0;
+
+// number of handles in the handle table
+static uint numHandles = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a file
+
+
+/**
+ * Loads the graphics handle table index file and preloads all the
+ * permanent graphics etc.
+ */
+void SetupHandleTable(void) {
+ enum { RECORD_SIZE = 20 };
+
+ int len;
+ uint i;
+ MEMHANDLE *pH;
+ Common::File f;
+
+ if (f.open(INDEX_FILENAME)) {
+ // get size of index file
+ len = f.size();
+
+ if (len > 0) {
+ if ((len % RECORD_SIZE) != 0) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // calc number of handles
+ numHandles = len / RECORD_SIZE;
+
+ // allocate memory for the index file
+ handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE));
+
+ // make sure memory allocated
+ assert(handleTable);
+
+ // load data
+ for (i = 0; i < numHandles; i++) {
+ f.read(handleTable[i].szName, 12);
+ handleTable[i].filesize = f.readUint32LE();
+ // The pointer should always be NULL. We don't
+ // need to read that from the file.
+ handleTable[i].pNode = NULL;
+ f.seek(4, SEEK_CUR);
+ }
+
+ if (f.ioFailed()) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // close the file
+ f.close();
+ } else { // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+ } else { // cannot find the index file
+ error("Cannot find file %s", INDEX_FILENAME);
+ }
+
+ // allocate memory nodes and load all permanent graphics
+ for (i = 0, pH = handleTable; i < numHandles; i++, pH++) {
+ if (pH->filesize & fPreload) {
+ // allocate a fixed memory node for permanent files
+ pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+
+ // load the data
+ LoadFile(pH, true);
+ }
+#ifdef BODGE
+ else if ((pH->filesize & FSIZE_MASK) == 8) {
+ pH->pNode = NULL;
+ }
+#endif
+ else {
+ // allocate a discarded memory node for other files
+ pH->pNode = MemoryAlloc(
+ DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC,
+ pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+ }
+ }
+}
+
+void FreeHandleTable(void) {
+ if (handleTable) {
+ free(handleTable);
+ handleTable = NULL;
+ }
+}
+
+/**
+ * Loads a memory block as a file.
+ * @param pH Memory block pointer
+ * @param bWarn If set, treat warnings as errors
+ */
+void LoadFile(MEMHANDLE *pH, bool bWarn) {
+ Common::File f;
+ char szFilename[sizeof(pH->szName) + 1];
+
+ if (pH->filesize & fCompressed) {
+ error("Compression handling has been removed!");
+ }
+
+ // extract and zero terminate the filename
+ strncpy(szFilename, pH->szName, sizeof(pH->szName));
+ szFilename[sizeof(pH->szName)] = 0;
+
+ if (f.open(szFilename)) {
+ // read the data
+ int bytes;
+ uint8 *addr;
+
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+#ifdef DEBUG
+ if (addr == NULL) {
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+ }
+#endif
+
+ // make sure address is valid
+ assert(addr);
+
+ bytes = f.read(addr, pH->filesize & FSIZE_MASK);
+
+ // close the file
+ f.close();
+
+ if ((pH->filesize & fPreload) == 0) {
+ // discardable - unlock the memory
+ MemoryUnlock(pH->pNode);
+ }
+
+ // set the loaded flag
+ pH->filesize |= fLoaded;
+
+ if (bytes == (pH->filesize & FSIZE_MASK)) {
+ return;
+ }
+
+ if (bWarn)
+ // file is corrupt
+ error("File %s is corrupt", szFilename);
+ }
+
+ if (bWarn)
+ // cannot find file
+ error("Cannot find file %s", szFilename);
+}
+
+/**
+ * Returns the address of a image, given its memory handle.
+ * @param offset Handle and offset to data
+ */
+uint8 *LockMem(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if (pH->filesize & fPreload) {
+ // permanent files are already loaded
+ return (uint8 *)pH->pNode + (offset & OFFSETMASK);
+ } else {
+ if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded))
+ // already allocated and loaded
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ // must have been discarded - reallocate the memory
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK,
+ DWM_MOVEABLE | DWM_DISCARDABLE);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ error("Out of memory");
+
+ LoadFile(pH, true);
+
+ // make sure address is valid
+ assert(pH->pNode->pBaseAddr);
+
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+ }
+}
+
+/**
+ * Called to make the current scene non-discardable.
+ * @param offset Handle and offset to data
+ */
+void LockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+#ifdef DEBUG
+ assert(!bLockedScene); // Trying to lock more than one scene
+#endif
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ // compact the heap to avoid fragmentation while scene is non-discardable
+ HeapCompact(MAX_INT, false);
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE);
+#ifdef DEBUG
+ bLockedScene = true;
+#endif
+ }
+}
+
+/**
+ * Called to make the current scene discardable again.
+ * @param offset Handle and offset to data
+ */
+void UnlockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE);
+#ifdef DEBUG
+ bLockedScene = false;
+#endif
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+#ifdef BODGE
+
+/**
+ * Validates that a specified handle pointer is valid
+ * @param offset Handle and offset to data
+ */
+bool ValidHandle(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ return (pH->filesize & FSIZE_MASK) != 8;
+}
+#endif
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h
new file mode 100644
index 0000000000..2cb1638d9d
--- /dev/null
+++ b/engines/tinsel/handle.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Graphics Memory Manager data structures
+ * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed
+ */
+
+#ifndef TINSEL_HANDLE_H // prevent multiple includes
+#define TINSEL_HANDLE_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void SetupHandleTable(void); // Loads the graphics handle table index file and preloads all the permanent graphics etc.
+void FreeHandleTable(void);
+
+uint8 *LockMem( // returns the addr of a image, given its memory handle
+ SCNHANDLE offset); // handle and offset to data
+
+void LockScene( // Called to make the current scene non-discardable
+ SCNHANDLE offset); // handle and offset to data
+
+void UnlockScene( // Called to make the current scene discardable again
+ SCNHANDLE offset); // handle and offset to data
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_HANDLE_H
diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp
new file mode 100644
index 0000000000..aff085d003
--- /dev/null
+++ b/engines/tinsel/heapmem.cpp
@@ -0,0 +1,594 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code.
+ */
+
+#include "tinsel/heapmem.h"
+#include "tinsel/timers.h" // For DwGetCurrentTime
+
+namespace Tinsel {
+
+// minimum memory required for MS-DOS version of game
+#define MIN_MEM 2506752L
+
+// list of all memory nodes
+MEM_NODE mnodeList[NUM_MNODES];
+
+// pointer to the linked list of free mnodes
+static MEM_NODE *pFreeMemNodes;
+
+#ifdef DEBUG
+// diagnostic mnode counters
+static int numNodes;
+static int maxNodes;
+#endif
+
+// the mnode heap sentinel
+static MEM_NODE heapSentinel;
+
+//
+static MEM_NODE *AllocMemNode(void);
+
+
+/**
+ * Initialises the memory manager.
+ */
+void MemoryInit(void) {
+ MEM_NODE *pNode;
+
+#ifdef DEBUG
+ // clear number of nodes in use
+ numNodes = 0;
+#endif
+
+ // place first node on free list
+ pFreeMemNodes = mnodeList;
+
+ // link all other objects after first
+ for (int i = 1; i < NUM_MNODES; i++) {
+ mnodeList[i - 1].pNext = mnodeList + i;
+ }
+
+ // null the last mnode
+ mnodeList[NUM_MNODES - 1].pNext = NULL;
+
+ // allocatea big chunk of memory
+ const uint32 size = 2*MIN_MEM+655360L;
+ uint8 *mem = (uint8 *)malloc(size);
+ assert(mem);
+
+ // allocate a mnode for this memory
+ pNode = AllocMemNode();
+
+ // make sure mnode was allocated
+ assert(pNode);
+
+ // convert segment to memory address
+ pNode->pBaseAddr = mem;
+
+ // set size of the memory heap
+ pNode->size = size;
+
+ // clear the memory
+ memset(pNode->pBaseAddr, 0, size);
+
+ // set cyclic links to the sentinel
+ heapSentinel.pPrev = pNode;
+ heapSentinel.pNext = pNode;
+ pNode->pPrev = &heapSentinel;
+ pNode->pNext = &heapSentinel;
+
+ // flag sentinel as locked
+ heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of mnodes used at once.
+ */
+
+void MemoryStats(void) {
+ printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES);
+}
+#endif
+
+/**
+ * Allocate a mnode from the free list.
+ */
+static MEM_NODE *AllocMemNode(void) {
+ // get the first free mnode
+ MEM_NODE *pMemNode = pFreeMemNodes;
+
+ // make sure a mnode is available
+ assert(pMemNode); // Out of memory nodes
+
+ // the next free mnode
+ pFreeMemNodes = pMemNode->pNext;
+
+ // wipe out the mnode
+ memset(pMemNode, 0, sizeof(MEM_NODE));
+
+#ifdef DEBUG
+ // one more mnode in use
+ if (++numNodes > maxNodes)
+ maxNodes = numNodes;
+#endif
+
+ // return new mnode
+ return pMemNode;
+}
+
+/**
+ * Return a mnode back to the free list.
+ * @param pMemNode Node of the memory object
+ */
+void FreeMemNode(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+#ifdef DEBUG
+ // one less mnode in use
+ --numNodes;
+ assert(numNodes >= 0);
+#endif
+
+ // place free list in mnode next
+ pMemNode->pNext = pFreeMemNodes;
+
+ // add mnode to top of free list
+ pFreeMemNodes = pMemNode;
+}
+
+
+/**
+ * Tries to make space for the specified number of bytes on the specified heap.
+ * @param size Number of bytes to free up
+ * @param bDiscard When set - will discard blocks to fullfill the request
+ */
+bool HeapCompact(long size, bool bDiscard) {
+ MEM_NODE *pHeap = &heapSentinel;
+ MEM_NODE *pPrev, *pCur, *pOldest;
+ long largest; // size of largest free block
+ uint32 oldest; // time of the oldest discardable block
+
+ while (true) {
+ bool bChanged;
+
+ do {
+ bChanged = false;
+ for (pPrev = pHeap->pNext, pCur = pPrev->pNext;
+ pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) {
+ if (pPrev->flags == 0 && pCur->flags == 0) {
+ // set the changed flag
+ bChanged = true;
+
+ // both blocks are free - merge them
+ pPrev->size += pCur->size;
+
+ // unlink the current mnode
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext->pPrev = pPrev;
+
+ // free the current mnode
+ FreeMemNode(pCur);
+
+ // leave the loop
+ break;
+ } else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE
+ && pCur->flags == 0) {
+ // a free block after a moveable block - swap them
+
+ // set the changed flag
+ bChanged = true;
+
+ // move the unlocked blocks data up (can overlap)
+ memmove(pPrev->pBaseAddr + pCur->size,
+ pPrev->pBaseAddr, pPrev->size);
+
+ // swap the order in the linked list
+ pPrev->pPrev->pNext = pCur;
+ pCur->pNext->pPrev = pPrev;
+
+ pCur->pPrev = pPrev->pPrev;
+ pPrev->pPrev = pCur;
+
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext = pPrev;
+
+ pCur->pBaseAddr = pPrev->pBaseAddr;
+ pPrev->pBaseAddr += pCur->size;
+
+ // leave the loop
+ break;
+ }
+ }
+ } while (bChanged);
+
+ // find the largest free block
+ for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if (pCur->flags == 0 && pCur->size > largest)
+ largest = pCur->size;
+ }
+
+ if (largest >= size)
+ // we have freed enough memory
+ return true;
+
+ if (!bDiscard)
+ // we cannot free enough without discarding blocks
+ return false;
+
+ // find the oldest discardable block
+ oldest = DwGetCurrentTime();
+ pOldest = NULL;
+ for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED))
+ == DWM_DISCARDABLE) {
+ // found a non-discarded discardable block
+ if (pCur->lruTime < oldest) {
+ oldest = pCur->lruTime;
+ pOldest = pCur;
+ }
+ }
+ }
+
+ if (pOldest)
+ // discard the oldest block
+ MemoryDiscard(pOldest);
+ else
+ // cannot discard any blocks
+ return false;
+ }
+}
+
+/**
+ * Allocates the specified number of bytes from the heap.
+ * @param flags Allocation attributes
+ * @param size Number of bytes to allocate
+ */
+MEM_NODE *MemoryAlloc(int flags, long size) {
+ MEM_NODE *pHeap = &heapSentinel;
+ MEM_NODE *pNode;
+ bool bCompacted = true; // set when heap has been compacted
+
+ // compact the heap if we are allocating fixed memory
+ if (flags & DWM_FIXED)
+ HeapCompact(MAX_INT, false);
+
+ while ((flags & DWM_NOALLOC) == 0 && bCompacted) {
+ // search the heap for a free block
+
+ for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) {
+ if (pNode->flags == 0 && pNode->size >= size) {
+ // a free block of the required size
+ pNode->flags = flags;
+
+ // update the LRU time
+ pNode->lruTime = DwGetCurrentTime() + 1;
+
+ if (pNode->size == size) {
+ // an exact fit
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (MEM_NODE *)MemoryLock(pNode);
+ else
+ // just return the node
+ return pNode;
+ } else {
+ // allocate a node for the remainder of the free block
+ MEM_NODE *pTemp = AllocMemNode();
+
+ // calc size of the free block
+ long freeSize = pNode->size - size;
+
+ // set size of free block
+ pTemp->size = freeSize;
+
+ // set size of node
+ pNode->size = size;
+
+ if (flags & DWM_FIXED) {
+ // place the free node after pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr + size;
+ pTemp->pNext = pNode->pNext;
+ pTemp->pPrev = pNode;
+ pNode->pNext->pPrev = pTemp;
+ pNode->pNext = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return (MEM_NODE *)MemoryLock(pNode);
+ } else {
+ // place the free node before pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr;
+ pNode->pBaseAddr += freeSize;
+ pTemp->pNext = pNode;
+ pTemp->pPrev = pNode->pPrev;
+ pNode->pPrev->pNext = pTemp;
+ pNode->pPrev = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return pNode;
+ }
+ }
+ }
+ }
+ // compact the heap if we get to here
+ bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true);
+ }
+
+ // not allocated a block if we get to here
+ if (flags & DWM_DISCARDABLE) {
+ // chain a discarded node onto the end of the heap
+ pNode = AllocMemNode();
+ pNode->flags = flags | DWM_DISCARDED;
+
+ // set mnode at the end of the list
+ pNode->pPrev = pHeap->pPrev;
+ pNode->pNext = pHeap;
+
+ // fix links to this mnode
+ pHeap->pPrev->pNext = pNode;
+ pHeap->pPrev = pNode;
+
+ // return the discarded node
+ return pNode;
+ }
+
+ // could not allocate a block
+ return NULL;
+}
+
+/**
+ * Discards the specified memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryDiscard(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // object must be discardable
+ assert(pMemNode->flags & DWM_DISCARDABLE);
+
+ // object cannot be locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ if ((pMemNode->flags & DWM_DISCARDED) == 0) {
+ // allocate a free node to replace this node
+ MEM_NODE *pTemp = AllocMemNode();
+
+ // copy node data
+ memcpy(pTemp, pMemNode, sizeof(MEM_NODE));
+
+ // flag as a free block
+ pTemp->flags = 0;
+
+ // link in the free node
+ pTemp->pPrev->pNext = pTemp;
+ pTemp->pNext->pPrev = pTemp;
+
+ // discard the node
+ pMemNode->flags |= DWM_DISCARDED;
+ pMemNode->pBaseAddr = NULL;
+ pMemNode->size = 0;
+
+ // and place it at the end of the heap
+ while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL)
+ pTemp = pTemp->pNext;
+
+ // pTemp now points to the heap sentinel
+ // set mnode at the end of the list
+ pMemNode->pPrev = pTemp->pPrev;
+ pMemNode->pNext = pTemp;
+
+ // fix links to this mnode
+ pTemp->pPrev->pNext = pMemNode;
+ pTemp->pPrev = pMemNode;
+ }
+}
+
+/**
+ * Frees the specified memory object and invalidates its node.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryFree(MEM_NODE *pMemNode) {
+ MEM_NODE *pPrev, *pNext;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // get pointer to the next mnode
+ pNext = pMemNode->pNext;
+
+ // get pointer to the previous mnode
+ pPrev = pMemNode->pPrev;
+
+ if (pPrev->flags == 0) {
+ // there is a previous free mnode
+ pPrev->size += pMemNode->size;
+
+ // unlink this mnode
+ pPrev->pNext = pNext; // previous to next
+ pNext->pPrev = pPrev; // next to previous
+
+ // free this mnode
+ FreeMemNode(pMemNode);
+
+ pMemNode = pPrev;
+ }
+ if (pNext->flags == 0) {
+ // the next mnode is free
+ pMemNode->size += pNext->size;
+
+ // flag as a free block
+ pMemNode->flags = 0;
+
+ // unlink the next mnode
+ pMemNode->pNext = pNext->pNext;
+ pNext->pNext->pPrev = pMemNode;
+
+ // free the next mnode
+ FreeMemNode(pNext);
+ }
+}
+
+/**
+ * Locks a memory object and returns a pointer to the first byte
+ * of the objects memory block.
+ * @param pMemNode Node of the memory object
+ */
+void *MemoryLock(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is not already locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ // check for a discarded or null memory object
+ if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0)
+ return NULL;
+
+ // set the lock flag
+ pMemNode->flags |= DWM_LOCKED;
+
+ // return memory objects base address
+ return pMemNode->pBaseAddr;
+}
+
+/**
+ * Changes the size or attributes of a specified memory object.
+ * @param pMemNode Node of the memory object
+ * @param size New size of block
+ * @param flags How to reallocate the object
+ */
+MEM_NODE *MemoryReAlloc(MEM_NODE *pMemNode, long size, int flags) {
+ MEM_NODE *pNew;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // validate the flags
+ // cannot be fixed and moveable
+ assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE));
+
+ // cannot be fixed and discardable
+ assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE));
+
+ // must be fixed or moveable
+ assert(flags & (DWM_FIXED | DWM_MOVEABLE));
+
+ // align the size to machine boundary requirements
+ size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1);
+
+ // validate the size
+ assert(size);
+
+ // make sure we want the node on the same heap
+ assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC)));
+
+ if (size == pMemNode->size) {
+ // must be just a change in flags
+
+ // update the nodes flags
+ pMemNode->flags = flags;
+ } else {
+ // unlink the mnode from the current heap
+ pMemNode->pNext->pPrev = pMemNode->pPrev;
+ pMemNode->pPrev->pNext = pMemNode->pNext;
+
+ // allocate a new node
+ pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size);
+
+ // make sure memory allocated
+ assert(pNew != NULL);
+
+ // update the nodes flags
+ pNew->flags = flags;
+
+ // copy the node to the current node
+ memcpy(pMemNode, pNew, sizeof(MEM_NODE));
+
+ // relink the mnode into the list
+ pMemNode->pPrev->pNext = pMemNode;
+ pMemNode->pNext->pPrev = pMemNode;
+
+ // free the new node
+ FreeMemNode(pNew);
+ }
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (MEM_NODE *)MemoryLock(pMemNode);
+ else
+ // just return the node
+ return pMemNode;
+}
+
+/**
+ * Unlocks a memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryUnlock(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is already locked
+ assert(pMemNode->flags & DWM_LOCKED);
+
+ // clear the lock flag
+ pMemNode->flags &= ~DWM_LOCKED;
+
+ // update the LRU time
+ pMemNode->lruTime = DwGetCurrentTime();
+}
+
+/**
+ * Retrieves the mnode associated with the specified pointer to a memory object.
+ * @param pMem Address of memory object
+ */
+MEM_NODE *MemoryHandle(void *pMem) {
+ MEM_NODE *pNode;
+ // search the DOS heap
+ for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) {
+ if (pNode->pBaseAddr == pMem)
+ // found it
+ return pNode;
+ }
+
+ // not found if we get to here
+ return NULL;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h
new file mode 100644
index 0000000000..7fb85985a9
--- /dev/null
+++ b/engines/tinsel/heapmem.h
@@ -0,0 +1,109 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager defines
+ */
+
+#ifndef TINSEL_HEAPMEM_H
+#define TINSEL_HEAPMEM_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192)
+
+struct MEM_NODE {
+ MEM_NODE *pNext; // link to the next node in the list
+ MEM_NODE *pPrev; // link to the previous node in the list
+ uint8 *pBaseAddr; // base address of the memory object
+ long size; // size of the memory object
+ uint32 lruTime; // time when memory object was last accessed
+ int flags; // allocation attributes
+};
+
+// allocation flags for the MemoryAlloc function
+#define DWM_FIXED 0x0001 // allocates fixed memory
+#define DWM_MOVEABLE 0x0002 // allocates movable memory
+#define DWM_DISCARDABLE 0x0004 // allocates discardable memory
+#define DWM_NOALLOC 0x0008 // when used with discardable memory - allocates a discarded block
+#define DWM_NOCOMPACT 0x0010 // does not discard memory to satisfy the allocation request
+#define DWM_ZEROINIT 0x0020 // initialises memory contents to zero
+#define DWM_SOUND 0x0040 // allocate from the sound pool
+#define DWM_GRAPHIC 0x0080 // allocate from the graphics pool
+
+// return value from the MemoryFlags function
+#define DWM_DISCARDED 0x0100 // the objects memory block has been discarded
+
+// internal allocation flags
+#define DWM_LOCKED 0x0200 // the objects memory block is locked
+#define DWM_SENTINEL 0x0400 // the objects memory block is a sentinel
+
+
+/*----------------------------------------------------------------------*\
+|* Memory Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void MemoryInit(void); // initialises the memory manager
+
+#ifdef DEBUG
+void MemoryStats(void); // Shows the maximum number of mnodes used at once
+#endif
+
+MEM_NODE *MemoryAlloc( // allocates the specified number of bytes from the heap
+ int flags, // allocation attributes
+ long size); // number of bytes to allocate
+
+void MemoryDiscard( // discards the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+int MemoryFlags( // returns information about the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+void MemoryFree( // frees the specified memory object and invalidates its node
+ MEM_NODE *pMemNode); // node of the memory object
+
+MEM_NODE *MemoryHandle( // Retrieves the mnode associated with the specified pointer to a memory object
+ void *pMem); // address of memory object
+
+void *MemoryLock( // locks a memory object and returns a pointer to the first byte of the objects memory block
+ MEM_NODE *pMemNode); // node of the memory object
+
+MEM_NODE *MemoryReAlloc( // changes the size or attributes of a specified memory object
+ MEM_NODE *pMemNode, // node of the memory object
+ long size, // new size of block
+ int flags); // how to reallocate the object
+
+long MemorySize( // returns the size, in bytes, of the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+void MemoryUnlock( // unlocks a memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+bool HeapCompact( // Allocates the specified number of bytes from the specified heap
+ long size, // number of bytes to free up
+ bool bDiscard); // when set - will discard blocks to fullfill the request
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp
new file mode 100644
index 0000000000..2a0f3695c0
--- /dev/null
+++ b/engines/tinsel/inventory.cpp
@@ -0,0 +1,4535 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles the inventory and conversation windows.
+ *
+ * And the save/load game windows. Some of this will be platform
+ * specific - I'll try to separate this ASAP.
+ *
+ * And there's still a bit of tidying and commenting to do yet.
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/polygons.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinsel.h" // For engine access
+#include "tinsel/token.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+
+#ifdef MAC_OPTIONS
+// In MAC_SOUND.C
+extern int volMaster;
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// Tag functions in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+//#define ALL_CURSORS
+
+#define INV_PICKUP BE_SLEFT // Local names
+#define INV_LOOK BE_SRIGHT // for button events
+#define INV_ACTION BE_DLEFT //
+
+
+// For SlideSlider() and similar
+enum SSFN {
+ S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN
+};
+
+/** attribute values - may become bit field if further attributes are added */
+enum {
+ IO_ONLYINV1 = 0x01,
+ IO_ONLYINV2 = 0x02,
+ IO_DROPCODE = 0x04
+};
+
+//-----------------------
+// Moveable window translucent rectangle position limits
+enum {
+ MAXLEFT = 315, //
+ MINRIGHT = 3, // These values keep 2 pixcells
+ MINTOP = -13, // of header on the screen.
+ MAXTOP = 195 //
+};
+
+//-----------------------
+// Indices into winPartsf's reels
+#define IX_SLIDE 0 // Slider
+#define IX_V26 1
+#define IX_V52 2
+#define IX_V78 3
+#define IX_V104 4
+#define IX_V130 5
+#define IX_H26 6
+#define IX_H52 7
+#define IX_H78 8
+#define IX_H104 9
+#define IX_H130 10
+#define IX_H156 11
+#define IX_H182 12
+#define IX_H208 13
+#define IX_H234 14
+#define IX_TL 15 // Top left corner
+#define IX_TR 16 // Top right corner
+#define IX_BL 17 // Bottom left corner
+#define IX_BR 18 // Bottom right corner
+#define IX_H25 19
+#define IX_V11 20
+#define IX_RTL 21 // Re-sizing top left corner
+#define IX_RTR 22 // Re-sizing top right corner
+#define IX_RBR 23 // Re-sizing bottom right corner
+#define IX_CURLR 24 // }
+#define IX_CURUD 25 // }
+#define IX_CURDU 26 // } Custom cursors
+#define IX_CURDD 27 // }
+#define IX_CURUP 28 // }
+#define IX_CURDOWN 29 // }
+#define IX_MDGROOVE 30 // 'Mixing desk' slider background
+#define IX_MDSLIDER 34 // 'Mixing desk' slider
+
+#define IX_BLANK1 35 //
+#define IX_BLANK2 36 //
+#define IX_BLANK3 37 //
+#define IX_CIRCLE1 38 //
+#define IX_CIRCLE2 39 //
+#define IX_CROSS1 40 //
+#define IX_CROSS2 41 //
+#define IX_CROSS3 42 //
+#define IX_QUIT1 43 //
+#define IX_QUIT2 44 //
+#define IX_QUIT3 45 //
+#define IX_TICK1 46 //
+#define IX_TICK2 47 //
+#define IX_TICK3 48 //
+#define IX_NTR 49 // New top right corner
+#define HOPEDFORREELS 50
+
+#define NORMGRAPH 0
+#define DOWNGRAPH 1
+#define HIGRAPH 2
+//-----------------------
+#define FIX_UK 0
+#define FIX_FR 1
+#define FIX_GR 2
+#define FIX_IT 3
+#define FIX_SP 4
+#define FIX_USA 5
+#define HOPEDFORFREELS 6 // Expected flag reels
+//-----------------------
+
+#define MAX_ININV 150 // Max in an inventory
+#define MAX_CONVBASIC 10 // Max permanent conversation icons
+
+#define MAXHICONS 10 // Max dimensions of
+#define MAXVICONS 6 // an inventory window
+
+#define ITEM_WIDTH 25 // Dimensions of an icon
+#define ITEM_HEIGHT 25 //
+
+// Number of objects that makes up an empty window
+#define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides
+ // + Bground + title + slider
+ // + more Needed for save game window
+
+#define MAX_ICONS MAXHICONS*MAXVICONS
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+//----- Permanent data (compiled in) -----
+
+// Save game name editing cursor
+
+#define CURSOR_CHAR '_'
+char sCursor[2] = { CURSOR_CHAR, 0 };
+static const int hFillers[MAXHICONS] = {
+ IX_H26, // 2 icons wide
+ IX_H52, // 3
+ IX_H78, // 4
+ IX_H104, // 5
+ IX_H130, // 6
+ IX_H156, // 7
+ IX_H182, // 8
+ IX_H208, // 9
+ IX_H234 // 10 icons wide
+};
+static const int vFillers[MAXVICONS] = {
+ IX_V26, // 2 icons high
+ IX_V52, // 3
+ IX_V78, // 4
+ IX_V104, // 5
+ IX_V130 // 6 icons high
+};
+
+
+//----- Permanent data (set once) -----
+
+static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data
+static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data
+static SCNHANDLE configStrings[20];
+
+static INV_OBJECT *pio = 0; // Inventory objects' data
+static int numObjects = 0; // Number of inventory objects
+
+
+//----- Permanent data (updated, valid while inventory closed) -----
+
+static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState;
+
+static int HeldItem = INV_NOICON; // Current held item
+
+struct INV_DEF {
+
+ int MinHicons; // }
+ int MinVicons; // } Dimension limits
+ int MaxHicons; // }
+ int MaxVicons; // }
+
+ int NoofHicons; // }
+ int NoofVicons; // } Current dimentsions
+
+ int ItemOrder[MAX_ININV]; // Contained items
+ int NoofItems; // Current number of held items
+
+ int FirstDisp; // Index to first item currently displayed
+
+ int inventoryX; // } Display position
+ int inventoryY; // }
+ int otherX; // } Display position
+ int otherY; // }
+
+ int MaxInvObj; // Max. allowed contents
+
+ SCNHANDLE hInvTitle; // Window heading
+
+ bool resizable; // Re-sizable window?
+ bool moveable; // Moveable window?
+
+ int sNoofHicons; // }
+ int sNoofVicons; // } Current dimensions
+
+ bool bMax; // Maximised last time open?
+
+};
+
+static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ...
+
+
+// Permanent contents of conversation inventory
+static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents
+static int Num0Order = 0; // - copy to conv. inventory at pop-up time
+
+
+
+//----- Data pertinant to current active inventory -----
+
+static int ino = 0; // Which inventory is currently active
+
+static bool InventoryHidden = false;
+static bool InventoryMaximised = false;
+
+static enum { ID_NONE, ID_MOVE, ID_SLIDE,
+ ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT,
+ ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT,
+ ID_CSLIDE, ID_MDCONT } InvDragging;
+
+static int SuppH = 0; // 'Linear' element of
+static int SuppV = 0; // dimensions during re-sizing
+
+static int Ychange = 0; //
+static int Ycompensate = 0; // All to do with re-sizing.
+static int Xchange = 0; //
+static int Xcompensate = 0; //
+
+static bool ItemsChanged = 0; // When set, causes items to be re-drawn
+
+static bool bOpenConf = 0;
+
+static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction
+static int TLwidth = 0, TLheight = 0; //
+static int TRwidth = 0; //
+static int BLheight = 0; //
+
+
+
+static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window)
+static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons)
+static ANIM iconAnims[MAX_ICONS];
+static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window)
+
+static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference
+ // objects are in objArray.
+
+static int slideY = 0; // For positioning the slider
+static int slideYmax = 0, slideYmin = 0; //
+
+// Also to do with the slider
+static struct { int n; int y; } slideStuff[MAX_ININV+1];
+
+#define MAXSLIDES 4
+struct MDSLIDES {
+ int num;
+ OBJECT *obj;
+ int min, max;
+};
+static MDSLIDES mdSlides[MAXSLIDES];
+static int numMdSlides = 0;
+
+static int GlitterIndex = 0;
+
+static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block
+static int thisConvIcon = 0; // Passed to polygon code via convIcon()
+static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call
+static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug'
+static int sX = 0; // used by SlideMSlider() - current x-coordinate
+static int lX = 0; // used by SlideMSlider() - last x-coordinate
+
+//----- Data pertinant to configure (incl. load/save game) -----
+
+#define COL_MAINBOX TBLUE1 // Base blue colour
+#define COL_BOX TBLUE1
+#define COL_HILIGHT TBLUE4
+
+#ifdef JAPAN
+#define BOX_HEIGHT 17
+#define EDIT_BOX1_WIDTH 149
+#else
+#define BOX_HEIGHT 13
+#define EDIT_BOX1_WIDTH 145
+#endif
+#define EDIT_BOX2_WIDTH 166
+
+// RGROUP Radio button group - 1 is selectable at a time. Action on double click
+// ARSBUT Action if a radio button is selected
+// AABUT Action always
+// AATBUT Action always, text box
+// AAGBUT Action always, graphic button
+// SLIDER Not a button at all
+enum BTYPE {
+ RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER,
+ TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING
+};
+
+enum BFUNC {
+ NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN,
+ OPENLOAD, OPENSAVE, OPENREST,
+ OPENSOUND, OPENCONT,
+#ifndef JAPAN
+ OPENSUBT,
+#endif
+ OPENQUIT,
+ INITGAME, MIDIVOL,
+ CLANG, RLANG
+#ifdef MAC_OPTIONS
+ , MASTERVOL, SAMPVOL
+#endif
+};
+
+struct CONFBOX {
+ BTYPE boxType;
+ BFUNC boxFunc;
+ char *boxText;
+ int ixText;
+ int xpos;
+ int ypos;
+ int w; // Doubles as max value for SLIDERs
+ int h; // Doubles as iteration size for SLIDERs
+ int *ival;
+ int bi; // Base index for AAGBUTs
+};
+
+
+#define NO_HEADING (-1)
+#define USE_POINTER (-1)
+#define SIX_LOAD_OPTION 0
+#define SIX_SAVE_OPTION 1
+#define SIX_RESTART_OPTION 2
+#define SIX_SOUND_OPTION 3
+#define SIX_CONTROL_OPTION 4
+#ifndef JAPAN
+#define SIX_SUBTITLES_OPTION 5
+#endif
+#define SIX_QUIT_OPTION 6
+#define SIX_RESUME_OPTION 7
+#define SIX_LOAD_HEADING 8
+#define SIX_SAVE_HEADING 9
+#define SIX_RESTART_HEADING 10
+#define SIX_MVOL_SLIDER 11
+#define SIX_SVOL_SLIDER 12
+#define SIX_VVOL_SLIDER 13
+#define SIX_DCLICK_SLIDER 14
+#define SIX_DCLICK_TEST 15
+#define SIX_SWAP_TOGGLE 16
+#define SIX_TSPEED_SLIDER 17
+#define SIX_STITLE_TOGGLE 18
+#define SIX_QUIT_HEADING 19
+
+
+/*-------------------------------------------------------------*\
+| This is the main menu (that comes up when you hit F1 on a PC) |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define FBY 11 // y-offset of first button
+#define FBX 13 // x-offset of first button
+#else
+#define FBY 20 // y-offset of first button
+#define FBX 15 // x-offset of first button
+#endif
+
+CONFBOX optionBox[] = {
+
+ { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifdef JAPAN
+// TODO: If in JAPAN mode, simply disable the subtitles button?
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#else
+ { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#endif
+
+};
+
+/*-------------------------------------------------------------*\
+| These are the load and save game menus. |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define NUM_SL_RGROUP 7 // number of visible slots
+#define SY 32 // y-position of first slot
+#else
+#define NUM_SL_RGROUP 9 // number of visible slots
+#define SY 31 // y-position of first slot
+#endif
+
+CONFBOX loadBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+CONFBOX saveBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the restart confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX restartBox[] = {
+
+#ifdef JAPAN
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the sound control 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifdef MAC_OPTIONS
+ CONFBOX soundBox[] = {
+ { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 },
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 }
+ };
+#else
+CONFBOX soundBox[] = {
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, MAXMIDIVOL, 2, &volMidi, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, MAXSAMPVOL, 2, &volSound, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, MAXSAMPVOL, 2, &volVoice, 0 }
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the (mouse) control 'menu'. |
+\*-------------------------------------------------------------*/
+
+int bFlipped; // looks like this is just so the code has something to alter!
+
+
+#ifdef MAC_OPTIONS
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }
+
+};
+#else
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 },
+#ifdef JAPAN
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 }
+#else
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the subtitles 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifndef JAPAN
+CONFBOX subtitlesBox[] = {
+
+#ifdef USE_5FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_4FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_3FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP },
+#endif
+
+ { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 },
+ { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 },
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the quit confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX quitBox[] = {
+#ifdef JAPAN
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+};
+
+
+CONFBOX topwinBox[] = {
+ { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 }
+};
+
+
+
+struct CONFINIT {
+ int h;
+ int v;
+ int x;
+ int y;
+ bool bExtraWin;
+ CONFBOX *Box;
+ int NumBoxes;
+ int ixHeading;
+};
+
+CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, ARRAYSIZE(optionBox), NO_HEADING };
+
+CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, ARRAYSIZE(loadBox), SIX_LOAD_HEADING };
+CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, ARRAYSIZE(saveBox), SIX_SAVE_HEADING };
+#ifdef JAPAN
+CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING };
+#else
+CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING };
+#endif
+CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, ARRAYSIZE(soundBox), NO_HEADING };
+#ifdef MAC_OPTIONS
+ CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING };
+#else
+ CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING };
+#endif
+#ifndef JAPAN
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+CONFINIT ciSubtitles = { 10, 6, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING };
+#else
+CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING };
+#endif
+#endif
+CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, ARRAYSIZE(quitBox), SIX_QUIT_HEADING };
+
+CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING };
+
+#define NOBOX (-1)
+
+// Conf window globals
+static struct {
+ CONFBOX *Box;
+ int NumBoxes;
+ bool bExtraWin;
+ int ixHeading;
+ bool editableRgroup;
+
+ int selBox;
+ int pointBox; // Box pointed to on last call
+ int saveModifier;
+ int fileBase;
+ int numSaved;
+} cd = {
+ NULL, 0, false, 0, false,
+ NOBOX, NOBOX, 0, 0, 0
+};
+
+// For editing save game names
+char sedit[SG_DESC_LEN+2];
+
+#define HL1 0 // Hilight that moves with the cursor
+#define HL2 1 // Hilight on selected RGROUP box
+#define HL3 2 // Text on selected RGROUP box
+#define NUMHL 3
+
+
+// Data for button press/toggle effects
+static struct {
+ bool bButAnim;
+ CONFBOX *box;
+ bool press; // true = button press; false = button toggle
+} g_buttonEffect = { false, 0, false };
+
+
+//----- LOCAL FORWARD REFERENCES -----
+
+enum {
+ IB_NONE = -1, //
+ IB_UP = -2, // negative numbers returned
+ IB_DOWN = -3, // by WhichInvBox()
+ IB_SLIDE = -4, //
+ IB_SLIDE_UP = -5, //
+ IB_SLIDE_DOWN = -6 //
+};
+
+enum {
+ HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit
+ IS_LEFT = HI_BIT,
+ IS_SLIDER = (IS_LEFT >> 1),
+ IS_RIGHT = (IS_SLIDER >> 1),
+ IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT)
+};
+
+static int WhichInvBox(int curX, int curY, bool bSlides);
+static void SlideMSlider(int x, SSFN fn);
+static OBJECT *AddObject(const FREEL *pfreel, int num);
+static void AddBoxes(bool posnSlide);
+
+static void ConfActionSpecial(int i);
+
+
+/*-------------------------------------------------------------------------*/
+/*** Magic numbers ***/
+
+#define M_SW 5 // Side width
+#define M_TH 5 // Top height
+#ifdef JAPAN
+#define M_TOFF 6 // Title text Y offset from top
+#define M_TBB 20 // Title box bottom Y offset
+#else
+#define M_TOFF 4 // Title text Y offset from top
+#define M_TBB 14 // Title box bottom Y offset
+#endif
+#define M_SBL 26 // Scroll bar left X offset
+#define M_SH 5 // Slider height (*)
+#define M_SW 5 // Slider width (*)
+#define M_SXOFF 9 // Slider X offset from right-hand side
+#ifdef JAPAN
+#define M_IUT 22 // Y offset of top of up arrow
+#define M_IUB 30 // Y offset of bottom of up arrow
+#else
+#define M_IUT 16 // Y offset of top of up arrow
+#define M_IUB 24 // Y offset of bottom of up arrow
+#endif
+#define M_IDT 10 // Y offset (from bottom) of top of down arrow
+#define M_IDB 3 // Y offset (from bottom) of bottom of down arrow
+#define M_IAL 12 // X offset (from right) of left of scroll arrows
+#define M_IAR 3 // X offset (from right) of right of scroll arrows
+
+#define START_ICONX (M_SW+1) // } Relative offset of first icon
+#define START_ICONY (M_TBB+M_TH+1) // } within the inventory window
+
+/*-------------------------------------------------------------------------*/
+
+
+
+#ifndef JAPAN
+bool LanguageChange(void) {
+ LANGUAGE nLang;
+
+#ifdef USE_3FLAGS
+ // VERY quick dodgy bodge
+ if (cd.selBox == 0)
+ nLang = TXT_FRENCH;
+ else if (cd.selBox == 1)
+ nLang = TXT_GERMAN;
+ else
+ nLang = TXT_SPANISH;
+ if (nLang != language) {
+#elif defined(USE_4FLAGS)
+ nLang = (LANGUAGE)(cd.selBox + 1);
+ if (nLang != language) {
+#else
+ if (cd.selBox != language) {
+ nLang = (LANGUAGE)cd.selBox;
+#endif
+ KillInventory();
+ ChangeLanguage(nLang);
+ language = nLang;
+ return true;
+ }
+ else
+ return false;
+}
+#endif
+
+/**************************************************************************/
+/******************** Some miscellaneous functions ************************/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| DumpIconArray()/DumpDobjArray()/DumpObjArray() |
+|-----------------------------------------------------------------------|
+| Delete all the objects in iconArray[]/DobjArray[]/objArray[] |
+\*---------------------------------------------------------------------*/
+static void DumpIconArray(void){
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]);
+ iconArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in DobjArray[]
+ */
+
+static void DumpDobjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (DobjArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]);
+ DobjArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in objArray[]
+ */
+
+static void DumpObjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (objArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]);
+ objArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Convert item ID number to pointer to item's compiled data
+ * i.e. Image data and Glitter code.
+ */
+INV_OBJECT *findInvObject(int num) {
+ INV_OBJECT *retval = pio;
+
+ for (int i = 0; i < numObjects; i++, retval++) {
+ if (retval->id == num)
+ return retval;
+ }
+
+ error("Trying to manipulate undefined inventory icon");
+}
+
+/**
+ * Returns position of an item in one of the inventories.
+ * The actual position is not important for the uses that this is put to.
+ */
+
+int InventoryPos(int num) {
+ int i;
+
+ for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory
+ if (InvD[INV_1].ItemOrder[i] == num)
+ return i;
+
+ for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory
+ if (InvD[INV_2].ItemOrder[i] == num)
+ return i;
+
+ if (HeldItem == num)
+ return INV_HELDNOTIN; // Held, but not in either inventory
+
+ return INV_NOICON; // Not held, not in either inventory
+}
+
+bool IsInInventory(int object, int invnum) {
+ assert(invnum == INV_1 || invnum == INV_2);
+
+ for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory
+ if (InvD[invnum].ItemOrder[i] == object)
+ return true;
+
+ return false;
+}
+
+/**
+ * Returns which item is held (INV_NOICON (-1) if none)
+ */
+
+int WhichItemHeld(void) {
+ return HeldItem;
+}
+
+/**
+ * Called from the cursor module when it re-initialises (at the start of
+ * a new scene). For if we are holding something at scene-change time.
+ */
+
+void InventoryIconCursor(void) {
+ INV_OBJECT *invObj;
+
+ if (HeldItem != INV_NOICON) {
+ invObj = findInvObject(HeldItem);
+ SetAuxCursor(invObj->hFilm);
+ }
+}
+
+/**
+ * Returns TRUE if the inventory is active.
+ */
+
+bool InventoryActive(void) {
+ return (InventoryState == ACTIVE_INV);
+}
+
+int WhichInventoryOpen(void) {
+ if (InventoryState != ACTIVE_INV)
+ return 0;
+ else
+ return ino;
+}
+
+
+/**************************************************************************/
+/************** Running inventory item's Glitter code *********************/
+/**************************************************************************/
+
+struct ITP_INIT {
+ INV_OBJECT *pinvo;
+ USER_EVENT event;
+ BUTEVENT bev;
+};
+
+/**
+ * Run inventory item's Glitter code
+ */
+static void InvTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ int ThisPointedWait; // Fix the 'repeated pressing bug'
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ITP_INIT *to = (ITP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev);
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ if (to->event == POINTED) {
+ _ctx->ThisPointedWait = ++PointedWaitCount;
+ while (1) {
+ CORO_SLEEP(1);
+ int x, y;
+ GetCursorXY(&x, &y, false);
+ if (InvItemId(x, y) != to->pinvo->id)
+ break;
+
+ // Fix the 'repeated pressing bug'
+ if (_ctx->ThisPointedWait != PointedWaitCount)
+ CORO_KILL_SELF();
+ }
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Run inventory item's Glitter code
+ */
+void RunInvTinselCode(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) {
+ ITP_INIT to = { pinvo, event, be };
+
+ if (InventoryHidden)
+ return;
+
+ GlitterIndex = index;
+ g_scheduler->createProcess(PID_TCODE, InvTinselProcess, &to, sizeof(to));
+}
+
+/**************************************************************************/
+/****************** Load/Save game specific functions *********************/
+/**************************************************************************/
+
+/**
+ * Set first load/save file entry displayed.
+ * Point Box[] text pointers to appropriate file descriptions.
+ */
+
+void firstFile(int first) {
+ int i, j;
+
+ i = getList();
+
+ cd.numSaved = i;
+
+ if (first < 0)
+ first = 0;
+ else if (first > MAX_SFILES-NUM_SL_RGROUP)
+ first = MAX_SFILES-NUM_SL_RGROUP;
+
+ if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) {
+ // Blank first entry for new save
+ cd.Box[0].boxText = NULL;
+ cd.saveModifier = j = 1;
+ } else {
+ cd.saveModifier = j = 0;
+ }
+
+ for (i = first; j < NUM_SL_RGROUP; j++, i++) {
+ cd.Box[j].boxText = ListEntry(i, LE_DESC);
+ }
+
+ cd.fileBase = first;
+}
+
+/**
+ * Save the game using filename from selected slot & current description.
+ */
+
+void InvSaveGame(void) {
+ if (cd.selBox != NOBOX) {
+#ifndef JAPAN
+ sedit[strlen(sedit)-1] = 0; // Don't include the cursor!
+#endif
+ SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit);
+ }
+}
+
+/**
+ * Load the selected saved game.
+ */
+void InvLoadGame(void) {
+ int rGame;
+
+ if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) {
+ rGame = cd.selBox;
+ cd.selBox = NOBOX;
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ RestoreGame(rGame+cd.fileBase);
+ }
+}
+
+/**
+ * Edit the string in sedit[]
+ * Returns TRUE if the string was altered.
+ */
+#ifndef JAPAN
+bool UpdateString(const Common::KeyState &kbd) {
+ int cpos;
+
+ if (!cd.editableRgroup)
+ return false;
+
+ cpos = strlen(sedit)-1;
+
+ if (kbd.keycode == Common::KEYCODE_BACKSPACE) {
+ if (!cpos)
+ return false;
+ sedit[cpos] = 0;
+ cpos--;
+ sedit[cpos] = CURSOR_CHAR;
+ return true;
+// } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) {
+ } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) {
+ if (cpos == SG_DESC_LEN)
+ return false;
+ sedit[cpos] = kbd.ascii;
+ cpos++;
+ sedit[cpos] = CURSOR_CHAR;
+ sedit[cpos+1] = 0;
+ return true;
+ }
+ return false;
+}
+#endif
+
+/**
+ * Keystrokes get sent here when load/save screen is up.
+ */
+bool InvKeyIn(const Common::KeyState &kbd) {
+ if (kbd.keycode == Common::KEYCODE_PAGEUP ||
+ kbd.keycode == Common::KEYCODE_PAGEDOWN ||
+ kbd.keycode == Common::KEYCODE_HOME ||
+ kbd.keycode == Common::KEYCODE_END)
+ return true; // Key needs processing
+
+ if (kbd.keycode == 0 && kbd.ascii == 0) {
+ ;
+ } else if (kbd.keycode == Common::KEYCODE_RETURN) {
+ return true; // Key needs processing
+ } else if (kbd.keycode == Common::KEYCODE_ESCAPE) {
+ return true; // Key needs processing
+ } else {
+#ifndef JAPAN
+ if (UpdateString(kbd)) {
+ /*
+ * Delete display of text currently being edited,
+ * and replace it with freshly edited text.
+ */
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ if (MultiRightmost(iconArray[HL3]) > 213) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE));
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ }
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ }
+#endif
+ }
+ return false;
+}
+
+/*---------------------------------------------------------------------*\
+| Select() |
+|-----------------------------------------------------------------------|
+| Highlights selected box. |
+| If it's editable (save game), copy existing description and add a |
+| cursor. |
+\*---------------------------------------------------------------------*/
+void Select(int i, bool force) {
+#ifdef JAPAN
+ time_t secs_now;
+ struct tm *time_now;
+#endif
+
+ i &= ~IS_MASK;
+
+ if (cd.selBox == i && !force)
+ return;
+
+ cd.selBox = i;
+
+ // Clear previous selected highlight and text
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+
+ // New highlight box
+ switch (cd.Box[i].boxType) {
+ case RGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos,
+ InvD[ino].inventoryY + cd.Box[i].ypos);
+
+ // Z-position of box, and add edit text if appropriate
+ if (cd.editableRgroup) {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1);
+
+ assert(cd.Box[i].ixText == USE_POINTER);
+#ifdef JAPAN
+ // Current date and time
+ time(&secs_now);
+ time_now = localtime(&secs_now);
+ strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now);
+#else
+ // Current description with cursor appended
+ if (cd.Box[i].boxText != NULL) {
+ strcpy(sedit, cd.Box[i].boxText);
+ strcat(sedit, sCursor);
+ } else {
+ strcpy(sedit, sCursor);
+ }
+#endif
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[i].xpos + 2,
+#ifdef JAPAN
+ InvD[ino].inventoryY + cd.Box[i].ypos + 2,
+#else
+ InvD[ino].inventoryY + cd.Box[i].ypos,
+#endif
+ hTagFontHandle(), 0);
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ } else {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1);
+ }
+
+ _vm->divertKeyInput(InvKeyIn);
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos - 2,
+ InvD[ino].inventoryY + cd.Box[i].ypos - 2);
+ MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1);
+
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * If the item is not already held, hold it.
+ */
+
+void HoldItem(int item) {
+ INV_OBJECT *invObj;
+
+ if (HeldItem != item) {
+ if (item == INV_NOICON && HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ if (item != INV_NOICON) {
+ invObj = findInvObject(item);
+ SetAuxCursor(invObj->hFilm); // and is aux. cursor
+ }
+
+ HeldItem = item; // Item held
+ }
+
+ // Redraw contents - held item not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stop holding an item.
+ */
+
+void DropItem(int item) {
+ if (HeldItem == item) {
+ HeldItem = INV_NOICON; // Item not held
+ DelAuxCursor(); // no longer aux cursor
+ }
+
+ // Redraw contents - held item was not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stick the item into an inventory list (ItemOrder[]), and hold the
+ * item if requested.
+ */
+
+void AddToInventory(int invno, int icon, bool hold) {
+ int i;
+ bool bOpen;
+#ifdef DEBUG
+ INV_OBJECT *invObj;
+#endif
+
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory
+
+ if (invno == INV_OPEN) {
+ assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open
+ invno = ino;
+ bOpen = true;
+
+ // Make sure it doesn't get in both!
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon);
+ } else
+ bOpen = false;
+
+#ifdef DEBUG
+ invObj = findInvObject(icon);
+ if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1)
+ || (invObj->attribute & IO_ONLYINV2 && invno != INV_2))
+ error("Trying to add resticted object to wrong inventory");
+#endif
+
+ if (invno == INV_1)
+ RemFromInventory(INV_2, icon);
+ else if (invno == INV_2)
+ RemFromInventory(INV_1, icon);
+
+ // See if it's already there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == InvD[invno].NoofItems) {
+ if (!bOpen) {
+ if (invno == INV_CONV) {
+ // For conversation, insert before last icon
+ // which will always be the goodbye icon
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1];
+ InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon;
+ InvD[invno].NoofItems++;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon;
+ }
+ ItemsChanged = true;
+ } else {
+ // It could be that the index is beyond what you'd expect
+ // as delinv may well have been called
+ if (GlitterIndex < InvD[invno].NoofItems) {
+ memmove(&InvD[invno].ItemOrder[GlitterIndex + 1],
+ &InvD[invno].ItemOrder[GlitterIndex],
+ (InvD[invno].NoofItems-GlitterIndex)*sizeof(int));
+ InvD[invno].ItemOrder[GlitterIndex] = icon;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon;
+ }
+ InvD[invno].NoofItems++;
+ }
+ }
+
+ // Hold it if requested
+ if (hold)
+ HoldItem(icon);
+}
+
+/**
+ * Take the item from the inventory list (ItemOrder[]).
+ * Return FALSE if item wasn't present, true if it was.
+ */
+
+bool RemFromInventory(int invno, int icon) {
+ int i;
+
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory
+
+ // See if it's there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ if (i == InvD[invno].NoofItems)
+ return false; // Item wasn't there
+ else {
+ memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int));
+ InvD[invno].NoofItems--;
+ ItemsChanged = true;
+ return true; // Item removed
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| InvArea() |
+|-----------------------------------------------------------------------|
+| Work out which area of the inventory window the cursor is in. |
+|-----------------------------------------------------------------------|
+| This used to be worked out with appropriately defined magic numbers. |
+| Then the graphic changed and I got it right again. Then the graphic |
+| changed and I got fed up of faffing about. It's probably easier just |
+| to rework all this. |
+\*---------------------------------------------------------------------*/
+enum { I_NOTIN, I_MOVE, I_BODY,
+ I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT,
+ I_TOP, I_BOTTOM, I_LEFT, I_RIGHT,
+ I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN,
+ I_ENDCHANGE
+};
+
+#define EXTRA 1 // This was introduced when we decided to increase
+ // the active area of the borders for re-sizing.
+
+/*---------------------------------*/
+#define LeftX InvD[ino].inventoryX
+#define TopY InvD[ino].inventoryY
+/*---------------------------------*/
+
+int InvArea(int x, int y) {
+ int RightX = MultiRightmost(RectObject) + 1;
+ int BottomY = MultiLowest(RectObject) + 1;
+
+// Outside the whole rectangle?
+ if (x <= LeftX - EXTRA || x > RightX + EXTRA
+ || y <= TopY - EXTRA || y > BottomY + EXTRA)
+ return I_NOTIN;
+
+// The bottom line
+ if (y > BottomY - 2 - EXTRA) { // Below top of bottom line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_BLEFT; // Bottom left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_BRIGHT; // Bottom right corner
+ else
+ return I_BOTTOM; // Just plain bottom
+ }
+
+// The top line
+ if (y <= TopY + 2 + EXTRA) { // Above bottom of top line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_TLEFT; // Top left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_TRIGHT; // Top right corner
+ else
+ return I_TOP; // Just plain top
+ }
+
+// Sides
+ if (x <= LeftX + 2 + EXTRA) // Left of right of left side?
+ return I_LEFT;
+ else if (x > RightX - 2 - EXTRA) // Right of left of right side?
+ return I_RIGHT;
+
+// From here down still needs fixing up properly
+/*
+* In the move area?
+*/
+ if (ino != INV_CONF
+ && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 &&
+ y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2)
+ return I_MOVE;
+
+/*
+* Scroll bits
+*/
+ if (ino == INV_CONF && cd.bExtraWin) {
+ } else {
+ if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) {
+ if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1)
+ return I_UP;
+ if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1)
+ return I_DOWN;
+
+ if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) {
+ if (y < TopY + slideY)
+ return I_SLIDE_UP;
+ if (y < TopY + slideY + M_SH)
+ return I_SLIDE;
+ else
+ return I_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return I_BODY;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ * Also return co-ordinates of items tag display position, if requested.
+ */
+
+int InvItem(int *x, int *y, bool update) {
+ int itop, ileft;
+ int row, col;
+ int item;
+ int IconsX;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (*x >= ileft && *x < ileft + ITEM_WIDTH &&
+ *y >= itop && *y < itop + ITEM_HEIGHT) {
+ if (update) {
+ *x = ileft + ITEM_WIDTH/2;
+ *y = itop /*+ ITEM_HEIGHT/4*/;
+ }
+ return item;
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ */
+
+int InvItemId(int x, int y) {
+ int itop, ileft;
+ int row, col;
+ int item;
+
+ if (InventoryHidden || InventoryState == IDLE_INV)
+ return INV_NOICON;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ int IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (x >= ileft && x < ileft + ITEM_WIDTH &&
+ y >= itop && y < itop + ITEM_HEIGHT) {
+ return InvD[ino].ItemOrder[item];
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/*---------------------------------------------------------------------*\
+| WhichInvBox() |
+|-----------------------------------------------------------------------|
+| Finds which box the cursor is in. |
+\*---------------------------------------------------------------------*/
+#define MD_YSLIDTOP 7
+#define MD_YSLIDBOT 18
+#define MD_YBUTTOP 9
+#define MD_YBUTBOT 16
+#define MD_XLBUTL 1
+#define MD_XLBUTR 10
+#define MD_XRBUTL 105
+#define MD_XRBUTR 114
+
+static int WhichInvBox(int curX, int curY, bool bSlides) {
+ if (bSlides) {
+ for (int i = 0; i < numMdSlides; i++) {
+ if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj)
+ && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj))
+ return mdSlides[i].num | IS_SLIDER;
+ }
+ }
+
+ curX -= InvD[ino].inventoryX;
+ curY -= InvD[ino].inventoryY;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ switch (cd.Box[i].boxType) {
+ case SLIDER:
+ if (bSlides) {
+ if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) {
+ if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR)
+ return i | IS_LEFT;
+ if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR)
+ return i | IS_RIGHT;
+ }
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ case TOGGLE:
+ case FLIP:
+ if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+
+ default:
+ // 'Normal' box
+ if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+ }
+ }
+
+ if (cd.bExtraWin) {
+ if (curX > 20 + 181 && curX < 20 + 181 + 8 &&
+ curY > 24 + 2 && curY < 24 + 139 + 5) {
+
+ if (curY < 24 + 2 + 5) {
+ return IB_UP;
+ } else if (curY > 24 + 139) {
+ return IB_DOWN;
+ } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) {
+ return IB_SLIDE;
+ } else if (curY+InvD[ino].inventoryY < slideY) {
+ return IB_SLIDE_UP;
+ } else if (curY+InvD[ino].inventoryY >= slideY + 5) {
+ return IB_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return IB_NONE;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * InBoxes
+ */
+void InvBoxes(bool InBody, int curX, int curY) {
+ int index; // Box pointed to on this call
+ const FILM *pfilm;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = -1;
+ else {
+ index = WhichInvBox(curX, curY, false);
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index < 0) {
+ // unhigh-light box (if one was)
+ cd.pointBox = NOBOX;
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ } else if (index != cd.pointBox) {
+ cd.pointBox = index;
+ // A new box is pointed to - high-light it
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) ||
+///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP ||
+ cd.Box[cd.pointBox].boxType == AATBUT ||
+ cd.Box[cd.pointBox].boxType == AABUT) {
+ iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ else if (cd.Box[cd.pointBox].boxType == AAGBUT ||
+ cd.Box[cd.pointBox].boxType == ARSGBUT ||
+ cd.Box[cd.pointBox].boxType == TOGGLE) {
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ }
+}
+
+static void ButtonPress(CORO_PARAM, CONFBOX *box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == AAGBUT || box->boxType == ARSGBUT);
+
+ // Replace highlight image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace normal image with depresses image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 2 frames
+ CORO_SLEEP(2);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace depressed image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_SLEEP(1);
+
+ CORO_END_CODE;
+}
+
+static void ButtonToggle(CORO_PARAM, CONFBOX *box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == TOGGLE);
+
+ // Remove hilight image
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // Add depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Toggle state
+ (*box->ival) = *(box->ival) ^ 1; // XOR with true
+ box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1;
+ AddBoxes(false);
+ // Keep highlight (e.g. flag)
+ if (cd.selBox != NOBOX)
+ Select(cd.selBox, true);
+
+ // New state, depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold new depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // New state, normal
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // New state, highlighted
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Monitors for POINTED event for inventory icons.
+ */
+
+void InvLabels(bool InBody, int aniX, int aniY) {
+ int index; // Icon pointed to on this call
+ INV_OBJECT *invObj;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = INV_NOICON;
+ else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (index >= InvD[ino].NoofItems)
+ index = INV_NOICON;
+ else
+ index = InvD[ino].ItemOrder[index];
+ }
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index == INV_NOICON || index == HeldItem) {
+ pointedIcon = INV_NOICON;
+ } else if (index != pointedIcon) {
+ // A new icon is pointed to - run its script with POINTED event
+ invObj = findInvObject(index);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, POINTED, BE_NONE, index);
+ pointedIcon = index;
+ }
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * All to do with the slider.
+ * I can't remember how it works - or, indeed, what it does.
+ * It seems to set up slideStuff[], an array of possible first-displayed
+ * icons set against the matching y-positions of the slider.
+ */
+
+void AdjustTop(void) {
+ int tMissing, bMissing, nMissing;
+ int nslideY;
+ int rowsWanted;
+ int slideRange;
+ int n, i;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons;
+
+ while (rowsWanted < InvD[ino].NoofVicons) {
+ if (InvD[ino].FirstDisp) {
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ rowsWanted++;
+ } else
+ break;
+ }
+ tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0;
+ bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0;
+
+ nMissing = tMissing + bMissing;
+ slideRange = slideYmax - slideYmin;
+
+ if (!tMissing)
+ nslideY = slideYmin;
+ else if (!bMissing)
+ nslideY = slideYmax;
+ else {
+ nslideY = tMissing*slideRange/nMissing;
+ nslideY += slideYmin;
+ }
+
+ if (nMissing) {
+ n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons;
+ for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) {
+ slideStuff[i].n = n;
+ slideStuff[i].y = (i*slideRange/nMissing) + slideYmin;
+ }
+ if (slideStuff[0].n < 0)
+ slideStuff[0].n = 0;
+ assert(i < MAX_ININV + 1);
+ slideStuff[i].n = -1;
+ } else {
+ slideStuff[0].n = 0;
+ slideStuff[0].y = slideYmin;
+ slideStuff[1].n = -1;
+ }
+
+ if (nslideY != slideY) {
+ MultiMoveRelXY(SlideObject, 0, nslideY - slideY);
+ slideY = nslideY;
+ }
+}
+
+/**
+ * Insert an inventory icon object onto the display list.
+ */
+
+OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) {
+ INV_OBJECT *invObj; // Icon data
+ const MULTI_INIT *pmi; // Its INIT structure - from the reel
+ IMAGE *pim; // ... you get the picture
+ OBJECT *pPlayObj; // The object we insert
+
+ invObj = findInvObject(num);
+
+ // Get pointer to image
+ pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Set up the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Create display objects for the displayed icons in an inventory window.
+ */
+
+void FillInInventory(void) {
+ int Index; // Index into ItemOrder[]
+ int n = 0; // index into iconArray[]
+ int xpos, ypos;
+ int row, col;
+ const FREEL *pfr;
+ const FILM *pfilm;
+
+ DumpIconArray();
+
+ if (InvDragging != ID_SLIDE)
+ AdjustTop(); // Set up slideStuff[]
+
+ Index = InvD[ino].FirstDisp; // Start from first displayed object
+ n = 0;
+ ypos = START_ICONY; // Y-offset of first display row
+
+ for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) {
+ xpos = START_ICONX; // X-offset of first display column
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++) {
+ if (Index >= InvD[ino].NoofItems)
+ break;
+ else if (InvD[ino].ItemOrder[Index] != HeldItem) {
+ // Create a display object and position it
+ iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm);
+ MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos);
+ MultiSetZPosition(iconArray[n], Z_INV_ICONS);
+
+ InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+
+ n++;
+ }
+ Index++;
+ xpos += ITEM_WIDTH + 1; // X-offset of next display column
+ }
+ }
+}
+
+/**
+ * Set up a rectangle as the background to the inventory window.
+ * Additionally, sticks the window title up.
+ */
+
+enum {FROM_HANDLE, FROM_STRING};
+
+void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) {
+ // Why not 2 ????
+ int width = TLwidth + extraH + TRwidth - 3;
+ int height = TLheight + extraV + BLheight - 3;
+
+ // Create a rectangle object
+ RectObject = *rect = TranslucentObject(width, height);
+
+ // add it to display list and position it
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect);
+ MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1);
+ MultiSetZPosition(*rect, Z_INV_BRECT);
+
+ // Create text object using title string
+ if (textFrom == FROM_HANDLE) {
+ LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) {
+ LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ }
+}
+
+/**
+ * Insert a part of the inventory window frame onto the display list.
+ */
+
+static OBJECT *AddObject(const FREEL *pfreel, int num) {
+ const MULTI_INIT *pmi; // Get the MULTI_INIT structure
+ IMAGE *pim;
+ OBJECT *pPlayObj;
+
+ // Get pointer to image
+ pim = GetImageFromReel(pfreel, &pmi);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Horrible bodge involving global variables to save
+ // width and/or height of some window frame components
+ if (num == TL) {
+ TLwidth = FROM_LE_16(pim->imgWidth);
+ TLheight = FROM_LE_16(pim->imgHeight);
+ } else if (num == TR) {
+ TRwidth = FROM_LE_16(pim->imgWidth);
+ } else if (num == BL) {
+ BLheight = FROM_LE_16(pim->imgHeight);
+ }
+
+ // Set up and insert the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+enum {
+ SLIDE_RANGE = 81,
+ SLIDE_MINX = 8,
+ SLIDE_MAXX = 8+SLIDE_RANGE,
+
+ MDTEXT_YOFF = 6,
+ MDTEXT_XOFF = -4
+};
+
+/**
+ * Display a box with some text in it.
+ */
+
+void AddBox(int *pi, int i) {
+ int x = InvD[ino].inventoryX + cd.Box[i].xpos;
+ int y = InvD[ino].inventoryY + cd.Box[i].ypos;
+ int *pival = cd.Box[i].ival;
+ int xdisp;
+ const FILM *pfilm;
+
+ switch (cd.Box[i].boxType) {
+ default:
+ // Give us a box
+ iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ if (cd.Box[i].ixText == USE_POINTER) {
+ if (cd.Box[i].boxText != NULL) {
+ if (cd.Box[i].boxType == RGROUP) {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+ x+2, y+2, hTagFontHandle(), 0);
+#else
+ x+2, y, hTagFontHandle(), 0);
+#endif
+ } else {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+// Note: it never seems to go here!
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ }
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ } else {
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code!
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+#ifdef JAPAN
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ assert(flagFilm != 0); // Language flags not declared!
+
+ pfilm = (const FILM *)LockMem(flagFilm);
+
+ if (bAmerica && cd.Box[i].bi == FIX_UK)
+ cd.Box[i].bi = FIX_USA;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2);
+ *pi += 1;
+
+ break;
+#endif
+ case FLIP:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ if (*(cd.Box[i].ival))
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ else
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case TOGGLE:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1;
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case SLIDER:
+ pfilm = (const FILM *)LockMem(winPartsf);
+ xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDGROOVE);
+ *pi += 1;
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1);
+ MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDSLIDER);
+ assert(numMdSlides < MAXSLIDES);
+ mdSlides[numMdSlides].num = i;
+ mdSlides[numMdSlides].min = x+SLIDE_MINX;
+ mdSlides[numMdSlides].max = x+SLIDE_MAXX;
+ mdSlides[numMdSlides++].obj = iconArray[*pi];
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+ }
+}
+
+/**
+ * Display some boxes.
+ */
+static void AddBoxes(bool posnSlide) {
+ int oCount = NUMHL; // Object count - allow for HL1, HL2 etc.
+
+ DumpIconArray();
+ numMdSlides = 0;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ AddBox(&oCount, i);
+ }
+
+ if (cd.bExtraWin) {
+ if (posnSlide)
+ slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP);
+ MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY);
+ }
+
+ assert(oCount < MAX_ICONS); // added too many icons
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddEWSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+/**
+ * AddExtraWindow
+ */
+
+int AddExtraWindow(int x, int y, OBJECT **retObj) {
+ int n = 0;
+ const FILM *pfilm;
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ x += 20;
+ y += 24;
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left
+ MultiSetAniXY(retObj[n], x, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right
+ MultiSetAniXY(retObj[n], x + 152, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left
+ MultiSetAniXY(retObj[n], x, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right
+ MultiSetAniXY(retObj[n], x + 152, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+// Draw the edges
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top
+ MultiSetAniXY(retObj[n], x + 6, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom
+ MultiSetAniXY(retObj[n], x + 6, y + 143);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left
+ MultiSetAniXY(retObj[n], x, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1
+ MultiSetAniXY(retObj[n], x + 179, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2
+ MultiSetAniXY(retObj[n], x + 188, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+ slideY = slideYmin = y + 9;
+ slideYmax = y + 134;
+ AddEWSlider(&retObj[n++], pfilm);
+
+ return n;
+}
+
+
+enum InventoryType { EMPTY, FULL, CONF };
+
+/**
+ * Construct an inventory window - either a standard one, with
+ * background, slider and icons, or a re-sizing window.
+ */
+void ConstructInventory(InventoryType filling) {
+ int eH, eV; // Extra width and height
+ int n = 0; // Index into object array
+ int zpos; // Z-position of frame
+ int invX = InvD[ino].inventoryX;
+ int invY = InvD[ino].inventoryY;
+ OBJECT **retObj;
+ const FILM *pfilm;
+
+ extern bool RePosition(void); // Forward reference
+ // Select the object array to use
+ if (filling == FULL || filling == CONF) {
+ retObj = objArray; // Standard window
+ zpos = Z_INV_MFRAME;
+ } else {
+ retObj = DobjArray; // Re-sizing window
+ zpos = Z_INV_RFRAME;
+ }
+
+ // Dispose of anything it may be replacing
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (retObj[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]);
+ retObj[i] = NULL;
+ }
+ }
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ // Standard window is of granular dimensions
+ if (filling == FULL) {
+ // Round-up/down to nearest number of icons
+ if (SuppH > ITEM_WIDTH / 2)
+ InvD[ino].NoofHicons++;
+ if (SuppV > ITEM_HEIGHT / 2)
+ InvD[ino].NoofVicons++;
+ SuppH = SuppV = 0;
+ }
+
+ // Extra width and height
+ eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH;
+ eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV;
+
+ // Which window frame corners to use
+ if (filling == FULL && ino != INV_CONV) {
+ TL = IX_TL;
+ TR = IX_TR;
+ BL = IX_BL;
+ BR = IX_BR;
+ } else {
+ TL = IX_RTL;
+ TR = IX_RTR;
+ BL = IX_BL;
+ BR = IX_RBR;
+ }
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[TL], TL);
+ MultiSetAniXY(retObj[n], invX, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[TR], TR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BL], BL);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BR], BR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+// Draw extra Top and bottom parts
+ if (InvD[ino].NoofHicons > 1) {
+ // Top side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom of header box
+ if (filling == FULL) {
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Extra bits for conversation - hopefully temporary
+ if (ino == INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ retObj[n] = AddObject(&pfilm->reels[IX_H52], -1);
+ MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ }
+
+ // Bottom side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppH) {
+ int offx = TLwidth + eH - 26;
+ if (offx < TLwidth) // Not too far!
+ offx = TLwidth;
+
+ // Top side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+// Draw extra side parts
+ if (InvD[ino].NoofVicons > 1) {
+ // Left side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Left side of scroll bar
+ if (filling == FULL && ino != INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ // Right side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppV) {
+ int offy = TLheight + eV - 26;
+ if (offy < 5)
+ offy = 5;
+
+ // Left side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Right side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ OBJECT **rect, **title;
+
+// Draw background, slider and icons
+ if (filling == FULL) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_HANDLE);
+
+ if (ino == INV_CONV)
+ SlideObject = NULL;
+ else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) {
+ slideYmin = TLheight - 2;
+ slideYmax = TLheight + eV + 10;
+ AddSlider(&retObj[n++], pfilm);
+ }
+
+ FillInInventory();
+ }
+ else if (filling == CONF) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_STRING);
+ if (cd.bExtraWin)
+ n += AddExtraWindow(invX, invY, &retObj[n]);
+ AddBoxes(true);
+ }
+
+ assert(n < MAX_WCOMP); // added more parts than we can handle!
+
+ // Reposition returns TRUE if needs to move
+ if (InvD[ino].moveable && filling == FULL && RePosition()) {
+ ConstructInventory(FULL);
+ }
+}
+
+
+/**
+ * Call this when drawing a 'FULL', movable inventory. Checks that the
+ * position of the Translucent object is within limits. If it isn't,
+ * adjusts the x/y position of the current inventory and returns TRUE.
+ */
+bool RePosition(void) {
+ int p;
+ bool bMoveitMoveit = false;
+
+ assert(RectObject); // no recangle object!
+
+ // Test for off-screen horizontally
+ p = MultiLeftmost(RectObject);
+ if (p > MAXLEFT) {
+ // Too far to the right
+ InvD[ino].inventoryX += MAXLEFT - p;
+ bMoveitMoveit = true; // I like to....
+ } else {
+ // Too far to the left?
+ p = MultiRightmost(RectObject);
+ if (p < MINRIGHT) {
+ InvD[ino].inventoryX += MINRIGHT - p;
+ bMoveitMoveit = true; // I like to....
+ }
+ }
+
+ // Test for off-screen vertically
+ p = MultiHighest(RectObject);
+ if (p < MINTOP) {
+ // Too high
+ InvD[ino].inventoryY += MINTOP - p;
+ bMoveitMoveit = true; // I like to....
+ } else if (p > MAXTOP) {
+ // Too low
+ InvD[ino].inventoryY += MAXTOP - p;
+ bMoveitMoveit = true; // I like to....
+ }
+
+ return bMoveitMoveit;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * Get the cursor's reel, poke in the background palette,
+ * and customise the cursor.
+ */
+void AlterCursor(int num) {
+ const FREEL *pfreel;
+ IMAGE *pim;
+
+ // Get pointer to image
+ pim = GetImageFromFilm(winPartsf, num, &pfreel);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ SetTempCursor(FROM_LE_32(pfreel->script));
+}
+
+enum InvCursorFN {IC_AREA, IC_DROP};
+
+/**
+ * InvCursor
+ */
+void InvCursor(InvCursorFN fn, int CurX, int CurY) {
+ static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR,
+ IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var
+
+ int area; // The part of the window the cursor is over
+ bool restoreMain = false;
+
+ // If currently dragging, don't be messing about with the cursor shape
+ if (InvDragging != ID_NONE)
+ return;
+
+ switch (fn) {
+ case IC_DROP:
+ ICursor = IC_NORMAL;
+ InvCursor(IC_AREA, CurX, CurY);
+ break;
+
+ case IC_AREA:
+ area = InvArea(CurX, CurY);
+
+ // Check for POINTED events
+ if (ino == INV_CONF)
+ InvBoxes(area == I_BODY, CurX, CurY);
+ else
+ InvLabels(area == I_BODY, CurX, CurY);
+
+ // No cursor trails while within inventory window
+ if (area == I_NOTIN)
+ UnHideCursorTrails();
+ else
+ HideCursorTrails();
+
+ switch (area) {
+ case I_NOTIN:
+ restoreMain = true;
+ break;
+
+ case I_TLEFT:
+ case I_BRIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_DR) {
+ AlterCursor(IX_CURDD);
+ ICursor = IC_DR;
+ }
+ break;
+
+ case I_TRIGHT:
+ case I_BLEFT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_UR) {
+ AlterCursor(IX_CURDU);
+ ICursor = IC_UR;
+ }
+ break;
+
+ case I_TOP:
+ case I_BOTTOM:
+ if (!InvD[ino].resizable) {
+ restoreMain = true;
+ break;
+ }
+ if (ICursor != IC_TB) {
+ AlterCursor(IX_CURUD);
+ ICursor = IC_TB;
+ }
+ break;
+
+ case I_LEFT:
+ case I_RIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_LR) {
+ AlterCursor(IX_CURLR);
+ ICursor = IC_LR;
+ }
+ break;
+
+ case I_UP:
+ case I_SLIDE_UP:
+ case I_DOWN:
+ case I_SLIDE_DOWN:
+ case I_SLIDE:
+ case I_MOVE:
+ case I_BODY:
+ restoreMain = true;
+ break;
+ }
+ break;
+ }
+
+ if (restoreMain && ICursor != IC_NORMAL) {
+ RestoreMainCursor();
+ ICursor = IC_NORMAL;
+ }
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/**************************************************************************/
+/******************** Conversation specific functions *********************/
+/**************************************************************************/
+
+
+void ConvAction(int index) {
+ assert(ino == INV_CONV); // not conv. window!
+
+ switch (index) {
+ case INV_NOICON:
+ return;
+
+ case INV_CLOSEICON:
+ thisConvIcon = -1; // Postamble
+ break;
+
+ case INV_OPENICON:
+ thisConvIcon = -2; // Preamble
+ break;
+
+ default:
+ thisConvIcon = InvD[ino].ItemOrder[index];
+ break;
+ }
+
+ RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true);
+}
+/*-------------------------------------------------------------------------*/
+
+void AddIconToPermanentDefaultList(int icon) {
+ int i;
+
+ // See if it's already there
+ for (i = 0; i < Num0Order; i++) {
+ if (Inv0Order[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == Num0Order) {
+ Inv0Order[Num0Order++] = icon;
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+
+void convPos(int fn) {
+ if (fn == CONV_DEF)
+ InvD[INV_CONV].inventoryY = 8;
+ else if (fn == CONV_BOTTOM)
+ InvD[INV_CONV].inventoryY = 150;
+}
+
+void ConvPoly(HPOLYGON hPoly) {
+ thisConvPoly = hPoly;
+}
+
+int convIcon(void) {
+ return thisConvIcon;
+}
+
+void CloseDownConv(void) {
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ KillInventory();
+ }
+}
+
+void convHide(bool hide) {
+ int aniX, aniY;
+ int i;
+
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ if (hide) {
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ InventoryHidden = true;
+
+ InvLabels(false, 0, 0);
+ } else {
+ InventoryHidden = false;
+
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0);
+ }
+
+ // Don't flash if items changed. If they have, will be redrawn anyway.
+ if (!ItemsChanged) {
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0);
+ }
+ }
+
+ GetCursorXY(&aniX, &aniY, false);
+ InvLabels(true, aniX, aniY);
+ }
+ }
+}
+
+bool convHid(void) {
+ return InventoryHidden;
+}
+
+
+/**************************************************************************/
+/******************* Open and closing functions ***************************/
+/**************************************************************************/
+
+/**
+ * Start up an inventory window.
+ */
+
+void PopUpInventory(int invno) {
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory
+
+ if (InventoryState == IDLE_INV) {
+ bOpenConf = false; // Better safe than sorry...
+
+ DisableTags(); // Tags disabled during inventory
+
+ if (invno == INV_CONV) { // Conversation window?
+ // Start conversation with permanent contents
+ memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int));
+ memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int));
+ InvD[INV_CONV].NoofItems = Num0Order;
+ thisConvIcon = 0;
+ } else if (invno == INV_CONF) { // Configuration window?
+ cd.selBox = NOBOX;
+ cd.pointBox = NOBOX;
+ }
+
+ ino = invno; // The open inventory
+
+ ItemsChanged = false; // Nothing changed
+ InvDragging = ID_NONE; // Not dragging
+ InventoryState = ACTIVE_INV; // Inventory actiive
+ InventoryHidden = false; // Not hidden
+ InventoryMaximised = InvD[ino].bMax;
+ if (invno != INV_CONF) // Configuration window?
+ ConstructInventory(FULL); // Draw it up
+ else {
+ ConstructInventory(CONF); // Draw it up
+ }
+ }
+}
+
+void SetConfGlobals(CONFINIT *ci) {
+ InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h;
+ InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v;
+ InvD[INV_CONF].inventoryX = ci->x;
+ InvD[INV_CONF].inventoryY = ci->y;
+ cd.bExtraWin = ci->bExtraWin;
+ cd.Box = ci->Box;
+ cd.NumBoxes = ci->NumBoxes;
+ cd.ixHeading = ci->ixHeading;
+}
+
+/**
+ * PopupConf
+ */
+
+void PopUpConf(CONFTYPE type) {
+ int curX, curY;
+
+ if (InventoryState != IDLE_INV)
+ return;
+
+ InvD[INV_CONF].resizable = false;
+ InvD[INV_CONF].moveable = false;
+
+ switch (type) {
+ case SAVE:
+ case LOAD:
+ if (type == SAVE) {
+ SetCursorScreenXY(262, 91);
+ SetConfGlobals(&ciSave);
+ cd.editableRgroup = true;
+ } else {
+ SetConfGlobals(&ciLoad);
+ cd.editableRgroup = false;
+ }
+ firstFile(0);
+ break;
+
+ case QUIT:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciQuit);
+ break;
+
+ case RESTART:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciRestart);
+ break;
+
+ case OPTION:
+ SetConfGlobals(&ciOption);
+ break;
+
+ case CONTROLS:
+ SetConfGlobals(&ciControl);
+ break;
+
+ case SOUND:
+ SetConfGlobals(&ciSound);
+ break;
+
+#ifndef JAPAN
+ case SUBT:
+ SetConfGlobals(&ciSubtitles);
+ break;
+#endif
+
+ case TOPWIN:
+ SetConfGlobals(&ciTopWin);
+ ino = INV_CONF;
+ ConstructInventory(CONF); // Draw it up
+ InventoryState = BOGUS_INV;
+ return;
+
+ default:
+ return;
+ }
+
+ if (HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ PopUpInventory(INV_CONF);
+
+ if (type == SAVE || type == LOAD)
+ Select(0, false);
+#ifndef JAPAN
+#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS)
+ else if (type == SUBT) {
+#ifdef USE_3FLAGS
+ // VERY quick dirty bodges
+ if (language == TXT_FRENCH)
+ Select(0, false);
+ else if (language == TXT_GERMAN)
+ Select(1, false);
+ else
+ Select(2, false);
+#elif defined(USE_4FLAGS)
+ Select(language-1, false);
+#else
+ Select(language, false);
+#endif
+ }
+#endif
+#endif // JAPAN
+
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+}
+
+/**
+ * Close down an inventory window.
+ */
+
+void KillInventory(void) {
+ if (objArray[0] != NULL) {
+ DumpObjArray();
+ DumpDobjArray();
+ DumpIconArray();
+ }
+
+ if (InventoryState == ACTIVE_INV) {
+ EnableTags();
+
+ InvD[ino].bMax = InventoryMaximised;
+
+ UnHideCursorTrails();
+ _vm->divertKeyInput(NULL);
+ }
+
+ InventoryState = IDLE_INV;
+
+ if (bOpenConf) {
+ bOpenConf = false;
+ PopUpConf(OPTION);
+ } else if (ino == INV_CONF)
+ InventoryIconCursor();
+}
+
+void CloseInventory(void) {
+ // If not active, ignore this
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // If hidden, a conversation action is still underway - ignore this
+ if (InventoryHidden)
+ return;
+
+ // If conversation, this is a closeing event
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+
+ KillInventory();
+
+ RestoreMainCursor();
+}
+
+
+
+/**************************************************************************/
+/************************ The inventory process ***************************/
+/**************************************************************************/
+
+/**
+ * Redraws the icons if appropriate. Also handle button press/toggle effects
+ */
+void InventoryProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ CORO_SLEEP(1); // allow scheduling
+
+ if (objArray[0] != NULL) {
+ if (ItemsChanged && ino != INV_CONF && !InventoryHidden) {
+ FillInInventory();
+
+ // Needed when clicking on scroll bar.
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+
+ ItemsChanged = false;
+ }
+ if (ino != INV_CONF) {
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL)
+ StepAnimScript(&iconAnims[i]);
+ }
+ }
+ if (InvDragging == ID_MDCONT) {
+ // Mixing desk control
+ int sval, index, *pival;
+
+ index = cd.selBox & ~IS_MASK;
+ pival = cd.Box[index].ival;
+ sval = *pival;
+
+ if (cd.selBox & IS_LEFT) {
+ *pival -= cd.Box[index].h;
+ if (*pival < 0)
+ *pival = 0;
+ } else if (cd.selBox & IS_RIGHT) {
+ *pival += cd.Box[index].h;
+ if (*pival > cd.Box[index].w)
+ *pival = cd.Box[index].w;
+ }
+
+ if (sval != *pival) {
+ SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN);
+ }
+ }
+ }
+
+ if (g_buttonEffect.bButAnim) {
+ assert(g_buttonEffect.box);
+ if (g_buttonEffect.press) {
+ if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT)
+ CORO_INVOKE_1(ButtonPress, g_buttonEffect.box);
+ switch (g_buttonEffect.box->boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ case IQUITGAME:
+ _vm->quitFlag = true;
+ break;
+ case CLOSEWIN:
+ KillInventory();
+ break;
+ case OPENLOAD:
+ KillInventory();
+ PopUpConf(LOAD);
+ break;
+ case OPENSAVE:
+ KillInventory();
+ PopUpConf(SAVE);
+ break;
+ case OPENREST:
+ KillInventory();
+ PopUpConf(RESTART);
+ break;
+ case OPENSOUND:
+ KillInventory();
+ PopUpConf(SOUND);
+ break;
+ case OPENCONT:
+ KillInventory();
+ PopUpConf(CONTROLS);
+ break;
+ #ifndef JAPAN
+ case OPENSUBT:
+ KillInventory();
+ PopUpConf(SUBT);
+ break;
+ #endif
+ case OPENQUIT:
+ KillInventory();
+ PopUpConf(QUIT);
+ break;
+ case INITGAME:
+ KillInventory();
+ bRestart = true;
+ break;
+ #if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case CLANG:
+ if (!LanguageChange())
+ KillInventory();
+ break;
+ case RLANG:
+ KillInventory();
+ break;
+ #endif
+ default:
+ break;
+ }
+ } else
+ CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box);
+
+ g_buttonEffect.bButAnim = false;
+ }
+
+ }
+ CORO_END_CODE;
+}
+
+/**************************************************************************/
+/*************** Drag stuff - Resizing and moving window ******************/
+/**************************************************************************/
+
+/**
+ * Appears to find the nearest entry in slideStuff[] to the supplied
+ * y-coordinate.
+ */
+int NearestSlideY(int fity) {
+ int nearDist = 1000;
+ int thisDist;
+ int nearI = 0; // Index of nearest fit
+ int i = 0;
+
+ do {
+ thisDist = ABS(slideStuff[i].y - fity);
+ if (thisDist < nearDist) {
+ nearDist = thisDist;
+ nearI = i;
+ }
+ } while (slideStuff[++i].n != -1);
+ return nearI;
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+void SlideSlider(int y, SSFN fn) {
+ static int newY = 0, lasti = 0; // FIXME: local static var
+ int gotoY, ati;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ lasti = NearestSlideY(slideY);
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(SlideObject, 0, gotoY - slideY);
+ slideY = gotoY;
+
+ // Re-draw icons if necessary
+ ati = NearestSlideY(slideY);
+ if (ati != lasti) {
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ assert(InvD[ino].FirstDisp >= 0); // negative first displayed
+ ItemsChanged = true;
+ lasti = ati;
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ // Draw icons from new start icon
+ ati = NearestSlideY(slideY);
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ ItemsChanged = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+
+void SlideCSlider(int y, SSFN fn) {
+ static int newY = 0; // FIXME: local static var
+ int gotoY;
+ int fc;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ slideY = gotoY;
+
+ fc = cd.fileBase;
+ firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin));
+ if (fc != cd.fileBase) {
+ AddBoxes(false);
+ fc -= cd.fileBase;
+ cd.selBox += fc;
+ if (cd.selBox < 0)
+ cd.selBox = 0;
+ else if (cd.selBox >= NUM_SL_RGROUP)
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on a mixing desk slider,
+ * and upon x-movement during such a drag.
+ */
+
+static void SlideMSlider(int x, SSFN fn) {
+ static int newX = 0; // FIXME: local static var
+ int gotoX;
+ int index, i;
+
+ if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN)
+ ;
+ else if (!(cd.selBox & IS_SLIDER))
+ return;
+
+ // Work out the indices
+ index = cd.selBox & ~IS_MASK;
+ for (i = 0; i < numMdSlides; i++)
+ if (mdSlides[i].num == index)
+ break;
+ assert(i < numMdSlides);
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ // can use index as a throw-away value
+ GetAniPosition(mdSlides[i].obj, &newX, &index);
+ lX = sX = newX;
+ break;
+
+ case S_SLIDE: // X-movement during drag
+ if (x == 0)
+ return;
+
+ newX = newX + x; // New x-position
+
+ if (newX < mdSlides[i].min)
+ gotoX = mdSlides[i].min; // Below bottom limit
+ else if (newX > mdSlides[i].max)
+ gotoX = mdSlides[i].max; // Above top limit
+ else
+ gotoX = newX; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0);
+ sX = gotoX;
+
+ if (lX != sX) {
+ *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE;
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ lX = sX;
+ }
+ break;
+
+ case S_TIMEUP:
+ case S_TIMEDN:
+ gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w;
+ MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX);
+
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ break;
+
+ case S_END: // End of a drag on the slider
+ AddBoxes(false); // Might change position slightly
+#ifndef JAPAN
+ if (ino == INV_CONF && cd.Box == subtitlesBox)
+ Select(language, false);
+#endif
+ break;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingTaller(void) {
+ if (SuppV) {
+ Ychange += SuppV;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += SuppV;
+ SuppV = 0;
+ }
+ while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ Ychange -= (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons++;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= (ITEM_HEIGHT+1);
+ }
+ if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ SuppV = Ychange;
+ Ychange = 0;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= SuppV;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingShorter(void) {
+ int StartNvi = InvD[ino].NoofVicons;
+ int StartUv = SuppV;
+
+ if (SuppV) {
+ Ychange += (SuppV - (ITEM_HEIGHT+1));
+ InvD[ino].NoofVicons++;
+ SuppV = 0;
+ }
+ while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) {
+ Ychange += (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons--;
+ }
+ if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) {
+ SuppV = (ITEM_HEIGHT+1) + Ychange;
+ InvD[ino].NoofVicons--;
+ Ychange = 0;
+ }
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingWider(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += SuppH;
+ SuppH = 0;
+ }
+ while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ Xchange -= (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons++;
+ }
+ if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ SuppH = Xchange;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingNarrower(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += (SuppH - (ITEM_WIDTH+1));
+ InvD[ino].NoofHicons++;
+ SuppH = 0;
+ }
+ while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) {
+ Xchange += (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons--;
+ }
+ if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) {
+ SuppH = (ITEM_WIDTH+1) + Xchange;
+ InvD[ino].NoofHicons--;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+
+/**
+ * Called from Xmovement()/Ymovement() during re-sizing.
+ */
+
+void ChangeingSize(void) {
+ /* Make it taller or shorter if necessary. */
+ if (Ychange > 0)
+ GettingTaller();
+ else if (Ychange < 0)
+ GettingShorter();
+
+ /* Make it wider or narrower if necessary. */
+ if (Xchange > 0)
+ GettingWider();
+ else if (Xchange < 0)
+ GettingNarrower();
+
+ ConstructInventory(EMPTY);
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Xmovement(int x) {
+ int aniX, aniY;
+ int i;
+
+ if (x && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY);
+ InvD[ino].inventoryX +=x;
+ MultiSetAniX(objArray[0], InvD[ino].inventoryX);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], x, 0);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], x, 0);
+ break;
+
+ case ID_LEFT:
+ case ID_TLEFT:
+ case ID_BLEFT:
+ Xchange -= x;
+ ChangeingSize();
+ break;
+
+ case ID_RIGHT:
+ case ID_TRIGHT:
+ case ID_BRIGHT:
+ Xchange += x;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ case ID_MDCONT:
+ SlideMSlider(x, S_SLIDE);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Ymovement(int y) {
+ int aniX, aniY;
+ int i;
+
+ if (y && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY);
+ InvD[ino].inventoryY +=y;
+ MultiSetAniY(objArray[0], InvD[ino].inventoryY);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], 0, y);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], 0, y);
+ break;
+
+ case ID_SLIDE:
+ SlideSlider(y, S_SLIDE);
+ break;
+
+ case ID_CSLIDE:
+ SlideCSlider(y, S_SLIDE);
+ break;
+
+ case ID_BOTTOM:
+ case ID_BLEFT:
+ case ID_BRIGHT:
+ Ychange += y;
+ ChangeingSize();
+ break;
+
+ case ID_TOP:
+ case ID_TLEFT:
+ case ID_TRIGHT:
+ Ychange -= y;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called when a drag is commencing.
+ */
+
+void InvDragStart(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+/*
+* Do something different for Save/Restore screens
+*/
+ if (ino == INV_CONF) {
+ int whichbox;
+
+ whichbox = WhichInvBox(curX, curY, true);
+
+ if (whichbox == IB_SLIDE) {
+ InvDragging = ID_CSLIDE;
+ SlideCSlider(0, S_START);
+ } else if (whichbox > 0 && (whichbox & IS_MASK)) {
+ InvDragging = ID_MDCONT; // Mixing desk control
+ cd.selBox = whichbox;
+ SlideMSlider(0, S_START);
+ }
+ return;
+ }
+
+/*
+* Normal operation
+*/
+ switch (InvArea(curX, curY)) {
+ case I_MOVE:
+ if (InvD[ino].moveable) {
+ InvDragging = ID_MOVE;
+ }
+ break;
+
+ case I_SLIDE:
+ InvDragging = ID_SLIDE;
+ SlideSlider(0, S_START);
+ break;
+
+ case I_BOTTOM:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_BOTTOM;
+ Ycompensate = 'B';
+ }
+ break;
+
+ case I_TOP:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_TOP;
+ Ycompensate = 'T';
+ }
+ break;
+
+ case I_LEFT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_LEFT;
+ Xcompensate = 'L';
+ }
+ break;
+
+ case I_RIGHT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_RIGHT;
+ Xcompensate = 'R';
+ }
+ break;
+
+ case I_TLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_TLEFT;
+ }
+ break;
+
+ case I_TRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_TRIGHT;
+ }
+ break;
+
+ case I_BLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_BLEFT;
+ }
+ break;
+
+ case I_BRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_BRIGHT;
+ }
+ break;
+ }
+}
+
+/**
+ * Called when a drag is over.
+ */
+
+void InvDragEnd(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+ if (InvDragging != ID_NONE) {
+ if (InvDragging == ID_SLIDE) {
+ SlideSlider(0, S_END);
+ } else if (InvDragging == ID_CSLIDE) {
+ ; // No action
+ } else if (InvDragging == ID_MDCONT) {
+ SlideMSlider(0, S_END);
+ } else if (InvDragging == ID_MOVE) {
+ ; // No action
+ } else {
+ // Were re-sizing. Redraw the whole thing.
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+
+ // If this was the maximised, it no longer is!
+ if (InventoryMaximised) {
+ InventoryMaximised = false;
+ InvD[ino].otherX = InvD[ino].inventoryX;
+ InvD[ino].otherY = InvD[ino].inventoryY;
+ }
+ }
+ InvDragging = ID_NONE;
+ }
+
+ // Cursor could well now be inappropriate
+ InvCursor(IC_AREA, curX, curY);
+
+ Xchange = Ychange = 0; // Probably no need, but does no harm!
+}
+
+
+/**************************************************************************/
+/************** Incoming events - further processing **********************/
+/**************************************************************************/
+
+/**
+ * ConfAction
+ */
+void ConfAction(int i, bool dbl) {
+
+ if (i >= 0) {
+ switch (cd.Box[i].boxType) {
+ case FLIP:
+ if (dbl) {
+ *(cd.Box[i].ival) ^= 1; // XOR with true
+ AddBoxes(false);
+ }
+ break;
+
+ case TOGGLE:
+ if (!g_buttonEffect.bButAnim) {
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = false;
+ }
+ break;
+
+ case RGROUP:
+ if (dbl) {
+ // Already highlighted
+ switch (cd.Box[i].boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ default:
+ break;
+ }
+ } else {
+ Select(i, false);
+ }
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ if (dbl) {
+ Select(i, false);
+ LanguageChange();
+ } else {
+ Select(i, false);
+ }
+ break;
+#endif
+
+ case AAGBUT:
+ case ARSGBUT:
+ case ARSBUT:
+ case AABUT:
+ case AATBUT:
+ if (g_buttonEffect.bButAnim)
+ break;
+
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ ConfActionSpecial(i);
+ }
+}
+
+static void ConfActionSpecial(int i) {
+ switch (i) {
+ case IB_NONE:
+ break;
+ case IB_UP: // Scroll up
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-1);
+ AddBoxes(true);
+ if (cd.selBox < NUM_SL_RGROUP-1)
+ cd.selBox += 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+1);
+ AddBoxes(true);
+ if (cd.selBox)
+ cd.selBox -= 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_UP:
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+ }
+}
+// SLIDE_UP and SLIDE_DOWN on d click??????
+
+void InvPutDown(int index) {
+ int aniX, aniY;
+ // index is the drop position
+ int hiIndex; // Current position of held item (if in)
+
+ // Find where the held item is positioned in this inventory (if it is)
+ for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++)
+ if (InvD[ino].ItemOrder[hiIndex] == HeldItem)
+ break;
+
+ // If drop position would leave a gap, move it up
+ if (index >= InvD[ino].NoofItems) {
+ if (hiIndex == InvD[ino].NoofItems) // Not in, add it
+ index = InvD[ino].NoofItems;
+ else
+ index = InvD[ino].NoofItems - 1;
+ }
+
+ if (hiIndex == InvD[ino].NoofItems) { // Not in, add it
+ if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) {
+ InvD[ino].NoofItems++;
+
+ // Don't leave it in the other inventory!
+ if (InventoryPos(HeldItem) != INV_HELDNOTIN)
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem);
+ } else {
+ // No room at the inn!
+ return;
+ }
+ }
+
+ // Position it in the inventory
+ if (index < hiIndex) {
+ memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else if (index > hiIndex) {
+ memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else {
+ InvD[ino].ItemOrder[index] = HeldItem;
+ }
+
+ HeldItem = INV_NOICON;
+ ItemsChanged = true;
+ DelAuxCursor();
+ RestoreMainCursor();
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_DROP, aniX, aniY);
+}
+
+void InvPdProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // get the stuff copied to process when it was created
+ int *pindex = (int *)param;
+
+ InvPutDown(*pindex);
+
+ CORO_END_CODE;
+}
+
+void InvPickup(int index) {
+ INV_OBJECT *invObj;
+
+ if (index != INV_NOICON) {
+ if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ // Pick-up
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index);
+ } else if (HeldItem != INV_NOICON) { // Put icon down
+ // Put-down
+ invObj = findInvObject(HeldItem);
+
+ if (invObj->attribute & IO_DROPCODE && invObj->hScript)
+ RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index);
+
+ else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1)
+ && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2))
+ g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index));
+ }
+ }
+}
+
+/**
+ * Pick up/put down icon
+ */
+void InvSLClick(void) {
+ int i;
+ int aniX, aniY; // Cursor's animation position
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+
+ case I_SLIDE_UP:
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_SLIDE_DOWN:
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ break;
+
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), false);
+ } else {
+ i = InvItem(&aniX, &aniY, false);
+
+ // Special bodge for David, to
+ // cater for drop in dead space between icons
+ if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) {
+ aniX += 1; // 1 to the right
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX -= 1; // 1 down
+ aniY += 1;
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX += 1; // 1 down-right
+ i = InvItem(&aniX, &aniY, false);
+ }
+ }
+ }
+
+ if (ino == INV_CONV) {
+ ConvAction(i);
+ } else
+ InvPickup(i);
+ }
+ break;
+ }
+}
+
+void InvAction(void) {
+ int index;
+ INV_OBJECT *invObj;
+ int aniX, aniY;
+ int i;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), true);
+ } else if (ino == INV_CONV) {
+ index = InvItem(&aniX, &aniY, false);
+ ConvAction(index);
+ } else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, ACTION, INV_ACTION, index);
+ }
+ }
+ }
+ break;
+
+ case I_MOVE: // Maximise/unmaximise inventory
+ if (!InvD[ino].resizable)
+ break;
+
+ if (!InventoryMaximised) {
+ InvD[ino].sNoofHicons = InvD[ino].NoofHicons;
+ InvD[ino].sNoofVicons = InvD[ino].NoofVicons;
+ InvD[ino].NoofHicons = InvD[ino].MaxHicons;
+ InvD[ino].NoofVicons = InvD[ino].MaxVicons;
+ InventoryMaximised = true;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ } else {
+ InvD[ino].NoofHicons = InvD[ino].sNoofHicons;
+ InvD[ino].NoofVicons = InvD[ino].sNoofVicons;
+ InventoryMaximised = false;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ }
+
+ // Delete current, and re-draw
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+ }
+
+}
+
+
+void InvLook(void) {
+ int index;
+ INV_OBJECT *invObj;
+ int aniX, aniY;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, LOOK, INV_LOOK, index);
+ }
+ }
+ break;
+
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/********************* Incoming events ************************************/
+/**************************************************************************/
+
+
+void ButtonToInventory(BUTEVENT be) {
+ if (InventoryHidden)
+ return;
+
+ switch (be) {
+ case INV_PICKUP: // BE_SLEFT
+ InvSLClick();
+ break;
+
+ case INV_LOOK: // BE_SRIGHT
+ if (IsConfWindow())
+ InvSLClick();
+ else
+ InvLook();
+ break;
+
+ case INV_ACTION: // BE_DLEFT
+ if (InvDragging != ID_MDCONT)
+ InvDragEnd();
+ InvAction();
+ break;
+
+ case BE_LDSTART: // Left drag start
+ InvDragStart();
+ break;
+
+ case BE_LDEND: // Left drag end
+ InvDragEnd();
+ break;
+
+// case BE_DLEFT: // Double click left (also ends left drag)
+// ButtonToInventory(LDEND);
+// break;
+
+ case BE_RDSTART:
+ case BE_RDEND:
+ case BE_UNKNOWN:
+ break;
+ default:
+ break;
+ }
+}
+
+void KeyToInventory(KEYEVENT ke) {
+ int i;
+
+ switch (ke) {
+ case ESC_KEY:
+ if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox)
+ bOpenConf = true;
+ CloseInventory();
+ break;
+
+ case PGDN_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_DOWN);
+ } else {
+ // This code is a copy of SLClick on IB_SLIDE_DOWN
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ }
+ break;
+
+ case PGUP_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_UP);
+ } else {
+ // This code is a copy of SLClick on I_SLIDE_UP
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case HOME_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(0);
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case END_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(MAX_SFILES); // Will get reduced to appropriate value
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ default:
+ error("We're at KeyToInventory(), with default");
+ }
+}
+
+/**************************************************************************/
+/************************* Odds and Ends **********************************/
+/**************************************************************************/
+
+/**
+ * Called from Glitter function invdepict()
+ * Changes (permanently) the animation film for that object.
+ */
+
+void invObjectFilm(int object, SCNHANDLE hFilm) {
+ INV_OBJECT *invObj;
+
+ invObj = findInvObject(object);
+ invObj->hFilm = hFilm;
+
+ if (HeldItem != object)
+ ItemsChanged = true;
+}
+
+/**
+ * (Un)serialize the inventory data for save/restore game.
+ */
+
+void syncInvInfo(Serializer &s) {
+ for (int i = 0; i < NUM_INV; i++) {
+ s.syncAsSint32LE(InvD[i].MinHicons);
+ s.syncAsSint32LE(InvD[i].MinVicons);
+ s.syncAsSint32LE(InvD[i].MaxHicons);
+ s.syncAsSint32LE(InvD[i].MaxVicons);
+ s.syncAsSint32LE(InvD[i].NoofHicons);
+ s.syncAsSint32LE(InvD[i].NoofVicons);
+ for (int j = 0; j < MAX_ININV; j++) {
+ s.syncAsSint32LE(InvD[i].ItemOrder[j]);
+ }
+ s.syncAsSint32LE(InvD[i].NoofItems);
+ s.syncAsSint32LE(InvD[i].FirstDisp);
+ s.syncAsSint32LE(InvD[i].inventoryX);
+ s.syncAsSint32LE(InvD[i].inventoryY);
+ s.syncAsSint32LE(InvD[i].otherX);
+ s.syncAsSint32LE(InvD[i].otherY);
+ s.syncAsSint32LE(InvD[i].MaxInvObj);
+ s.syncAsSint32LE(InvD[i].hInvTitle);
+ s.syncAsSint32LE(InvD[i].resizable);
+ s.syncAsSint32LE(InvD[i].moveable);
+ s.syncAsSint32LE(InvD[i].sNoofHicons);
+ s.syncAsSint32LE(InvD[i].sNoofVicons);
+ s.syncAsSint32LE(InvD[i].bMax);
+ }
+}
+
+/**************************************************************************/
+/************************ Initialisation stuff ****************************/
+/**************************************************************************/
+
+/**
+ * Called from PlayGame(), stores handle to inventory objects' data -
+ * its id, animation film and Glitter script.
+ */
+// Note: the SCHANDLE type here has been changed to a void*
+void RegisterIcons(void *cptr, int num) {
+ numObjects = num;
+ pio = (INV_OBJECT *) cptr;
+}
+
+/**
+ * Called from Glitter function 'dec_invw()' - Declare the bits that the
+ * inventory windows are constructed from, and special cursors.
+ */
+
+void setInvWinParts(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ winPartsf = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected
+#endif
+}
+
+/**
+ * Called from Glitter function 'dec_flags()' - Declare the language
+ * flag films
+ */
+
+void setFlagFilms(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ flagFilm = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected
+#endif
+}
+
+void setConfigStrings(SCNHANDLE *tp) {
+ memcpy(configStrings, tp, sizeof(configStrings));
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv(int num, SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight,
+ int startx, int starty, bool moveable) {
+ if (MaxWidth > MAXHICONS)
+ MaxWidth = MAXHICONS; // Max window width
+ if (MaxHeight > MAXVICONS)
+ MaxHeight = MAXVICONS; // Max window height
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InventoryState = IDLE_INV;
+
+ InvD[num].MaxHicons = MaxWidth;
+ InvD[num].MinHicons = MinWidth;
+ InvD[num].MaxVicons = MaxHeight;
+ InvD[num].MinVicons = MinHeight;
+
+ InvD[num].NoofHicons = StartWidth;
+ InvD[num].NoofVicons = StartHeight;
+
+ memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder));
+ InvD[num].NoofItems = 0;
+
+ InvD[num].FirstDisp = 0;
+
+ InvD[num].inventoryX = startx;
+ InvD[num].inventoryY = starty;
+ InvD[num].otherX = 21;
+ InvD[num].otherY = 15;
+
+ InvD[num].MaxInvObj = MaxContents;
+
+ InvD[num].hInvTitle = text;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[num].resizable = true;
+
+ InvD[num].moveable = moveable;
+
+ InvD[num].bMax = false;
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_convw(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 20, 8, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+int InvGetLimit(int invno) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ return InvD[invno].MaxInvObj;
+}
+
+void InvSetLimit(int invno, int MaxContents) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+ assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents
+
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ InvD[invno].MaxInvObj = MaxContents;
+}
+
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InvD[invno].MaxHicons = MaxWidth;
+ InvD[invno].MinHicons = MinWidth;
+ InvD[invno].MaxVicons = MaxHeight;
+ InvD[invno].MinVicons = MinHeight;
+
+ InvD[invno].NoofHicons = StartWidth;
+ InvD[invno].NoofVicons = StartHeight;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[invno].resizable = true;
+ else
+ InvD[invno].resizable = false;
+
+ InvD[invno].bMax = false;
+}
+
+/**************************************************************************/
+
+bool IsTopWindow(void) {
+ return (InventoryState == BOGUS_INV);
+}
+
+
+bool IsConfWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONF);
+}
+
+
+bool IsConvWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONV);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h
new file mode 100644
index 0000000000..d83439c68f
--- /dev/null
+++ b/engines/tinsel/inventory.h
@@ -0,0 +1,142 @@
+
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Inventory related functions
+ */
+
+#ifndef TINSEL_INVENTORY_H // prevent multiple includes
+#define TINSEL_INVENTORY_H
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // for KEYEVENT, BUTEVENT
+
+namespace Tinsel {
+
+class Serializer;
+
+enum {
+ INV_OPEN = -1,
+ INV_CONV = 0,
+ INV_1 = 1,
+ INV_2 = 2,
+ INV_CONF = 3,
+
+ NUM_INV = 4
+};
+
+/** structure of each inventory object */
+struct INV_OBJECT {
+ int32 id; // inventory objects id
+ SCNHANDLE hFilm; // inventory objects animation film
+ SCNHANDLE hScript; // inventory objects event handling script
+ int32 attribute; // inventory object's attribute
+};
+
+void PopUpInventory(int invno);
+
+enum CONFTYPE {
+ SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN
+};
+
+void PopUpConf(CONFTYPE type);
+
+
+void Xmovement(int x);
+void Ymovement(int y);
+
+void ButtonToInventory(BUTEVENT be);
+
+void KeyToInventory(KEYEVENT ke);
+
+
+int WhichItemHeld(void);
+
+void HoldItem(int item);
+void DropItem(int item);
+void AddToInventory(int invno, int icon, bool hold);
+bool RemFromInventory(int invno, int icon);
+
+
+void RegisterIcons(void *cptr, int num);
+
+void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+bool InventoryActive(void);
+
+void AddIconToPermanentDefaultList(int icon);
+
+void convPos(int bpos);
+void ConvPoly(HPOLYGON hp);
+int convIcon(void);
+void CloseDownConv(void);
+void convHide(bool hide);
+bool convHid(void);
+
+enum {
+ INV_NOICON = -1,
+ INV_CLOSEICON = -2,
+ INV_OPENICON = -3,
+ INV_HELDNOTIN = -4
+};
+
+void ConvAction(int index);
+
+void InventoryIconCursor(void);
+
+void setInvWinParts(SCNHANDLE hf);
+void setFlagFilms(SCNHANDLE hf);
+void setConfigStrings(SCNHANDLE *tp);
+
+int InvItem(int *x, int *y, bool update);
+int InvItemId(int x, int y);
+
+int InventoryPos(int num);
+
+bool IsInInventory(int object, int invnum);
+
+void KillInventory(void);
+
+void invObjectFilm(int object, SCNHANDLE hFilm);
+
+void syncInvInfo(Serializer &s);
+
+int InvGetLimit(int invno);
+void InvSetLimit(int invno, int n);
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+int WhichInventoryOpen(void);
+
+bool IsTopWindow(void);
+bool IsConfWindow(void);
+bool IsConvWindow(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_INVENTRY_H */
diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp
new file mode 100644
index 0000000000..4c64eaf091
--- /dev/null
+++ b/engines/tinsel/mareels.cpp
@@ -0,0 +1,132 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Functions to set up moving actors' reels.
+ */
+
+#include "tinsel/pcode.h" // For D_UP, D_DOWN
+#include "tinsel/rince.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+enum {
+ NUM_INTERVALS = NUM_MAINSCALES - 1,
+
+ // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth
+ MAX_SCRENTRIES = NUM_INTERVALS*2*3
+};
+
+struct SCIdataStruct {
+ int actor;
+ int scale;
+ int direction;
+ SCNHANDLE reels[4];
+};
+
+static SCIdataStruct SCIdata[MAX_SCRENTRIES];
+
+static int scrEntries = 0;
+
+/**
+ * Return handle to actor's talk reel at present scale and direction.
+ */
+SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) {
+ assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES);
+ switch (dirn) {
+ case TF_NONE:
+ return pActor->TalkReels[pActor->scale-1][pActor->dirn];
+
+ case TF_UP:
+ return pActor->TalkReels[pActor->scale-1][AWAY];
+
+ case TF_DOWN:
+ return pActor->TalkReels[pActor->scale-1][FORWARD];
+
+ case TF_LEFT:
+ return pActor->TalkReels[pActor->scale-1][LEFTREEL];
+
+ case TF_RIGHT:
+ return pActor->TalkReels[pActor->scale-1][RIGHTREEL];
+
+ default:
+ error("GetMactorTalkReel() - illegal direction!");
+ }
+}
+
+/**
+ * scalingreels
+ */
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+ assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale
+ assert(!(scale == 1 && direction == D_UP) &&
+ !(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale
+
+ assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached!
+
+ SCIdata[scrEntries].actor = actor;
+ SCIdata[scrEntries].scale = scale;
+ SCIdata[scrEntries].direction = direction;
+ SCIdata[scrEntries].reels[LEFTREEL] = left;
+ SCIdata[scrEntries].reels[RIGHTREEL] = right;
+ SCIdata[scrEntries].reels[FORWARD] = forward;
+ SCIdata[scrEntries].reels[AWAY] = away;
+ scrEntries++;
+}
+
+/**
+ * ScalingReel
+ */
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) {
+ int d; // Direction
+
+ // The smaller the number, the bigger the scale
+ if (scale1 < scale2)
+ d = D_DOWN;
+ else
+ d = D_UP;
+
+ for (int i = 0; i < scrEntries; i++) {
+ if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) {
+ if (SCIdata[i].reels[reel] == TF_NONE)
+ return 0;
+ else
+ return SCIdata[i].reels[reel];
+ }
+ }
+ return 0;
+}
+
+/**
+ * RebootScalingReels
+ */
+void RebootScalingReels(void) {
+ scrEntries = 0;
+ memset(SCIdata, 0, sizeof(SCIdata));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
new file mode 100644
index 0000000000..b00afcddbc
--- /dev/null
+++ b/engines/tinsel/module.mk
@@ -0,0 +1,52 @@
+MODULE := engines/tinsel
+
+MODULE_OBJS = \
+ actors.o \
+ anim.o \
+ background.o \
+ bg.o \
+ cliprect.o \
+ config.o \
+ cursor.o \
+ debugger.o \
+ detection.o \
+ effect.o \
+ events.o \
+ faders.o \
+ font.o \
+ graphics.o \
+ handle.o \
+ heapmem.o \
+ inventory.o \
+ mareels.o \
+ move.o \
+ multiobj.o \
+ music.o \
+ object.o \
+ palette.o \
+ pcode.o \
+ pdisplay.o \
+ play.o \
+ polygons.o \
+ rince.o \
+ saveload.o \
+ savescn.o \
+ scene.o \
+ sched.o \
+ scn.o \
+ scroll.o \
+ sound.o \
+ strres.o \
+ text.o \
+ timers.o \
+ tinlib.o \
+ tinsel.o \
+ token.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp
new file mode 100644
index 0000000000..803bc5fd7b
--- /dev/null
+++ b/engines/tinsel/move.cpp
@@ -0,0 +1,1618 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles walking and use of the path system.
+ *
+ * Contains the dodgiest code in the whole system.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/tinlib.h" // For stand()
+
+namespace Tinsel {
+
+//----------------- DEVELOPMENT OPTIONS --------------------
+
+#define SLOW_RINCE_DOWN 0
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+// in POLYGONS.C
+// Deliberatley defined here, and not in polygons.h
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta);
+
+//----------------- LOCAL DEFINES --------------------
+
+#define XMDIST 4
+#define XHMDIST 2
+#define YMDIST 2
+#define YHMDIST 2
+
+#define XTHERE 1
+#define XRESTRICT 2
+#define YTHERE 4
+#define YRESTRICT 8
+#define STUCK 16
+
+#define LEAVING_PATH 0x100
+#define ENTERING_BLOCK 0x200
+#define ENTERING_MBLOCK 0x400
+
+#define ALL_SORTED 1
+#define NOT_SORTED 0
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#if SLOW_RINCE_DOWN
+static int Interlude = 0; // For slowing down walking, for testing
+static int BogusVar = 0; // For slowing down walking, for testing
+#endif
+
+static int32 DefaultRefer = 0;
+static int hSlowVar = 0; // used by MoveActor()
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p,
+ bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor = 0);
+
+
+#if SLOW_RINCE_DOWN
+/**
+ * AddInterlude
+ */
+
+void AddInterlude(int n) {
+ Interlude += n;
+ if (Interlude < 0)
+ Interlude = 0;
+}
+#endif
+
+/**
+ * Given (x, y) of a click within a path polygon, checks that the
+ * co-ordinates are not within a blocking polygon. If it is not, the
+ * destination is the click point, otherwise tries to find a legal point
+ * below or above the click point.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int Loffset, Toffset;
+ int i;
+
+ /*--------------------------------------
+ Clicked within a path,
+ go to where requested unless blocked.
+ --------------------------------------*/
+ if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) {
+ // Not in a blocking polygon - go to where requested.
+ *ptgtX = clickX;
+ *ptgtY = clickY;
+ } else {
+ /*------------------------------------------------------
+ In a Blocking polygon - try searching down and up.
+ If still nowhere (for now) give up!
+ ------------------------------------------------------*/
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = SCREEN_HEIGHT;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ if (i == SCREEN_HEIGHT) {
+ for (i = clickY-1; i >= Toffset; i--) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = -1;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ }
+ if (i < 0) {
+ return ALL_SORTED;
+ }
+ }
+ return NOT_SORTED;
+}
+
+/**
+ * Given (x, y) of a click within a referral polygon, works out the
+ * destination according to the referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *ptgtX = *ptgtY = -1;
+
+ switch (PolySubtype(hRefpoly)) {
+ case REF_POINT: // Go to specified node
+ getPolyNode(hRefpoly, ptgtX, ptgtY);
+ assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point
+ break;
+
+ case REF_DOWN: // Search downwards
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_UP: // Search upwards
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_RIGHT: // Search to the right
+ end = BackgroundWidth();
+ for (i = clickX+1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+
+ case REF_LEFT: // Search to the left
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+ }
+ if (*ptgtX != -1 && *ptgtY != -1) {
+ return NOT_SORTED;
+ } else
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of a click, works out the destination according to the
+ * default referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (DefaultRefer) {
+ case REF_DEFAULT:
+ // Try searching down and up (onscreen).
+ for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= Toffset; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ // Try searching down and up (offscreen).
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_UP:
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_DOWN:
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_LEFT:
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_RIGHT:
+ end = BackgroundWidth();
+ for (i = clickX + 1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+ }
+
+ // Going nowhere!
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of the click, ascertains whether the click is within a
+ * path, within a referral poly, or niether. The appropriate function
+ * then gets called to give us a revised destination.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ HPOLYGON hPoly;
+
+ /*--------------------------------------
+ Clicked within a path?
+ if not, within a referral poly?
+ if not, try and sort something out.
+ ---------------------------------------*/
+ if (InPolygon(clickX, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, clickY, ptgtX, ptgtY);
+ } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) {
+ return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY);
+ } else {
+ return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY);
+ }
+}
+
+/**
+ * Work out which reel to adopt for a section of movement.
+ */
+static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) {
+ int xchange = 0, ychange = 0;
+ enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir;
+ enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir;
+
+ DIRREEL reel = lastreel; // Leave alone if can't decide
+
+ /*
+ * Determine size and direction of X movement.
+ * i.e. left, right, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_VERT)
+ xdir = X_NO;
+ else if (tox == -1)
+ xdir = X_NONE;
+ else {
+ xchange = tox - fromx;
+ if (xchange > 0)
+ xdir = X_RIGHT;
+ else if (xchange < 0) {
+ xchange = -xchange;
+ xdir = X_LEFT;
+ } else
+ xdir = X_NONE;
+ }
+
+ /*
+ * Determine size and direction of Y movement.
+ * i.e. up, down, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_HORIZ)
+ ydir = Y_NO;
+ else if (toy == -1)
+ ydir = Y_NONE;
+ else {
+ ychange = toy - fromy;
+ if (ychange > 0)
+ ydir = Y_DOWN;
+ else if (ychange < 0) {
+ ychange = -ychange;
+ ydir = Y_UP;
+ } else
+ ydir = Y_NONE;
+ }
+
+ /*
+ * Some adjustment to allow for different x and y pixell sizes.
+ */
+ ychange += ychange; // Double y distance to cover
+
+ /*
+ * Determine which reel to use.
+ */
+ if (xdir == X_NO) {
+ // Forced to be FORWARD or AWAY
+ switch (ydir) {
+ case Y_DOWN:
+ reel = FORWARD;
+ break;
+ case Y_UP:
+ reel = AWAY;
+ break;
+ default:
+ if (reel != AWAY) // No gratuitous turn
+ reel = FORWARD;
+ break;
+ }
+ } else if (ydir == Y_NO) {
+ // Forced to be LEFTREEL or RIGHTREEL
+ switch (xdir) {
+ case X_LEFT:
+ reel = LEFTREEL;
+ break;
+ case X_RIGHT:
+ reel = RIGHTREEL;
+ break;
+ default:
+ if (reel != LEFTREEL) // No gratuitous turn
+ reel = RIGHTREEL;
+ break;
+ }
+ } else if (xdir != X_NONE || ydir != Y_NONE) {
+ if (xdir == X_NONE)
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ else if (ydir == Y_NONE)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else {
+ bool DontBother = false;
+
+ if (xchange <= 4 && ychange <= 4) {
+ switch (reel) {
+ case LEFTREEL:
+ if (xdir == X_LEFT)
+ DontBother = true;
+ break;
+ case RIGHTREEL:
+ if (xdir == X_RIGHT)
+ DontBother = true;
+ break;
+ case FORWARD:
+ if (ydir == Y_DOWN)
+ DontBother = true;
+ break;
+ case AWAY:
+ if (ydir == Y_UP)
+ DontBother = true;
+ break;
+ }
+ }
+ if (!DontBother) {
+ if (xchange > ychange)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ }
+ }
+ }
+ return reel;
+}
+
+/**
+ * Haven't moved, look towards the cursor.
+ */
+static void GotThereWithoutMoving(PMACTOR pActor) {
+ int curX, curY;
+ DIRREEL reel;
+
+ if (!pActor->TagReelRunning) {
+ GetCursorXYNoWait(&curX, &curY, true);
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath);
+
+ if (reel != pActor->dirn)
+ SetMActorWalkReel(pActor, reel, pActor->scale, false);
+ }
+}
+
+/**
+ * Arrived at final destination.
+ */
+static void GotThere(PMACTOR pActor) {
+ pActor->targetX = pActor->targetY = -1; // 4/1/95
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->UtargetX = pActor->UtargetY = -1;
+
+ // Perhaps we have'nt moved.
+ if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy)
+ GotThereWithoutMoving(pActor);
+
+ ReTagActor(pActor->actorID); // Tag allowed while stationary
+
+ SetMActorStanding(pActor);
+
+ pActor->bMoving = false;
+}
+
+enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY };
+
+/**
+ * Can we get straight there?
+ */
+static cgt CanGetThere(PMACTOR pActor, int tx, int ty) {
+ int s1, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ int nextx, nexty;
+
+ int targetX = tx;
+ int targetY = ty; // Ultimate destination
+ int x = pActor->objx;
+ int y = pActor->objy; // Present position
+
+ while (targetX != -1 || targetY != -1) {
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (s1 == (XTHERE | YTHERE)) {
+ return GT_OK; // Can get there directly.
+ } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ return GT_MAY; // Can't get there directly.
+ } else if (s1 & STUCK) {
+ if (s2 == LEAVING_PATH)
+ return GT_NOTL; // Can't get there.
+ else
+ return GT_NOTB; // Can't get there.
+ } else if (x == nextx && y == nexty) {
+ return GT_NOT2; // Can't get there.
+ }
+ x = nextx;
+ y = nexty;
+ }
+ return GT_MAY;
+}
+
+
+/**
+ * Set final destination.
+ */
+static void SetMoverUltDest(PMACTOR pActor, int x, int y) {
+ pActor->UtargetX = x;
+ pActor->UtargetY = y;
+ pActor->hUpath = InPolygon(x, y, PATH);
+
+ assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination
+}
+
+/**
+ * Set intermediate destination.
+ *
+ * If in final destination path, go straight to target.
+ * If in a neighbouring path to the final destination, if the target path
+ * is a follow nodes path, head for the end node, otherwise head straight
+ * for the target.
+ * Otherwise, head towards the pseudo-centre or end node of the first
+ * en-route path.
+ */
+static void SetMoverIntDest(PMACTOR pActor, int x, int y) {
+ HPOLYGON hIpath, hTpath;
+ int node;
+
+ hTpath = InPolygon(x, y, PATH); // Target path
+#ifdef DEBUG
+ if (!pActor->bIgPath)
+ assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path
+#endif
+
+ if (pActor->hCpath == hTpath || pActor->bIgPath
+ || IsInPolygon(pActor->objx, pActor->objy, hTpath)) {
+ // In destination path - head straight for the target.
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ pActor->hIpath = hTpath;
+ } else if (IsAdjacentPath(pActor->hCpath, hTpath)) {
+ // In path adjacent to target
+ if (PolySubtype(hTpath) != NODE) {
+ // Target path is normal - head for target.
+ // Added 26/01/95, innroom
+ if (CanGetThere(pActor, x, y) == GT_NOTL) {
+ NearestCorner(&x, &y, pActor->hCpath, hTpath);
+ }
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ // Target path is node - head for end node.
+ node = NearestEndNode(hTpath, pActor->objx, pActor->objy);
+ getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY);
+
+ }
+ pActor->hIpath = hTpath;
+ } else {
+ assert(hTpath != NOPOLY); // Error 701
+ hIpath = getPathOnTheWay(pActor->hCpath, hTpath);
+
+ if (hIpath != NOPOLY) {
+ /* Head for an en-route path */
+ if (PolySubtype(hIpath) != NODE) {
+ /* En-route path is normal - head for pseudo centre. */
+ if (CanGetThere(pActor, x, y) == GT_OK) {
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ pActor->ItargetX = PolyCentreX(hIpath);
+ pActor->ItargetY = PolyCentreY(hIpath);
+ }
+ } else {
+ /* En-route path is node - head for end node. */
+ node = NearestEndNode(hIpath, pActor->objx, pActor->objy);
+ getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY);
+ }
+ pActor->hIpath = hIpath;
+ }
+ }
+
+ pActor->InDifficulty = NO_PROB;
+}
+
+/**
+ * Set short-term destination and adopt the appropriate reel.
+ */
+static void SetMoverDest(PMACTOR pActor, int x, int y) {
+ int scale;
+ DIRREEL reel;
+
+ // Set the co-ordinates requested.
+ pActor->targetX = x;
+ pActor->targetY = y;
+ pActor->InDifficulty = NO_PROB;
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath);
+ scale = GetScale(pActor->hCpath, pActor->objy);
+ if (scale != pActor->scale || reel != pActor->dirn) {
+ SetMActorWalkReel(pActor, reel, scale, false);
+ }
+}
+
+/**
+ * SetNextDest
+ */
+static void SetNextDest(PMACTOR pActor) {
+ int targetX, targetY; // Ultimate destination
+ int x, y; // Present position
+ int nextx, nexty;
+ int s1, lstatus = 0;
+ int s2;
+ HPOLYGON hS2p;
+ int i;
+ HPOLYGON hNpoly;
+ HPOLYGON hPath;
+ int znode;
+ int nx, ny;
+ int sx, sy;
+ HPOLYGON hEb;
+
+ int ss1, ss2;
+ HPOLYGON shS2p;
+ PMACTOR collisionActor;
+#if 1
+ int sTargetX, sTargetY;
+#endif
+
+ /*
+ * Desired destination (Itarget) is already set
+ */
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+
+ /*
+ * If we're where we're headed, end it all (the moving).
+ */
+// if (x == targetX && y == targetY)
+ if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) {
+ if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) {
+ // Desired position
+ GotThere(pActor);
+ return;
+ } else {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ }
+ }
+
+ if (pActor->bNoPath || pActor->bIgPath) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, targetX, targetY);
+ pActor->over = false;
+ return;
+ }
+
+ /*----------------------------------------------------------------------
+ | Some work to do here if we're in a follow nodes polygon - basically
+ | head for the next node.
+ ----------------------------------------------------------------------*/
+ hNpoly = pActor->hFnpath; // The node path we're in (if any)
+ switch (pActor->npstatus) {
+ case NOT_IN:
+ break;
+
+ case ENTERING:
+ znode = NearestEndNode(hNpoly, x, y);
+ /* Hang on, we're probably here already! */
+ if (znode) {
+ pActor->npstatus = GOING_DOWN;
+ pActor->line = znode-1;
+ getNpathNode(hNpoly, znode - 1, &nx, &ny);
+ } else {
+ pActor->npstatus = GOING_UP;
+ pActor->line = znode;
+ getNpathNode(hNpoly, 1, &nx, &ny);
+ }
+ SetMoverDest(pActor, nx, ny);
+
+ // Test for pseudo-one-node npaths
+ if (numNodes(hNpoly) == 2 &&
+ ABS(pActor->objx - pActor->targetX) < XMDIST &&
+ ABS(pActor->objy - pActor->targetY) < YMDIST) {
+ // That's enough, we're leaving
+ pActor->npstatus = LEAVING;
+ } else {
+ // Normal situation
+ pActor->over = true;
+ return;
+ }
+ // Fall through for LEAVING
+
+ case LEAVING:
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+
+ case GOING_UP:
+ i = pActor->line; // The line we're on
+
+ // Is this the final target line?
+ if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ pActor->line = i+1;
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ i++; // The node we're at
+ if (++i < numNodes(hNpoly)) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line = i-1;
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+
+ case GOING_DOWN:
+ i = pActor->line; // The line we're on and the node we're at
+
+ // Is this the final target line?
+ if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->line = i-1;
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ if (--i >= 0) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line--; /* The next node to head for */
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+ }
+
+
+
+
+ /*------------------------------------------------------
+ | See if it can get there directly. There may be an
+ | intermediate destination to head for.
+ ------------------------------------------------------*/
+
+ while (targetX != -1 || targetY != -1) {
+#if 1
+ // 'push' the target
+ sTargetX = targetX;
+ sTargetY = targetY;
+#endif
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor);
+
+ if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) {
+ ss1 = s1;
+ ss2 = s2;
+ shS2p = hS2p;
+#if 1
+ // 'pop' the target
+ targetX = sTargetX;
+ targetY = sTargetY;
+#endif
+ // Note: this aint right - targetX/Y (may) have been
+ // nobbled by that last call to NewCoOrdinates()
+ // Re-instating them (can) leads to oscillation
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor);
+
+ if (x == nextx && y == nexty) {
+ s1 = ss1;
+ s2 = ss2;
+ hS2p = shS2p;
+ }
+ }
+
+ if (s1 == (XTHERE | YTHERE)) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT)
+ || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ /*-------------------------------------------------
+ Can't go any further in this direction. |
+ If it's because of a blocking polygon, try to do |
+ something about it. |
+ -------------------------------------------------*/
+ if (s2 & ENTERING_BLOCK) {
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ // Go to the nearest corner of the blocking polygon concerned
+ BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ } else if (s2 & ENTERING_MBLOCK) {
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ // The best we're going to achieve
+ SetMoverUltDest(pActor, x, y);
+ SetMoverDest(pActor, x, y);
+ } else {
+ sx = pActor->objx;
+ sy = pActor->objy;
+// pActor->objx = x;
+// pActor->objy = y;
+
+ hEb = InitExtraBlock(pActor, collisionActor);
+ x = pActor->objx;
+ y = pActor->objy;
+ BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY);
+
+ pActor->objx = sx;
+ pActor->objy = sy;
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ }
+ } else {
+ /*----------------------------------------
+ Currently, this is as far as we can go. |
+ Definitely room for improvement here! |
+ ----------------------------------------*/
+ hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH);
+ if (hPath != pActor->hIpath) {
+ if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath))
+ hPath = pActor->hIpath;
+ }
+ assert(hPath == pActor->hIpath);
+
+ if (pActor->InDifficulty == NO_PROB) {
+ x = PolyCentreX(hPath);
+ y = PolyCentreY(hPath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CENTRE;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CENTRE) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CORNER;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CORNER) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_NEXTCORNER;
+ pActor->over = false;
+ }
+ }
+ break;
+ }
+ else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT))
+ || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) {
+ /*-----------------------------------------------
+ A restriction in a direction has been removed. |
+ Use this as an intermediate destination. |
+ -----------------------------------------------*/
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ }
+
+ x = nextx;
+ y = nexty;
+
+ /*-------------------------
+ Change of path polygon? |
+ -------------------------*/
+ hPath = InPolygon(x, y, PATH);
+ if (pActor->hCpath != hPath &&
+ !IsInPolygon(x, y, pActor->hCpath) &&
+ !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) {
+ /*----------------------------------------------------------
+ If just entering a follow nodes polygon, go to first node.|
+ Else if just going to pass through, go to pseudo-centre. |
+ ----------------------------------------------------------*/
+ if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) {
+ int node = NearestEndNode(hPath, x, y);
+ getNpathNode(hPath, node, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->over = true;
+ } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) &&
+ !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) {
+ SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath));
+ pActor->over = true;
+ } else {
+ SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY);
+ }
+ break;
+ }
+
+ lstatus = s1;
+ }
+}
+
+/**
+ * Work out where the next position should be.
+ * Check that it's in a path and not in a blocking polygon.
+ */
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2,
+ HPOLYGON *hS2p, bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor) {
+ HPOLYGON hPoly;
+ int sidem, depthm;
+ int sidesteps, depthsteps;
+ PMACTOR ma;
+
+ *s1 = *s2 = 0;
+
+ /*------------------------------------------------
+ Don't overrun if this is the final destination. |
+ ------------------------------------------------*/
+ if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) ||
+ *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX))
+ bOver = false;
+
+ /*----------------------------------------------------
+ Decide how big a step to attempt in each direction. |
+ ----------------------------------------------------*/
+ sidesteps = *targetX == -1 ? 0 : *targetX - fromx;
+ sidesteps = ABS(sidesteps);
+
+ depthsteps = *targetY == -1 ? 0 : *targetY - fromy;
+ depthsteps = ABS(depthsteps);
+
+ if (sidesteps && depthsteps > sidesteps) {
+ depthm = YMDIST;
+ sidem = depthm * sidesteps/depthsteps;
+
+ if (!sidem)
+ sidem = 1;
+ } else if (depthsteps && sidesteps > depthsteps) {
+ sidem = XMDIST;
+ depthm = sidem * depthsteps/sidesteps;
+
+ if (!depthm) {
+ if (bBodge)
+ depthm = 1;
+ } else if (depthm > YMDIST)
+ depthm = YMDIST;
+ } else {
+ sidem = sidesteps ? XMDIST : 0;
+ depthm = depthsteps ? YMDIST : 0;
+ }
+
+ *newx = fromx;
+ *newy = fromy;
+
+ /*------------------------------------------------------------
+ If Left-Right movement is required - then make the move, |
+ but don't overshoot, and do notice when we're already there |
+ ------------------------------------------------------------*/
+ if (*targetX == -1)
+ *s1 |= XTHERE;
+ else {
+ if (*targetX > fromx) { /* To the right? */
+ *newx += sidem; // Move to the right...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx > *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else if (*targetX < fromx) { /* To the left? */
+ *newx -= sidem; // Move to the left...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx < *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else {
+ *targetX = -1; // We're already there!
+ *s1 |= XTHERE;
+ }
+ }
+
+ /*--------------------------------------------------------------
+ If Up-Down movement is required - then make the move,
+ but don't overshoot, and do notice when we're already there
+ --------------------------------------------------------------*/
+ if (*targetY == -1)
+ *s1 |= YTHERE;
+ else {
+ if (*targetY > fromy) { /* Downwards? */
+ *newy += depthm; // Move down...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy > *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else if (*targetY < fromy) { /* Upwards? */
+ *newy -= depthm; // Move up...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy < *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else {
+ *targetY = -1; // We're already there!
+ *s1 |= YTHERE;
+ }
+ }
+
+ /* Give over if this is it */
+ if (*s1 == (XTHERE | YTHERE))
+ return;
+
+ /*------------------------------------------------------
+ Have worked out where an optimum step would take us.
+ Must now check if it's in a legal spot.
+ ------------------------------------------------------*/
+
+ if (!pActor->bNoPath && !pActor->bIgPath) {
+ /*------------------------------
+ Must stay in a path polygon.
+ -------------------------------*/
+ hPoly = InPolygon(*newx, *newy, PATH);
+ if (hPoly == NOPOLY) {
+ *s2 = LEAVING_PATH; // Trying to leave the path polygons
+
+ if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ Must stay out of blocking polygons.
+ --------------------------------------*/
+ hPoly = InPolygon(*newx, *newy, BLOCKING);
+ if (hPoly != NOPOLY) {
+ *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly
+ *hS2p = hPoly;
+
+ if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ /*------------------------------------------------------
+ Must stay out of moving actors' blocking polygons.
+ ------------------------------------------------------*/
+ ma = InMActorBlock(pActor, *newx, *newy);
+ if (ma != NULL) {
+ // Ignore if already in it (it may have just appeared)
+ if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) {
+ *s2 = ENTERING_MBLOCK; // Trying to walk through an actor
+
+ *hS2p = -1;
+ if (collisionActor)
+ *collisionActor = ma;
+
+ if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL
+ && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL
+ && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * SetOffWithinNodePath
+ */
+static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath,
+ int targetX, int targetY) {
+ int endnode;
+ HPOLYGON hIpath;
+ int nx, ny;
+ int x, y;
+
+ if (StartPath == DestPath) {
+ if (pActor->line == pActor->Tline) {
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ } else if (pActor->line < pActor->Tline) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else if (pActor->line > pActor->Tline) {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ } else {
+ /*
+ * Leaving this path - work out
+ * which end of this path to head for.
+ */
+ assert(DestPath != NOPOLY); // Error 702
+ if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) {
+ // This should never happen!
+ // It's the old code that didn't always work.
+ endnode = NearestEndNode(StartPath, targetX, targetY);
+ } else {
+ if (PolySubtype(hIpath) != NODE) {
+ x = PolyCentreX(hIpath);
+ y = PolyCentreY(hIpath);
+ endnode = NearestEndNode(StartPath, x, y);
+ } else {
+ endnode = NearEndNode(StartPath, hIpath);
+ }
+ }
+
+#if 1
+ if ((pActor->npstatus == LEAVING) &&
+ endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) {
+ // Leave it be
+ } else
+#endif
+ {
+ if (endnode) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ }
+ }
+}
+
+/**
+ * Restore a movement, called from restoreMovement() in ACTORS.CPP
+ */
+void SSetActorDest(PMACTOR pActor) {
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY,
+ pActor->bIgPath, 0);
+ }
+ } else {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+ }
+}
+
+/**
+ * Initiate a movement, called from WalkTo_Event()
+ */
+void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) {
+ HPOLYGON StartPath, DestPath = 0;
+ int targetX, targetY;
+
+ if (pActor->actorID == LeadId()) // Now only for lead actor
+ UnTagActor(pActor->actorID); // Tag not allowed while moving
+ pActor->ticket++;
+ pActor->stop = false;
+ pActor->over = false;
+ pActor->fromx = pActor->objx;
+ pActor->fromy = pActor->objy;
+ pActor->bMoving = true;
+ pActor->bIgPath = igPath;
+
+ // Use the supplied reel or restore the normal actor.
+ if (film != 0)
+ AlterMActor(pActor, film, AR_WALKREEL);
+ else
+ AlterMActor(pActor, 0, AR_NORMAL);
+
+ if (igPath) {
+ targetX = clickX;
+ targetY = clickY;
+ } else {
+ if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) {
+ GotThere(pActor);
+ return;
+ }
+ assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination!
+ assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination!
+ }
+
+
+ /***** Now have a destination to aim for. *****/
+
+ /*----------------------------------
+ | Don't move if it's not worth it.
+ ----------------------------------*/
+ if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ /*------------------------------------------------------
+ | If the destiation is within a follow nodes polygon,
+ | set destination as the nearest node.
+ ------------------------------------------------------*/
+ if (!igPath) {
+ DestPath = InPolygon(targetX, targetY, PATH);
+ if (PolySubtype(DestPath) == NODE) {
+ // Find the nearest point on a line, or nearest node
+ FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline);
+ }
+ }
+
+ assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005
+ SetMoverUltDest(pActor, targetX, targetY);
+ SetMoverIntDest(pActor, targetX, targetY);
+
+ /*-------------------------------------------------------------------
+ | If in a follow nodes path, need to set off in the right direction! |
+ -------------------------------------------------------------------*/
+ if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) {
+ SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY);
+ } else {
+ // Set off!
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Change scale if appropriate.
+ */
+static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) {
+ int scale;
+
+ scale = GetScale(hPath, ypos);
+ if (scale != pActor->scale) {
+ SetMActorWalkReel(pActor, pActor->dirn, scale, false);
+ }
+}
+
+/**
+ * Not going anywhere - Kick off again if not at final destination.
+ */
+static void NotMoving(PMACTOR pActor, int x, int y) {
+ pActor->targetX = pActor->targetY = -1;
+
+// if (x == pActor->UtargetX && y == pActor->UtargetY)
+ if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (pActor->ItargetX != -1 || pActor->ItargetY != -1) {
+ SetNextDest(pActor);
+ } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Does the necessary business when entering a different path polygon.
+ */
+static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) {
+ int firstnode; // First node to go to
+ int lastnode; // Last node to go to
+ HPOLYGON hIpath;
+ int nx, ny;
+ int nxl, nyl;
+
+ pActor->hCpath = hPath; // current path
+
+ if (hPath == NOPOLY) {
+ // Not proved this ever happens, but just in case
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ return;
+ }
+
+ // Is new path a node path?
+ if (PolySubtype(hPath) == NODE) {
+ // Node path - usually go to nearest end node
+ firstnode = NearestEndNode(hPath, x, y);
+ lastnode = -1;
+
+ // If this is not the destination path,
+ // find which end nodfe we wish to leave via
+ if (hPath != pActor->hUpath) {
+ if (pActor->bIgPath) {
+ lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ assert(pActor->hUpath != NOPOLY); // Error 703
+ hIpath = getPathOnTheWay(hPath, pActor->hUpath);
+ assert(hIpath != NOPOLY); // No path on the way
+
+ if (PolySubtype(hIpath) != NODE) {
+ lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath));
+ } else {
+ lastnode = NearEndNode(hPath, hIpath);
+ }
+ }
+ }
+ // Test for pseudo-one-node npaths
+ if (lastnode != -1 && numNodes(hPath) == 2) {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ getNpathNode(hPath, lastnode, &nxl, &nyl);
+ if (nxl == nx && nyl == ny)
+ firstnode = lastnode;
+ }
+
+ // If leaving by same node as entering, don't bother.
+ if (lastnode == firstnode) {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ } else {
+ // Head for first node
+ pActor->over = true;
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ pActor->line = firstnode ? firstnode - 1 : firstnode;
+ if (pActor->line == pActor->Tline && hPath == pActor->hUpath) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ // This doesn't seem right
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ if (ABS(pActor->objx - nx) < XMDIST
+ && ABS(pActor->objy - ny) < YMDIST) {
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ SetNextDest(pActor);
+ } else {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ }
+ }
+ }
+ return;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009
+// Added 26/01/95
+ if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY))
+ return;
+
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Move
+ */
+void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) {
+ MultiSetAniXY(pActor->actorObj, newx, newy);
+ MAsetZPos(pActor, newy, getPolyZfactor(hPath));
+ if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) {
+ // The end of a scale-change reel
+ // Revert to normal walking reel
+ pActor->walkReel = false;
+ pActor->scount = 0;
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ }
+ pActor->objx = newx;
+ pActor->objy = newy;
+
+ // Synchronised walking reels
+ if (++pActor->scount >= 6)
+ pActor->scount = 0;
+}
+
+/**
+ * Called from MActorProcess() on every tick.
+ *
+ * Moves the actor as appropriate.
+ */
+void MoveActor(PMACTOR pActor) {
+ int newx, newy;
+ HPOLYGON hPath;
+ int status, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ HPOLYGON hEb;
+ PMACTOR ma;
+ int sTargetX, sTargetY;
+ bool bNewPath = false;
+
+ // Only do anything if the actor needs to move!
+ if (pActor->targetX == -1 && pActor->targetY == -1)
+ return;
+
+ if (pActor->stop) {
+ GotThere(pActor);
+ pActor->stop = false;
+ SetMActorStanding(pActor);
+ return;
+ }
+
+#if SLOW_RINCE_DOWN
+/**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code
+/**/ return; //
+/**/ BogusVar = 0; //
+#endif
+
+ // During swalk()s, movement while hidden may be slowed down.
+ if (pActor->aHidden) {
+ if (++hSlowVar < pActor->SlowFactor)
+ return;
+ hSlowVar = 0;
+ }
+
+ // 'push' the target
+ sTargetX = pActor->targetX;
+ sTargetY = pActor->targetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY,
+ &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (newx == pActor->objx && newy == pActor->objy) {
+ // 'pop' the target
+ pActor->targetX = sTargetX;
+ pActor->targetY = sTargetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy,
+ &status, &s2, &hS2p, pActor->over, true, pActor);
+ if (newx == pActor->objx && newy == pActor->objy) {
+ NotMoving(pActor, newx, newy);
+ return;
+ }
+ }
+
+ // Find out which path we're in now
+ hPath = InPolygon(newx, newy, PATH);
+ if (hPath == NOPOLY) {
+ if (pActor->bNoPath) {
+ Move(pActor, newx, newy, pActor->hCpath);
+ return;
+ } else {
+ // May be marginally outside!
+ // OR bIgPath may be set.
+ hPath = pActor->hCpath;
+ }
+ } else if (pActor->bNoPath) {
+ pActor->bNoPath = false;
+ bNewPath = true;
+ } else if (hPath != pActor->hCpath) {
+ if (IsInPolygon(newx, newy, pActor->hCpath))
+ hPath = pActor->hCpath;
+ }
+
+ CheckScale(pActor, hPath, newy);
+
+ /*
+ * Must stay out of moving actors' blocking polygons.
+ */
+ ma = InMActorBlock(pActor, newx, newy);
+ if (ma != NULL) {
+ // Stop if there's no chance of arriving
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (InMActorBlock(pActor, pActor->objx, pActor->objy))
+ ;
+ else {
+ hEb = InitExtraBlock(pActor, ma);
+ newx = pActor->objx;
+ newy = pActor->objy;
+ BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, newx, newy);
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ This is where it actually gets moved.
+ --------------------------------------*/
+ Move(pActor, newx, newy, hPath);
+
+ // Entering a new path polygon?
+ if (hPath != pActor->hCpath || bNewPath)
+ EnteringNewPath(pActor, hPath, newx, newy);
+}
+
+/**
+ * Store the default refer type for the current scene.
+ */
+void SetDefaultRefer(int32 defRefer) {
+ DefaultRefer = defRefer;
+}
+
+/**
+ * DoMoveActor
+ */
+void DoMoveActor(PMACTOR pActor) {
+ int wasx, wasy;
+ int i;
+
+#define NUMBER 1
+
+ wasx = pActor->objx;
+ wasy = pActor->objy;
+
+ MoveActor(pActor);
+
+ if ((pActor->targetX != -1 || pActor->targetY != -1)
+ && (wasx == pActor->objx && wasy == pActor->objy)) {
+ for (i=0; i < NUMBER; i++) {
+ MoveActor(pActor);
+ if (wasx != pActor->objx || wasy != pActor->objy)
+ break;
+ }
+// assert(i<NUMBER);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h
new file mode 100644
index 0000000000..2c5f2cfe73
--- /dev/null
+++ b/engines/tinsel/move.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_MOVE_H // prevent multiple includes
+#define TINSEL_MOVE_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct MACTOR;
+
+void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film);
+void SSetActorDest(MACTOR *pActor);
+void DoMoveActor(MACTOR *pActor);
+
+void SetDefaultRefer(int32 defRefer);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_MOVE_H */
diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp
new file mode 100644
index 0000000000..c60592069f
--- /dev/null
+++ b/engines/tinsel/multiobj.cpp
@@ -0,0 +1,533 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle multi-part objects.
+ */
+
+#include "tinsel/multiobj.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+
+namespace Tinsel {
+
+// from object.c
+extern OBJECT *objectList;
+
+/**
+ * Initialise a multi-part object using a list of images to init
+ * each object piece. One object is created for each image in the list.
+ * All objects are given the same palette as the first image. A pointer
+ * to the first (master) object created is returned.
+ * @param pInitTbl Pointer to multi-object initialisation table
+ */
+OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) {
+ OBJ_INIT obj_init; // object init table
+ OBJECT *pFirst, *pObj; // object pointers
+ FRAME *pFrame; // list of images for the multi-part object
+
+ if (pInitTbl->hMulFrame) {
+ // we have a frame handle
+ pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame));
+
+ obj_init.hObjImg = READ_LE_UINT32(pFrame); // first objects shape
+ } else { // this must be a animation list for a NULL object
+ pFrame = NULL;
+ obj_init.hObjImg = 0; // first objects shape
+ }
+
+ // init the object init table
+ obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags); // all objects have same flags
+ obj_init.objID = (int)FROM_LE_32(pInitTbl->mulID); // all objects have same ID
+ obj_init.objX = (int)FROM_LE_32(pInitTbl->mulX); // all objects have same X ani pos
+ obj_init.objY = (int)FROM_LE_32(pInitTbl->mulY); // all objects have same Y ani pos
+ obj_init.objZ = (int)FROM_LE_32(pInitTbl->mulZ); // all objects have same Z pos
+
+ // create and init the first object
+ pObj = pFirst = InitObject(&obj_init);
+
+ if (pFrame) {
+ // if we have any animation frames
+
+ pFrame++;
+
+ while (READ_LE_UINT32(pFrame) != 0) {
+ // set next objects shape
+ obj_init.hObjImg = READ_LE_UINT32(pFrame);
+
+ // create next object and link to previous
+ pObj = pObj->pSlave = InitObject(&obj_init);
+
+ pFrame++;
+ }
+ }
+
+ // null end of list for final object
+ pObj->pSlave = NULL;
+
+ // return master object
+ return pFirst;
+}
+
+/**
+ * Inserts the multi-part object onto the specified object list.
+ * @param pObjList List to insert multi-part object onto
+* @param pInsObj Head of multi-part object to insert
+
+ */
+
+void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // add next part to the specified list
+ InsertObject(pObjList, pInsObj);
+
+ // next obj in list
+ pInsObj = pInsObj->pSlave;
+ } while (pInsObj != NULL);
+}
+
+/**
+ * Deletes all the pieces of a multi-part object from the
+ * specified object list.
+ * @param pObjList List to delete multi-part object from
+ * @param pMultiObj Multi-part object to be deleted
+ */
+
+void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // delete object
+ DelObject(pObjList, pMultiObj);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Hides a multi-part object by giving each object a "NullImage"
+ * image pointer.
+ * @param pMultiObj Multi-part object to be hidden
+ */
+
+void MultiHideObject(OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // set master shape to null animation frame
+ pMultiObj->hShape = 0;
+
+ // change all objects
+ MultiReshape(pMultiObj);
+}
+
+/**
+ * Horizontally flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiHorizontalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // horizontally flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ } while (pFlipObj != NULL);
+}
+
+/**
+ * Vertically flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiVerticalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // vertically flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ }
+ while (pFlipObj != NULL);
+}
+
+/**
+ * Adjusts the coordinates of a multi-part object. The adjustments
+ * take into account the orientation of the object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X adjustment
+ * @param deltaY Y adjustment
+ */
+
+void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ if (pMultiObj->flags & DMA_FLIPH) {
+ // image is flipped horizontally - flip the x direction
+ deltaX = -deltaX;
+ }
+
+ if (pMultiObj->flags & DMA_FLIPV) {
+ // image is flipped vertically - flip the y direction
+ deltaY = -deltaY;
+ }
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Moves all the pieces of a multi-part object by the specified
+ * amount. Does not take into account the objects orientation.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X movement
+ * @param deltaY Y movement
+ */
+
+void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Sets the x & y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose position is to be changed
+ * @param newAniX New x animation position
+ * @param newAniY New y animation position
+ */
+
+void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc difference between current and new positions
+ newAniX -= curAniX;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, newAniY);
+}
+
+/**
+ * Sets the x anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New x animation position
+ */
+
+void MultiSetAniX(OBJECT *pMultiObj, int newAniX) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc x difference between current and new positions
+ newAniX -= curAniX;
+ curAniY = 0;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, curAniY);
+}
+
+/**
+ * Sets the y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New y animation position
+ */
+
+void MultiSetAniY(OBJECT *pMultiObj, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc y difference between current and new positions
+ curAniX = 0;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, curAniX, newAniY);
+}
+
+/**
+ * Sets the Z position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param newZ New Z order
+ */
+
+void MultiSetZPosition(OBJECT *pMultiObj, int newZ) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // set the new z position
+ pMultiObj->zPos = newZ;
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Reshape a multi-part object.
+ * @param pMultiObj Multi-part object to re-shape
+ */
+
+void MultiReshape(OBJECT *pMultiObj) {
+ SCNHANDLE hFrame;
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get objects current anim frame
+ hFrame = pMultiObj->hShape;
+
+ if (hFrame != 0 && hFrame != pMultiObj->hMirror) {
+ // a valid shape frame which is different from previous
+
+ // get pointer to frame
+ const FRAME *pFrame = (const FRAME *)LockMem(hFrame);
+
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) {
+ // a normal image - update the current object with this image
+ AnimateObject(pMultiObj, READ_LE_UINT32(pFrame));
+
+ // move to next image for this frame
+ pFrame++;
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+
+ // null the remaining object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ } else if (hFrame == 0) {
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ // null all the object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ }
+}
+
+/**
+ * Returns the left-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLeftmost(OBJECT *pMulti) {
+ int left;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init leftmost point to first object
+ left = fracToInt(pMulti->xPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) < left)
+ // this object is further left
+ left = fracToInt(pMulti->xPos);
+ }
+ }
+
+ // return left-most point
+ return left;
+}
+
+/**
+ * Returns the right-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiRightmost(OBJECT *pMulti) {
+ int right;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init right-most point to first object
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) + pMulti->width > right)
+ // this object is further right
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+ }
+ }
+
+ // return right-most point
+ return right - 1;
+}
+
+/**
+ * Returns the highest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiHighest(OBJECT *pMulti) {
+ int highest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init highest point to first object
+ highest = fracToInt(pMulti->yPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) < highest)
+ // this object is higher
+ highest = fracToInt(pMulti->yPos);
+ }
+ }
+
+ // return highest point
+ return highest;
+}
+
+/**
+ * Returns the lowest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLowest(OBJECT *pMulti) {
+ int lowest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init lowest point to first object
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) + pMulti->height > lowest)
+ // this object is lower
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+ }
+ }
+
+ // return lowest point
+ return lowest - 1;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h
new file mode 100644
index 0000000000..6d25600ea2
--- /dev/null
+++ b/engines/tinsel/multiobj.h
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Multi-part object definitions
+ */
+
+#ifndef TINSEL_MULTIOBJ_H // prevent multiple includes
+#define TINSEL_MULTIOBJ_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * multi-object initialisation structure (parallels OBJ_INIT struct)
+ */
+struct MULTI_INIT {
+ SCNHANDLE hMulFrame; //!< multi-objects shape - NULL terminated list of IMAGE structures
+ int32 mulFlags; //!< multi-objects flags
+ int32 mulID; //!< multi-objects id
+ int32 mulX; //!< multi-objects initial x ani position
+ int32 mulY; //!< multi-objects initial y ani position
+ int32 mulZ; //!< multi-objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/*----------------------------------------------------------------------*\
+|* Multi Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *MultiInitObject( // Initialise a multi-part object
+ const MULTI_INIT *pInitTbl); // pointer to multi-object initialisation table
+
+void MultiInsertObject( // Insert a multi-part object onto a object list
+ OBJECT *pObjList, // list to insert multi-part object onto
+ OBJECT *pInsObj); // head of multi-part object to insert
+
+void MultiDeleteObject( // Delete all the pieces of a multi-part object
+ OBJECT *pObjList, // list to delete multi-part object from
+ OBJECT *pMultiObj); // multi-part object to be deleted
+
+void MultiHideObject( // Hide a multi-part object
+ OBJECT *pMultiObj); // multi-part object to be hidden
+
+void MultiHorizontalFlip( // Hortizontally flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiVerticalFlip( // Vertically flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiAdjustXY( // Adjust coords of a multi-part object. Takes into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int deltaX, // x adjustment
+ int deltaY); // y adjustment
+
+void MultiMoveRelXY( // Move multi-part object relative. Does not take into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be moved
+ int deltaX, // x movement
+ int deltaY); // y movement
+
+void MultiSetAniXY( // Set the x & y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose position is to be changed
+ int newAniX, // new x animation position
+ int newAniY); // new y animation position
+
+void MultiSetAniX( // Set the x anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose x position is to be changed
+ int newAniX); // new x animation position
+
+void MultiSetAniY( // Set the y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose y position is to be adjusted
+ int newAniY); // new y animation position
+
+void MultiSetZPosition( // Sets the z position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int newZ); // new Z order
+
+void MultiMatchAniPoints( // Matches a multi-parts pos and orientation to be the same as a reference object
+ OBJECT *pMoveObj, // multi-part object to be moved
+ OBJECT *pRefObj); // multi-part object to match with
+
+void MultiReshape( // Reshape a multi-part object
+ OBJECT *pMultiObj); // multi-part object to re-shape
+
+int MultiLeftmost( // Returns the left-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiRightmost( // Returns the right-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiHighest( // Returns the highest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiLowest( // Returns the lowest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_MULTIOBJ_H
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
new file mode 100644
index 0000000000..7d4efd8079
--- /dev/null
+++ b/engines/tinsel/music.cpp
@@ -0,0 +1,551 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// FIXME: This code is taken from MADE and may need more work (e.g. setVolume).
+
+// MIDI and digital music class
+
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/audiocd.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+
+#include "tinsel/config.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+namespace Tinsel {
+
+//--------------------------- Midi data -------------------------------------
+
+// sound buffer structure used for MIDI data and samples
+struct SOUND_BUFFER {
+ uint8 *pDat; // pointer to actual buffer
+ uint32 size; // size of the buffer
+};
+
+// get set when music driver is installed
+//static MDI_DRIVER *mDriver;
+//static HSEQUENCE mSeqHandle;
+
+// if non-zero this is the index position of the next MIDI sequence to play
+static uint32 dwMidiIndex = 0;
+
+// MIDI buffer
+static SOUND_BUFFER midiBuffer = { 0, 0 };
+
+static SCNHANDLE currentMidi = 0;
+static bool currentLoop = false;
+
+const SCNHANDLE midiOffsetsGRAVersion[] = {
+ 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450,
+ 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898,
+ 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762,
+ 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988,
+ 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566,
+ 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174,
+ 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946,
+ 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540,
+ 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680,
+ 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306,
+ 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812,
+ 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832,
+ 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708,
+ 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016,
+ 794630, 796422, 798998
+};
+
+const SCNHANDLE midiOffsetsSCNVersion[] = {
+ 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108,
+ 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264,
+ 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638,
+ 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754,
+ 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712,
+ 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478,
+ 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296,
+ 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680,
+ 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716,
+ 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562,
+ 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016,
+ 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982,
+ 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342,
+ 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042,
+ 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066,
+ 869822, 875436, 877234, 879818
+};
+
+// TODO: finish this (currently unmapped tracks are 0)
+const int enhancedAudioSCNVersion[] = {
+ 0, 0, 2, 0, 0, 0, 0, 3, 3, 4,
+ 4, 0, 0, 0, 0, 0, 0, 10, 3, 11,
+ 11, 0, 13, 13, 13, 13, 13, 0, 13, 13,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 24, 0, 0, 27, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 55, 56, 56, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 4, 83, 83, 83, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 52, 4,
+ 0, 0, 0, 0
+};
+
+int GetTrackNumber(SCNHANDLE hMidi) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) {
+ if (midiOffsetsSCNVersion[i] == hMidi)
+ return i;
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) {
+ if (midiOffsetsGRAVersion[i] == hMidi)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+SCNHANDLE GetTrackOffset(int trackNumber) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion));
+ return midiOffsetsSCNVersion[trackNumber];
+ } else {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion));
+ return midiOffsetsGRAVersion[trackNumber];
+ }
+}
+
+/**
+ * Plays the specified MIDI sequence through the sound driver.
+ * @param dwFileOffset File offset of MIDI sequence data
+ * @param bLoop Whether to loop the sequence
+ */
+bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) {
+ currentMidi = dwFileOffset;
+ currentLoop = bLoop;
+
+ if (volMidi != 0) {
+ SetMidiVolume(volMidi);
+ // Support for compressed music from the music enhancement project
+ AudioCD.stop();
+
+ int trackNumber = GetTrackNumber(dwFileOffset);
+ if (trackNumber >= 0) {
+#if 0
+ // TODO: GRA version
+ int track = enhancedAudioSCNVersion[trackNumber];
+ if (track > 0)
+ AudioCD.play(track, -1, 0, 0);
+#endif
+ } else {
+ warning("Unknown MIDI offset %d", dwFileOffset);
+ }
+
+ if (AudioCD.isPlaying())
+ return true;
+ }
+
+ // set file offset for this sequence
+ dwMidiIndex = dwFileOffset;
+
+ // the index and length of the last tune loaded
+ static uint32 dwLastMidiIndex;
+ static uint32 dwLastSeqLen;
+
+ uint32 dwSeqLen = 0; // length of the sequence
+
+ if (dwMidiIndex == 0)
+ return true;
+
+ if (dwMidiIndex != dwLastMidiIndex) {
+ Common::File midiStream;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // update index of last tune loaded
+ dwLastMidiIndex = dwMidiIndex;
+
+ // move to correct position in the file
+ midiStream.seek(dwMidiIndex, SEEK_SET);
+
+ // read the length of the sequence
+ dwSeqLen = midiStream.readUint32LE();
+
+ // make sure buffer is large enough for this sequence
+ assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size);
+
+ // stop any currently playing tune
+ _vm->_music->stop();
+
+ // read the sequence
+ if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen)
+ error("File %s is corrupt", MIDI_FILE);
+
+ midiStream.close();
+
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+
+ // Store the length
+ dwLastSeqLen = dwSeqLen;
+ } else {
+ // dwMidiIndex == dwLastMidiIndex
+ _vm->_music->stop();
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+ }
+
+ // allow another sequence to play
+ dwMidiIndex = 0;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if a Midi tune is currently playing.
+ */
+bool MidiPlaying(void) {
+ if (AudioCD.isPlaying()) return true;
+ return _vm->_music->isPlaying();
+}
+
+/**
+ * Stops any currently playing midi.
+ */
+bool StopMidi(void) {
+ currentMidi = 0;
+ currentLoop = false;
+
+ AudioCD.stop();
+ _vm->_music->stop();
+ return true;
+}
+
+
+/**
+ * Gets the volume of the MIDI music.
+ */
+int GetMidiVolume() {
+ return volMidi;
+}
+
+/**
+ * Sets the volume of the MIDI music.
+ * @param vol New volume - 0..MAXMIDIVOL
+ */
+void SetMidiVolume(int vol) {
+ assert(vol >= 0 && vol <= MAXMIDIVOL);
+
+ if (vol == 0 && volMidi == 0) {
+ // Nothing to do
+ } else if (vol == 0 && volMidi != 0) {
+ // Stop current midi sequence
+ AudioCD.stop();
+ StopMidi();
+ } else if (vol != 0 && volMidi == 0) {
+ // Perhaps restart last midi sequence
+ if (currentLoop) {
+ PlayMidiSequence(currentMidi, true);
+ _vm->_music->setVolume(vol);
+ }
+ } else if (vol != 0 && volMidi != 0) {
+ // Alter current volume
+ _vm->_music->setVolume(vol);
+ }
+
+ volMidi = vol;
+}
+
+/**
+ * Opens and inits all MIDI sequence files.
+ */
+void OpenMidiFiles(void) {
+ Common::File midiStream;
+
+ // Demo version has no midi file
+ if (_vm->getFeatures() & GF_DEMO)
+ return;
+
+ if (midiBuffer.pDat)
+ // already allocated
+ return;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // gen length of the largest sequence
+ midiBuffer.size = midiStream.readUint32LE();
+ if (midiStream.ioFailed())
+ error("File %s is corrupt", MIDI_FILE);
+
+ if (midiBuffer.size) {
+ // allocate a buffer big enough for the largest MIDI sequence
+ if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) {
+ // clear out the buffer
+ memset(midiBuffer.pDat, 0, midiBuffer.size);
+// VMM_lock(midiBuffer.pDat, midiBuffer.size);
+ } else {
+ //mSeqHandle = NULL;
+ }
+ }
+
+ midiStream.close();
+}
+
+void DeleteMidiBuffer() {
+ free(midiBuffer.pDat);
+ midiBuffer.pDat = NULL;
+}
+
+MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ this->open();
+ _xmidiParser = MidiParser::createParser_XMIDI();
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stop();
+ this->close();
+ _xmidiParser->setMidiDriver(NULL);
+ delete _xmidiParser;
+}
+
+void MusicPlayer::setVolume(int volume) {
+ Common::StackLock lock(_mutex);
+
+ // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range?
+ volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+
+ if (_masterVolume == volume)
+ return;
+
+ _masterVolume = volume;
+
+ for (int i = 0; i < 16; ++i) {
+ if (_channel[i]) {
+ _channel[i]->volume(_channelVolume[i] * _masterVolume / 255);
+ }
+ }
+}
+
+int MusicPlayer::open() {
+ // Don't ever call open without first setting the output driver!
+ if (!_driver)
+ return 255;
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ _driver->setTimerCallback(this, &onTimer);
+ return 0;
+}
+
+void MusicPlayer::close() {
+ stop();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::send(uint32 b) {
+ byte channel = (byte)(b & 0x0F);
+ if ((b & 0xFFF0) == 0x07B0) {
+ // Adjust volume changes by master volume
+ byte volume = (byte)((b >> 16) & 0x7F);
+ _channelVolume[channel] = volume;
+ volume = volume * _masterVolume / 255;
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ } else if ((b & 0xFFF0) == 0x007BB0) {
+ //Only respond to All Notes Off if this channel
+ //has currently been allocated
+ if (_channel[b & 0x0F])
+ return;
+ }
+
+ if (!_channel[channel])
+ _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+
+ if (_channel[channel]) {
+ _channel[channel]->send(b);
+
+ if ((b & 0xFFF0) == 0x0079B0) {
+ // We've just Reset All Controllers, so we need to
+ // re-adjust the volume. Otherwise, volume is reset to
+ // default whenever the music changes.
+ _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel);
+ }
+ }
+}
+
+void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ switch (type) {
+ case 0x2F: // End of Track
+ if (_looping)
+ _parser->jumpToTick(0);
+ else
+ stop();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ Common::StackLock lock(music->_mutex);
+
+ if (music->_isPlaying)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) {
+ if (_isPlaying)
+ return;
+
+ stop();
+
+ // It seems like not all music (the main menu music, for instance) set
+ // all the instruments explicitly. That means the music will sound
+ // different, depending on which music played before it. This appears
+ // to be a genuine glitch in the original. For consistency, reset all
+ // instruments to the default one (piano).
+
+ for (int i = 0; i < 16; i++) {
+ _driver->send(0xC0 | i, 0, 0);
+ }
+
+ // Load XMID resource data
+
+ if (_xmidiParser->loadMusic(midiData, size)) {
+ MidiParser *parser = _xmidiParser;
+ parser->setTrack(0);
+ parser->setMidiDriver(this);
+ parser->setTimerRate(getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _parser = parser;
+
+ _looping = loop;
+ _isPlaying = true;
+ }
+}
+
+void MusicPlayer::stop() {
+ Common::StackLock lock(_mutex);
+
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+}
+
+void MusicPlayer::pause() {
+ setVolume(-1);
+ _isPlaying = false;
+}
+
+void MusicPlayer::resume() {
+ setVolume(GetMidiVolume());
+ _isPlaying = true;
+}
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) {
+ *pMidi = currentMidi;
+ *pLoop = currentLoop;
+}
+
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) {
+ AudioCD.stop();
+ StopMidi();
+
+ currentMidi = Midi;
+ currentLoop = Loop;
+
+ if (volMidi != 0 && Loop) {
+ PlayMidiSequence(currentMidi, true);
+ SetMidiVolume(volMidi);
+ }
+}
+
+#if 0
+// Dumps all of the game's music in external XMIDI *.xmi files
+void dumpMusic() {
+ Common::File midiFile;
+ Common::DumpFile outFile;
+ char outName[20];
+ midiFile.open(MIDI_FILE);
+ int outFileSize = 0;
+ char buffer[20000];
+
+ int total = (_vm->getFeatures() & GF_SCNFILES) ?
+ ARRAYSIZE(midiOffsetsSCNVersion) :
+ ARRAYSIZE(midiOffsetsGRAVersion);
+
+ for (int i = 0; i < total; i++) {
+ sprintf(outName, "track%03d.xmi", i + 1);
+ outFile.open(outName);
+
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ if (i < total - 1)
+ outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET);
+ } else {
+ if (i < total - 1)
+ outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET);
+ }
+
+ assert(outFileSize < 20000);
+ midiFile.read(buffer, outFileSize);
+ outFile.write(buffer, outFileSize);
+
+ outFile.close();
+ }
+
+ midiFile.close();
+}
+#endif
+
+} // End of namespace Made
diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h
new file mode 100644
index 0000000000..80456e2a76
--- /dev/null
+++ b/engines/tinsel/music.h
@@ -0,0 +1,118 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Music class
+
+#ifndef TINSEL_MUSIC_H
+#define TINSEL_MUSIC_H
+
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "common/mutex.h"
+
+namespace Tinsel {
+
+#define MAXMIDIVOL 127
+
+bool PlayMidiSequence( // Plays the specified MIDI sequence through the sound driver
+ uint32 dwFileOffset, // handle of MIDI sequence data
+ bool bLoop); // Whether to loop the sequence
+
+bool MidiPlaying(void); // Returns TRUE if a Midi tune is currently playing
+
+bool StopMidi(void); // Stops any currently playing midi
+
+void SetMidiVolume( // Sets the volume of the MIDI music. Returns the old volume
+ int vol); // new volume - 0..MAXMIDIVOL
+
+int GetMidiVolume();
+
+void OpenMidiFiles();
+void DeleteMidiBuffer();
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop);
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop);
+
+int GetTrackNumber(SCNHANDLE hMidi);
+SCNHANDLE GetTrackOffset(int trackNumber);
+
+void dumpMusic();
+
+
+class MusicPlayer : public MidiDriver {
+public:
+ MusicPlayer(MidiDriver *driver);
+ ~MusicPlayer();
+
+ bool isPlaying() { return _isPlaying; }
+ void setPlaying(bool playing) { _isPlaying = playing; }
+
+ void setVolume(int volume);
+ int getVolume() { return _masterVolume; }
+
+ void playXMIDI(byte *midiData, uint32 size, bool loop);
+ void stop();
+ void pause();
+ void resume();
+ void setLoop(bool loop) { _looping = loop; }
+
+ //MidiDriver interface implementation
+ int open();
+ void close();
+ void send(uint32 b);
+
+ void metaEvent(byte type, byte *data, uint16 length);
+
+ void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { }
+
+ // The original sets the "sequence timing" to 109 Hz, whatever that
+ // means. The default is 120.
+
+ uint32 getBaseTempo(void) { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; }
+
+ //Channel allocation functions
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+
+ MidiParser *_parser;
+ Common::Mutex _mutex;
+
+protected:
+
+ static void onTimer(void *data);
+
+ MidiChannel *_channel[16];
+ MidiDriver *_driver;
+ MidiParser *_xmidiParser;
+ byte _channelVolume[16];
+
+ bool _isPlaying;
+ bool _looping;
+ byte _masterVolume;
+};
+
+} // End of namespace Made
+
+#endif
diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp
new file mode 100644
index 0000000000..709fa4fad9
--- /dev/null
+++ b/engines/tinsel/object.cpp
@@ -0,0 +1,530 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Object Manager code.
+ */
+
+#include "tinsel/object.h"
+#include "tinsel/background.h"
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // low level interface
+#include "tinsel/handle.h"
+
+#define OID_EFFECTS 0x2000 // generic special effects object id
+
+namespace Tinsel {
+
+/** screen clipping rectangle */
+static const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+// list of all objects
+OBJECT *objectList = 0;
+
+// pointer to free object list
+static OBJECT *pFreeObjects = 0;
+
+#ifdef DEBUG
+// diagnostic object counters
+static int numObj = 0;
+static int maxObj = 0;
+#endif
+
+void FreeObjectList(void) {
+ if (objectList) {
+ free(objectList);
+ objectList = NULL;
+ }
+}
+
+/**
+ * Kills all objects and places them on the free list.
+ */
+
+void KillAllObjects(void) {
+ int i;
+
+#ifdef DEBUG
+ // clear number of objects in use
+ numObj = 0;
+#endif
+
+ if (objectList == NULL) {
+ // first time - allocate memory for object list
+ objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT));
+
+ // make sure memory allocated
+ if (objectList == NULL) {
+ error("Cannot allocate memory for object data");
+ }
+ }
+
+ // place first object on free list
+ pFreeObjects = objectList;
+
+ // link all other objects after first
+ for (i = 1; i < NUM_OBJECTS; i++) {
+ objectList[i - 1].pNext = objectList + i;
+ }
+
+ // null the last object
+ objectList[NUM_OBJECTS - 1].pNext = NULL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of objects used at once.
+ */
+
+void ObjectStats(void) {
+ printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS);
+}
+#endif
+
+/**
+ * Allocate a object from the free list.
+ */
+OBJECT *AllocObject(void) {
+ OBJECT *pObj = pFreeObjects; // get a free object
+
+ // check for no free objects
+ assert(pObj != NULL);
+
+ // a free object exists
+
+ // get link to next free object
+ pFreeObjects = pObj->pNext;
+
+ // clear out object
+ memset(pObj, 0, sizeof(OBJECT));
+
+ // set default drawing mode and set changed bit
+ pObj->flags = DMA_WNZ | DMA_CHANGED;
+
+#ifdef DEBUG
+ // one more object in use
+ if (++numObj > maxObj)
+ maxObj = numObj;
+#endif
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Copy one object to another.
+ * @param pDest Destination object
+ * @param pSrc Source object
+ */
+void CopyObject(OBJECT *pDest, OBJECT *pSrc) {
+ // save previous dimensions etc.
+ Common::Rect rcSave = pDest->rcPrev;
+
+ // make a copy
+ memcpy(pDest, pSrc, sizeof(OBJECT));
+
+ // restore previous dimensions etc.
+ pDest->rcPrev = rcSave;
+
+ // set changed flag in destination
+ pDest->flags |= DMA_CHANGED;
+
+ // null the links
+ pDest->pNext = pDest->pSlave = NULL;
+}
+
+/**
+ * Inserts an object onto the specified object list. The object
+ * lists are sorted in Z Y order.
+ * @param pObjList List to insert object onto
+ * @param pInsObj Object to insert
+ */
+
+void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pInsObj->zPos < pObj->zPos) {
+ // object Z is lower than list Z - insert here
+ break;
+ } else if (pInsObj->zPos == pObj->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) {
+ // object Y is lower than or same as list Y - insert here
+ break;
+ }
+ }
+ }
+
+ // insert obj between pPrev and pObj
+ pInsObj->pNext = pObj;
+ pPrev->pNext = pInsObj;
+}
+
+
+/**
+ * Deletes an object from the specified object list and places it
+ * on the free list.
+ * @param pObjList List to delete object from
+ * @param pDelObj Object to delete
+ */
+void DelObject(OBJECT *pObjList, OBJECT *pDelObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1);
+
+#ifdef DEBUG
+ // one less object in use
+ --numObj;
+ assert(numObj >= 0);
+#endif
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ if (pObj == pDelObj) {
+ // found object to delete
+
+ if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) {
+ // allocate a clipping rect for objects previous pos
+ AddClipRect(pDelObj->rcPrev);
+ }
+
+ // make PREV next = OBJ next - removes OBJ from list
+ pPrev->pNext = pObj->pNext;
+
+ // place free list in OBJ next
+ pObj->pNext = pFreeObjects;
+
+ // add OBJ to top of free list
+ pFreeObjects = pObj;
+
+ // delete objects palette
+ if (pObj->pPal)
+ FreePalette(pObj->pPal);
+
+ // quit
+ return;
+ }
+ }
+
+ // if we get to here - object has not been found on the list
+ error("DelObject(): formally 'assert(0)!'");
+}
+
+
+/**
+ * Sort the specified object list in Z Y order.
+ * @param pObjList List to sort
+ */
+void SortObjectList(OBJECT *pObjList) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+ OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT
+
+ // put at head of list
+ head.pNext = pObjList->pNext;
+
+ // set head of list dummy OBJ Z Y values to lowest possible
+ head.yPos = intToFrac(MIN_INT16);
+ head.zPos = MIN_INT;
+
+ for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pObj->zPos < pPrev->zPos) {
+ // object Z is lower than previous Z
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ } else if (pObj->zPos == pPrev->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) {
+ // object Y is lower than previous Y
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ }
+ }
+ }
+}
+
+/**
+ * Returns the animation offsets of a image, dependent on the
+ * images orientation flags.
+ * @param hImg Iimage to get animation offset of
+ * @param flags Images current flags
+ * @param pAniX Gets set to new X animation offset
+ * @param pAniY Gets set to new Y animation offset
+ */
+void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) {
+ if (hImg) {
+ const IMAGE *pImg = (const IMAGE *)LockMem(hImg);
+
+ // set ani X
+ *pAniX = (int16) FROM_LE_16(pImg->anioffX);
+
+ // set ani Y
+ *pAniY = (int16) FROM_LE_16(pImg->anioffY);
+
+ if (flags & DMA_FLIPH) {
+ // we are flipped horizontally
+
+ // set ani X = -ani X + width - 1
+ *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1;
+ }
+
+ if (flags & DMA_FLIPV) {
+ // we are flipped vertically
+
+ // set ani Y = -ani Y + height - 1
+ *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1;
+ }
+ } else
+ // null image
+ *pAniX = *pAniY = 0;
+}
+
+
+/**
+ * Returns the x,y position of an objects animation point.
+ * @param pObj Pointer to object
+ * @param pPosX Gets set to objects X animation position
+ * @param pPosY Gets set to objects Y animation position
+ */
+void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) {
+ // validate object pointer
+ assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1);
+
+ // get the animation offset of the object
+ GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY);
+
+ // from animation offset and objects position - determine objects animation point
+ *pPosX += fracToInt(pObj->xPos);
+ *pPosY += fracToInt(pObj->yPos);
+}
+
+/**
+ * Initialise a object using a OBJ_INIT structure to supply parameters.
+ * @param pInitTbl Pointer to object initialisation table
+ */
+OBJECT *InitObject(const OBJ_INIT *pInitTbl) {
+ // allocate a new object
+ OBJECT *pObj = AllocObject();
+
+ // make sure object created
+ assert(pObj != NULL);
+
+ // set objects shape
+ pObj->hImg = pInitTbl->hObjImg;
+
+ // set objects ID
+ pObj->oid = pInitTbl->objID;
+
+ // set objects flags
+ pObj->flags = DMA_CHANGED | pInitTbl->objFlags;
+
+ // set objects Z position
+ pObj->zPos = pInitTbl->objZ;
+
+ // get pointer to image
+ if (pInitTbl->hObjImg) {
+ int aniX, aniY; // objects animation offsets
+ PALQ *pPalQ; // palette queue pointer
+ const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal));
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pObj->pPal = pPalQ;
+
+ // set objects size
+ pObj->width = FROM_LE_16(pImg->imgWidth);
+ pObj->height = FROM_LE_16(pImg->imgHeight);
+
+ // set objects bitmap definition
+ pObj->hBits = FROM_LE_32(pImg->hImgBits);
+
+ // get animation offset of object
+ GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY);
+
+ // set objects X position - subtract ani offset
+ pObj->xPos = intToFrac(pInitTbl->objX - aniX);
+
+ // set objects Y position - subtract ani offset
+ pObj->yPos = intToFrac(pInitTbl->objY - aniY);
+ } else { // no image handle - null image
+
+ // set objects X position
+ pObj->xPos = intToFrac(pInitTbl->objX);
+
+ // set objects Y position
+ pObj->yPos = intToFrac(pInitTbl->objY);
+ }
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Give a object a new image and new orientation flags.
+ * @param pAniObj Object to be updated
+ * @param newflags Objects new flags
+ * @param hNewImg Objects new image
+ */
+void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) {
+ // validate object pointer
+ assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1);
+
+ if (pAniObj->hImg != hNewImg
+ || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) {
+ // something has changed
+
+ int oldAniX, oldAniY; // objects old animation offsets
+ int newAniX, newAniY; // objects new animation offsets
+
+ // get objects old animation offsets
+ GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY);
+
+ // get objects new animation offsets
+ GetAniOffset(hNewImg, newflags, &newAniX, &newAniY);
+
+ if (hNewImg) {
+ // get pointer to image
+ const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg);
+
+ // setup new shape
+ pAniObj->width = FROM_LE_16(pNewImg->imgWidth);
+ pAniObj->height = FROM_LE_16(pNewImg->imgHeight);
+
+ // set objects bitmap definition
+ pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits);
+ } else { // null image
+ pAniObj->width = 0;
+ pAniObj->height = 0;
+ pAniObj->hBits = 0;
+ }
+
+ // set objects flags and signal a change
+ pAniObj->flags = newflags | DMA_CHANGED;
+
+ // set objects image
+ pAniObj->hImg = hNewImg;
+
+ // adjust objects position - subtract new from old for difference
+ pAniObj->xPos += intToFrac(oldAniX - newAniX);
+ pAniObj->yPos += intToFrac(oldAniY - newAniY);
+ }
+}
+
+/**
+ * Give an object a new image.
+ * @param pAniObj Object to animate
+ * @param hNewImg Objects new image
+ */
+void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) {
+ // dont change the objects flags
+ AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg);
+}
+
+/**
+ * Creates a rectangle object of the given dimensions and returns
+ * a pointer to the object.
+ * @param hPal Palette for the rectangle object
+ * @param colour Which colour offset from the above palette
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0};
+ PALQ *pPalQ; // palette queue pointer
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(hPal);
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pRect->pPal = pPalQ;
+
+ // set colour in the palette
+ pRect->constant = colour;
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+/**
+ * Creates a translucent rectangle object of the given dimensions
+ * and returns a pointer to the object.
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *TranslucentObject(int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0};
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h
new file mode 100644
index 0000000000..8b61571a3e
--- /dev/null
+++ b/engines/tinsel/object.h
@@ -0,0 +1,206 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object Manager data structures
+ */
+
+#ifndef TINSEL_OBJECT_H // prevent multiple includes
+#define TINSEL_OBJECT_H
+
+#include "tinsel/dw.h"
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+enum {
+ /** the maximum number of objects */
+ NUM_OBJECTS = 256,
+
+ // object flags
+ DMA_WNZ = 0x0001, //!< write non-zero data
+ DMA_CNZ = 0x0002, //!< write constant on non-zero data
+ DMA_CONST = 0x0004, //!< write constant on both zero & non-zero data
+ DMA_WA = 0x0008, //!< write all data
+ DMA_FLIPH = 0x0010, //!< flip object horizontally
+ DMA_FLIPV = 0x0020, //!< flip object vertically
+ DMA_CLIP = 0x0040, //!< clip object
+ DMA_TRANS = 0x0084, //!< translucent rectangle object
+ DMA_ABS = 0x0100, //!< position of object is absolute
+ DMA_CHANGED = 0x0200, //!< object has changed in some way since the last frame
+ DMA_USERDEF = 0x0400, //!< user defined flags start here
+
+ /** flags that effect an objects appearance */
+ DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS)
+};
+
+/** structure for image */
+struct IMAGE {
+ short imgWidth; //!< image width
+ short imgHeight; //!< image height
+ short anioffX; //!< image x animation offset
+ short anioffY; //!< image y animation offset
+ SCNHANDLE hImgBits; //!< image bitmap handle
+ SCNHANDLE hImgPal; //!< image palette handle
+};
+
+
+/** a multi-object animation frame is a list of multi-image handles */
+typedef uint32 FRAME;
+
+
+// object structure
+struct OBJECT {
+ OBJECT *pNext; //!< pointer to next object in list
+ OBJECT *pSlave; //!< pointer to slave object (multi-part objects)
+// char *pOnDispList; //!< pointer to display list byte for background objects
+// frac_t xVel; //!< x velocity of object
+// frac_t yVel; //!< y velocity of object
+ frac_t xPos; //!< x position of object
+ frac_t yPos; //!< y position of object
+ int zPos; //!< z position of object
+ Common::Rect rcPrev; //!< previous screen coordinates of object bounding rectangle
+ int flags; //!< object flags - see above for list
+ PALQ *pPal; //!< objects palette Q position
+ int constant; //!< which colour in palette for monochrome objects
+ int width; //!< width of object
+ int height; //!< height of object
+ SCNHANDLE hBits; //!< image bitmap handle
+ SCNHANDLE hImg; //!< handle to object image definition
+ SCNHANDLE hShape; //!< objects current animation frame
+ SCNHANDLE hMirror; //!< objects previous animation frame
+ int oid; //!< object identifier
+};
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+// object initialisation structure
+struct OBJ_INIT {
+ SCNHANDLE hObjImg; // objects shape - handle to IMAGE structure
+ int32 objFlags; // objects flags
+ int32 objID; // objects id
+ int32 objX; // objects initial x position
+ int32 objY; // objects initial y position
+ int32 objZ; // objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/*----------------------------------------------------------------------*\
+|* Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void KillAllObjects(void); // kill all objects and place them on free list
+
+void FreeObjectList(void); // free the object list
+
+#ifdef DEBUG
+void ObjectStats(void); // Shows the maximum number of objects used at once
+#endif
+
+OBJECT *AllocObject(void); // allocate a object from the free list
+
+void FreeObject( // place a object back on the free list
+ OBJECT *pFreeObj); // object to free
+
+void CopyObject( // copy one object to another
+ OBJECT *pDest, // destination object
+ OBJECT *pSrc); // source object
+
+void InsertObject( // insert a object onto a sorted object list
+ OBJECT *pObjList, // list to insert object onto
+ OBJECT *pInsObj); // object to insert
+
+void DelObject( // delete a object from a object list and add to free list
+ OBJECT *pObjList, // list to delete object from
+ OBJECT *pDelObj); // object to delete
+
+void SortObjectList( // re-sort an object list
+ OBJECT *pObjList); // list to sort
+
+OBJECT *GetNextObject( // object list iterator - returns next obj in list
+ OBJECT *pObjList, // which object list
+ OBJECT *pStrtObj); // object to start from - when NULL will start from beginning of list
+
+OBJECT *FindObject( // Searches the specified obj list for a object matching the specified OID
+ OBJECT *pObjList, // object list to search
+ int oidDesired, // object identifer of object to find
+ int oidMask); // mask to apply to object identifiers before comparison
+
+void GetAniOffset( // returns the anim offsets of a image, takes into account orientation
+ SCNHANDLE hImg, // image to get animation offset of
+ int flags, // images current flags
+ int *pAniX, // gets set to new X animation offset
+ int *pAniY); // gets set to new Y animation offset
+
+void GetAniPosition( // Returns a objects x,y animation point
+ OBJECT *pObj, // pointer to object
+ int *pPosX, // gets set to objects X animation position
+ int *pPosY); // gets set to objects Y animation position
+
+OBJECT *InitObject( // Init a object using a OBJ_INIT struct
+ const OBJ_INIT *pInitTbl); // pointer to object initialisation table
+
+void AnimateObjectFlags( // Give a object a new image and new orientation flags
+ OBJECT *pAniObj, // object to be updated
+ int newflags, // objects new flags
+ SCNHANDLE hNewImg); // objects new image
+
+void AnimateObject( // give a object a new image
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewImg); // objects new image
+
+void HideObject( // Hides a object by giving it a "NullImage" image pointer
+ OBJECT *pObj); // object to be hidden
+
+OBJECT *RectangleObject( // create a rectangle object of the given dimensions
+ SCNHANDLE hPal, // palette for the rectangle object
+ int colour, // which colour offset from the above palette
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+OBJECT *TranslucentObject( // create a translucent rectangle object of the given dimensions
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+void ResizeRectangle( // resizes a rectangle object
+ OBJECT *pRect, // rectangle object pointer
+ int width, // new width of rectangle
+ int height); // new height of rectangle
+
+
+// FIXME: This does not belong here
+struct FILM;
+struct FREEL;
+struct MULTI_INIT;
+IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0);
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0,
+ const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_OBJECT_H
diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp
new file mode 100644
index 0000000000..3bc2b514b5
--- /dev/null
+++ b/engines/tinsel/palette.cpp
@@ -0,0 +1,440 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator for IBM PC.
+ */
+
+#include "tinsel/dw.h" // TBLUE1 definition
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem definition
+#include "tinsel/palette.h" // palette allocator structures etc.
+#include "tinsel/tinsel.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+/** video DAC transfer Q structure */
+struct VIDEO_DAC_Q {
+ union {
+ SCNHANDLE hRGBarray; //!< handle of palette or
+ COLORREF *pRGBarray; //!< list of palette colours
+ } pal;
+ bool bHandle; //!< when set - use handle of palette
+ int destDACindex; //!< start index of palette in video DAC
+ int numColours; //!< number of colours in "hRGBarray"
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+/** background colour */
+static COLORREF bgndColour = BLACK;
+
+/** palette allocator data */
+static PALQ palAllocData[NUM_PALETTES];
+
+
+/** video DAC transfer Q length */
+#define VDACQLENGTH (NUM_PALETTES+2)
+
+/** video DAC transfer Q */
+static VIDEO_DAC_Q vidDACdata[VDACQLENGTH];
+
+/** video DAC transfer Q head pointer */
+static VIDEO_DAC_Q *pDAChead;
+
+/** colour index of the 4 colours used for the translucent palette */
+#define COL_HILIGHT TBLUE1
+
+/** the translucent palette lookup table */
+uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp
+
+#ifdef DEBUG
+// diagnostic palette counters
+static int numPals = 0;
+static int maxPals = 0;
+static int maxDACQ = 0;
+#endif
+
+/**
+ * Transfer palettes in the palette Q to Video DAC.
+ */
+void PalettesToVideoDAC(void) {
+ PALQ *pPalQ; // palette Q iterator
+ VIDEO_DAC_Q *pDACtail = vidDACdata; // set tail pointer
+ bool needUpdate = false;
+
+ // while Q is not empty
+ while (pDAChead != pDACtail) {
+ PALETTE *pPalette; // pointer to hardware palette
+ COLORREF *pColours; // pointer to list of RGB triples
+
+#ifdef DEBUG
+ // make sure palette does not overlap
+ assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS);
+#else
+ // make sure palette does not overlap
+ if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS)
+ pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex;
+#endif
+
+ if (pDACtail->bHandle) {
+ // we are using a palette handle
+
+ // get hardware palette pointer
+ pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray);
+
+ // get RGB pointer
+ pColours = pPalette->palRGB;
+ } else {
+ // we are using a palette pointer
+ pColours = pDACtail->pal.pRGBarray;
+ }
+
+ if (pDACtail->numColours > 0)
+ needUpdate = true;
+
+ // update the system palette
+ g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours);
+
+ // update tail pointer
+ pDACtail++;
+
+ }
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+
+ // clear all palette moved bits
+ for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++)
+ pPalQ->posInDAC &= ~PALETTE_MOVED;
+
+ if (needUpdate)
+ g_system->updateScreen();
+}
+
+/**
+ * Commpletely reset the palette allocator.
+ */
+void ResetPalAllocator(void) {
+#ifdef DEBUG
+ // clear number of palettes in use
+ numPals = 0;
+#endif
+
+ // wipe out the palette allocator data
+ memset(palAllocData, 0, sizeof(palAllocData));
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+}
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of palettes used at once.
+ */
+void PaletteStats(void) {
+ printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES);
+ printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH);
+}
+#endif
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours Number of colours in palette
+ * @param hPalette Handle to palette
+ */
+void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + VDACQLENGTH);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.hRGBarray = hPalette; // set handle of palette
+ pDAChead->bHandle = true; // we are using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours, Number of colours in palette
+ * @param pColours List of RGB triples
+ */
+void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + NUM_PALETTES);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.pRGBarray = pColours; // set addr of palette
+ pDAChead->bHandle = false; // we are not using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Allocate a palette.
+ * @param hNewPal Palette to allocate
+ */
+PALQ *AllocPalette(SCNHANDLE hNewPal) {
+ PALQ *pPrev, *p; // walks palAllocData
+ int iDAC; // colour index in video DAC
+ PALQ *pNxtPal; // next PALQ struct in palette allocator
+ PALETTE *pNewPal;
+
+ // get pointer to new palette
+ pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // search all structs in palette allocator - see if palette already allocated
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == hNewPal) {
+ // found the desired palette in palette allocator
+ p->objCount++; // update number of objects using palette
+ return p; // return palette queue position
+ }
+ }
+
+ // search all structs in palette allocator - find a free slot
+ iDAC = FGND_DAC_INDEX; // init DAC index to first available foreground colour
+
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == 0) {
+ // found a free slot in palette allocator
+ p->objCount = 1; // init number of objects using palette
+ p->posInDAC = iDAC; // set palettes start pos in video DAC
+ p->hPal = hNewPal; // set hardware palette data
+ p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette
+
+#ifdef DEBUG
+ // one more palette in use
+ if (++numPals > maxPals)
+ maxPals = numPals;
+#endif
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal);
+
+ // move all palettes after this one down (if necessary)
+ for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) {
+ if (pNxtPal->hPal != 0) {
+ // palette slot is in use
+ if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down - indicate change
+ pNxtPal->posInDAC = pPrev->posInDAC
+ + pPrev->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPal->posInDAC,
+ pNxtPal->numColours,
+ pNxtPal->hPal);
+
+ // update previous palette to current palette
+ pPrev = pNxtPal;
+ }
+ }
+
+ // return palette pointer
+ return p;
+ }
+
+ // set new DAC index
+ iDAC = p->posInDAC + p->numColours;
+ }
+
+ // no free palettes
+ error("AllocPalette(): formally 'assert(0)!'");
+}
+
+/**
+ * Free a palette allocated with "AllocPalette".
+ * @param pFreePal Palette queue entry to free
+ */
+void FreePalette(PALQ *pFreePal) {
+ // validate palette Q pointer
+ assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1);
+
+ // reduce the palettes object reference count
+ pFreePal->objCount--;
+
+ // make sure palette has not been deallocated too many times
+ assert(pFreePal->objCount >= 0);
+
+ if (pFreePal->objCount == 0) {
+ pFreePal->hPal = 0; // palette is no longer in use
+
+#ifdef DEBUG
+ // one less palette in use
+ --numPals;
+ assert(numPals >= 0);
+#endif
+ }
+}
+
+/**
+ * Find the specified palette.
+ * @param hSrchPal Hardware palette to search for
+ */
+PALQ *FindPalette(SCNHANDLE hSrchPal) {
+ PALQ *pPal; // palette allocator iterator
+
+ // search all structs in palette allocator
+ for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) {
+ if (pPal->hPal == hSrchPal)
+ // found palette in palette allocator
+ return pPal;
+ }
+
+ // palette not found
+ return NULL;
+}
+
+/**
+ * Swaps the palettes at the specified palette queue position.
+ * @param pPalQ Palette queue position
+ * @param hNewPal New palette
+ */
+void SwapPalette(PALQ *pPalQ, SCNHANDLE hNewPal) {
+ // convert handle to palette pointer
+ PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // validate palette Q pointer
+ assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1);
+
+ if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) {
+ // new palette will fit the slot
+
+ // install new palette
+ pPalQ->hPal = hNewPal;
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal);
+ } else {
+ // # colours are different - will have to update all following palette entries
+
+ PALQ *pNxtPalQ; // next palette queue position
+
+ for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) {
+ if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down
+ pNxtPalQ->posInDAC = pPalQ->posInDAC
+ + pPalQ->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPalQ->posInDAC,
+ pNxtPalQ->numColours,
+ pNxtPalQ->hPal);
+
+ // update previous palette to current palette
+ pPalQ = pNxtPalQ;
+ }
+ }
+}
+
+/**
+ * Statless palette iterator. Returns the next palette in the list
+ * @param pStrtPal Palette to start from - when NULL will start from beginning of list
+ */
+PALQ *GetNextPalette(PALQ *pStrtPal) {
+ if (pStrtPal == NULL) {
+ // start of palette iteration - return 1st palette
+ return (palAllocData[0].objCount) ? palAllocData : NULL;
+ }
+
+ // validate palette Q pointer
+ assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1);
+
+ // return next active palette in list
+ while (++pStrtPal < palAllocData + NUM_PALETTES) {
+ if (pStrtPal->objCount)
+ // active palette found
+ return pStrtPal;
+ }
+
+ // non found
+ return NULL;
+}
+
+/**
+ * Sets the current background colour.
+ * @param colour Colour to set the background to
+ */
+void SetBgndColour(COLORREF colour) {
+ // update background colour struct
+ bgndColour = colour;
+
+ // Q the change to the video DAC
+ UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour);
+}
+
+/**
+ * Builds the translucent palette from the current backgrounds palette.
+ * @param hPalette Handle to current background palette
+ */
+void CreateTranslucentPalette(SCNHANDLE hPalette) {
+ // get a pointer to the palette
+ PALETTE *pPal = (PALETTE *)LockMem(hPalette);
+
+ // leave background colour alone
+ transPalette[0] = 0;
+
+ for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) {
+ // get the RGB colour model values
+ uint8 red = GetRValue(pPal->palRGB[i]);
+ uint8 green = GetGValue(pPal->palRGB[i]);
+ uint8 blue = GetBValue(pPal->palRGB[i]);
+
+ // calculate the Value field of the HSV colour model
+ unsigned val = (red > green) ? red : green;
+ val = (val > blue) ? val : blue;
+
+ // map the Value field to one of the 4 colours reserved for the translucent palette
+ val /= 63;
+ transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h
new file mode 100644
index 0000000000..fdc4826dbd
--- /dev/null
+++ b/engines/tinsel/palette.h
@@ -0,0 +1,144 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator Definitions
+ */
+
+#ifndef TINSEL_PALETTE_H // prevent multiple includes
+#define TINSEL_PALETTE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+typedef uint32 COLORREF;
+
+#define RGB(r,g,b) ((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16)))
+
+#define GetRValue(rgb) ((uint8)(FROM_LE_32(rgb)))
+#define GetGValue(rgb) ((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8))
+#define GetBValue(rgb) ((uint8)((FROM_LE_32(rgb))>>16))
+
+enum {
+ MAX_COLOURS = 256, //!< maximum number of colours - for VGA 256
+ BITS_PER_PIXEL = 8, //!< number of bits per pixel for VGA 256
+ MAX_INTENSITY = 255, //!< the biggest value R, G or B can have
+ NUM_PALETTES = 3, //!< number of palettes
+
+ // Discworld has some fixed apportioned bits in the palette.
+ BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC
+ FGND_DAC_INDEX = 1, //!< index of first foreground colour in Video DAC
+ TBLUE1 = 228, //!< Blue used in translucent rectangles
+ TBLUE2 = 229, //!< Blue used in translucent rectangles
+ TBLUE3 = 230, //!< Blue used in translucent rectangles
+ TBLUE4 = 231, //!< Blue used in translucent rectangles
+ TALKFONT_COL = 233
+};
+
+// some common colours
+
+#define BLACK (RGB(0, 0, 0))
+#define WHITE (RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY))
+#define RED (RGB(MAX_INTENSITY, 0, 0))
+#define GREEN (RGB(0, MAX_INTENSITY, 0))
+#define BLUE (RGB(0, 0, MAX_INTENSITY))
+#define YELLOW (RGB(MAX_INTENSITY, MAX_INTENSITY, 0))
+#define MAGENTA (RGB(MAX_INTENSITY, 0, MAX_INTENSITY))
+#define CYAN (RGB(0, MAX_INTENSITY, MAX_INTENSITY))
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** hardware palette structure */
+struct PALETTE {
+ int32 numColours; //!< number of colours in the palette
+ COLORREF palRGB[MAX_COLOURS]; //!< actual palette colours
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** palette queue structure */
+struct PALQ {
+ SCNHANDLE hPal; //!< handle to palette data struct
+ int objCount; //!< number of objects using this palette
+ int posInDAC; //!< palette position in the video DAC
+ int numColours; //!< number of colours in the palette
+};
+
+
+#define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC"
+ // field - the palette entry has moved
+
+// Translucent objects have NULL pPal
+#define HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED))
+
+
+/*----------------------------------------------------------------------*\
+|* Palette Manager Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetPalAllocator(void); // wipe out all palettes
+
+#ifdef DEBUG
+void PaletteStats(void); // Shows the maximum number of palettes used at once
+#endif
+
+void PalettesToVideoDAC(void); // Update the video DAC with palettes currently the the DAC queue
+
+void UpdateDACqueueHandle(
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ SCNHANDLE hPalette); // handle to palette
+
+void UpdateDACqueue( // places a palette in the video DAC queue
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ COLORREF *pColours); // list of RGB tripples
+
+PALQ *AllocPalette( // allocate a new palette
+ SCNHANDLE hNewPal); // palette to allocate
+
+void FreePalette( // free a palette allocated with "AllocPalette"
+ PALQ *pFreePal); // palette queue entry to free
+
+PALQ *FindPalette( // find a palette in the palette queue
+ SCNHANDLE hSrchPal); // palette to search for
+
+void SwapPalette( // swaps palettes at the specified palette queue position
+ PALQ *pPalQ, // palette queue position
+ SCNHANDLE hNewPal); // new palette
+
+PALQ *GetNextPalette( // returns the next palette in the queue
+ PALQ *pStrtPal); // queue position to start from - when NULL will start from beginning of queue
+
+COLORREF GetBgndColour(void); // returns current background colour
+
+void SetBgndColour( // sets current background colour
+ COLORREF colour); // colour to set the background to
+
+void CreateTranslucentPalette(SCNHANDLE BackPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PALETTE_H
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
new file mode 100644
index 0000000000..023417fe3c
--- /dev/null
+++ b/engines/tinsel/pcode.cpp
@@ -0,0 +1,593 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // 'POINTED' etc.
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/inventory.h" // for inventory id's
+#include "tinsel/pcode.h" // opcodes etc.
+#include "tinsel/scn.h" // FindChunk()
+#include "tinsel/serializer.h"
+#include "tinsel/tinlib.h" // Library routines
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState);
+
+//----------------- LOCAL DEFINES --------------------
+
+/** list of all opcodes */
+enum OPCODE {
+ OP_HALT = 0, //!< end of program
+ OP_IMM = 1, //!< loads signed immediate onto stack
+ OP_ZERO = 2, //!< loads zero onto stack
+ OP_ONE = 3, //!< loads one onto stack
+ OP_MINUSONE = 4, //!< loads minus one onto stack
+ OP_STR = 5, //!< loads string offset onto stack
+ OP_FILM = 6, //!< loads film offset onto stack
+ OP_FONT = 7, //!< loads font offset onto stack
+ OP_PAL = 8, //!< loads palette offset onto stack
+ OP_LOAD = 9, //!< loads local variable onto stack
+ OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable
+ OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable
+ OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable
+ OP_CALL = 13, //!< procedure call
+ OP_LIBCALL = 14, //!< library procedure call - long offset to procedure
+ OP_RET = 15, //!< procedure return
+ OP_ALLOC = 16, //!< allocate storage on stack
+ OP_JUMP = 17, //!< unconditional jump - signed word offset
+ OP_JMPFALSE = 18, //!< conditional jump - signed word offset
+ OP_JMPTRUE = 19, //!< conditional jump - signed word offset
+ OP_EQUAL = 20, //!< tests top two items on stack for equality
+ OP_LESS, //!< tests top two items on stack
+ OP_LEQUAL, //!< tests top two items on stack
+ OP_NEQUAL, //!< tests top two items on stack
+ OP_GEQUAL, //!< tests top two items on stack
+ OP_GREAT = 25, //!< tests top two items on stack
+ OP_PLUS, //!< adds top two items on stack and replaces with result
+ OP_MINUS, //!< subs top two items on stack and replaces with result
+ OP_LOR, //!< logical or of top two items on stack and replaces with result
+ OP_MULT, //!< multiplies top two items on stack and replaces with result
+ OP_DIV = 30, //!< divides top two items on stack and replaces with result
+ OP_MOD, //!< divides top two items on stack and replaces with modulus
+ OP_AND, //!< bitwise ands top two items on stack and replaces with result
+ OP_OR, //!< bitwise ors top two items on stack and replaces with result
+ OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result
+ OP_LAND = 35, //!< logical ands top two items on stack and replaces with result
+ OP_NOT, //!< logical nots top item on stack
+ OP_COMP, //!< complements top item on stack
+ OP_NEG, //!< negates top item on stack
+ OP_DUP, //!< duplicates top item on stack
+ OP_ESCON = 40, //!< start of escapable sequence
+ OP_ESCOFF = 41, //!< end of escapable sequence
+ OP_CIMM, //!< loads signed immediate onto stack (special to case statements)
+ OP_CDFILM //!< loads film offset onto stack but not in current scene
+};
+
+// modifiers for the above opcodes
+#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits
+#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits
+
+#define OPMASK 0x3F //!< mask to isolate the opcode
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int32 *pGlobals = 0; // global vars
+
+static int numGlobals = 0; // How many global variables to save/restore
+
+static INT_CONTEXT *icList = 0;
+
+/**
+ * Keeps the code array pointer up to date.
+ */
+void LockCode(INT_CONTEXT *ic) {
+ if (ic->GSort == GS_MASTER)
+ ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
+ else
+ ic->code = (byte *)LockMem(ic->hCode);
+}
+
+/**
+ * Find a free interpret context and allocate it to the calling process.
+ */
+static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_NONE) {
+ pic->pProc = g_scheduler->getCurrentProcess();
+ pic->GSort = gsort;
+ return pic;
+ }
+#ifdef DEBUG
+ else {
+ if (pic->pProc == g_scheduler->getCurrentProcess())
+ error("Found unreleased interpret context");
+ }
+#endif
+ }
+
+ error("Out of interpret contexts");
+}
+
+/**
+ * Normal release of an interpret context.
+ * Called from the end of Interpret().
+ */
+static void FreeInterpretContextPi(INT_CONTEXT *pic) {
+ pic->GSort = GS_NONE;
+}
+
+/**
+ * Free interpret context owned by a dying process.
+ * Ensures that interpret contexts don't get lost when an Interpret()
+ * call doesn't complete.
+ */
+void FreeInterpretContextPr(PROCESS *pProc) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_NONE && pic->pProc == pProc) {
+ pic->GSort = GS_NONE;
+ break;
+ }
+ }
+}
+
+/**
+ * Free all interpret contexts except for the master script's
+ */
+void FreeMostInterpretContexts(void) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_MASTER) {
+ pic->GSort = GS_NONE;
+ }
+ }
+}
+
+/**
+ * Free the master script's interpret context.
+ */
+void FreeMasterInterpretContext(void) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_MASTER) {
+ pic->GSort = GS_NONE;
+ return;
+ }
+ }
+}
+
+/**
+ * Allocate and initialise an interpret context.
+ * Called from a process prior to Interpret().
+ * @param gsort which sort of code
+ * @param hCode Handle to code to execute
+ * @param event Causal event
+ * @param hpoly Associated polygon (if any)
+ * @param actorId Associated actor (if any)
+ * @param pinvo Associated inventory object
+ */
+INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event,
+ HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo) {
+ INT_CONTEXT *ic;
+
+ ic = AllocateInterpretContext(gsort);
+
+ // Previously parameters to Interpret()
+ ic->hCode = hCode;
+ LockCode(ic);
+ ic->event = event;
+ ic->hpoly = hpoly;
+ ic->actorid = actorid;
+ ic->pinvo = pinvo;
+
+ // Previously local variables in Interpret()
+ ic->bHalt = false; // set to exit interpeter
+ ic->escOn = false;
+ ic->myescEvent = 0; // only initialised to prevent compiler warning!
+ ic->sp = 0;
+ ic->bp = ic->sp + 1;
+ ic->ip = 0; // start of code
+
+ ic->resumeState = RES_NOT;
+
+ return ic;
+}
+
+/**
+ * Allocate and initialise an interpret context with restored data.
+ */
+INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) {
+ INT_CONTEXT *ic;
+
+ ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden
+
+ memcpy(ic, ric, sizeof(INT_CONTEXT));
+ ic->pProc = g_scheduler->getCurrentProcess();
+ ic->resumeState = RES_1;
+
+ LockCode(ic);
+
+ return ic;
+}
+
+/**
+ * Allocates enough RAM to hold the global Glitter variables.
+ */
+void RegisterGlobals(int num) {
+ if (pGlobals == NULL) {
+ numGlobals = num;
+
+ // Allocate RAM for pGlobals and make sure it's allocated
+ pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
+ if (pGlobals == NULL) {
+ error("Cannot allocate memory for global data");
+ }
+
+ // Allocate RAM for interpret contexts and make sure it's allocated
+ icList = (INT_CONTEXT *)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT));
+ if (icList == NULL) {
+ error("Cannot allocate memory for interpret contexts");
+ }
+
+ g_scheduler->setResourceCallback(FreeInterpretContextPr);
+ } else {
+ // Check size is still the same
+ assert(numGlobals == num);
+
+ memset(pGlobals, 0, numGlobals * sizeof(int32));
+ memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT));
+ }
+}
+
+void FreeGlobals(void) {
+ free(pGlobals);
+ pGlobals = NULL;
+
+ free(icList);
+ icList = NULL;
+}
+
+/**
+ * (Un)serialize the global data for save/restore game.
+ */
+void syncGlobInfo(Serializer &s) {
+ for (int i = 0; i < numGlobals; i++) {
+ s.syncAsSint32LE(pGlobals[i]);
+ }
+}
+
+/**
+ * (Un)serialize an interpreter context for save/restore game.
+ */
+void INT_CONTEXT::syncWithSerializer(Serializer &s) {
+ if (s.isLoading()) {
+ // Null out the pointer fields
+ pProc = NULL;
+ code = NULL;
+ pinvo = NULL;
+ }
+ // Write out used fields
+ s.syncAsUint32LE(GSort);
+ s.syncAsUint32LE(hCode);
+ s.syncAsUint32LE(event);
+ s.syncAsSint32LE(hpoly);
+ s.syncAsSint32LE(actorid);
+
+ for (int i = 0; i < PCODE_STACK_SIZE; ++i)
+ s.syncAsSint32LE(stack[i]);
+
+ s.syncAsSint32LE(sp);
+ s.syncAsSint32LE(bp);
+ s.syncAsSint32LE(ip);
+ s.syncAsUint32LE(bHalt);
+ s.syncAsUint32LE(escOn);
+ s.syncAsSint32LE(myescEvent);
+}
+
+/**
+ * Return pointer to and size of global data for save/restore game.
+ */
+void SaveInterpretContexts(INT_CONTEXT *sICInfo) {
+ memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT));
+}
+
+/**
+ * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
+ * stream and advance the instruction pointer accordingly.
+ */
+static int32 Fetch(byte opcode, byte *code, int &ip) {
+ int32 tmp;
+ if (opcode & OPSIZE8) {
+ // Fetch and sign extend a 8 bit value to 32 bits.
+ tmp = *(int8 *)(code + ip);
+ ip += 1;
+ } else if (opcode & OPSIZE16) {
+ // Fetch and sign extend a 16 bit value to 32 bits.
+ tmp = (int16)READ_LE_UINT16(code + ip);
+ ip += 2;
+ } else {
+ // Fetch a 32 bit value.
+ tmp = (int32)READ_LE_UINT32(code + ip);
+ ip += 4;
+ }
+ return tmp;
+}
+
+/**
+ * Interprets the PCODE instructions in the code array.
+ */
+void Interpret(CORO_PARAM, INT_CONTEXT *ic) {
+ do {
+ int tmp, tmp2;
+ int ip = ic->ip;
+ byte opcode = ic->code[ip++];
+ debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK);
+ switch (opcode & OPMASK) {
+ case OP_HALT: // end of program
+
+ ic->bHalt = true;
+ break;
+
+ case OP_IMM: // loads immediate data onto stack
+ case OP_STR: // loads string handle onto stack
+ case OP_FILM: // loads film handle onto stack
+ case OP_CDFILM: // loads film handle onto stack
+ case OP_FONT: // loads font handle onto stack
+ case OP_PAL: // loads palette handle onto stack
+
+ ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_ZERO: // loads zero onto stack
+ ic->stack[++ic->sp] = 0;
+ break;
+
+ case OP_ONE: // loads one onto stack
+ ic->stack[++ic->sp] = 1;
+ break;
+
+ case OP_MINUSONE: // loads minus one onto stack
+ ic->stack[++ic->sp] = -1;
+ break;
+
+ case OP_LOAD: // loads local variable onto stack
+
+ ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)];
+ break;
+
+ case OP_GLOAD: // loads global variable onto stack
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ ic->stack[++ic->sp] = pGlobals[tmp];
+ break;
+
+ case OP_STORE: // pops stack and stores in local variable
+
+ ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--];
+ break;
+
+ case OP_GSTORE: // pops stack and stores in global variable
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ pGlobals[tmp] = ic->stack[ic->sp--];
+ break;
+
+ case OP_CALL: // procedure call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds
+ ic->stack[ic->sp + 1] = 0; // static link
+ ic->stack[ic->sp + 2] = ic->bp; // dynamic link
+ ic->stack[ic->sp + 3] = ip; // return address
+ ic->bp = ic->sp + 1; // set new base pointer
+ ip = tmp; // set ip to procedure address
+ break;
+
+ case OP_LIBCALL: // library procedure or function call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ // NOTE: Interpret() itself is not using the coroutine facilities,
+ // but still accepts a CORO_PARAM, so from the outside it looks
+ // like a coroutine. In fact it may still acts as a kind of "proxy"
+ // for some underlying coroutine. To enable this, we just pass on
+ // 'coroParam' to CallLibraryRoutine(). If we then detect that
+ // coroParam was set to a non-zero value, this means that some
+ // coroutine code did run at some point, and we are now supposed
+ // to sleep or die -- hence, we 'return' if coroParam != 0.
+ //
+ // This works because Interpret() is fully re-entrant: If we return
+ // now and are later called again, then we will end up in the very
+ // same spot (i.e. here).
+ //
+ // The reasons we do it this way, instead of turning Interpret into
+ // a 'proper' coroutine are (1) we avoid implementation problems
+ // (CORO_INVOKE involves adding 'case' statements, but Interpret
+ // already has a huge switch/case, so that would not work out of the
+ // box), (2) we incurr less overhead, (3) it's easier to debug,
+ // (4) it's simply cool ;).
+ tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
+ if (coroParam)
+ return;
+ ic->sp += tmp2;
+ LockCode(ic);
+ break;
+
+ case OP_RET: // procedure return
+
+ ic->sp = ic->bp - 1; // restore stack
+ ip = ic->stack[ic->sp + 3]; // return address
+ ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer
+ break;
+
+ case OP_ALLOC: // allocate storage on stack
+
+ ic->sp += Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JUMP: // unconditional jump
+
+ ip = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JMPFALSE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] == 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_JMPTRUE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] != 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_EQUAL: // tests top two items on stack for equality
+ case OP_LESS: // tests top two items on stack
+ case OP_LEQUAL: // tests top two items on stack
+ case OP_NEQUAL: // tests top two items on stack
+ case OP_GEQUAL: // tests top two items on stack
+ case OP_GREAT: // tests top two items on stack
+ case OP_LOR: // logical or of top two items on stack and replaces with result
+ case OP_LAND: // logical ands top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_EQUAL: tmp = (tmp == tmp2); break;
+ case OP_LESS: tmp = (tmp < tmp2); break;
+ case OP_LEQUAL: tmp = (tmp <= tmp2); break;
+ case OP_NEQUAL: tmp = (tmp != tmp2); break;
+ case OP_GEQUAL: tmp = (tmp >= tmp2); break;
+ case OP_GREAT: tmp = (tmp > tmp2); break;
+
+ case OP_LOR: tmp = (tmp || tmp2); break;
+ case OP_LAND: tmp = (tmp && tmp2); break;
+ }
+
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_PLUS: // adds top two items on stack and replaces with result
+ case OP_MINUS: // subs top two items on stack and replaces with result
+ case OP_MULT: // multiplies top two items on stack and replaces with result
+ case OP_DIV: // divides top two items on stack and replaces with result
+ case OP_MOD: // divides top two items on stack and replaces with modulus
+ case OP_AND: // bitwise ands top two items on stack and replaces with result
+ case OP_OR: // bitwise ors top two items on stack and replaces with result
+ case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_PLUS: tmp += tmp2; break;
+ case OP_MINUS: tmp -= tmp2; break;
+ case OP_MULT: tmp *= tmp2; break;
+ case OP_DIV: tmp /= tmp2; break;
+ case OP_MOD: tmp %= tmp2; break;
+ case OP_AND: tmp &= tmp2; break;
+ case OP_OR: tmp |= tmp2; break;
+ case OP_EOR: tmp ^= tmp2; break;
+ }
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_NOT: // logical nots top item on stack
+
+ ic->stack[ic->sp] = !ic->stack[ic->sp];
+ break;
+
+ case OP_COMP: // complements top item on stack
+ ic->stack[ic->sp] = ~ic->stack[ic->sp];
+ break;
+
+ case OP_NEG: // negates top item on stack
+ ic->stack[ic->sp] = -ic->stack[ic->sp];
+ break;
+
+ case OP_DUP: // duplicates top item on stack
+ ic->stack[ic->sp + 1] = ic->stack[ic->sp];
+ ic->sp++;
+ break;
+
+ case OP_ESCON:
+ ic->escOn = true;
+ ic->myescEvent = GetEscEvents();
+ break;
+
+ case OP_ESCOFF:
+ ic->escOn = false;
+ break;
+
+ default:
+ error("Interpret() - Unknown opcode");
+ }
+
+ // check for stack under-overflow
+ assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
+ ic->ip = ip;
+ } while (!ic->bHalt);
+
+ // make sure stack is unwound
+ assert(ic->sp == 0);
+
+ FreeInterpretContextPi(ic);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h
new file mode 100644
index 0000000000..1c7e0a942c
--- /dev/null
+++ b/engines/tinsel/pcode.h
@@ -0,0 +1,155 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor definitions
+ */
+
+#ifndef TINSEL_PCODE_H // prevent multiple includes
+#define TINSEL_PCODE_H
+
+#include "tinsel/events.h" // for USER_EVENT
+#include "tinsel/sched.h" // for PROCESS
+
+namespace Tinsel {
+
+// forward declaration
+class Serializer;
+struct INV_OBJECT;
+
+enum RESUME_STATE {
+ RES_NOT, RES_1, RES_2
+};
+
+enum {
+ PCODE_STACK_SIZE = 128 //!< interpeters stack size
+};
+
+enum GSORT {
+ GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE
+};
+
+struct INT_CONTEXT {
+
+ // Elements for interpret context management
+ PROCESS *pProc; //!< processes owning this context
+ GSORT GSort; //!< sort of this context
+
+ // Previously parameters to Interpret()
+ SCNHANDLE hCode; //!< scene handle of the code to execute
+ byte *code; //!< pointer to the code to execute
+ USER_EVENT event; //!< causal event
+ HPOLYGON hpoly; //!< associated polygon (if any)
+ int actorid; //!< associated actor (if any)
+ INV_OBJECT *pinvo; //!< associated inventory object
+
+ // Previously local variables in Interpret()
+ int32 stack[PCODE_STACK_SIZE]; //!< interpeters run time stack
+ int sp; //!< stack pointer
+ int bp; //!< base pointer
+ int ip; //!< instruction pointer
+ bool bHalt; //!< set to exit interpeter
+ bool escOn;
+ int myescEvent; //!< only initialised to prevent compiler warning!
+
+ RESUME_STATE resumeState;
+
+ void syncWithSerializer(Serializer &s);
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Interpreter Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void Interpret(CORO_PARAM, INT_CONTEXT *ic); // Interprets the PCODE instructions in the code array
+
+INT_CONTEXT *InitInterpretContext(
+ GSORT gsort,
+ SCNHANDLE hCode, // code to execute
+ USER_EVENT event, // causal event
+ HPOLYGON hpoly, // associated polygon (if any)
+ int actorid, // associated actor (if any)
+ INV_OBJECT *pinvo); // associated inventory object
+
+INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric);
+
+void FreeMostInterpretContexts(void);
+void FreeMasterInterpretContext(void);
+
+void SaveInterpretContexts(INT_CONTEXT *sICInfo);
+
+void RegisterGlobals(int num);
+void FreeGlobals(void);
+
+
+#define MAX_INTERPRET (NUM_PROCESS - 20)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes parameter enums *|
+\*----------------------------------------------------------------------*/
+
+#define TAG_DEF 0 // For tagactor()
+#define TAG_Q1TO3 1 // tag types
+#define TAG_Q1TO4 2 // tag types
+
+#define CONV_DEF 0 //
+#define CONV_BOTTOM 1 // conversation() parameter
+#define CONV_END 2 //
+
+#define CONTROL_OFF 0 // control()
+#define CONTROL_ON 1 // parameter
+#define CONTROL_OFFV 2 //
+#define CONTROL_OFFV2 3 //
+#define CONTROL_STARTOFF 4 //
+
+#define NULL_ACTOR (-1) // For actor parameters
+#define LEAD_ACTOR (-2) //
+
+#define RAND_NORM 0 // For random() frills
+#define RAND_NORPT 1 //
+
+#define D_UP 1
+#define D_DOWN 0
+
+#define TW_START 1 // topwindow() parameter
+#define TW_END 2 //
+
+#define MIDI_DEF 0
+#define MIDI_LOOP 1
+
+#define TRANS_DEF 0
+#define TRANS_CUT 1
+#define TRANS_FADE 2
+
+#define FM_IN 0 //
+#define FM_OUT 1 // fademidi()
+
+#define FG_ON 0 //
+#define FG_OFF 1 // FrameGrab()
+
+#define ST_ON 0 //
+#define ST_OFF 1 // SubTitles()
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PCODE_H
diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp
new file mode 100644
index 0000000000..b5488da3e8
--- /dev/null
+++ b/engines/tinsel/pdisplay.cpp
@@ -0,0 +1,652 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * CursorPositionProcess()
+ * TagProcess()
+ * PointProcess()
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+//extern int Overrun; // The overrun counter, in DOS_DW.C
+
+extern int newestString; // The overrun counter, in STRRES.C
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LPOSX 295 // X-co-ord of lead actor's position display
+#define CPOSX 24 // X-co-ord of cursor's position display
+#define OPOSX SCRN_CENTRE_X // X-co-ord of overrun counter's display
+#define SPOSX SCRN_CENTRE_X // X-co-ord of string numbner's display
+
+#define POSY 0 // Y-co-ord of these position displays
+
+enum HotSpotTag {
+ NO_HOTSPOT_TAG,
+ POLY_HOTSPOT_TAG,
+ ACTOR_HOTSPOT_TAG
+};
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool DispPath = false;
+static bool bShowString = false;
+
+static int TaggedActor = 0;
+static HPOLYGON hTaggedPolygon = NOPOLY;
+
+static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON;
+
+
+#ifdef DEBUG
+/**
+ * Displays the cursor and lead actor's co-ordinates and the overrun
+ * counter. Also which path polygon the cursor is in, if required.
+ *
+ * This process is only started up if a Glitter showpos() call is made.
+ * Obviously, this is for testing purposes only...
+ */
+void CursorPositionProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int prevsX, prevsY; // Last screen top left
+ int prevcX, prevcY; // Last displayed cursor position
+ int prevlX, prevlY; // Last displayed lead actor position
+// int prevOver; // Last displayed overrun
+ int prevString; // Last displayed string number
+
+ OBJECT *cpText; // cursor position text object pointer
+ OBJECT *cpathText; // cursor path text object pointer
+ OBJECT *rpText; // text object pointer
+// OBJECT *opText; // text object pointer
+ OBJECT *spText; // string number text object pointer
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->prevsX = -1;
+ _ctx->prevsY = -1;
+ _ctx->prevcX = -1;
+ _ctx->prevcY = -1;
+ _ctx->prevlX = -1;
+ _ctx->prevlY = -1;
+// _ctx->prevOver = -1;
+ _ctx->prevString = -1;
+
+ _ctx->cpText = NULL;
+ _ctx->cpathText = NULL;
+ _ctx->rpText = NULL;
+// _ctx->opText = NULL;
+ _ctx->spText = NULL;
+
+
+ int aniX, aniY; // cursor/lead actor position
+ int Loffset, Toffset; // Screen top left
+
+ char PositionString[64]; // sprintf() things into here
+
+ PMACTOR pActor; // Lead actor
+
+ while (1) {
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*-----------------------------------*\
+ | Cursor's position and path display. |
+ \*-----------------------------------*/
+ GetCursorXY(&aniX, &aniY, false);
+
+ // Change in cursor position?
+ if (aniX != _ctx->prevcX || aniY != _ctx->prevcY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // kill current text objects
+ if (_ctx->cpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText);
+ }
+ if (_ctx->cpathText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText);
+ _ctx->cpathText = NULL;
+ }
+
+ // New text objects
+ sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset);
+ _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+ if (DispPath) {
+ HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH);
+ if (hp == NOPOLY)
+ sprintf(PositionString, "No path");
+ else
+ sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d",
+ PolyCornerX(hp, 0), PolyCornerY(hp, 0),
+ PolyCornerX(hp, 1), PolyCornerY(hp, 1),
+ PolyCornerX(hp, 2), PolyCornerY(hp, 2),
+ PolyCornerX(hp, 3), PolyCornerY(hp, 3));
+ _ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, 4, POSY+ 10, hTagFontHandle(), 0);
+ }
+
+ // update previous position
+ _ctx->prevcX = aniX;
+ _ctx->prevcY = aniY;
+ }
+
+#if 0
+ /*------------------------*\
+ | Overrun counter display. |
+ \*------------------------*/
+ if (Overrun != _ctx->prevOver) {
+ // kill current text objects
+ if (_ctx->opText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText);
+ }
+
+ sprintf(PositionString, "%d", Overrun);
+ _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevOver = Overrun;
+ }
+#endif
+
+ /*----------------------*\
+ | Lead actor's position. |
+ \*----------------------*/
+ pActor = GetMover(LEAD_ACTOR);
+ if (pActor && pActor->MActorState == NORM_MACTOR) {
+ // get lead's animation position
+ GetActorPos(LEAD_ACTOR, &aniX, &aniY);
+
+ // Change in position?
+ if (aniX != _ctx->prevlX || aniY != _ctx->prevlY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // Kill current text objects
+ if (_ctx->rpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText);
+ }
+
+ // create new text object list
+ sprintf(PositionString, "%d %d", aniX, aniY);
+ _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous position
+ _ctx->prevlX = aniX;
+ _ctx->prevlY = aniY;
+ }
+ }
+
+ /*-------------*\
+ | String number |
+ \*-------------*/
+ if (bShowString && newestString != _ctx->prevString) {
+ // kill current text objects
+ if (_ctx->spText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText);
+ }
+
+ sprintf(PositionString, "String: %d", newestString);
+ _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevString = newestString;
+ }
+
+ // update previous playfield position
+ _ctx->prevsX = Loffset;
+ _ctx->prevsY = Toffset;
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+#endif
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedActor(int ano) {
+ TaggedActor = ano;
+}
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+int GetTaggedActor(void) {
+ return TaggedActor;
+}
+
+/**
+ * Tag process keeps us updated as to which polygon is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedPoly(HPOLYGON hp) {
+ hTaggedPolygon = hp;
+}
+
+HPOLYGON GetTaggedPoly(void) {
+ return hTaggedPolygon;
+}
+
+/**
+ * Given cursor position and an actor number, ascertains whether the
+ * cursor is within the actor's tag area.
+ * Returns TRUE for a positive result, FALSE for negative.
+ * If TRUE, the mid-top co-ordinates of the actor's tag area are also
+ * returned.
+ */
+static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) {
+ int Top, Bot; // Top and bottom limits of active area
+ int left, right; // left and right of active area
+ int qrt = 0; // 1/4 of height (sometimes 1/2)
+
+ // First check if within x-range
+ if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) {
+ Top = GetActorTop(ano);
+ Bot = GetActorBottom(ano);
+
+ // y-range varies according to tag-type
+ switch (TagType(ano)) {
+ case TAG_DEF:
+ // Next to bottom 1/4 of the actor's area
+ qrt = (Bot - Top) >> 1; // Half actor's height
+ Top += qrt; // Top = mid-height
+
+ qrt = qrt >> 1; // Quarter height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO3:
+ // Top 3/4 of the actor's area
+ qrt = (Bot - Top) >> 2; // 1/4 actor's height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO4:
+ // All the actor's area
+ break;
+
+ default:
+ error("illegal tag area type");
+ }
+
+ // Now check if within y-range
+ if (aniY >= Top && aniY <= Bot) {
+ if (TagType(ano) == TAG_Q1TO3)
+ *pytext = Top + qrt;
+ else
+ *pytext = Top;
+ *pxtext = (left + right) / 2;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * See if the cursor is over a tagged actor's hot-spot. If so, display
+ * the tag or, if tag already displayed, maintain the tag's position on
+ * the screen.
+ */
+static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ int ano;
+ int xtext, ytext;
+ bool newActor;
+
+ // For each actor with a tag....
+ FirstTaggedActor();
+ while ((ano = NextTaggedActor()) != 0) {
+ if (InHotSpot(ano, curX, curY, &xtext, &ytext)) {
+ // Put up or maintain actor tag
+ if (*pTag != ACTOR_HOTSPOT_TAG)
+ newActor = true;
+ else if (ano != GetTaggedActor())
+ newActor = true; // Different actor
+ else
+ newActor = false; // Same actor
+
+ if (newActor) {
+ // Display actor's tag
+
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = ACTOR_HOTSPOT_TAG;
+ SaveTaggedActor(ano); // This actor tagged
+ SaveTaggedPoly(NOPOLY); // No tagged polygon
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ);
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Actor tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+ } else {
+ // Maintain actor tag's position
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged actor
+ if (*pTag == ACTOR_HOTSPOT_TAG) {
+ *pTag = NO_HOTSPOT_TAG;
+ SaveTaggedActor(0);
+ }
+ return false;
+}
+
+/**
+ * Perhaps some comment in due course.
+ *
+ * Under control of PointProcess(), when the cursor is over a TAG or
+ * EXIT polygon, its pointState flag is set to POINTING. If its Glitter
+ * code contains a printtag() call, its tagState flag gets set to TAG_ON.
+ */
+static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ HPOLYGON hp;
+ bool newPoly;
+ int shift;
+
+ int tagx, tagy; // Tag display co-ordinates
+ SCNHANDLE hTagtext; // Tag text
+
+ // For each polgon with a tag....
+ for (int i = 0; i < MAX_POLY; i++) {
+ hp = GetPolyHandle(i);
+
+ // Added code for un-tagged tags
+ if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) {
+ // This poly is entitled to be tagged
+ if (hp != GetTaggedPoly()) {
+ if (*ppText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+ *ppText = NULL;
+ }
+ *pTag = POLY_HOTSPOT_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+ }
+ return true;
+ } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) {
+ // Put up or maintain polygon tag
+ if (*pTag != POLY_HOTSPOT_TAG)
+ newPoly = true; // A new polygon (no current)
+ else if (hp != GetTaggedPoly())
+ newPoly = true; // Different polygon
+ else
+ newPoly = false; // Same polygon
+
+ if (newPoly) {
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = POLY_HOTSPOT_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ getPolyTagInfo(hp, &hTagtext, &tagx, &tagy);
+
+ int strLen;
+ if (PolyTagHandle(hp) != 0)
+ strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ);
+ else
+ strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ);
+
+ if (strLen == 0)
+ // No valid string returned, so leave ppText as NULL
+ ppText = NULL;
+ else {
+ // Handle displaying the tag text on-screen
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, tagx - Loffset, tagy - Toffset,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Polygon tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ shift = MultiRightmost(*ppText) + Loffset + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(*ppText) + Loffset - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(*ppText, -shift, 0);
+ shift = MultiLowest(*ppText) + Toffset;
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift);
+ }
+ } else {
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged polygon
+ if (*pTag == POLY_HOTSPOT_TAG) {
+ *pTag = NO_HOTSPOT_TAG;
+ SaveTaggedPoly(NOPOLY);
+ }
+ return false;
+}
+
+/**
+ * Handle display of tagged actor and polygon tags.
+ * Tagged actor's get priority over polygons.
+ */
+void TagProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ HotSpotTag Tag;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->Tag = NO_HOTSPOT_TAG;
+
+ SaveTaggedActor(0); // No tagged actor yet
+ SaveTaggedPoly(NOPOLY); // No tagged polygon yet
+
+ while (1) {
+ if (TagsActive == TAGS_ON) {
+ int curX, curY; // cursor position
+ while (!GetCursorXYNoWait(&curX, &curY, true))
+ CORO_SLEEP(1);
+
+ if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText)
+ && !PolyTag(&_ctx->Tag, &_ctx->pText)) {
+ // Nothing tagged. Remove tag, if there is one
+ if (_ctx->pText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ }
+ }
+ } else {
+ SaveTaggedActor(0);
+ SaveTaggedPoly(NOPOLY);
+
+ // Remove tag, if there is one
+ if (_ctx->pText) {
+ // kill current text objects
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ _ctx->Tag = NO_HOTSPOT_TAG;
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void enteringpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, POINTING);
+
+ RunPolyTinselCode(hp, POINTED, BE_NONE, false);
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void leavingpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, NOT_POINTING);
+
+ if (PolyTagState(hp) == TAG_ON) {
+ // Delete this tag entry
+ SetPolyTagState(hp, TAG_OFF);
+ }
+}
+
+/**
+ * For TAG and EXIT polygons, monitor cursor entering and leaving.
+ * Maintain the polygons' pointState and tagState flags accordingly.
+ * Also run the polygon's Glitter code when the cursor enters.
+ */
+void PointProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ int aniX, aniY; // cursor/tagged actor position
+ while (!GetCursorXYNoWait(&aniX, &aniY, true))
+ CORO_SLEEP(1);
+
+ /*----------------------------------*\
+ | For polygons of type TAG and EXIT. |
+ \*----------------------------------*/
+ for (int i = 0; i < MAX_POLY; i++) {
+ HPOLYGON hp = GetPolyHandle(i);
+
+ if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) {
+ if (PolyPointState(hp) == NOT_POINTING) {
+ if (IsInPolygon(aniX, aniY, hp)) {
+ enteringpoly(hp);
+ }
+ } else if (PolyPointState(hp) == POINTING) {
+ if (!IsInPolygon(aniX, aniY, hp)) {
+ leavingpoly(hp);
+ }
+ }
+ }
+ }
+
+ // allow re-scheduling
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+void DisableTags(void) {
+ TagsActive = TAGS_OFF;
+}
+
+void EnableTags(void) {
+ TagsActive = TAGS_ON;
+}
+
+bool DisableTagsIfEnabled(void) {
+ if (TagsActive == TAGS_OFF)
+ return false;
+ else {
+ TagsActive = TAGS_OFF;
+ return true;
+ }
+}
+
+/**
+ * For testing purposes only.
+ * Causes CursorPositionProcess() to display, or not, the path that the
+ * cursor is in.
+ */
+void TogglePathDisplay(void) {
+ DispPath ^= 1; // Toggle path display (XOR with true)
+}
+
+
+void setshowstring(void) {
+ bShowString = true;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h
new file mode 100644
index 0000000000..c2af1a5fcb
--- /dev/null
+++ b/engines/tinsel/pid.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * List of all process identifiers
+ */
+
+#ifndef TINSEL_PID_H // prevent multiple includes
+#define TINSEL_PID_H
+
+namespace Tinsel {
+
+#define PID_DESTROY 0x8000 // process id of any process that is to be destroyed between scenes
+
+#define PID_EFFECTS (0x0010 | PID_DESTROY) // generic special effects process id
+#define PID_FLASH (PID_EFFECTS + 1) // flash colour process
+#define PID_CYCLE (PID_EFFECTS + 2) // cycle colour range process
+#define PID_MORPH (PID_EFFECTS + 3) // morph process
+#define PID_FADER (PID_EFFECTS + 4) // fader process
+#define PID_FADE_BGND (PID_EFFECTS + 5) // fade background colour process
+
+#define PID_BACKGND (0x0020 | PID_DESTROY) // background update process id
+
+#define PID_MOUSE 0x0030 // mouse button checking process id
+
+#define PID_JOYSTICK 0x0040 // joystick button checking process id
+
+#define PID_KEYBOARD 0x0050 // keyboard scanning process
+
+#define PID_CURSOR 0x0060 // cursor process
+#define PID_CUR_TRAIL (PID_CURSOR + 1) // cursor trail process
+
+#define PID_SCROLL (0x0070 | PID_DESTROY) // scroll process
+
+#define PID_INVENTORY 0x0080 // inventory process
+
+#define PID_POSITION (0x0090 | PID_DESTROY) // cursor position process
+
+#define PID_TAG (0x00A0 | PID_DESTROY) // tag process
+
+#define PID_TCODE (0x00B0 | PID_DESTROY) // tinsel code process
+
+#define PID_MASTER_SCR 0x00C0 // tinsel master script process
+
+#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process
+
+#define PID_REEL (0x00E0 | PID_DESTROY) // process for each film reel
+
+#define PID_MIDI (0x00F0 | PID_DESTROY) // process to poll MIDI sound driver
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PID_H
diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp
new file mode 100644
index 0000000000..e32fc88d3d
--- /dev/null
+++ b/engines/tinsel/play.cpp
@@ -0,0 +1,507 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays films within a scene, takes into account the actor in each 'column'. |
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h" // stand()
+
+namespace Tinsel {
+
+/**
+ * Poke the background palette into an image.
+ */
+static void PokeInPalette(SCNHANDLE hMulFrame) {
+ const FRAME *pFrame; // Pointer to frame
+ IMAGE *pim; // Pointer to image
+
+ // Could be an empty column
+ if (hMulFrame) {
+ pFrame = (const FRAME *)LockMem(hMulFrame);
+
+ // get pointer to image
+ pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+
+ pim->hImgPal = TO_LE_32(BackPal());
+ }
+}
+
+
+int32 NoNameFunc(int actorID, bool bNewMover) {
+ PMACTOR pActor;
+ int32 retval;
+
+ pActor = GetMover(actorID);
+
+ if (pActor != NULL && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ retval = getPolyZfactor(FirstPathPoly());
+ else
+ retval = getPolyZfactor(pActor->hCpath);
+ } else {
+ switch (actorMaskType(actorID)) {
+ case ACT_DEFAULT:
+ retval = 0;
+ break;
+ case ACT_MASK:
+ retval = 0;
+ break;
+ case ACT_ALWAYS:
+ retval = 10;
+ break;
+ default:
+ retval = actorMaskType(actorID);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+struct PPINIT {
+ SCNHANDLE hFilm; // The 'film'
+ int16 x; // } Co-ordinates from the play()
+ int16 y; // } - set to (-1, -1) if none.
+ int16 z; // normally 0, set if from restore
+ int16 speed; // Film speed
+ int16 actorid; // Set if called from an actor code block
+ uint8 splay; // Set if called from splay()
+ uint8 bTop; // Set if called from topplay()
+ int16 sf; // SlowFactor - only used for moving actors
+ int16 column; // Column number, first column = 0
+
+ uint8 escOn;
+ int32 myescEvent;
+};
+
+
+/**
+ * - Don't bother if this reel is already playing for this actor.
+ * - If explicit co-ordinates, use these, If embedded co-ordinates,
+ * leave alone, otherwise use actor's current position.
+ * - Moving actors get hidden during this play, other actors get
+ * _ctx->replaced by this play.
+ * - Column 0 of a film gets its appropriate Z-position, slave columns
+ * get slightly bigger Z-positions, in column order.
+ * - Play proceeds until the script finishes, another reel starts up for
+ * this actor, or the actor gets killed.
+ * - If called from an splay(), moving actor's co-ordinates are updated
+ * after the play, any walk still in progress will go on from there.
+ */
+void PlayReel(CORO_PARAM, const PPINIT *ppi) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pPlayObj; // Object
+ ANIM thisAnim; // Animation structure
+
+ bool mActor; // Gets set if this is a moving actor
+ bool lifeNoMatter;
+ bool replaced;
+
+ const FREEL *pfreel; // The 'column' to play
+ int stepCount;
+ int frameCount;
+ int reelActor;
+ CORO_END_CONTEXT(_ctx);
+
+ static int firstColZ = 0; // Z-position of column zero
+ static int32 fColZfactor = 0; // Z-factor of column zero's actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+ PMACTOR pActor;
+ bool bNewMover; // Gets set if a moving actor that isn't in scene yet
+
+ const FILM *pfilm;
+
+ _ctx->lifeNoMatter = false;
+ _ctx->replaced = false;
+ pActor = NULL;
+ bNewMover = false;
+
+ pfilm = (const FILM *)LockMem(ppi->hFilm);
+ _ctx->pfreel = &pfilm->reels[ppi->column];
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj));
+
+ // Save actor's ID
+ _ctx->reelActor = (int32)FROM_LE_32(pmi->mulID);
+
+ /**** New (experimental? bit 5/1/95 ****/
+ if (!actorAlive(_ctx->reelActor))
+ return;
+ /**** Delete a bit down there if this stays ****/
+
+ updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent);
+
+ // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+ if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) {
+ // This in not the last film scheduled for this actor
+
+ // It may be the last non-talk film though
+ if (isActorTalking(_ctx->reelActor))
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+
+ return;
+ }
+ if (isActorTalking(_ctx->reelActor)) {
+ // Note: will delete this and there'll be no need to store the talk film!
+ if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+ return;
+ }
+ } else {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm);
+ }
+
+ // If this reel is already playing for this actor, just forget it.
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ return;
+
+ // Poke in the background palette
+ PokeInPalette(FROM_LE_32(pmi->hMulFrame));
+
+ // Set up and insert the multi-object
+ _ctx->pPlayObj = MultiInitObject(pmi);
+ if (!ppi->bTop)
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ // If co-ordinates are specified, use specified.
+ // Otherwise, use actor's position if there are not embedded co-ords.
+ // Add this first test for nth columns with offsets
+ // in plays with (x,y)
+ int tmpX, tmpY;
+ tmpX = ppi->x;
+ tmpY = ppi->y;
+ if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) {
+ } else if (tmpX != -1 || tmpY != -1) {
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ } else if (!pmi->mulX && !pmi->mulY) {
+ GetActorPos(_ctx->reelActor, &tmpX, &tmpY);
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ }
+
+ // If it's a moving actor, this hides the moving actor
+ // used to do this only if (actorid == 0) - I don't know why
+ _ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf);
+
+ // If it's a moving actor, get its MACTOR structure.
+ // If it isn't in the scene yet, get its task running - using
+ // stand() - to prevent a glitch at the end of the play.
+ if (_ctx->mActor) {
+ pActor = GetMover(_ctx->reelActor);
+ if (getMActorState(pActor) == NO_MACTOR) {
+ stand(_ctx->reelActor, MAGICX, MAGICY, 0);
+ bNewMover = true;
+ }
+ }
+
+ // Register the fact that we're playing this for this actor
+ storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY);
+
+ /**** Will get rid of this if the above is kept ****/
+ // We may be temporarily resuscitating a dead actor
+ if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor))
+ _ctx->lifeNoMatter = true;
+
+ InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed);
+
+ // If first column, set Z position as per
+ // Otherwise, column 0's + column number
+ // N.B. It HAS been ensured that the first column gets here first
+ if (ppi->z != 0) {
+ MultiSetZPosition(_ctx->pPlayObj, ppi->z);
+ storeActorZpos(_ctx->reelActor, ppi->z);
+ } else if (ppi->bTop) {
+ if (ppi->column == 0) {
+ firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+ } else if (ppi->column == 0) {
+ if (_ctx->mActor && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ fColZfactor = getPolyZfactor(FirstPathPoly());
+ else
+ fColZfactor = getPolyZfactor(pActor->hCpath);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ } else {
+ switch (actorMaskType(_ctx->reelActor)) {
+ case ACT_DEFAULT:
+ fColZfactor = 0;
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_MASK:
+ fColZfactor = 0;
+ firstColZ = MultiLowest(_ctx->pPlayObj);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_ALWAYS:
+ fColZfactor = 10;
+ firstColZ = 10000;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ default:
+ fColZfactor = actorMaskType(_ctx->reelActor);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ if (firstColZ < 2) {
+ // This is an experiment!
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ }
+ break;
+ }
+ }
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) {
+ fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover);
+ firstColZ = fColZfactor << 10;
+ }
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+
+ /*
+ * Play until the script finishes,
+ * another reel starts up for this actor,
+ * or the actor gets killed.
+ */
+ _ctx->stepCount = 0;
+ _ctx->frameCount = 0;
+ do {
+ if (_ctx->stepCount++ == 0) {
+ _ctx->frameCount++;
+ storeActorSteps(_ctx->reelActor, _ctx->frameCount);
+ }
+ if (_ctx->stepCount == ppi->speed)
+ _ctx->stepCount = 0;
+
+ if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished)
+ break;
+
+ int x, y;
+ GetAniPosition(_ctx->pPlayObj, &x, &y);
+ storeActorPos(_ctx->reelActor, x, y);
+
+ CORO_SLEEP(1);
+
+ if (actorReel(_ctx->reelActor) != _ctx->pfreel) {
+ _ctx->replaced = true;
+ break;
+ }
+
+ if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents())
+ break;
+
+ } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor));
+
+ // Register the fact that we're NOT playing this for this actor
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0);
+
+ // Ditch the object
+ if (!ppi->bTop)
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ if (_ctx->mActor) {
+ if (!_ctx->replaced)
+ unHideMovingActor(_ctx->reelActor); // Restore moving actor
+
+ // Update it's co-ordinates if this is an splay()
+ if (ppi->splay)
+ restoreMovement(_ctx->reelActor);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Run all animations that comprise the play film.
+ */
+static void playProcess(CORO_PARAM, const void *param) {
+ // get the stuff copied to process when it was created
+ PPINIT *ppi = (PPINIT *)param;
+
+ PlayReel(coroParam, ppi);
+}
+
+// *******************************************************
+
+
+// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+void newestFilm(SCNHANDLE film, const FREEL *reel) {
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj));
+
+ setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film);
+}
+
+// *******************************************************
+
+/**
+ * Start up a play process for each column in a film.
+ *
+ * NOTE: The processes are started in reverse order so that the first
+ * column's process kicks in first.
+ */
+void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn,
+ int myescEvent, bool bTop) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ assert(film != 0); // Trying to play NULL film
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Nothing to do!
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = 0;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = actorid;
+ ppi.splay = splay;
+ ppi.bTop = bTop;
+ ppi.sf = sfact;
+ ppi.escOn = escOn;
+ ppi.myescEvent = myescEvent;
+
+ // Start display process for each reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ ppi.column = i;
+ g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(PPINIT));
+ }
+}
+
+/**
+ * Start up a play process for each slave column in a film.
+ * Play the first column directly from the parent process.
+ */
+void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ CORO_BEGIN_CONTEXT;
+ PPINIT ppi;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // Trying to play NULL film
+ const FILM *pfilm;
+
+ pfilm = (const FILM *)LockMem(film);
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Already played to completion!
+
+ _ctx->ppi.hFilm = film;
+ _ctx->ppi.x = x;
+ _ctx->ppi.y = y;
+ _ctx->ppi.z = 0;
+ _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ _ctx->ppi.actorid = actorid;
+ _ctx->ppi.splay = splay;
+ _ctx->ppi.bTop = bTop;
+ _ctx->ppi.sf = sfact;
+ _ctx->ppi.escOn = escOn;
+ _ctx->ppi.myescEvent = myescEvent;
+
+ // Start display process for each secondary reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ _ctx->ppi.column = i;
+ g_scheduler->createProcess(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT));
+ }
+
+ newestFilm(film, &pfilm->reels[0]);
+
+ _ctx->ppi.column = 0;
+ CORO_INVOKE_1(PlayReel, &_ctx->ppi);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Start up a play process for a particular column in a film.
+ *
+ * NOTE: This is specifically for actors during a restore scene.
+ */
+void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = z;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = 0;
+ ppi.splay = false;
+ ppi.bTop = false;
+ ppi.sf = 0;
+ ppi.column = reelnum;
+
+ // FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when
+ // returning to a scene because escOn and myescEvent were undefined. Need to make sure whether
+ // restored objects should have any particular combination of these two values
+ ppi.escOn = false;
+ ppi.myescEvent = GetEscEvents();
+
+ assert(pfilm->numreels);
+
+ newestFilm(film, &pfilm->reels[reelnum]);
+
+ // Start display process for the reel
+ g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(ppi));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp
new file mode 100644
index 0000000000..d73e290277
--- /dev/null
+++ b/engines/tinsel/polygons.cpp
@@ -0,0 +1,1862 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+/** different types of polygon */
+enum POLY_TYPE {
+ POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT,
+ POLY_EXIT, POLY_TAG
+};
+
+
+// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4
+// so reduced this back to 6 (from 12) for now.
+#define MAXADJ 6 // Max number of known adjacent paths
+
+struct POLYGON {
+
+ PTYPE polytype; // Polygon type
+
+ int subtype; // refer type in REFER polygons
+ // NODE/NORMAL in PATH polygons
+
+ int pIndex; // Index into compiled polygon data
+
+ /*
+ * Data duplicated from compiled polygon data
+ */
+ short cx[4]; // Corners (clockwise direction)
+ short cy[4];
+ int polyID;
+
+ /* For TAG and EXIT (and EFFECT in future?) polygons only */
+ TSTATE tagState;
+ PSTATE pointState;
+ SCNHANDLE oTagHandle; // Override tag.
+
+ /* For Path polygons only */
+ bool tried;
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by FiddlyBit()
+ */
+ short ptop; //
+ short pbottom; // Enclosing external rectangle
+ short pleft; //
+ short pright; //
+
+ short ltop[4]; //
+ short lbottom[4]; // Rectangles enclosing each side
+ short lleft[4]; //
+ short lright[4]; //
+
+ int a[4]; // y1-y2 }
+ int b[4]; // x2-x1 } See IsInPolygon()
+ long c[4]; // y1x2 - x1y2 }
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by PseudoCentre()
+ */
+ int pcentrex; // Pseudo-centre
+ int pcentrey; //
+
+ /**
+ * List of adjacent polygons. For Path polygons only.
+ * set up by SetPathAdjacencies()
+ */
+ POLYGON *adjpaths[MAXADJ];
+
+};
+
+
+#define MAXONROUTE 40
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** lineinfo struct - one per (node-1) in a node path */
+struct LINEINFO {
+
+ int32 a;
+ int32 b;
+ int32 c;
+
+ int32 a2; //!< a squared
+ int32 b2; //!< b squared
+ int32 a2pb2; //!< a squared + b squared
+ int32 ra2pb2; //!< root(a squared + b squared)
+
+ int32 ab;
+ int32 ac;
+ int32 bc;
+} PACKED_STRUCT;
+
+/** polygon struct - one per polygon */
+struct POLY {
+ int32 type; //!< type of polygon
+ int32 x[4], y[4]; // Polygon definition
+
+ int32 tagx, tagy; // } For tagged polygons
+ SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT
+
+ int32 nodex, nodey; // EXIT, TAG, REFER
+ SCNHANDLE hFilm; //!< film reel handle for EXIT, TAG
+
+ int32 reftype; //!< Type of REFER
+
+ int32 id; // } EXIT and TAG
+
+ int32 scale1, scale2; // }
+ int32 reel; // } PATH and NPATH
+ int32 zFactor; // }
+
+ //The arrays now stored externally
+ int32 nodecount; //!<The number of nodes in this polygon
+ int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present
+ int32 plinelist;
+
+ SCNHANDLE hScript; //!< handle of code segment for polygon events
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int MaxPolys = MAX_POLY;
+
+static POLYGON *Polys[MAX_POLY+1];
+
+static POLYGON *Polygons = 0;
+
+static SCNHANDLE pHandle = 0; // } Set at start of each scene
+static int noofPolys = 0; // }
+
+static POLYGON extraBlock; // Used for dynamic blocking
+
+static int pathsOnRoute = 0;
+static const POLYGON *RoutePaths[MAXONROUTE];
+
+static POLYGON *RouteEnd = 0;
+
+#ifdef DEBUG
+int highestYet = 0;
+#endif
+
+
+
+//----------------- LOCAL MACROS --------------------
+
+// The str parameter is no longer used
+#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY);
+#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys);
+
+static HPOLYGON PolyIndex(const POLYGON *pp) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] == pp)
+ return j;
+ }
+
+ error("PolyIndex(): polygon not found");
+ return NOPOLY;
+}
+
+/**
+ * Returns TRUE if the point is within the polygon supplied.
+ *
+ * Firstly, the point must be within the smallest imaginary rectangle
+ * which encloses the polygon.
+ *
+ * Then, from each corner of the polygon, if the point is within an
+ * imaginary rectangle enclosing the clockwise-going side from that
+ * corner, the gradient of a line from the corner to the point must be
+ * less than (or more negative than) the gradient of that side:
+ *
+ * If the corners' coordinates are designated (x1, y1) and (x2, y2), and
+ * the point in question's (xt, yt), then:
+ * gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt)
+ * (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1)
+ * (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1)
+ * xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1
+ * xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2
+ *
+ * If the point passed one of the four 'side tests', and failed none,
+ * then it must be within the polygon. If the point was not tested, it
+ * may be within the internal rectangle not covered by the above tests.
+ *
+ * Most polygons contain an internal rectangle which does not fall into
+ * any of the above side-related tests. Such a rectangle will always
+ * have two polygon corners above it and two corners to the left of it.
+ */
+bool IsInPolygon(int xt, int yt, HPOLYGON hp) {
+ const POLYGON *pp;
+ int i;
+ bool BeenTested = false;
+ int pl = 0, pa = 0;
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (1)");
+ pp = Polys[hp];
+ assert(pp != NULL); // Testing whether in a NULL polygon
+
+ /* Is point within the external rectangle? */
+ if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom)
+ return false;
+
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // If within this side's 'testable' area
+ // i.e. within the width of the line in y direction of end of line
+ // or within the height of the line in x direction of end of line
+ if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i])))
+ || (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) {
+ if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i])
+ return false;
+ else
+ BeenTested = true;
+ }
+ }
+
+ if (BeenTested) {
+ // New dodgy code 29/12/94
+ if (pp->polytype == BLOCKING) {
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // Pretend the corners of blocking polys are not in the poly.
+ if (xt == pp->cx[i] && yt == pp->cy[i])
+ return false;
+ }
+ }
+ return true;
+ } else {
+ // Is point within the internal rectangle?
+ for (i = 0; i < 4; i++) {
+ if (pp->cx[i] < xt)
+ pl++;
+ if (pp->cy[i] < yt)
+ pa++;
+ }
+
+ if (pa == 2 && pl == 2)
+ return true;
+ else
+ return false;
+ }
+}
+
+/**
+ * Finds a polygon of the specified type containing the supplied point.
+ */
+
+HPOLYGON InPolygon(int xt, int yt, PTYPE type) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == type) {
+ if (IsInPolygon(xt, yt, j))
+ return j;
+ }
+ }
+ return NOPOLY;
+}
+
+/**
+ * Given a blocking polygon, current co-ordinates of an actor, and the
+ * co-ordinates of where the actor is heading, works out which corner of
+ * the blocking polygon to head around.
+ */
+
+void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) {
+ const POLYGON *pp;
+ int i;
+ int xd, yd; // distance per axis
+ int ThisD, SmallestD = 1000;
+ int D1, D2;
+ int NearestToHere = 1000, NearestToTarget;
+ unsigned At = 10; // Corner already at
+
+ int bcx[4], bcy[4]; // Bogus corners
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (2)");
+ pp = Polys[hp];
+
+ // Work out a point outside each corner
+ for (i = 0; i < 4; i++) {
+ int next, prev;
+
+ // X-direction
+ next = pp->cx[i] - pp->cx[(i+1)%4];
+ prev = pp->cx[i] - pp->cx[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcx[i] = pp->cx[i] - 4; // Both points to the right
+ else if (next >= 0 && prev >= 0)
+ bcx[i] = pp->cx[i] + 4; // Both points to the left
+ else
+ bcx[i] = pp->cx[i];
+
+ // Y-direction
+ next = pp->cy[i] - pp->cy[(i+1)%4];
+ prev = pp->cy[i] - pp->cy[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcy[i] = pp->cy[i] - 4; // Both points below
+ else if (next >= 0 && prev >= 0)
+ bcy[i] = pp->cy[i] + 4; // Both points above
+ else
+ bcy[i] = pp->cy[i];
+ }
+
+ // Find nearest corner to where we are,
+ // but not the one we're stood at.
+
+ for (i = 0; i < 4; i++) { // For 4 corners
+// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]);
+ ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]);
+ if (ThisD < SmallestD) {
+ // Ignore this corner if it's not in a path
+ if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY ||
+ InPolygon(bcx[i], bcy[i], PATH) == NOPOLY)
+ continue;
+
+ // Are we stood at this corner?
+ if (ThisD > 4) {
+ // No - it's the nearest we've found yet.
+ NearestToHere = i;
+ SmallestD = ThisD;
+ } else {
+ // Stood at/next to this corner
+ At = i;
+ }
+ }
+ }
+
+ // If we're not already at a corner, go to the nearest corner
+
+ if (At == 10) {
+ // Not stood at a corner
+// assert(NearestToHere != 1000); // At blocking corner, not found near corner!
+ // Better to give up than to assert fail!
+ if (NearestToHere == 1000) {
+ // Send it to where it is now
+ // i.e. leave x and y alone
+ } else {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ }
+ } else {
+ // Already at a corner. Go to an adjacent corner.
+ // First, find out which adjacent corner is nearest the target.
+ xd = ABS(tarx - pp->cx[(At + 1) % 4]);
+ yd = ABS(tary - pp->cy[(At + 1) % 4]);
+ D1 = xd + yd;
+ xd = ABS(tarx - pp->cx[(At + 3) % 4]);
+ yd = ABS(tary - pp->cy[(At + 3) % 4]);
+ D2 = xd + yd;
+ NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4;
+ if (NearestToTarget == NearestToHere) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ // Need to decide whether it's better to go to the nearest to
+ // here and then on to the target, or to the nearest to the
+ // target and on from there.
+ xd = ABS(pp->cx[At] - pp->cx[NearestToHere]);
+ D1 = xd;
+ xd = ABS(pp->cx[NearestToHere] - tarx);
+ D1 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToHere]);
+ D1 += yd;
+ yd = ABS(pp->cy[NearestToHere] - tary);
+ D1 += yd;
+
+ xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]);
+ D2 = xd;
+ xd = ABS(pp->cx[NearestToTarget] - tarx);
+ D2 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]);
+ D2 += yd;
+ yd = ABS(pp->cy[NearestToTarget] - tary);
+ D2 += yd;
+
+ if (D2 > D1) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ *x = bcx[NearestToTarget];
+ *y = bcy[NearestToTarget];
+ }
+ }
+ }
+}
+
+
+/**
+ * Try do drop a perpendicular to each inter-node line from the point
+ * and remember the shortest (if any).
+ * Find which node is nearest to the point.
+ * The shortest of these gives the best point in the node path.
+*/
+void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) {
+ const POLYGON *pp;
+
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Compiled polygon data
+ int dropD; // length of perpendicular (i.e. distance of point from line)
+ int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line
+ int d1, d2; // distance from perpendicular intersect to line's end nodes
+ int32 *nlistx, *nlisty;
+
+ int shortestD = 10000; // Shortest distance found
+ int nearestL = -1; // Nearest line
+ int nearestN; // Nearest Node
+
+ int h = *x; // For readability/conveniance
+ int k = *y; // - why aren't these #defines?
+ LINEINFO *llist; // Inter-node line structure
+
+ CHECK_HP(hp, "Out of range polygon handle (3)");
+ pp = Polys[hp];
+
+ // Pointer to polygon data
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+ llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist));
+
+ // Look for fit of perpendicular to lines between nodes
+ for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) {
+ const int32 a = (int)FROM_LE_32(llist[i].a);
+ const int32 b = (int)FROM_LE_32(llist[i].b);
+ const int32 c = (int)FROM_LE_32(llist[i].c);
+
+#if 1
+ if (true) {
+ //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b);
+
+ // TODO: If the comments of the LINEINFO struct are correct, then it contains mostly
+ // duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices
+ // we support, calculatin a product of two ints is not an issue.
+ // So we can just load & endian convert a,b,c, then replace stuff like
+ // (int)FROM_LE_32(line->ab)
+ // by simply a*b, which makes it easier to understand what the code does, too.
+ // Just in case there is some bugged data, I leave this code here for verifying it.
+ // Let's leave it in for some time.
+ //
+ // One bad thing: We use sqrt to compute a square root. Might not be a good idea,
+ // speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later.
+
+ LINEINFO *line = &llist[i];
+ int32 a2 = (int)FROM_LE_32(line->a2); //!< a squared
+ int32 b2 = (int)FROM_LE_32(line->b2); //!< b squared
+ int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); //!< a squared + b squared
+ int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); //!< root(a squared + b squared)
+
+ int32 ab = (int)FROM_LE_32(line->ab);
+ int32 ac = (int)FROM_LE_32(line->ac);
+ int32 bc = (int)FROM_LE_32(line->bc);
+
+ assert(a*a == a2);
+ assert(b*b == b2);
+ assert(a*b == ab);
+ assert(a*c == ac);
+ assert(b*c == bc);
+
+ assert(a2pb2 == a*a + b*b);
+ assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b));
+ }
+#endif
+
+
+ if (a == 0 && b == 0)
+ continue; // Line is just a point!
+
+ // X position of dropped perpendicular intersection with line
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+
+ // X distances from intersection to end nodes
+ d1 = dropX - (int)FROM_LE_32(nlistx[i]);
+ d2 = dropX - (int)FROM_LE_32(nlistx[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#if 0
+ // Y position of sidweays perpendicular intersection with line
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+
+ // Y distances from intersection to end nodes
+ d1 = dropY - (int)FROM_LE_32(nlisty[i]);
+ d2 = dropY - (int)FROM_LE_32(nlisty[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#endif
+ dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b);
+ dropD = ABS(dropD);
+ if (dropD < shortestD) {
+ shortestD = dropD;
+ nearestL = i;
+ }
+ }
+
+ // Distance to nearest node
+ nearestN = NearestNodeWithin(hp, h, k);
+ dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN]));
+
+ // Go to a node or a point on a line
+ if (dropD < shortestD) {
+ // A node is nearest
+ *x = (int)FROM_LE_32(nlistx[nearestN]);
+ *y = (int)FROM_LE_32(nlisty[nearestN]);
+ *pline = nearestN;
+ } else {
+ assert(nearestL != -1);
+
+ // A point on a line is nearest
+ const int32 a = (int)FROM_LE_32(llist[nearestL].a);
+ const int32 b = (int)FROM_LE_32(llist[nearestL].b);
+ const int32 c = (int)FROM_LE_32(llist[nearestL].c);
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+ *x = dropX;
+ *y = dropY;
+ *pline = nearestL;
+ }
+
+ assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!)
+}
+
+/**
+ * Returns TRUE if two paths are asdjacent.
+ */
+bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) {
+ const POLYGON *pp1, *pp2;
+
+ CHECK_HP(hPath1, "Out of range polygon handle (4)");
+ CHECK_HP(hPath2, "Out of range polygon handle (500)");
+
+ if (hPath1 == hPath2)
+ return true;
+
+ pp1 = Polys[hPath1];
+ pp2 = Polys[hPath2];
+
+ for (int j = 0; j < MAXADJ; j++)
+ if (pp1->adjpaths[j] == pp2)
+ return true;
+
+ return false;
+}
+
+static const POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) {
+ POLYGON *x;
+
+ // For each path adjacent to this one
+ for (int j = 0; j < MAXADJ; j++) {
+ x = current->adjpaths[j]; // call the adj. path x
+ if (x == whereto) {
+ RoutePaths[pathsOnRoute++] = x;
+ return x; // Got there!
+ }
+
+ if (x == NULL)
+ break; // no more adj. paths to look at
+
+ if (x->tried)
+ continue; // don't double back
+
+ if (x == last)
+ continue; // don't double back
+
+ x->tried = true;
+ if (TryPath(current, whereto, x) != NULL) {
+ RoutePaths[pathsOnRoute++] = x;
+ assert(pathsOnRoute < MAXONROUTE);
+ return x; // Got there in this direction
+ } else
+ x->tried = false;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Sort out the first path to head to for the imminent leg of a walk.
+ */
+static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) {
+ // TODO: Fingolfin says: This code currently uses DFS (depth first search),
+ // in the TryPath function, to compute a path between 'from' and 'to'.
+ // However, a BFS (breadth first search) might yield more natural results,
+ // at least in cases where there are multiple possible paths.
+ // There is a small risk of regressions caused by such a change, though.
+ //
+ // Also, the overhead of computing a DFS again and again could be avoided
+ // by computing a path matrix (like we do in the SCUMM engine).
+ int i;
+
+ CHECK_HP(from, "Out of range polygon handle (501a)");
+ CHECK_HP(to, "Out of range polygon handle (501b)");
+
+ if (IsAdjacentPath(from, to))
+ return to;
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ POLYGON *p = Polys[i];
+ if (p && p->polytype == PATH) //...if it's a path
+ p->tried = false;
+ }
+ Polys[from]->tried = true;
+ pathsOnRoute = 0;
+
+ const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]);
+
+ assert(p != NULL); // Trying to find route between unconnected paths
+
+ // Don't go a roundabout way to an adjacent path.
+ for (i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)");
+ if (IsAdjacentPath(from, PolyIndex(RoutePaths[i])))
+ return PolyIndex(RoutePaths[i]);
+ }
+ return PolyIndex(p);
+}
+
+/**
+ * Indirect method of calling PathOnTheWay(), to put the burden of
+ * recursion onto the main stack.
+ */
+HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) {
+ CHECK_HP(hFrom, "Out of range polygon handle (6)");
+ CHECK_HP(hTo, "Out of range polygon handle (7)");
+
+ // Reuse already computed result
+ if (RouteEnd == Polys[hTo]) {
+ for (int i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)");
+ if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) {
+ return PolyIndex(RoutePaths[i]);
+ }
+ }
+ }
+
+ RouteEnd = Polys[hTo];
+ return PathOnTheWay(hFrom, hTo);
+}
+
+
+/**
+ * Given a node path, work out which end node is nearest the given point.
+ */
+
+int NearestEndNode(HPOLYGON hPath, int x, int y) {
+ const POLYGON *pp;
+
+ int d1, d2;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hPath, "Out of range polygon handle (8)");
+ pp = Polys[hPath];
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ const int nodecount = (int)FROM_LE_32(ptp->nodecount);
+
+ d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0]));
+ d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1]));
+
+ return (d2 > d1) ? 0 : nodecount - 1;
+}
+
+
+/**
+ * Given a start path and a destination path, find which pair of end
+ * nodes is nearest together.
+ * Return which node in the start path is part of the closest pair.
+ */
+
+int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) {
+ const POLYGON *pSpath, *pDpath;
+
+ int ns, nd; // 'top' nodes in each path
+ int dist, NearDist;
+ int NearNode;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ps, *pd; // Pointer to compiled polygon data
+ int32 *snlistx, *snlisty;
+ int32 *dnlistx, *dnlisty;
+
+ CHECK_HP(hSpath, "Out of range polygon handle (9)");
+ CHECK_HP(hDpath, "Out of range polygon handle (10)");
+ pSpath = Polys[hSpath];
+ pDpath = Polys[hDpath];
+
+ pps = LockMem(pHandle); // All polygons
+ ps = (const POLY *)pps + pSpath->pIndex; // Start polygon
+ pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon
+
+ ns = (int)FROM_LE_32(ps->nodecount) - 1;
+ nd = (int)FROM_LE_32(pd->nodecount) - 1;
+
+ snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx));
+ snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty));
+ dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx));
+ dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty));
+
+ // start[0] to dest[0]
+ NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0]));
+ NearNode = 0;
+
+ // start[0] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist)
+ NearDist = dist;
+
+ // start[top] to dest[0]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0]));
+ if (dist < NearDist) {
+ NearDist = dist;
+ NearNode = ns;
+ }
+
+ // start[top] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist) {
+ NearNode = ns;
+ }
+
+ return NearNode;
+}
+
+/**
+ * Given a follow nodes path and a co-ordinate, finds which node in the
+ * path is nearest to the co-ordinate.
+ */
+int NearestNodeWithin(HPOLYGON hNpath, int x, int y) {
+ int ThisDistance, SmallestDistance = 1000;
+ int NumNodes; // Number of nodes in this follow nodes path
+ int NearestYet = 0; // Number of nearest node
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (11)");
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ NumNodes = (int)FROM_LE_32(ptp->nodecount);
+
+ for (int i = 0; i < NumNodes; i++) {
+ ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i]));
+
+ if (ThisDistance < SmallestDistance) {
+ NearestYet = i;
+ SmallestDistance = ThisDistance;
+ }
+ }
+
+ return NearestYet;
+}
+
+/**
+ * Given a point and start and destination paths, find the nearest
+ * corner (if any) of the start path which is within the destination
+ * path. If there is no such corner, find the nearest corner of the
+ * destination path which falls within the source path.
+ */
+void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) {
+ const POLYGON *psp, *pdp;
+ int j;
+ int ncorn = 0; // nearest corner
+ HPOLYGON hNpath = NOPOLY; // path containing nearest corner
+ int ThisD, SmallestD = 1000;
+
+ CHECK_HP(hStartPoly, "Out of range polygon handle (12)");
+ CHECK_HP(hDestPoly, "Out of range polygon handle (13)");
+
+ psp = Polys[hStartPoly];
+ pdp = Polys[hDestPoly];
+
+ // Nearest corner of start path in destination path.
+
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) {
+ ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hStartPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ if (SmallestD == 1000) {
+ // Nearest corner of destination path in start path.
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) {
+ ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hDestPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ }
+
+ if (hNpath != NOPOLY) {
+ *x = Polys[hNpath]->cx[ncorn];
+ *y = Polys[hNpath]->cy[ncorn];
+ } else
+ error("NearestCorner() failure");
+}
+
+bool IsPolyCorner(HPOLYGON hPath, int x, int y) {
+ CHECK_HP(hPath, "Out of range polygon handle (37)");
+
+ for (int i = 0; i < 4; i++) {
+ if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Given a path polygon and a Y co-ordinate, return a scale value.
+ */
+int GetScale(HPOLYGON hPath, int y) {
+ const POLY *ptp; // Pointer to compiled polygon data
+ int zones; // Number of different scales
+ int zlen; // Depth of each scale zone
+ int scale;
+ int top;
+
+ // To try and fix some unknown potential bug
+ if (hPath == NOPOLY)
+ return SCALE_LARGE;
+
+ CHECK_HP(hPath, "Out of range polygon handle (14)");
+
+ ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex;
+
+ // Path is of a constant scale?
+ if (FROM_LE_32(ptp->scale2) == 0)
+ return FROM_LE_32(ptp->scale1);
+
+ assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2));
+
+ zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1;
+ zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones;
+
+ scale = FROM_LE_32(ptp->scale1);
+ top = Polys[hPath]->ptop;
+
+ do {
+ top += zlen;
+ if (y < top)
+ return scale;
+ } while (--scale);
+
+ return FROM_LE_32(ptp->scale2);
+}
+
+/**
+ * Give the co-ordinates of a node in a node path.
+ */
+void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) {
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (15)");
+ assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path!
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ // Might have just walked to the node from above.
+ if (node == (int)FROM_LE_32(ptp->nodecount))
+ node -= 1;
+
+ *px = (int)FROM_LE_32(nlistx[node]);
+ *py = (int)FROM_LE_32(nlisty[node]);
+}
+
+/**
+ * Get tag text handle and tag co-ordinates of a polygon.
+ */
+
+void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (16)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *tagx = (int)FROM_LE_32(pp->tagx);
+ *tagy = (int)FROM_LE_32(pp->tagy);
+ *hTagText = FROM_LE_32(pp->hTagtext);
+}
+
+/**
+ * Get polygon's film reel handle.
+ */
+
+SCNHANDLE getPolyFilm(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (17)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hFilm);
+}
+
+/**
+ * Get polygon's associated node.
+ */
+
+void getPolyNode(HPOLYGON hp, int *px, int *py) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (18)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *px = (int)FROM_LE_32(pp->nodex);
+ *py = (int)FROM_LE_32(pp->nodey);
+}
+
+/**
+ * Get handle to polygon's glitter code.
+ */
+
+SCNHANDLE getPolyScript(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (19)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hScript);
+}
+
+REEL getPolyReelType(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ // To try and fix some unknown potential bug (toyshop entrance)
+ if (hp == NOPOLY)
+ return REEL_ALL;
+
+ CHECK_HP(hp, "Out of range polygon handle (20)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (REEL)FROM_LE_32(pp->reel);
+}
+
+int32 getPolyZfactor(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (21)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->zFactor);
+}
+
+int numNodes(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (22)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->nodecount);
+}
+
+// *************************************************************************
+//
+// Code concerned with killing and reviving TAG and EXIT polygons.
+// And code to enable this information to be saved and restored.
+//
+// *************************************************************************
+
+struct TAGSTATE {
+ int tid;
+ bool enabled;
+};
+
+#define MAX_SCENES 256
+#define MAX_TAGS 2048
+#define MAX_EXITS 512
+
+static struct {
+ SCNHANDLE sid;
+ int nooftags;
+ int offset;
+} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES];
+
+static TAGSTATE TagStates[MAX_TAGS];
+static TAGSTATE ExitStates[MAX_EXITS];
+
+static int nextfreeT = 0, numScenesT = 0;
+static int nextfreeE = 0, numScenesE = 0;
+
+static int currentTScene = 0;
+static int currentEScene = 0;
+
+bool deadPolys[MAX_POLY]; // Currently just for dead blocks
+
+void RebootDeadTags(void) {
+ nextfreeT = numScenesT = 0;
+ nextfreeE = numScenesE = 0;
+
+ memset(SceneTags, 0, sizeof(SceneTags));
+ memset(SceneExits, 0, sizeof(SceneExits));
+ memset(TagStates, 0, sizeof(TagStates));
+ memset(ExitStates, 0, sizeof(ExitStates));
+ memset(deadPolys, 0, sizeof(deadPolys));
+}
+
+/**
+ * (Un)serialize the dead tag and exit data for save/restore game.
+ */
+void syncPolyInfo(Serializer &s) {
+ int i;
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneTags[i].sid);
+ s.syncAsSint32LE(SceneTags[i].nooftags);
+ s.syncAsSint32LE(SceneTags[i].offset);
+ }
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneExits[i].sid);
+ s.syncAsSint32LE(SceneExits[i].nooftags);
+ s.syncAsSint32LE(SceneExits[i].offset);
+ }
+
+ for (i = 0; i < MAX_TAGS; i++) {
+ s.syncAsUint32LE(TagStates[i].tid);
+ s.syncAsSint32LE(TagStates[i].enabled);
+ }
+
+ for (i = 0; i < MAX_EXITS; i++) {
+ s.syncAsUint32LE(ExitStates[i].tid);
+ s.syncAsSint32LE(ExitStates[i].enabled);
+ }
+
+ s.syncAsSint32LE(nextfreeT);
+ s.syncAsSint32LE(numScenesT);
+ s.syncAsSint32LE(nextfreeE);
+ s.syncAsSint32LE(numScenesE);
+}
+
+/**
+ * This is all totally different to the way the rest of the way polygon
+ * data is stored and restored, more specifically, different to how dead
+ * tags and exits are handled, because of the piecemeal design-by-just-
+ * thought-of-this approach employed.
+ */
+
+void SaveDeadPolys(bool *sdp) {
+ memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool));
+}
+
+void RestoreDeadPolys(bool *sdp) {
+ memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool));
+}
+
+/**
+ * Convert a BLOCKING to an EX_BLOCK poly.
+ */
+void DisableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = EX_BLOCK;
+ deadPolys[i] = true;
+ }
+ }
+}
+
+/**
+ * Convert an EX_BLOCK to a BLOCKING poly.
+ */
+void EnableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = BLOCKING;
+ deadPolys[i] = false;
+ }
+ }
+}
+
+/**
+ * Convert an EX_TAG to a TAG poly.
+ */
+void EnableTag(int tagno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = TAG;
+ }
+ }
+
+ TAGSTATE *pts;
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert an EX_EXIT to a EXIT poly.
+ */
+void EnableExit(int exitno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EXIT;
+ }
+ }
+
+ TAGSTATE *pts;
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert a TAG to an EX_TAG poly.
+ */
+void DisableTag(int tagno) {
+ TAGSTATE *pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = EX_TAG;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert a EXIT to an EX_EXIT poly.
+ */
+void DisableExit(int exitno) {
+ TAGSTATE *pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EX_EXIT;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+HPOLYGON FirstPathPoly(void) {
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]->polytype == PATH)
+ return i;
+ }
+ error("FirstPathPoly() - no PATH polygons!");
+ return NOPOLY;
+}
+
+HPOLYGON GetPolyHandle(int i) {
+ assert(i >= 0 && i <= MAX_POLY);
+
+ return (Polys[i] != NULL) ? i : NOPOLY;
+}
+
+// **************************************************************************
+//
+// Code called to initialise or wrap up a scene:
+//
+// **************************************************************************
+
+/**
+ * Called at the start of a scene, when all polygons have been
+ * initialised, to work out which paths are adjacent to which.
+ */
+static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) {
+ const POLYGON *pp1, *pp2;
+ int i, j;
+ int retval = 0;
+
+ CHECK_HP(hp1, "Out of range polygon handle (23)");
+ CHECK_HP(hp2, "Out of range polygon handle (24)");
+ pp1 = Polys[hp1];
+ pp2 = Polys[hp2];
+
+ // Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1)
+ for (i = 0; i < 4; i++) {
+ if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2))
+ retval++;
+ if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1))
+ retval++;
+ }
+
+ // Common corners only count once
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j])
+ retval--;
+ }
+ }
+ return retval;
+}
+
+static void SetPathAdjacencies() {
+ POLYGON *p1, *p2; // Polygon pointers
+
+ // For each polygon..
+ for (int i1 = 0; i1 < MAX_POLY-1; i1++) {
+ // Get polygon, but only carry on if it's a path
+ p1 = Polys[i1];
+ if (!p1 || p1->polytype != PATH)
+ continue;
+
+ // For each subsequent polygon..
+ for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) {
+ // Get polygon, but only carry on if it's a path
+ p2 = Polys[i2];
+ if (!p2 || p2->polytype != PATH)
+ continue;
+
+ int j = DistinctCorners(i1, i2);
+
+ if (j >= 2) {
+ // Paths are adjacent
+ for (j = 0; j < MAXADJ; j++)
+ if (p1->adjpaths[j] == NULL) {
+ p1->adjpaths[j] = p2;
+ break;
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ for (j = 0; j < MAXADJ; j++) {
+ if (p2->adjpaths[j] == NULL) {
+ p2->adjpaths[j] = p1;
+ break;
+ }
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ }
+ }
+ }
+}
+
+/**
+ * Ensure NPATH nodes are not inside another PATH/NPATH polygon.
+ * Only bother with end nodes for now.
+ */
+#ifdef DEBUG
+void CheckNPathIntegrity() {
+ uint8 *pps; // Compiled polygon data
+ const POLYGON *rp; // Run-time polygon structure
+ HPOLYGON hp;
+ const POLY *cp; // Compiled polygon structure
+ int i, j; // Loop counters
+ int n; // Last node in current path
+ int32 *nlistx, *nlisty;
+
+ pps = LockMem(pHandle); // All polygons
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ rp = Polys[i];
+ if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path
+ // Get compiled polygon structure
+ cp = (const POLY *)pps + rp->pIndex; // This polygon
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty));
+
+ n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node
+ assert(n >= 1); // Node paths must have at least 2 nodes
+
+ hp = PolyIndex(rp);
+ for (j = 0; j <= n; j++) {
+ if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]);
+ error(tBufferAddr());
+ }
+ }
+
+ // Check end nodes are not in adjacent path
+ for (j = 0; j < MAXADJ; j++) { // For each adjacent path
+ if (rp->adjpaths[j] == NULL)
+ break;
+
+ if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ }
+ }
+ }
+}
+#endif
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExBlocks() {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (deadPolys[i]) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING)
+ Polys[i]->polytype = EX_BLOCK;
+#ifdef DEBUG
+ else
+ error("Impossible message!");
+#endif
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExTags(SCNHANDLE ph) {
+ TAGSTATE *pts;
+ int i, j;
+
+ for (i = 0; i < numScenesT; i++) {
+ if (SceneTags[i].sid == ph) {
+ currentTScene = i;
+
+ pts = &TagStates[SceneTags[i].offset];
+ for (j = 0; j < SceneTags[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableTag(pts->tid);
+ }
+ return;
+ }
+ }
+
+ i = numScenesT++;
+ currentTScene = i;
+ assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit
+
+ SceneTags[i].sid = ph;
+ SceneTags[i].offset = nextfreeT;
+ SceneTags[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == TAG) {
+ TagStates[nextfreeT].tid = Polys[j]->polyID;
+ TagStates[nextfreeT].enabled = true;
+ nextfreeT++;
+ assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit
+ SceneTags[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles EXIT polygons which should be dead.
+ */
+static void SetExExits(SCNHANDLE ph) {
+ TAGSTATE *pts;
+ int i, j;
+
+ for (i = 0; i < numScenesE; i++) {
+ if (SceneExits[i].sid == ph) {
+ currentEScene = i;
+
+ pts = &ExitStates[SceneExits[i].offset];
+ for (j = 0; j < SceneExits[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableExit(pts->tid);
+ }
+ return;
+ }
+ }
+
+ i = numScenesE++;
+ currentEScene = i;
+ assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit
+
+ SceneExits[i].sid = ph;
+ SceneExits[i].offset = nextfreeE;
+ SceneExits[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == EXIT) {
+ ExitStates[nextfreeE].tid = Polys[j]->polyID;
+ ExitStates[nextfreeE].enabled = true;
+ nextfreeE++;
+ assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit
+ SceneExits[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Works out some fixed numbers for a polygon.
+ */
+static void FiddlyBit(POLYGON *p) {
+ int t1, t2; // General purpose temp. variables
+
+ // Enclosing external rectangle
+ t1 = MAX(p->cx[0], p->cx[1]);
+ t2 = MAX(p->cx[2], p->cx[3]);
+ p->pright = MAX(t1, t2);
+
+ t1 = MIN(p->cx[0], p->cx[1]);
+ t2 = MIN(p->cx[2], p->cx[3]);
+ p->pleft = MIN(t1, t2);
+
+ t1 = MAX(p->cy[0], p->cy[1]);
+ t2 = MAX(p->cy[2], p->cy[3]);
+ p->pbottom = MAX(t1, t2);
+
+ t1 = MIN(p->cy[0], p->cy[1]);
+ t2 = MIN(p->cy[2], p->cy[3]);
+ p->ptop = MIN(t1, t2);
+
+ // Rectangles enclosing each side and each side's magic numbers
+ for (t1 = 0; t1 < 4; t1++) {
+ p->lright[t1] = MAX(p->cx[t1], p->cx[(t1+1)%4]);
+ p->lleft[t1] = MIN(p->cx[t1], p->cx[(t1+1)%4]);
+
+ p->ltop[t1] = MIN(p->cy[t1], p->cy[(t1+1)%4]);
+ p->lbottom[t1] = MAX(p->cy[t1], p->cy[(t1+1)%4]);
+
+ p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4];
+ p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1];
+ p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4];
+ }
+}
+
+/**
+ * Calculate a point approximating to the centre of a polygon.
+ * Not very sophisticated.
+ */
+static void PseudoCentre(POLYGON *p) {
+ p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4;
+ p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4;
+
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ int i, top = 0, bot = 0;
+
+ for (i = p->ptop; i <= p->pbottom; i++) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ top = i;
+ break;
+ }
+ }
+ for (i = p->pbottom; i >= p->ptop; i--) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ bot = i;
+ break;
+ }
+ }
+ p->pcentrex = (top+bot)/2;
+ }
+#ifdef DEBUG
+ // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?",
+ p->cx[0], p->cy[0]);
+ error(tBufferAddr());
+ }
+#endif
+}
+
+/**
+ * Allocate a POLYGON structure.
+ */
+static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) {
+ for (int i = 0; i < MaxPolys; i++) {
+ if (!Polys[i]) {
+ POLYGON *p = Polys[i] = &Polygons[i];
+ memset(p, 0, sizeof(POLYGON));
+
+ p->polytype = type; // Polygon type
+ p->pIndex = pno;
+ p->tagState = TAG_OFF;
+ p->pointState = NOT_POINTING;
+ p->polyID = FROM_LE_32(pp->id); // Identifier
+
+ for (int j = 0; j < 4; j++) { // Polygon definition
+ p->cx[j] = (short)FROM_LE_32(pp->x[j]);
+ p->cy[j] = (short)FROM_LE_32(pp->y[j]);
+ }
+
+ return p;
+ }
+ }
+
+ error("Exceeded MaxPolys");
+ return NULL;
+}
+
+/**
+ * Initialise an EXIT polygon.
+ */
+static void InitExit(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EXIT, pp, pno));
+}
+
+/**
+ * Initialise a PATH or NPATH polygon.
+ */
+static void InitPath(const POLY *pp, bool NodePath, int pno) {
+ POLYGON *p;
+
+ p = GetPolyEntry(PATH, pp, pno); // Obtain a slot
+
+ if (NodePath) {
+ p->subtype = NODE;
+ } else {
+ p->subtype = NORMAL;
+ }
+
+ // Clear out ajacent path pointers
+ memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *));
+
+ FiddlyBit(p);
+ PseudoCentre(p);
+}
+
+
+/**
+ * Initialise a BLOCKING polygon.
+ */
+static void InitBlock(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(BLOCKING, pp, pno));
+}
+
+/**
+ * Initialise an extra BLOCKING polygon related to a moving actor.
+ * The width of the polygon depends on the width of the actor which is
+ * trying to walk through the actor you first thought of.
+ * This is for dynamic blocking.
+ */
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) {
+ int caX, caY; // Calling actor co-ords
+ int taX, taY; // Test actor co-ords
+ int left, right;
+
+ GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords
+ GetMActorPosition(ta, &taX, &taY); // Test actor co-ords
+
+ left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX);
+ right = GetMActorRight(ta) + (caX - GetMActorLeft(ca));
+
+ memset(&extraBlock, 0, sizeof(extraBlock));
+
+ // The 3s on the y co-ordinates used to be 10s
+ extraBlock.cx[0] = (short)(left - 2);
+ extraBlock.cy[0] = (short)(taY - 3);
+ extraBlock.cx[1] = (short)(right + 2);
+ extraBlock.cy[1] = (short)(taY - 3);
+ extraBlock.cx[2] = (short)(right + 2);
+ extraBlock.cy[2] = (short)(taY + 3);
+ extraBlock.cx[3] = (short)(left - 2);
+ extraBlock.cy[3] = (short)(taY + 3);
+
+ FiddlyBit(&extraBlock); // Is this necessary?
+
+ Polys[MAX_POLY] = &extraBlock;
+ return MAX_POLY;
+}
+
+/**
+ * Initialise an EFFECT polygon.
+ */
+static void InitEffect(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EFFECT, pp, pno));
+}
+
+
+/**
+ * Initialise a REFER polygon.
+ */
+static void InitRefer(const POLY *pp, int pno) {
+ POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot
+
+ p->subtype = FROM_LE_32(pp->reftype); // Refer type
+
+ FiddlyBit(p);
+}
+
+
+/**
+ * Initialise a TAG polygon.
+ */
+static void InitTag(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(TAG, pp, pno));
+}
+
+
+/**
+ * Called at the start of a scene to initialise the polys in that scene.
+ */
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) {
+ const POLY *pp; // Pointer to compiled data polygon structure
+
+ pHandle = ph;
+ noofPolys = numPoly;
+
+ if (Polygons == NULL) {
+ // first time - allocate memory for process list
+ Polygons = (POLYGON *)calloc(MaxPolys, sizeof(POLYGON));
+
+ // make sure memory allocated
+ if (Polygons == NULL) {
+ error("Cannot allocate memory for polygon data");
+ }
+ }
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+
+ if (!bRestart)
+ memset(deadPolys, 0, sizeof(deadPolys));
+
+ pp = (const POLY *)LockMem(ph);
+ for (int i = 0; i < numPoly; i++, pp++) {
+ switch (FROM_LE_32(pp->type)) {
+ case POLY_PATH:
+ InitPath(pp, false, i);
+ break;
+
+ case POLY_NPATH:
+ InitPath(pp, true, i);
+ break;
+
+ case POLY_BLOCK:
+ InitBlock(pp, i);
+ break;
+
+ case POLY_REFER:
+ InitRefer(pp, i);
+ break;
+
+ case POLY_EFFECT:
+ InitEffect(pp, i);
+ break;
+
+ case POLY_EXIT:
+ InitExit(pp, i);
+ break;
+
+ case POLY_TAG:
+ InitTag(pp, i);
+ break;
+
+ default:
+ error("Unknown polygon type");
+ }
+ }
+ SetPathAdjacencies(); // Paths need to know the facts
+#ifdef DEBUG
+ CheckNPathIntegrity();
+#endif
+ SetExTags(ph); // Some tags may have been killed
+ SetExExits(ph); // Some exits may have been killed
+
+ if (bRestart)
+ SetExBlocks(); // Some blocks may have been killed
+}
+
+/**
+ * Called at the end of a scene to ditch all polygons.
+ */
+void DropPolygons() {
+ pathsOnRoute = 0;
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+ RouteEnd = NULL;
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+ noofPolys = 0;
+ free(Polygons);
+ Polygons = NULL;
+}
+
+
+
+PTYPE PolyType(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (25)");
+
+ return Polys[hp]->polytype;
+}
+
+int PolySubtype(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (26)");
+
+ return Polys[hp]->subtype;
+}
+
+int PolyCentreX(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (27)");
+
+ return Polys[hp]->pcentrex;
+}
+
+int PolyCentreY(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (28)");
+
+ return Polys[hp]->pcentrey;
+}
+
+int PolyCornerX(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (29)");
+
+ return Polys[hp]->cx[n];
+}
+
+int PolyCornerY(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (30)");
+
+ return Polys[hp]->cy[n];
+}
+
+PSTATE PolyPointState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (31)");
+
+ return Polys[hp]->pointState;
+}
+
+TSTATE PolyTagState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (32)");
+
+ return Polys[hp]->tagState;
+}
+
+SCNHANDLE PolyTagHandle(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (33)");
+
+ return Polys[hp]->oTagHandle;
+}
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps) {
+ CHECK_HP(hp, "Out of range polygon handle (34)");
+
+ Polys[hp]->pointState = ps;
+}
+
+void SetPolyTagState(HPOLYGON hp, TSTATE ts) {
+ CHECK_HP(hp, "Out of range polygon handle (35)");
+
+ Polys[hp]->tagState = ts;
+}
+
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) {
+ CHECK_HP(hp, "Out of range polygon handle (36)");
+
+ Polys[hp]->oTagHandle = th;
+}
+
+void MaxPolygons(int numPolys) {
+ assert(numPolys <= MAX_POLY);
+
+ MaxPolys = numPolys;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h
new file mode 100644
index 0000000000..90c57d5f99
--- /dev/null
+++ b/engines/tinsel/polygons.h
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Definition of POLYGON structure and functions in POLYGONS.C
+ */
+
+#ifndef TINSEL_POLYGONS_H // prevent multiple includes
+#define TINSEL_POLYGONS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/scene.h" // for PPOLY and REEL
+
+namespace Tinsel {
+
+
+// Polygon Types
+enum PTYPE {
+ TEST, PATH, EXIT, BLOCKING,
+ EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK
+};
+
+// subtype
+enum {
+ NORMAL = 0,
+ NODE = 1 // For paths
+};
+
+// tagState
+enum TSTATE {
+ TAG_OFF, TAG_ON
+};
+
+// pointState
+enum PSTATE {
+ NO_POINT, NOT_POINTING, POINTING
+};
+
+
+enum {
+ NOPOLY = -1
+};
+
+
+
+/*-------------------------------------------------------------------------*/
+
+bool IsInPolygon(int xt, int yt, HPOLYGON p);
+HPOLYGON InPolygon(int xt, int yt, PTYPE type);
+void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary);
+void FindBestPoint(HPOLYGON path, int *x, int *y, int *line);
+bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2);
+HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to);
+int NearestEndNode(HPOLYGON path, int x, int y);
+int NearEndNode(HPOLYGON spath, HPOLYGON dpath);
+int NearestNodeWithin(HPOLYGON npath, int x, int y);
+void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath);
+bool IsPolyCorner(HPOLYGON hPath, int x, int y);
+int GetScale(HPOLYGON path, int y);
+void getNpathNode(HPOLYGON npath, int node, int *px, int *py);
+void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy);
+SCNHANDLE getPolyFilm(HPOLYGON p);
+void getPolyNode(HPOLYGON p, int *px, int *py);
+SCNHANDLE getPolyScript(HPOLYGON p);
+REEL getPolyReelType(HPOLYGON p);
+int32 getPolyZfactor(HPOLYGON p);
+int numNodes(HPOLYGON pp);
+void RebootDeadTags(void);
+void DisableBlock(int blockno);
+void EnableBlock(int blockno);
+void DisableTag(int tagno);
+void EnableTag(int tagno);
+void DisableExit(int exitno);
+void EnableExit(int exitno);
+HPOLYGON FirstPathPoly(void);
+HPOLYGON GetPolyHandle(int i);
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart);
+void DropPolygons(void);
+
+
+void SaveDeadPolys(bool *sdp);
+void RestoreDeadPolys(bool *sdp);
+
+/*-------------------------------------------------------------------------*/
+
+PTYPE PolyType(HPOLYGON hp); // ->type
+int PolySubtype(HPOLYGON hp); // ->subtype
+int PolyCentreX(HPOLYGON hp); // ->pcentrex
+int PolyCentreY(HPOLYGON hp); // ->pcentrey
+int PolyCornerX(HPOLYGON hp, int n); // ->cx[n]
+int PolyCornerY(HPOLYGON hp, int n); // ->cy[n]
+PSTATE PolyPointState(HPOLYGON hp); // ->pointState
+TSTATE PolyTagState(HPOLYGON hp); // ->tagState
+SCNHANDLE PolyTagHandle(HPOLYGON hp); // ->oTagHandle
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps); // ->pointState
+void SetPolyTagState(HPOLYGON hp, TSTATE ts); // ->tagState
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle
+
+void MaxPolygons(int maxPolys);
+
+/*-------------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_POLYGONS_H */
diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp
new file mode 100644
index 0000000000..a9b24bcac9
--- /dev/null
+++ b/engines/tinsel/rince.cpp
@@ -0,0 +1,709 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.c"
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static MACTOR Movers[MAX_MOVERS];
+
+
+/**
+ * RebootMovers
+ */
+void RebootMovers(void) {
+ memset(Movers, 0, sizeof(Movers));
+}
+
+/**
+ * Given an actor number, return pointer to its moving actor structure,
+ * if it is a moving actor.
+ */
+PMACTOR GetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR)
+ return &Movers[0];
+
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (Movers[i].actorID == ano)
+ return &Movers[i];
+
+ return NULL;
+}
+
+/**
+ * Register an actor as being a moving one.
+ */
+PMACTOR SetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR) {
+ Movers[0].actorToken = TOKEN_LEAD;
+ Movers[0].actorID = LeadId();
+ return &Movers[0];
+ }
+
+ // Check it hasn't already been declared
+ for (i = 1; i < MAX_MOVERS; i++) {
+ if (Movers[i].actorID == ano) {
+ // Actor is already a moving actor
+ return &Movers[i];
+ }
+ }
+
+ // Find an empty slot
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (!Movers[i].actorID) {
+ Movers[i].actorToken = TOKEN_LEAD + i;
+ Movers[i].actorID = ano;
+ return &Movers[i];
+ }
+
+ error("Too many moving actors");
+}
+
+/**
+ * Given an index, returns the associated moving actor.
+ *
+ * At the time of writing, used by the effect process.
+ */
+PMACTOR GetLiveMover(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ if (Movers[index].MActorState == NORM_MACTOR)
+ return &Movers[index];
+ else
+ return NULL;
+}
+
+bool IsMAinEffectPoly(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ return Movers[index].InEffect;
+}
+
+void SetMAinEffectPoly(int index, bool tf) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ Movers[index].InEffect = tf;
+}
+
+/**
+ * Remove a moving actor from the current scene.
+ */
+void KillMActor(PMACTOR pActor) {
+ if (pActor->MActorState == NORM_MACTOR) {
+ pActor->MActorState = NO_MACTOR;
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ pActor->actorObj = NULL;
+ assert(g_scheduler->getCurrentProcess() != pActor->pProc);
+ g_scheduler->killProcess(pActor->pProc);
+ }
+}
+
+/**
+ * getMActorState
+ */
+MAS getMActorState(PMACTOR pActor) {
+ return pActor->MActorState;
+}
+
+/**
+ * If the actor's object exists, move it behind the background.
+ * MultiHideObject() is deliberately not used, as StepAnimScript() calls
+ * cause the object to re-appear.
+ */
+void hideMActor(PMACTOR pActor, int sf) {
+ assert(pActor); // Hiding null moving actor
+
+ pActor->aHidden = true;
+ pActor->SlowFactor = sf;
+
+ if (pActor->actorObj)
+ MultiSetZPosition(pActor->actorObj, -1);
+}
+
+/**
+ * getMActorHideState
+ */
+bool getMActorHideState(PMACTOR pActor) {
+ if (pActor)
+ return pActor->aHidden;
+ else
+ return false;
+}
+
+/**
+ * unhideMActor
+ */
+void unhideMActor(PMACTOR pActor) {
+ assert(pActor); // unHiding null moving actor
+
+ pActor->aHidden = false;
+
+ // Make visible on the screen
+ if (pActor->actorObj) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+ }
+}
+
+/**
+ * Get it into our heads that there's nothing doing.
+ * Called at the end of a scene.
+ */
+void DropMActors(void) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ Movers[i].MActorState = NO_MACTOR;
+ Movers[i].objx = 0;
+ Movers[i].objy = 0;
+ Movers[i].actorObj = NULL; // No moving actor objects
+
+ Movers[i].hCpath = NOPOLY; // No moving actor path
+ }
+}
+
+
+/**
+ * Reposition a moving actor.
+ */
+void MoveMActor(PMACTOR pActor, int x, int y) {
+ int z;
+ int node;
+ HPOLYGON hPath;
+
+ assert(pActor); // Moving null moving actor
+ assert(pActor->actorObj);
+
+ pActor->objx = x;
+ pActor->objy = y;
+ MultiSetAniXY(pActor->actorObj, x, y);
+
+ hPath = InPolygon(x, y, PATH);
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, x, y);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ pActor->scale = z;
+ SetMActorStanding(pActor);
+ } else {
+ pActor->bNoPath = true;
+
+ pActor->hFnpath = NOPOLY; // Ain't in one
+ pActor->npstatus = NOT_IN;
+
+ // Ensure legal reel and scale
+ if (pActor->dirn < 0 || pActor->dirn > 3)
+ pActor->dirn = FORWARD;
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+ }
+}
+
+/**
+ * Get position of a moving actor.
+ */
+void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) {
+ assert(pActor); // Getting null moving actor's position
+
+ if (pActor->actorObj != NULL)
+ GetAniPosition(pActor->actorObj, paniX, paniY);
+ else {
+ *paniX = 0;
+ *paniY = 0;
+ }
+}
+
+/**
+ * Moving actor's mid-top position.
+ */
+void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) {
+ assert(pActor); // Getting null moving actor's mid-top position
+ assert(pActor->actorObj); // Getting null moving actor's mid-top position
+
+ *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2;
+ *aniY = MultiHighest(pActor->actorObj);
+}
+
+/**
+ * Moving actor's left-most co-ordinate.
+ */
+int GetMActorLeft(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's leftmost position
+ assert(pActor->actorObj); // Getting null moving actor's leftmost position
+
+ return MultiLeftmost(pActor->actorObj);
+}
+
+/**
+ * Moving actor's right-most co-ordinate.
+ */
+int GetMActorRight(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's rightmost position
+ assert(pActor->actorObj); // Getting null moving actor's rightmost position
+
+ return MultiRightmost(pActor->actorObj);
+}
+
+/**
+ * See if moving actor is stood within a polygon.
+ */
+bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) {
+ assert(pActor); // Checking if null moving actor is in polygon
+ assert(pActor->actorObj); // Checking if null moving actor is in polygon
+
+ int aniX, aniY;
+ GetAniPosition(pActor->actorObj, &aniX, &aniY);
+
+ return IsInPolygon(aniX, aniY, hp);
+}
+
+/**
+ * Change which reel is playing for a moving actor.
+ */
+void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) {
+ const FILM *pfilm;
+
+ assert(pActor->actorObj); // Altering null moving actor's animation script
+
+ if (fn == AR_POPREEL) {
+ film = pActor->pushedfilm; // Use the saved film
+ }
+ if (fn == AR_PUSHREEL) {
+ // Save the one we're replacing
+ pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0;
+ }
+
+ if (film == 0) {
+ if (pActor->TagReelRunning) {
+ // Revert to 'normal' actor
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ pActor->TagReelRunning = false;
+ }
+ } else {
+ pActor->lastfilm = film; // Remember this one
+
+ pfilm = (const FILM *)LockMem(film);
+ assert(pfilm != NULL);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ if (fn == AR_WALKREEL) {
+ pActor->TagReelRunning = false;
+ pActor->walkReel = true;
+ } else {
+ pActor->TagReelRunning = true;
+ pActor->walkReel = false;
+
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim); // 04/01/95
+#endif
+ }
+
+ // Hang on, we may not want him yet! 04/01/95
+ if (pActor->aHidden)
+ MultiSetZPosition(pActor->actorObj, -1);
+ }
+}
+
+/**
+ * Return the actor's direction.
+ */
+DIRREEL GetMActorDirection(PMACTOR pActor) {
+ return pActor->dirn;
+}
+
+/**
+ * Return the actor's scale.
+ */
+int GetMActorScale(PMACTOR pActor) {
+ return pActor->scale;
+}
+
+/**
+ * Point actor in specified derection
+ */
+void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) {
+ pActor->dirn = dirn;
+}
+
+/**
+ * MAmoving
+ */
+bool MAmoving(PMACTOR pActor) {
+ return pActor->bMoving;
+}
+
+/**
+ * Return an actor's walk ticket.
+ */
+int GetActorTicket(PMACTOR pActor) {
+ return pActor->ticket;
+}
+
+/**
+ * Get actor to adopt its appropriate standing reel.
+ */
+void SetMActorStanding(PMACTOR pActor) {
+ assert(pActor->actorObj);
+ AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL);
+}
+
+/**
+ * Get actor to adopt its appropriate walking reel.
+ */
+void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) {
+ SCNHANDLE whichReel;
+ const FILM *pfilm;
+
+ // Kill off any play that may be going on for this actor
+ // and restore the real actor
+ storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0);
+ unhideMActor(pActor);
+
+ // Don't do it if using a special walk reel
+ if (pActor->walkReel)
+ return;
+
+ if (force || pActor->scale != scale || pActor->dirn != reel) {
+ assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel
+
+ // If scale change and both are regular scales
+ // and there's a scaling reel in the right direction
+ if (pActor->scale != scale
+ && scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES
+ && (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) {
+// error("Cripes!");
+ ; // Use what is now in 'whichReel'
+ } else {
+ whichReel = pActor->WalkReels[scale-1][reel];
+ assert(whichReel); // no reel
+ }
+
+ pfilm = (const FILM *)LockMem(whichReel);
+ assert(pfilm != NULL); // no film
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1);
+
+ // Synchronised walking reels
+ SkipFrames(&pActor->actorAnim, pActor->scount);
+
+ pActor->scale = scale;
+ pActor->dirn = reel;
+ }
+}
+
+/**
+ * Sort some stuff out at actor start-up time.
+ */
+static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) {
+ HPOLYGON hPath;
+ int node;
+ int z;
+
+ pActor->objx = xpos;
+ pActor->objy = ypos;
+
+ /*--------------------------------------
+ | If Actor is in a follow nodes path, |
+ | position it at the nearest node. |
+ --------------------------------------*/
+ hPath = InPolygon(xpos, ypos, PATH);
+
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, xpos, ypos);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ } else {
+ pActor->bNoPath = true;
+
+ z = GetScale(FirstPathPoly(), pActor->objy);
+ }
+ SetMActorWalkReel(pActor, FORWARD, z, false);
+}
+
+/**
+ * Clear everything out at actor start-up time.
+ */
+static void InitMActor(PMACTOR pActor) {
+
+ pActor->objx = pActor->objy = 0;
+ pActor->targetX = pActor->targetY = -1;
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->hIpath = NOPOLY;
+ pActor->UtargetX = pActor->UtargetY = -1;
+ pActor->hUpath = NOPOLY;
+ pActor->hCpath = NOPOLY;
+
+ pActor->over = false;
+ pActor->InDifficulty = NO_PROB;
+
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ pActor->line = 0;
+
+ pActor->Tline = 0;
+
+ pActor->TagReelRunning = false;
+
+ if (pActor->dirn != FORWARD || pActor->dirn != AWAY
+ || pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL)
+ pActor->dirn = FORWARD;
+
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+
+ pActor->scount = 0;
+
+ pActor->fromx = pActor->fromy = 0;
+
+ pActor->bMoving = false;
+ pActor->bNoPath = false;
+ pActor->bIgPath = false;
+ pActor->walkReel = false;
+
+ pActor->actorObj = NULL;
+
+ pActor->lastfilm = 0;
+ pActor->pushedfilm = 0;
+
+ pActor->InEffect = false;
+ pActor->aHidden = false; // 20/2/95
+}
+
+static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) {
+ const FILM *pfilm;
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+ IMAGE *pim;
+
+
+ assert(BackPal()); // Can't start actor without a background palette
+ assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels
+
+ InitMActor(pActor);
+ InitialPathChecks(pActor, X, Y);
+
+ pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]);
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj));
+
+//---
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+ pActor->actorObj = MultiInitObject(pmi);
+
+/**/ assert(pActor->actorID == id);
+ pActor->actorID = id;
+
+ // add it to display list
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy);
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ // Make him the right size
+ SetMActorStanding(pActor);
+
+//**** if added 18/11/94, am
+ if (X != MAGICX && Y != MAGICY) {
+ hideMActor(pActor, 0); // Allows a play to come in before this appears
+ pActor->aHidden = false; // ...but don't stay hidden
+ }
+
+ pActor->MActorState = NORM_MACTOR;
+}
+
+/**
+ * Moving actor process - 1 per moving actor in current scene.
+ */
+void MActorProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = *(PMACTOR *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ if (pActor->TagReelRunning) {
+ if (!pActor->aHidden)
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim);
+#endif
+ } else
+ DoMoveActor(pActor);
+
+ CORO_SLEEP(1); // allow rescheduling
+
+ }
+
+ CORO_END_CODE;
+}
+
+void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) {
+ MActorProcessHelper(X, Y, id, pActor);
+ pActor->pProc = g_scheduler->createProcess(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR));
+}
+
+
+/**
+ * Check for moving actor collision.
+ */
+PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) {
+ int caX; // Calling actor's pos'n
+ int caL, caR; // Calling actor's left and right
+ int taX, taY; // Test actor's pos'n
+ int taL, taR; // Test actor's left and right
+
+ caX = pActor->objx;
+ if (pActor->hFnpath != NOPOLY || bNoBlocking)
+ return NULL;
+
+ caL = GetMActorLeft(pActor) + x - caX;
+ caR = GetMActorRight(pActor) + x - caX;
+
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR)
+ continue;
+
+ // At around the same height?
+ GetMActorPosition(&Movers[i], &taX, &taY);
+ if (Movers[i].hFnpath != NOPOLY)
+ continue;
+
+ if (ABS(y - taY) > 2) // 2 was 8
+ continue;
+
+ // To the left?
+ taL = GetMActorLeft(&Movers[i]);
+ if (caR <= taL)
+ continue;
+
+ // To the right?
+ taR = GetMActorRight(&Movers[i]);
+ if (caL >= taR)
+ continue;
+
+ return &Movers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Copies key information for savescn.c to store away.
+ */
+void SaveMovers(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ sMoverInfo[i].MActorState= Movers[i].MActorState;
+ sMoverInfo[i].actorID = Movers[i].actorID;
+ sMoverInfo[i].objx = Movers[i].objx;
+ sMoverInfo[i].objy = Movers[i].objy;
+ sMoverInfo[i].lastfilm = Movers[i].lastfilm;
+
+ memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h
new file mode 100644
index 0000000000..7ccf017c65
--- /dev/null
+++ b/engines/tinsel/rince.h
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_RINCE_H // prevent multiple includes
+#define TINSEL_RINCE_H
+
+#include "tinsel/anim.h" // for ANIM
+#include "tinsel/scene.h" // for TFTYPE
+
+namespace Tinsel {
+
+struct OBJECT;
+struct PROCESS;
+
+enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING};
+
+enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER};
+
+enum MAS {NO_MACTOR, NORM_MACTOR};
+
+enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY };
+
+enum {
+ NUM_MAINSCALES = 5,
+ NUM_AUXSCALES = 5,
+ TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES
+};
+
+struct MACTOR {
+
+ int objx; /* Co-ordinates object */
+ int objy;
+
+ int targetX, targetY;
+ int ItargetX, ItargetY; /* Intermediate destination */
+ HPOLYGON hIpath;
+ int UtargetX, UtargetY; /* Ultimate destination */
+ HPOLYGON hUpath;
+
+ HPOLYGON hCpath;
+
+ bool over;
+ int ticket;
+
+ IND InDifficulty;
+
+ /* For use in 'follow nodes' polygons */
+ HPOLYGON hFnpath;
+ NPS npstatus;
+ int line;
+
+ int Tline; // NEW
+
+ bool TagReelRunning;
+
+
+ /* Used internally */
+ DIRREEL dirn; // Current reel
+ int scale; // Current scale
+ int scount; // Step count for walking reel synchronisation
+
+ unsigned fromx;
+ unsigned fromy;
+
+ bool bMoving; // Set this to TRUE during a walk
+
+ bool bNoPath;
+ bool bIgPath;
+ bool walkReel;
+
+ OBJECT *actorObj; // Actor's object
+ ANIM actorAnim; // Actor's animation script
+
+ SCNHANDLE lastfilm; // } Used by AlterActor()
+ SCNHANDLE pushedfilm; // }
+
+ int actorID;
+ int actorToken;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+ MAS MActorState;
+
+ bool aHidden;
+ int SlowFactor; // Slow down movement while hidden
+
+ bool stop;
+
+ /* NOTE: If effect polys can overlap, this needs improving */
+ bool InEffect;
+
+ PROCESS *pProc;
+};
+typedef MACTOR *PMACTOR;
+
+//---------------------------------------------------------------------------
+
+
+void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor);
+
+
+enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL };
+
+
+MACTOR *GetMover(int ano);
+MACTOR *SetMover(int ano);
+void KillMActor(MACTOR *pActor);
+MACTOR *GetLiveMover(int index);
+
+MAS getMActorState(MACTOR *psActor);
+
+void hideMActor(MACTOR *pActor, int sf);
+bool getMActorHideState(MACTOR *pActor);
+void unhideMActor(MACTOR *pActor);
+void DropMActors(void);
+void MoveMActor(MACTOR *pActor, int x, int y);
+
+void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY);
+void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY);
+int GetMActorLeft(MACTOR *pActor);
+int GetMActorRight(MACTOR *pActor);
+
+bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly);
+void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn);
+DIRREEL GetMActorDirection(MACTOR *pActor);
+int GetMActorScale(MACTOR *pActor);
+void SetMActorDirection(MACTOR *pActor, DIRREEL dirn);
+void SetMActorStanding(MACTOR *actor);
+void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force);
+
+MACTOR *InMActorBlock(MACTOR *pActor, int x, int y);
+
+void RebootMovers(void);
+
+bool IsMAinEffectPoly(int index);
+void SetMAinEffectPoly(int index, bool tf);
+
+bool MAmoving(MACTOR *pActor);
+
+int GetActorTicket(MACTOR *pActor);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_MOVER {
+
+ MAS MActorState;
+ int actorID;
+ int objx;
+ int objy;
+ SCNHANDLE lastfilm;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+};
+
+void SaveMovers(SAVED_MOVER *sMoverInfo);
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo);
+
+/*----------------------------------------------------------------------*/
+
+/*
+* Dodgy bit...
+* These functions are now in MAREELS.C, but I can't be bothered to
+* create a new header file.
+*/
+SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn);
+
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away);
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel);
+void RebootScalingReels(void);
+
+enum {
+ MAGICX = -101,
+ MAGICY = -102
+};
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_RINCE_H */
diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp
new file mode 100644
index 0000000000..1a6cc1202a
--- /dev/null
+++ b/engines/tinsel/saveload.cpp
@@ -0,0 +1,475 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/inventory.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+
+#include "common/savefile.h"
+
+namespace Tinsel {
+
+
+/**
+ * The current savegame format version.
+ * Our save/load system uses an elaborate scheme to allow us to modify the
+ * savegame while keeping full backward compatibility, in the sense that newer
+ * ScummVM versions always are able to load old savegames.
+ * In order to achieve that, we store a version in the savegame files, and whenever
+ * the savegame layout is modified, the version is incremented.
+ *
+ * This roughly works by marking each savegame entry with a range of versions
+ * for which it is valid; the save/load code iterates over all entries, but
+ * only saves/loads those which are valid for the version of the savegame
+ * which is being loaded/saved currently.
+ */
+#define CURRENT_VER 1
+// TODO: Not yet used
+
+/**
+ * An auxillary macro, used to specify savegame versions. We use this instead
+ * of just writing the raw version, because this way they stand out more to
+ * the reading eye, making it a bit easier to navigate through the code.
+ */
+#define VER(x) x
+
+
+
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in DOS_DW.C
+extern void syncSCdata(Serializer &s);
+
+// in DOS_MAIN.C
+//char HardDriveLetter(void);
+
+// in PCODE.C
+extern void syncGlobInfo(Serializer &s);
+
+// in POLYGONS.C
+extern void syncPolyInfo(Serializer &s);
+
+// in SAVESCN.CPP
+extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut);
+
+//----------------- LOCAL DEFINES --------------------
+
+struct SaveGameHeader {
+ uint32 id;
+ uint32 size;
+ uint32 ver;
+ char desc[SG_DESC_LEN];
+ struct tm dateTime;
+};
+
+enum {
+ SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM"
+ SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int numSfiles = 0;
+static SFILES savedFiles[MAX_SFILES];
+
+static bool NeedLoad = true;
+
+static SAVED_DATA *srsd = 0;
+static int RestoreGameNumber = 0;
+static char *SaveSceneName = 0;
+static const char *SaveSceneDesc = 0;
+static int *SaveSceneSsCount = 0;
+static char *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]'
+
+static SRSTATE SRstate = SR_IDLE;
+
+//------------- SAVE/LOAD SUPPORT METHODS ----------------
+
+static void syncTime(Serializer &s, struct tm &t) {
+ s.syncAsUint16LE(t.tm_year);
+ s.syncAsByte(t.tm_mon);
+ s.syncAsByte(t.tm_mday);
+ s.syncAsByte(t.tm_hour);
+ s.syncAsByte(t.tm_min);
+ s.syncAsByte(t.tm_sec);
+ if (s.isLoading()) {
+ t.tm_wday = 0;
+ t.tm_yday = 0;
+ t.tm_isdst = 0;
+ }
+}
+
+static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) {
+ s.syncAsUint32LE(hdr.id);
+ s.syncAsUint32LE(hdr.size);
+ s.syncAsUint32LE(hdr.ver);
+
+ s.syncBytes((byte *)hdr.desc, SG_DESC_LEN);
+ hdr.desc[SG_DESC_LEN - 1] = 0;
+
+ syncTime(s, hdr.dateTime);
+
+ int tmp = hdr.size - s.bytesSynced();
+ // Perform sanity check
+ if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024)
+ return false;
+ // Skip over any extra bytes
+ while (tmp-- > 0) {
+ byte b = 0;
+ s.syncAsByte(b);
+ }
+ return true;
+}
+
+static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) {
+ SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels };
+
+ s.syncAsUint32LE(sm.MActorState);
+ s.syncAsSint32LE(sm.actorID);
+ s.syncAsSint32LE(sm.objx);
+ s.syncAsSint32LE(sm.objy);
+ s.syncAsUint32LE(sm.lastfilm);
+
+ for (int pIndex = 0; pIndex < 3; ++pIndex) {
+ SCNHANDLE *p = pList[pIndex];
+ for (int i = 0; i < TOTAL_SCALES * 4; ++i)
+ s.syncAsUint32LE(*p++);
+ }
+}
+
+static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) {
+ s.syncAsUint16LE(sa.actorID);
+ s.syncAsUint16LE(sa.z);
+ s.syncAsUint32LE(sa.bAlive);
+ s.syncAsUint32LE(sa.presFilm);
+ s.syncAsUint16LE(sa.presRnum);
+ s.syncAsUint16LE(sa.presX);
+ s.syncAsUint16LE(sa.presY);
+}
+
+extern void syncAllActorsAlive(Serializer &s);
+
+static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) {
+ s.syncAsSint32LE(ns.ln);
+ s.syncAsSint32LE(ns.c1);
+ s.syncAsSint32LE(ns.c2);
+}
+
+static void syncSavedData(Serializer &s, SAVED_DATA &sd) {
+ s.syncAsUint32LE(sd.SavedSceneHandle);
+ s.syncAsUint32LE(sd.SavedBgroundHandle);
+ for (int i = 0; i < MAX_MOVERS; ++i)
+ syncSavedMover(s, sd.SavedMoverInfo[i]);
+ for (int i = 0; i < MAX_SAVED_ACTORS; ++i)
+ syncSavedActor(s, sd.SavedActorInfo[i]);
+
+ s.syncAsSint32LE(sd.NumSavedActors);
+ s.syncAsSint32LE(sd.SavedLoffset);
+ s.syncAsSint32LE(sd.SavedToffset);
+ for (int i = 0; i < MAX_INTERPRET; ++i)
+ sd.SavedICInfo[i].syncWithSerializer(s);
+ for (int i = 0; i < MAX_POLY; ++i)
+ s.syncAsUint32LE(sd.SavedDeadPolys[i]);
+ s.syncAsUint32LE(sd.SavedControl);
+ s.syncAsUint32LE(sd.SavedMidi);
+ s.syncAsUint32LE(sd.SavedLoop);
+ s.syncAsUint32LE(sd.SavedNoBlocking);
+
+ // SavedNoScrollData
+ for (int i = 0; i < MAX_VNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]);
+ for (int i = 0; i < MAX_HNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH);
+}
+
+
+/**
+ * Called when saving a game to a new file.
+ * Generates a new, unique, filename.
+ */
+static char *NewName(void) {
+ static char result[FNAMELEN];
+ int i;
+ int ano = 1; // Allocated number
+
+ while (1) {
+ Common::String fname = _vm->getSavegameFilename(ano);
+ strcpy(result, fname.c_str());
+
+ for (i = 0; i < numSfiles; i++)
+ if (!strcmp(savedFiles[i].name, result))
+ break;
+
+ if (i == numSfiles)
+ break;
+ ano++;
+ }
+
+ return result;
+}
+
+/**
+ * Interrogate the current DOS directory for saved game files.
+ * Store the file details, ordered by time, in savedFiles[] and return
+ * the number of files found).
+ */
+int getList(void) {
+ // No change since last call?
+ // TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
+ if (!NeedLoad)
+ return numSfiles;
+
+ int i;
+
+ const Common::String pattern = _vm->getSavegamePattern();
+ Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str());
+
+ numSfiles = 0;
+
+ for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {
+ if (numSfiles >= MAX_SFILES)
+ break;
+
+ const Common::String &fname = *file;
+ Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str());
+ if (f == NULL) {
+ continue;
+ }
+
+ // Try to load save game header
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ bool validHeader = syncSaveGameHeader(s, hdr);
+ delete f;
+ if (!validHeader) {
+ continue; // Invalid header, or savegame too new -> skip it
+ // TODO: In SCUMM, we still show an entry for the save, but with description
+ // "incompatible version".
+ }
+
+ i = numSfiles;
+#ifndef DISABLE_SAVEGAME_SORTING
+ for (i = 0; i < numSfiles; i++) {
+ if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) {
+ Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]);
+ break;
+ }
+ }
+#endif
+
+ strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN);
+ strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
+ savedFiles[i].desc[SG_DESC_LEN - 1] = 0;
+ savedFiles[i].dateTime = hdr.dateTime;
+
+ ++numSfiles;
+ }
+
+ // Next getList() needn't do its stuff again
+ NeedLoad = false;
+
+ return numSfiles;
+}
+
+
+char *ListEntry(int i, letype which) {
+ if (i == -1)
+ i = numSfiles;
+
+ assert(i >= 0);
+
+ if (i < numSfiles)
+ return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc;
+ else
+ return NULL;
+}
+
+static void DoSync(Serializer &s) {
+ int sg;
+
+ syncSavedData(s, *srsd);
+ syncGlobInfo(s); // Glitter globals
+ syncInvInfo(s); // Inventory data
+
+ // Held object
+ if (s.isSaving())
+ sg = WhichItemHeld();
+ s.syncAsSint32LE(sg);
+ if (s.isLoading())
+ HoldItem(sg);
+
+ syncTimerInfo(s); // Timer data
+ syncPolyInfo(s); // Dead polygon data
+ syncSCdata(s); // Hook Scene and delayed scene
+
+ s.syncAsSint32LE(*SaveSceneSsCount);
+
+ if (*SaveSceneSsCount != 0) {
+ SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData;
+ for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr)
+ syncSavedData(s, *sdPtr);
+ }
+
+ syncAllActorsAlive(s);
+}
+
+/**
+ * DoRestore
+ */
+static bool DoRestore(void) {
+ Common::InSaveFile *f;
+ uint32 id;
+
+ f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name);
+ if (f == NULL) {
+ return false;
+ }
+
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ if (!syncSaveGameHeader(s, hdr)) {
+ delete f; // Invalid header, or savegame too new -> skip it
+ return false;
+ }
+
+ DoSync(s);
+
+ id = f->readSint32LE();
+ if (id != (uint32)0xFEEDFACE)
+ error("Incompatible saved game");
+
+ bool failed = f->ioFailed();
+
+ delete f;
+
+ return !failed;
+}
+
+/**
+ * DoSave
+ */
+static void DoSave(void) {
+ Common::OutSaveFile *f;
+ const char *fname;
+
+ // Next getList() must do its stuff again
+ NeedLoad = true;
+
+ if (SaveSceneName == NULL)
+ SaveSceneName = NewName();
+ if (SaveSceneDesc[0] == 0)
+ SaveSceneDesc = "unnamed";
+
+ fname = SaveSceneName;
+
+ f = _vm->getSaveFileMan()->openForSaving(fname);
+ if (f == NULL)
+ return;
+
+ Serializer s(0, f);
+
+ // Write out a savegame header
+ SaveGameHeader hdr;
+ hdr.id = SAVEGAME_ID;
+ hdr.size = SAVEGAME_HEADER_SIZE;
+ hdr.ver = CURRENT_VER;
+ memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN);
+ hdr.desc[SG_DESC_LEN - 1] = 0;
+ g_system->getTimeAndDate(hdr.dateTime);
+ if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) {
+ goto save_failure;
+ }
+
+ DoSync(s);
+
+ // Write out the special Id for Discworld savegames
+ f->writeUint32LE(0xFEEDFACE);
+ if (f->ioFailed())
+ goto save_failure;
+
+ f->finalize();
+ delete f;
+ return;
+
+save_failure:
+ delete f;
+ _vm->getSaveFileMan()->removeSavefile(fname);
+}
+
+/**
+ * ProcessSRQueue
+ */
+void ProcessSRQueue(void) {
+ switch (SRstate) {
+ case SR_DORESTORE:
+ if (DoRestore()) {
+ RestoreScene(srsd, false);
+ }
+ SRstate = SR_IDLE;
+ break;
+
+ case SR_DOSAVE:
+ DoSave();
+ SRstate = SR_IDLE;
+ break;
+ default:
+ break;
+ }
+}
+
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(SRstate == SR_IDLE);
+
+ SaveSceneName = name;
+ SaveSceneDesc = desc;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DOSAVE;
+}
+
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(num >= 0);
+
+ RestoreGameNumber = num;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DORESTORE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp
new file mode 100644
index 0000000000..9f0d7b9039
--- /dev/null
+++ b/engines/tinsel/savescn.cpp
@@ -0,0 +1,336 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h" // FadeOutFast()
+#include "tinsel/graphics.h" // ClearScreen()
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern SCNHANDLE GetBgroundHandle(void);
+extern void SetDoFadeIn(bool tf);
+
+// In DOS_DW.C
+void RestoreMasterProcess(INT_CONTEXT *pic);
+
+// in EVENTS.C (declared here and not in events.h because of strange goings-on)
+void RestoreProcess(INT_CONTEXT *pic);
+
+// in PLAY.C
+extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y);
+
+// in SCENE.C
+extern SCNHANDLE GetSceneHandle(void);
+extern void NewScene(SCNHANDLE scene, int entry);
+
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+enum {
+ RS_COUNT = 5, // Restore scene count
+
+ MAX_NEST = 4
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool ASceneIsSaved = false;
+
+static int savedSceneCount = 0;
+
+//static SAVED_DATA s_ssData[MAX_NEST];
+static SAVED_DATA *s_ssData = 0;
+static SAVED_DATA sgData;
+
+static SAVED_DATA *s_rsd = 0;
+
+static int s_restoreSceneCount = 0;
+
+static bool bNoFade = false;
+
+//----------------- FORWARD REFERENCES --------------------
+
+
+
+void InitialiseSs(void) {
+ if (s_ssData == NULL) {
+ s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA));
+ if (s_ssData == NULL) {
+ error("Cannot allocate memory for scene changes");
+ }
+ } else
+ savedSceneCount = 0;
+}
+
+void FreeSs(void) {
+ if (s_ssData) {
+ free(s_ssData);
+ s_ssData = NULL;
+ }
+}
+
+/**
+ * Save current scene.
+ * @param sd Pointer to the scene data
+ */
+void SaveScene(SAVED_DATA *sd) {
+ sd->SavedSceneHandle = GetSceneHandle();
+ sd->SavedBgroundHandle = GetBgroundHandle();
+ SaveMovers(sd->SavedMoverInfo);
+ sd->NumSavedActors = SaveActors(sd->SavedActorInfo);
+ PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset);
+ SaveInterpretContexts(sd->SavedICInfo);
+ SaveDeadPolys(sd->SavedDeadPolys);
+ sd->SavedControl = TestToken(TOKEN_CONTROL);
+ CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop);
+ sd->SavedNoBlocking = bNoBlocking;
+ GetNoScrollData(&sd->SavedNoScrollData);
+
+ ASceneIsSaved = true;
+}
+
+/**
+ * Initiate restoration of the saved scene.
+ * @param sd Pointer to the scene data
+ * @param bFadeOut Flag to perform a fade out
+ */
+void RestoreScene(SAVED_DATA *sd, bool bFadeOut) {
+ s_rsd = sd;
+
+ if (bFadeOut)
+ s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count
+ else
+ s_restoreSceneCount = RS_COUNT; // Set restore scene count
+}
+
+/**
+ * Checks that all non-moving actors are playing the same reel as when
+ * the scene was saved.
+ * Also 'stand' all the moving actors at their saved positions.
+ */
+void sortActors(SAVED_DATA *rsd) {
+ for (int i = 0; i < rsd->NumSavedActors; i++) {
+ ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive);
+
+ // Should be playing the same reel.
+ if (rsd->SavedActorInfo[i].presFilm != 0) {
+ if (!actorAlive(rsd->SavedActorInfo[i].actorID))
+ continue;
+
+ playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z,
+ rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY);
+ }
+ }
+
+ RestoreAuxScales(rsd->SavedMoverInfo);
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR)
+ stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx,
+ rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+
+void ResumeInterprets(SAVED_DATA *rsd) {
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData) {
+ g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1);
+ FreeMasterInterpretContext();
+ }
+
+ for (int i = 0; i < MAX_INTERPRET; i++) {
+ switch (rsd->SavedICInfo[i].GSort) {
+ case GS_NONE:
+ break;
+
+ case GS_INVENTORY:
+ if (rsd->SavedICInfo[i].event != POINTED) {
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ }
+ break;
+
+ case GS_MASTER:
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData)
+ RestoreMasterProcess(&rsd->SavedICInfo[i]);
+ break;
+
+ case GS_ACTOR:
+ RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]);
+ break;
+
+ case GS_POLYGON:
+ case GS_SCENE:
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ break;
+ }
+ }
+}
+
+/**
+ * Do restore scene
+ * @param n Id
+ */
+static int DoRestoreScene(SAVED_DATA *rsd, int n) {
+ switch (n) {
+ case RS_COUNT + COUNTOUT_COUNT:
+ // Trigger pre-load and fade and start countdown
+ FadeOutFast(NULL);
+ break;
+
+ case RS_COUNT:
+ _vm->_sound->stopAllSamples();
+ ClearScreen();
+ RestoreDeadPolys(rsd->SavedDeadPolys);
+ NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM);
+ SetDoFadeIn(!bNoFade);
+ bNoFade = false;
+ startupBackground(rsd->SavedBgroundHandle);
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset);
+ bNoBlocking = rsd->SavedNoBlocking;
+ RestoreNoScrollData(&rsd->SavedNoScrollData);
+/*
+ break;
+
+ case RS_COUNT - 1:
+*/
+ sortActors(rsd);
+ break;
+
+ case 2:
+ break;
+
+ case 1:
+ RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop);
+ if (rsd->SavedControl)
+ control(CONTROL_ON); // TOKEN_CONTROL was free
+ ResumeInterprets(rsd);
+ }
+
+ return n - 1;
+}
+
+/**
+ * Restore game
+ * @param num num
+ */
+void RestoreGame(int num) {
+ KillInventory();
+
+ RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual restoring is performed by ProcessSRQueue
+}
+
+/**
+ * Save game
+ * @param name Name of savegame
+ * @param desc Description of savegame
+ */
+void SaveGame(char *name, char *desc) {
+ // Get current scene data
+ SaveScene(&sgData);
+
+ RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual saving is performed by ProcessSRQueue
+}
+
+
+//---------------------------------------------------------------------------------
+
+bool IsRestoringScene() {
+ if (s_restoreSceneCount) {
+ s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount);
+ }
+
+ return s_restoreSceneCount ? true : false;
+}
+
+/**
+ * Please Restore Scene
+ */
+void PleaseRestoreScene(bool bFade) {
+ // only called by restore_scene PCODE
+ if (s_restoreSceneCount == 0) {
+ assert(savedSceneCount >= 1); // No saved scene to restore
+
+ if (ASceneIsSaved)
+ RestoreScene(&s_ssData[--savedSceneCount], bFade);
+ if (!bFade)
+ bNoFade = true;
+ }
+}
+
+/**
+ * Please Save Scene
+ */
+void PleaseSaveScene(CORO_PARAM) {
+ // only called by save_scene PCODE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(savedSceneCount < MAX_NEST); // nesting limit reached
+
+ // Don't save the same thing multiple times!
+ // FIXME/TODO: Maybe this can be changed to an assert?
+ if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle())
+ CORO_KILL_SELF();
+
+ SaveScene(&s_ssData[savedSceneCount++]);
+
+ CORO_END_CODE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h
new file mode 100644
index 0000000000..a999c9bffa
--- /dev/null
+++ b/engines/tinsel/savescn.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_SAVESCN_H
+#define TINSEL_SAVESCN_H
+
+#include <time.h> // for time_t struct
+
+#include "tinsel/actors.h" // SAVED_ACTOR
+#include "tinsel/dw.h" // SCNHANDLE
+#include "tinsel/rince.h" // SAVED_MOVER
+#include "tinsel/pcode.h" // INT_CONTEXT
+#include "tinsel/scroll.h" // SCROLLDATA
+
+namespace Tinsel {
+
+enum {
+ SG_DESC_LEN = 40, // Max. saved game description length
+ MAX_SFILES = 30,
+
+ // FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the
+ // name field in savedFiles. Raising it to 256 as a preliminary fix.
+ FNAMELEN = 256 // 8.3
+};
+
+struct SFILES {
+ char name[FNAMELEN];
+ char desc[SG_DESC_LEN + 2];
+ struct tm dateTime;
+};
+
+struct SAVED_DATA {
+ SCNHANDLE SavedSceneHandle; // Scene handle
+ SCNHANDLE SavedBgroundHandle; // Background handle
+ SAVED_MOVER SavedMoverInfo[MAX_MOVERS]; // Moving actors
+ SAVED_ACTOR SavedActorInfo[MAX_SAVED_ACTORS]; // } Actors
+ int NumSavedActors; // }
+ int SavedLoffset, SavedToffset; // Screen offsets
+ INT_CONTEXT SavedICInfo[MAX_INTERPRET]; // Interpret contexts
+ bool SavedDeadPolys[MAX_POLY];
+ bool SavedControl;
+ SCNHANDLE SavedMidi; // }
+ bool SavedLoop; // } Midi
+ bool SavedNoBlocking;
+ SCROLLDATA SavedNoScrollData;
+};
+
+
+enum SRSTATE {
+ SR_IDLE, SR_DORESTORE, SR_DONERESTORE,
+ SR_DOSAVE, SR_DONESAVE, SR_ABORTED
+};
+
+void PleaseRestoreScene(bool bFade);
+void PleaseSaveScene(CORO_PARAM);
+
+bool IsRestoringScene();
+
+
+enum letype{
+ LE_NAME, LE_DESC
+};
+
+char *ListEntry(int i, letype which);
+int getList(void);
+
+void RestoreGame(int num);
+void SaveGame(char *name, char *desc);
+
+void ProcessSRQueue(void);
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+
+void InitialiseSs(void);
+void FreeSs(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SAVESCN_H */
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
new file mode 100644
index 0000000000..70700c16a3
--- /dev/null
+++ b/engines/tinsel/scene.cpp
@@ -0,0 +1,306 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Starts up new scenes.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/film.h"
+#include "tinsel/move.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h" // stopAllSamples()
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h" // process IDs
+#include "tinsel/polygons.h"
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void DropBackground(void);
+
+// in EFFECT.C
+extern void EffectPolyProcess(CORO_PARAM, const void *);
+
+// in PDISPLAY.C
+#ifdef DEBUG
+extern void CursorPositionProcess(CORO_PARAM, const void *);
+#endif
+extern void TagProcess(CORO_PARAM, const void *);
+extern void PointProcess(CORO_PARAM, const void *);
+extern void EnableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** scene structure - one per scene */
+struct SCENE_STRUC {
+ int32 numEntrance; //!< number of entrances in this scene
+ int32 numPoly; //!< number of various polygons in this scene
+ int32 numActor; //!< number of actors in this scene
+ int32 defRefer; //!< Default refer direction
+ SCNHANDLE hSceneScript; //!< handle to scene script
+ SCNHANDLE hEntrance; //!< handle to table of entrances
+ SCNHANDLE hPoly; //!< handle to table of polygons
+ SCNHANDLE hActor; //!< handle to table of actors
+} PACKED_STRUCT;
+
+/** entrance structure - one per entrance */
+struct ENTRANCE_STRUC {
+ int32 eNumber; //!< entrance number
+ SCNHANDLE hScript; //!< handle to entrance script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+static bool ShowPosition = false; // Set when showpos() has been called
+#endif
+
+static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene()
+
+
+/**
+ * Started up for scene script and entrance script.
+ */
+static void SceneTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ SCNHANDLE *ss = (SCNHANDLE *)param;
+ assert(*ss); // Must have some code to run
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Get the SCENE_STRUC
+ * Initialise polygons for the scene
+ * Initialise the actors for this scene
+ * Run the appropriate entrance code (if any)
+ * Get the default refer type
+ */
+static void LoadScene(SCNHANDLE scene, int entry) {
+ const SCENE_STRUC *ss;
+ const ENTRANCE_STRUC *es;
+ uint i;
+
+ // Scene structure
+ SceneHandle = scene; // Save scene handle in case of Save_Scene()
+
+ LockMem(SceneHandle); // Make sure scene is loaded
+ LockScene(SceneHandle); // Prevent current scene from being discarded
+
+ ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE);
+ assert(ss != NULL);
+
+ // Initialise all the polygons for this scene
+ InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM));
+
+ // Initialise the actors for this scene
+ StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM));
+
+ if (entry != NO_ENTRY_NUM) {
+
+ // Run the appropriate entrance code (if any)
+ es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance));
+ for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) {
+ if (FROM_LE_32(es->eNumber) == (uint)entry) {
+ if (es->hScript)
+ g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript));
+ break;
+ }
+ }
+
+ if (i == FROM_LE_32(ss->numEntrance))
+ error("Non-existant scene entry number");
+
+ if (ss->hSceneScript)
+ g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript));
+ }
+
+ // Default refer type
+ SetDefaultRefer(FROM_LE_32(ss->defRefer));
+}
+
+
+/**
+ * Wrap up the last scene.
+ */
+void EndScene(void) {
+ if (SceneHandle != 0) {
+ UnlockScene(SceneHandle);
+ SceneHandle = 0;
+ }
+
+ KillInventory(); // Close down any open inventory
+
+ DropPolygons(); // No polygons
+ DropNoScrolls(); // No no-scrolls
+ DropBackground(); // No background
+ DropMActors(); // No moving actors
+ DropCursor(); // No cursor
+ DropActors(); // No actor reels running
+ FreeAllTokens(); // No-one has tokens
+ FreeMostInterpretContexts(); // Only master script still interpreting
+
+ _vm->_sound->stopAllSamples(); // Kill off any still-running sample
+
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // kill all destructable process
+ g_scheduler->killMatchingProcess(PID_DESTROY, PID_DESTROY);
+}
+
+/**
+ *
+ */
+void PrimeBackground(void) {
+ // structure for playfields
+ static PLAYFIELD playfield[] = {
+ { // FIELD WORLD
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ },
+ { // FIELD STATUS
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ }
+ };
+
+ // structure for background
+ static BACKGND backgnd = {
+ BLACK, // sky colour
+ Common::Point(0, 0), // initial world pos
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // scroll limits
+ 0, // no background update process
+ NULL, // no x scroll table
+ NULL, // no y scroll table
+ 2, // 2 playfields
+ playfield, // playfield pointer
+ false // no auto-erase
+ };
+
+ InitBackground(&backgnd);
+}
+
+/**
+ * Start up the standard stuff for the next scene.
+ */
+
+void PrimeScene(void) {
+
+ bNoBlocking = false;
+
+ RestartCursor(); // Restart the cursor
+ EnableTags(); // Next scene with tags enabled
+
+ g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0);
+ g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0);
+
+#ifdef DEBUG
+ if (ShowPosition)
+ g_scheduler->createProcess(PID_POSITION, CursorPositionProcess, NULL, 0);
+#endif
+
+ g_scheduler->createProcess(PID_TAG, TagProcess, NULL, 0);
+ g_scheduler->createProcess(PID_TAG, PointProcess, NULL, 0);
+
+ // init the current background
+ PrimeBackground();
+}
+
+/**
+ * Wrap up the last scene and start up the next scene.
+ */
+
+void NewScene(SCNHANDLE scene, int entry) {
+ EndScene(); // Wrap up the last scene.
+
+ PrimeScene(); // Start up the standard stuff for the next scene.
+
+ LoadScene(scene, entry);
+}
+
+#ifdef DEBUG
+/**
+ * Sets the ShowPosition flag, causing the cursor position process to be
+ * created in each scene.
+ */
+
+void setshowpos(void) {
+ ShowPosition = true;
+}
+#endif
+
+/**
+ * Return the current scene handle.
+ */
+
+SCNHANDLE GetSceneHandle(void) {
+ return SceneHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h
new file mode 100644
index 0000000000..d0fc6e1ae3
--- /dev/null
+++ b/engines/tinsel/scene.h
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Scene parsing defines
+ */
+
+#ifndef TINSEL_SCENE_H
+#define TINSEL_SCENE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+enum {
+ MAX_NODES = 32, //!< maximum nodes in a Node Path
+ MAX_NOSCROLL = 16, //!< maximum number of NoScroll commands in a scene
+ MAX_ENTRANCE = 25, //!< maximum number of entrances in a scene
+ MAX_POLY = 256, //!< maximum number of polygons in a scene
+ MAX_ACTOR = 32 //!< maximum number of actors in a scene
+};
+
+/** reference direction */
+enum REFTYPE {
+ REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT
+};
+
+enum TFTYPE {
+ TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS
+};
+
+/** different actor masks */
+enum MASK_TYPE{
+ ACT_DEFAULT,
+ ACT_MASK = -1,
+ ACT_ALWAYS = -2
+};
+
+/** different scales */
+enum SCALE {
+ SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL,
+ SCALE_COMPACT, SCALE_TINY,
+ SCALE_AUX1, SCALE_AUX2, SCALE_AUX3,
+ SCALE_AUX4, SCALE_AUX5
+};
+
+/** different reels */
+enum REEL {
+ REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCENE_H
diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp
new file mode 100644
index 0000000000..72cfeaf6b0
--- /dev/null
+++ b/engines/tinsel/sched.cpp
@@ -0,0 +1,345 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Process scheduler.
+ */
+
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+Scheduler *g_scheduler = 0;
+
+/** process structure */
+struct PROCESS {
+ PROCESS *pNext; //!< pointer to next process in active or free list
+
+ CoroContext state; //!< the state of the coroutine
+ CORO_ADDR coroAddr; //!< the entry point of the coroutine
+
+ int sleepTime; //!< number of scheduler cycles to sleep
+ int pid; //!< process ID
+ char param[PARAM_SIZE]; //!< process specific info
+};
+
+
+Scheduler::Scheduler() {
+ processList = 0;
+ pFreeProcesses = 0;
+ pCurrent = 0;
+
+#ifdef DEBUG
+ // diagnostic process counters
+ numProcs = 0;
+ maxProcs = 0;
+#endif
+
+ pRCfunction = 0;
+
+ active = new PROCESS;
+
+ g_scheduler = this; // FIXME HACK
+}
+
+Scheduler::~Scheduler() {
+ free(processList);
+ processList = NULL;
+
+ delete active;
+ active = 0;
+}
+
+/**
+ * Kills all processes and places them on the free list.
+ */
+void Scheduler::reset() {
+
+#ifdef DEBUG
+ // clear number of process in use
+ numProcs = 0;
+#endif
+
+ if (processList == NULL) {
+ // first time - allocate memory for process list
+ processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS));
+
+ // make sure memory allocated
+ if (processList == NULL) {
+ error("Cannot allocate memory for process data");
+ }
+
+ // fill with garbage
+ memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS));
+ }
+
+ // no active processes
+ pCurrent = active->pNext = NULL;
+
+ // place first process on free list
+ pFreeProcesses = processList;
+
+ // link all other processes after first
+ for (int i = 1; i < NUM_PROCESS; i++) {
+ processList[i - 1].pNext = processList + i;
+ }
+
+ // null the last process
+ processList[NUM_PROCESS - 1].pNext = NULL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of process used at once.
+ */
+void Scheduler::printStats(void) {
+ printf("%i process of %i used.\n", maxProcs, NUM_PROCESS);
+}
+#endif
+
+
+/**
+ * Give all active processes a chance to run
+ */
+void Scheduler::schedule(void) {
+ // start dispatching active process list
+ PROCESS *pPrevProc = active;
+ PROCESS *pProc = active->pNext;
+ while (pProc != NULL) {
+ if (--pProc->sleepTime <= 0) {
+ // process is ready for dispatch, activate it
+ pCurrent = pProc;
+ pProc->coroAddr(pProc->state, pProc->param);
+ pCurrent = NULL;
+ if (!pProc->state || pProc->state->_sleep <= 0) {
+ // Coroutine finished
+ killProcess(pProc);
+ pProc = pPrevProc;
+ } else {
+ pProc->sleepTime = pProc->state->_sleep;
+ }
+ }
+ pPrevProc = pProc;
+ pProc = pProc->pNext;
+ }
+}
+
+
+/**
+ * Creates a new process.
+ *
+ * @param pid process identifier
+ * @param CORO_ADDR coroutine start address
+ * @param pParam process specific info
+ * @param sizeParam size of process specific info
+ */
+PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
+ PROCESS *pProc;
+
+ // get a free process
+ pProc = pFreeProcesses;
+
+ // trap no free process
+ assert(pProc != NULL); // Out of processes
+
+#ifdef DEBUG
+ // one more process in use
+ if (++numProcs > maxProcs)
+ maxProcs = numProcs;
+#endif
+
+ // get link to next free process
+ pFreeProcesses = pProc->pNext;
+
+ if (pCurrent != NULL) {
+ // place new process before the next active process
+ pProc->pNext = pCurrent->pNext;
+
+ // make this new process the next active process
+ pCurrent->pNext = pProc;
+ } else { // no active processes, place process at head of list
+ pProc->pNext = active->pNext;
+ active->pNext = pProc;
+ }
+
+ // set coroutine entry point
+ pProc->coroAddr = coroAddr;
+
+ // clear coroutine state
+ pProc->state = 0;
+
+ // wake process up as soon as possible
+ pProc->sleepTime = 1;
+
+ // set new process id
+ pProc->pid = pid;
+
+ // set new process specific info
+ if (sizeParam) {
+ assert(sizeParam > 0 && sizeParam <= PARAM_SIZE);
+
+ // set new process specific info
+ memcpy(pProc->param, pParam, sizeParam);
+ }
+
+
+ // return created process
+ return pProc;
+}
+
+/**
+ * Kills the specified process.
+ *
+ * @param pKillProc which process to kill
+ */
+void Scheduler::killProcess(PROCESS *pKillProc) {
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ // make sure a valid process pointer
+ assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1);
+
+ // can not kill the current process using killProcess !
+ assert(pCurrent != pKillProc);
+
+#ifdef DEBUG
+ // one less process in use
+ --numProcs;
+ assert(numProcs >= 0);
+#endif
+
+ // search the active list for the process
+ for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if (pProc == pKillProc) {
+ // found process in active list
+
+ // Free process' resources
+ if (pRCfunction != NULL)
+ (pRCfunction)(pProc);
+
+ delete pProc->state;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ return;
+ }
+ }
+
+ // process not found in active list if we get to here
+ error("killProcess(): tried to kill a process not in the list of active processes");
+}
+
+
+
+/**
+ * Returns a pointer to the currently running process.
+ */
+PROCESS *Scheduler::getCurrentProcess(void) {
+ return pCurrent;
+}
+
+/**
+ * Returns the process identifier of the specified process.
+ *
+ * @param pProc which process
+ */
+int Scheduler::getCurrentPID() const {
+ PROCESS *pProc = pCurrent;
+
+ // make sure a valid process pointer
+ assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1);
+
+ // return processes PID
+ return pProc->pid;
+}
+
+/**
+ * Kills any process matching the specified PID. The current
+ * process cannot be killed.
+ *
+ * @param pidKill process identifier of process to kill
+ * @param pidMask mask to apply to process identifiers before comparison
+ * @return The number of processes killed is returned.
+ */
+int Scheduler::killMatchingProcess(int pidKill, int pidMask) {
+ int numKilled = 0;
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if ((pProc->pid & pidMask) == pidKill) {
+ // found a matching process
+
+ // dont kill the current process
+ if (pProc != pCurrent) {
+ // kill this process
+ numKilled++;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ // set to a process on the active list
+ pProc = pPrev;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ // adjust process in use
+ numProcs -= numKilled;
+ assert(numProcs >= 0);
+#endif
+
+ // return number of processes killed
+ return numKilled;
+}
+
+
+
+/**
+ * Set pointer to a function to be called by killProcess().
+ *
+ * May be called by a resource allocator, the function supplied is
+ * called by killProcess() to allow the resource allocator to free
+ * resources allocated to the dying process.
+ *
+ * @param pFunc Function to be called by killProcess()
+ */
+void Scheduler::setResourceCallback(VFPTRPP pFunc) {
+ pRCfunction = pFunc;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h
new file mode 100644
index 0000000000..0d90b3bb9f
--- /dev/null
+++ b/engines/tinsel/sched.h
@@ -0,0 +1,110 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the process scheduler
+ */
+
+#ifndef TINSEL_SCHED_H // prevent multiple includes
+#define TINSEL_SCHED_H
+
+#include "tinsel/dw.h" // new data types
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+// the size of process specific info
+#define PARAM_SIZE 32
+
+// the maximum number of processes
+#define NUM_PROCESS 64
+
+typedef void (*CORO_ADDR)(CoroContext &, const void *);
+
+
+struct PROCESS;
+
+/**
+ * Create and manage "processes" (really coroutines).
+ */
+class Scheduler {
+public:
+ /** Pointer to a function of the form "void function(PPROCESS)" */
+ typedef void (*VFPTRPP)(PROCESS *);
+
+private:
+
+ /** list of all processes */
+ PROCESS *processList;
+
+ /** active process list - also saves scheduler state */
+ PROCESS *active;
+
+ /** pointer to free process list */
+ PROCESS *pFreeProcesses;
+
+ /** the currently active process */
+ PROCESS *pCurrent;
+
+#ifdef DEBUG
+ // diagnostic process counters
+ int numProcs;
+ int maxProcs;
+#endif
+
+ /**
+ * Called from killProcess() to enable other resources
+ * a process may be allocated to be released.
+ */
+ VFPTRPP pRCfunction;
+
+
+public:
+
+ Scheduler();
+ ~Scheduler();
+
+ void reset();
+
+ #ifdef DEBUG
+ void printStats();
+ #endif
+
+ void schedule();
+
+ PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam);
+ void killProcess(PROCESS *pKillProc);
+
+ PROCESS *getCurrentProcess();
+ int getCurrentPID() const;
+ int killMatchingProcess(int pidKill, int pidMask);
+
+
+ void setResourceCallback(VFPTRPP pFunc);
+
+};
+
+extern Scheduler *g_scheduler; // FIXME: Temporary global var, to be used until everything has been OOifyied
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCHED_H
diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp
new file mode 100644
index 0000000000..b14b1c5962
--- /dev/null
+++ b/engines/tinsel/scn.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * A (some would say very) small collection of utility functions.
+ */
+
+#include "common/endian.h"
+#include "common/util.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/scn.h"
+#include "tinsel/tinsel.h" // for _vm
+
+namespace Tinsel {
+
+/**
+ * Given a scene handle and a chunk id, gets the scene in RAM and
+ * locates the requested chunk.
+ * @param handle Scene handle
+ * @param chunk Chunk Id
+ */
+byte *FindChunk(SCNHANDLE handle, uint32 chunk) {
+ byte *bptr = LockMem(handle);
+ uint32 *lptr = (uint32 *)bptr;
+ uint32 add;
+
+ // V1 chunk types can be found by substracting 2 from the
+ // chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are
+ // the same in V1 and V2
+ if (_vm->getVersion() == TINSEL_V1 &&
+ chunk != CHUNK_STRING && chunk != CHUNK_BITMAP)
+ chunk -= 0x2L;
+
+ while (1) {
+ if (READ_LE_UINT32(lptr) == chunk)
+ return (byte *)(lptr + 2);
+
+ ++lptr;
+ add = READ_LE_UINT32(lptr);
+ if (!add)
+ return NULL;
+
+ lptr = (uint32 *)(bptr + add);
+ }
+}
+
+/**
+ * Get the actor id from a film (column 0)
+ */
+int extractActor(SCNHANDLE film) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ const FREEL *preel = &pfilm->reels[0];
+ const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj));
+ return (int)FROM_LE_32(pmi->mulID);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h
new file mode 100644
index 0000000000..29f3dc51fc
--- /dev/null
+++ b/engines/tinsel/scn.h
@@ -0,0 +1,68 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCN_H // prevent multiple includes
+#define TINSEL_SCN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+
+// chunk identifier numbers
+
+// V2 chunks
+
+#define CHUNK_STRING 0x33340001L // same in V1 and V2
+#define CHUNK_BITMAP 0x33340002L // same in V1 and V2
+#define CHUNK_CHARPTR 0x33340003L // not used!
+#define CHUNK_CHARMATRIX 0x33340004L // not used!
+#define CHUNK_PALETTE 0x33340005L // not used!
+#define CHUNK_IMAGE 0x33340006L // not used!
+#define CHUNK_ANI_FRAME 0x33340007L // not used!
+#define CHUNK_FILM 0x33340008L // not used!
+#define CHUNK_FONT 0x33340009L // not used!
+#define CHUNK_PCODE 0x3334000AL
+#define CHUNK_ENTRANCE 0x3334000BL // not used!
+#define CHUNK_POLYGONS 0x3334000CL // not used!
+#define CHUNK_ACTORS 0x3334000DL // not used!
+#define CHUNK_SCENE 0x3334000EL
+#define CHUNK_TOTAL_ACTORS 0x3334000FL
+#define CHUNK_TOTAL_GLOBALS 0x33340010L
+#define CHUNK_TOTAL_OBJECTS 0x33340011L
+#define CHUNK_OBJECTS 0x33340012L
+#define CHUNK_MIDI 0x33340013L // not used!
+#define CHUNK_SAMPLE 0x33340014L // not used!
+#define CHUNK_TOTAL_POLY 0x33340015L
+#define CHUNK_MBSTRING 0x33340022L // Multi-byte characters
+
+#define INDEX_FILENAME "index" // name of index file
+
+byte *FindChunk(SCNHANDLE handle, uint32 chunk);
+int extractActor(SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCN_H */
diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp
new file mode 100644
index 0000000000..aa1bc67298
--- /dev/null
+++ b/engines/tinsel/scroll.cpp
@@ -0,0 +1,432 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles scrolling
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sched.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LEFT 'L'
+#define RIGHT 'R'
+#define UP 'U'
+#define DOWN 'D'
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding
+
+static int scrollActor = 0;
+static PMACTOR psActor = 0;
+static int oldx = 0, oldy = 0;
+
+/** Boundaries and numbers of boundaries */
+static SCROLLDATA sd = {
+ {
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
+ },
+ {
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
+ },
+ 0,
+ 0
+ };
+
+static int ImageH = 0, ImageW = 0;
+
+static bool ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on,
+ // the cursor is kept over that polygon
+ // whilst scrolling
+
+static int scrollPixels = SCROLLPIXELS;
+
+
+/**
+ * Reset the ScrollCursor flag
+ */
+void DontScrollCursor(void) {
+ ScrollCursor = false;
+}
+
+/**
+ * Set the ScrollCursor flag
+ */
+void DoScrollCursor(void) {
+ ScrollCursor = true;
+}
+
+/**
+ * Configure a no-scroll boundary for a scene.
+ */
+void SetNoScroll(int x1, int y1, int x2, int y2) {
+ if (x1 == x2) {
+ /* Vertical line */
+ assert(sd.NumNoH < MAX_HNOSCROLL);
+
+ sd.NoHScroll[sd.NumNoH].ln = x1; // X pos of vertical line
+ sd.NoHScroll[sd.NumNoH].c1 = y1;
+ sd.NoHScroll[sd.NumNoH].c2 = y2;
+ sd.NumNoH++;
+ } else if (y1 == y2) {
+ /* Horizontal line */
+ assert(sd.NumNoV < MAX_VNOSCROLL);
+
+ sd.NoVScroll[sd.NumNoV].ln = y1; // Y pos of horizontal line
+ sd.NoVScroll[sd.NumNoV].c1 = x1;
+ sd.NoVScroll[sd.NumNoV].c2 = x2;
+ sd.NumNoV++;
+ } else {
+ /* No-scroll lines must be horizontal or vertical */
+ }
+}
+
+/**
+ * Does the obvious - called at the end of a scene.
+ */
+void DropNoScrolls(void) {
+ sd.NumNoH = sd.NumNoV = 0;
+}
+
+/**
+ * Called from scroll process when it thinks that a scroll is in order.
+ * Checks for no-scroll boundaries and sets off a scroll if allowed.
+ */
+static void NeedScroll(int direction) {
+ uint i;
+ int BottomLine, RightCol;
+ int Loffset, Toffset;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (direction) {
+ case LEFT: /* Picture will go left, 'camera' right */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = RLSCROLL;
+ }
+ break;
+
+ case RIGHT: /* Picture will go right, 'camera' left */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = -RLSCROLL;
+ }
+ break;
+
+ case UP: /* Picture will go upwards, 'camera' downwards */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = UDSCROLL;
+ }
+ break;
+
+ case DOWN: /* Picture will go downwards, 'camera' upwards */
+
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if (Toffset >= sd.NoVScroll[i].ln - 1 && Toffset <= sd.NoVScroll[i].ln + 1 &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = -UDSCROLL;
+ }
+ break;
+ }
+}
+
+/**
+ * Called from scroll process - Scrolls the image as appropriate.
+ */
+static void ScrollImage(void) {
+ int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag
+ int Loffset, Toffset;
+ int curX, curY;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Keeping cursor on a tag?
+ */
+ if (ScrollCursor) {
+ GetCursorXY(&curX, &curY, true);
+ if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) {
+ OldLoffset = Loffset;
+ OldToffset = Toffset;
+ } else
+ ScrollCursor = false;
+ }
+
+ /*
+ * Horizontal scrolling
+ */
+ if (LeftScroll > 0) {
+ LeftScroll -= scrollPixels;
+ if (LeftScroll < 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset += scrollPixels; // Move right
+ if (Loffset > ImageW - SCREEN_WIDTH)
+ Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right
+ } else if (LeftScroll < 0) {
+ LeftScroll += scrollPixels;
+ if (LeftScroll > 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset -= scrollPixels; // Move left
+ if (Loffset < 0)
+ Loffset = 0; // Now at extreme left
+ }
+
+ /*
+ * Vertical scrolling
+ */
+ if (DownScroll > 0) {
+ DownScroll -= scrollPixels;
+ if (DownScroll < 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset += scrollPixels; // Move down
+
+ if (Toffset > ImageH - SCREEN_HEIGHT)
+ Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom
+
+ } else if (DownScroll < 0) {
+ DownScroll += scrollPixels;
+ if (DownScroll > 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset -= scrollPixels; // Move up
+
+ if (Toffset < 0)
+ Toffset = 0; // Now at extreme top
+ }
+
+ /*
+ * Move cursor if keeping cursor on a tag.
+ */
+ if (ScrollCursor)
+ AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset);
+
+ PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset);
+}
+
+
+/**
+ * See if the actor on whom the camera is is approaching an edge.
+ * Request a scroll if he is.
+ */
+static void MonitorScroll(void) {
+ int newx, newy;
+ int Loffset, Toffset;
+
+ /*
+ * Only do it if the actor is there and is visible
+ */
+ if (!psActor || getMActorHideState(psActor)
+ || getMActorState(psActor) == NO_MACTOR)
+ return;
+
+ GetActorPos(scrollActor, &newx, &newy);
+
+ if (oldx == newx && oldy == newy)
+ return;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Approaching right side or left side of the screen?
+ */
+ if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) {
+ if (newx > oldx)
+ NeedScroll(LEFT);
+ } else if (newx < Loffset + RLDISTANCE && Loffset) {
+ if (newx < oldx)
+ NeedScroll(RIGHT);
+ }
+
+ /*
+ * Approaching bottom or top of the screen?
+ */
+ if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) {
+ if (newy > oldy)
+ NeedScroll(UP);
+ } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) {
+ if (newy < oldy)
+ NeedScroll(DOWN);
+ }
+
+ oldx = newx;
+ oldy = newy;
+}
+
+/**
+ * Decide when to scroll and scroll when decided to.
+ */
+void ScrollProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ ImageH = BackgroundHeight(); // Dimensions
+ ImageW = BackgroundWidth(); // of this scene.
+
+ // Give up if there'll be no purpose in this process
+ if (ImageW == SCREEN_WIDTH && ImageH == SCREEN_HEIGHT)
+ CORO_KILL_SELF();
+
+ LeftScroll = DownScroll = 0; // No iterations outstanding
+ oldx = oldy = 0;
+ scrollPixels = SCROLLPIXELS;
+
+ if (!scrollActor)
+ scrollActor = LeadId();
+
+ psActor = GetMover(scrollActor);
+
+ while (1) {
+ MonitorScroll(); // Set scroll requirement
+
+ if (LeftScroll || DownScroll) // Scroll if required
+ ScrollImage();
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Change which actor the camera is following.
+ */
+void ScrollFocus(int ano) {
+ if (scrollActor != ano) {
+ oldx = oldy = 0;
+ scrollActor = ano;
+
+ psActor = ano ? GetMover(scrollActor) : NULL;
+ }
+}
+
+/**
+ * Scroll to abslote position.
+ */
+void ScrollTo(int x, int y, int iter) {
+ int Loffset, Toffset; // for background offsets
+
+ scrollPixels = iter != 0 ? iter : SCROLLPIXELS;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets
+
+ LeftScroll = x - Loffset;
+ DownScroll = y - Toffset;
+}
+
+/**
+ * Kill of any current scroll.
+ */
+void KillScroll(void) {
+ LeftScroll = DownScroll = 0;
+}
+
+
+void GetNoScrollData(SCROLLDATA *ssd) {
+ memcpy(ssd, &sd, sizeof(SCROLLDATA));
+}
+
+void RestoreNoScrollData(SCROLLDATA *ssd) {
+ memcpy(&sd, ssd, sizeof(SCROLLDATA));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h
new file mode 100644
index 0000000000..ac903157f2
--- /dev/null
+++ b/engines/tinsel/scroll.h
@@ -0,0 +1,77 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCROLL_H // prevent multiple includes
+#define TINSEL_SCROLL_H
+
+namespace Tinsel {
+
+#define SCROLLPIXELS 8 // Number of pixels to scroll per iteration
+
+#define RLDISTANCE 50 // Distance from edge that triggers a scroll
+#define UDDISTANCE 20
+
+// Number of iterations to make
+#define RLSCROLL 160 // 20*8 = 160 = half a screen
+#define UDSCROLL 100 // 12.5*8 = 100 = half a screen
+
+
+// These structures defined here so boundaries can be saved
+struct NOSCROLLB {
+ int ln;
+ int c1;
+ int c2;
+};
+
+#define MAX_HNOSCROLL 10
+#define MAX_VNOSCROLL 10
+
+struct SCROLLDATA{
+ NOSCROLLB NoVScroll[MAX_VNOSCROLL]; // Vertical no-scroll boundaries
+ NOSCROLLB NoHScroll[MAX_HNOSCROLL]; // Horizontal no-scroll boundaries
+ unsigned NumNoV, NumNoH; // Counts of no-scroll boundaries
+};
+
+
+
+void DontScrollCursor(void);
+void DoScrollCursor(void);
+
+void SetNoScroll(int x1, int y1, int x2, int y2);
+void DropNoScrolls(void);
+
+void ScrollProcess(CORO_PARAM, const void *);
+
+void ScrollFocus(int actor);
+void ScrollTo(int x, int y, int iter);
+
+void KillScroll(void);
+
+void GetNoScrollData(SCROLLDATA *ssd);
+void RestoreNoScrollData(SCROLLDATA *ssd);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCROLL_H */
diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h
new file mode 100644
index 0000000000..98ee398ef8
--- /dev/null
+++ b/engines/tinsel/serializer.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_SERIALIZER_H
+#define TINSEL_SERIALIZER_H
+
+#include "common/scummsys.h"
+#include "common/savefile.h"
+
+
+namespace Tinsel {
+
+
+#define SYNC_AS(SUFFIX,TYPE,SIZE) \
+ template <class T> \
+ void syncAs ## SUFFIX(T &val) { \
+ if (_loadStream) \
+ val = static_cast<T>(_loadStream->read ## SUFFIX()); \
+ else { \
+ TYPE tmp = val; \
+ _saveStream->write ## SUFFIX(tmp); \
+ } \
+ _bytesSynced += SIZE; \
+ }
+
+
+// TODO: Write comment for this
+// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines?
+class Serializer {
+public:
+ Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out)
+ : _loadStream(in), _saveStream(out), _bytesSynced(0) {
+ assert(in || out);
+ }
+
+ bool isSaving() { return (_saveStream != 0); }
+ bool isLoading() { return (_loadStream != 0); }
+
+ uint bytesSynced() const { return _bytesSynced; }
+
+ void syncBytes(byte *buf, uint16 size) {
+ if (_loadStream)
+ _loadStream->read(buf, size);
+ else
+ _saveStream->write(buf, size);
+ _bytesSynced += size;
+ }
+
+ SYNC_AS(Byte, byte, 1)
+
+ SYNC_AS(Uint16LE, uint16, 2)
+ SYNC_AS(Uint16BE, uint16, 2)
+ SYNC_AS(Sint16LE, int16, 2)
+ SYNC_AS(Sint16BE, int16, 2)
+
+ SYNC_AS(Uint32LE, uint32, 4)
+ SYNC_AS(Uint32BE, uint32, 4)
+ SYNC_AS(Sint32LE, int32, 4)
+ SYNC_AS(Sint32BE, int32, 4)
+
+protected:
+ Common::SeekableReadStream *_loadStream;
+ Common::OutSaveFile *_saveStream;
+
+ uint _bytesSynced;
+};
+
+#undef SYNC_AS
+
+// TODO: Make a subclass "VersionedSerializer", which makes it easy to support
+// multiple versions of a savegame format (again inspired by SCUMM).
+/*
+class VersionedSerializer : public Serializer {
+public:
+ // "version" is the version of the savegame we are loading/creating
+ VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version)
+ : Serializer(in, out), _version(version) {
+ assert(in || out);
+ }
+
+ void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) {
+ if (_version < minVersion || _version > maxVersion)
+ return; // Do nothing if too old or too new
+ if (_loadStream) {
+ _loadStream->read(buf, size);
+ } else {
+ _saveStream->write(buf, size);
+ }
+ }
+ ...
+
+};
+
+*/
+
+// Mixin class / interface
+// TODO Maybe call it ISerializable or SerializableMixin ?
+// TODO: Taken from SCUMM engine -- move to common/ code?
+class Serializable {
+public:
+ virtual ~Serializable() {}
+ virtual void saveLoadWithSerializer(Serializer *ser) = 0;
+};
+
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp
new file mode 100644
index 0000000000..e2a24dbd47
--- /dev/null
+++ b/engines/tinsel/sound.cpp
@@ -0,0 +1,211 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * sound functionality
+ */
+
+#include "tinsel/sound.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/config.h"
+#include "tinsel/music.h"
+#include "tinsel/strres.h"
+#include "tinsel/tinsel.h"
+
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+namespace Tinsel {
+
+//--------------------------- General data ----------------------------------
+
+SoundManager::SoundManager(TinselEngine *vm) :
+ //_vm(vm), // TODO: Enable this once global _vm var is gone
+ _sampleIndex(0), _sampleIndexLen(0) {
+}
+
+SoundManager::~SoundManager() {
+ free(_sampleIndex);
+}
+
+/**
+ * Plays the specified sample through the sound driver.
+ * @param id Identifier of sample to be played
+ * @param type type of sound (voice or sfx)
+ * @param handle sound handle
+ */
+bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
+ // Floppy version has no sample file
+ if (_vm->getFeatures() & GF_FLOPPY)
+ return false;
+
+ // no sample driver?
+ if (!_vm->_mixer->isReady())
+ return false;
+
+ // stop any currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+
+ // make sure id is in range
+ assert(id > 0 && id < _sampleIndexLen);
+
+ // get file offset for this sample
+ uint32 dwSampleIndex = _sampleIndex[id];
+
+ // move to correct position in the sample file
+ _sampleStream.seek(dwSampleIndex);
+ if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // read the length of the sample
+ uint32 sampleLen = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // allocate a buffer
+ void *sampleBuf = malloc(sampleLen);
+ assert(sampleBuf);
+
+ // read all of the sample
+ if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // FIXME: Should set this in a different place ;)
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound);
+ //_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice);
+
+
+ // play it
+ _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050,
+ Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED);
+
+ if (handle)
+ *handle = _handle;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if there is a sample for the specified sample identifier.
+ * @param id Identifier of sample to be checked
+ */
+bool SoundManager::sampleExists(int id) {
+ if (_vm->_mixer->isReady()) {
+ // make sure id is in range
+ if (id > 0 && id < _sampleIndexLen) {
+ // check for a sample index
+ if (_sampleIndex[id])
+ return true;
+ }
+ }
+
+ // no sample driver or no sample
+ return false;
+}
+
+/**
+ * Returns true if a sample is currently playing.
+ */
+bool SoundManager::sampleIsPlaying(void) {
+ return _vm->_mixer->isSoundHandleActive(_handle);
+}
+
+/**
+ * Stops any currently playing sample.
+ */
+void SoundManager::stopAllSamples(void) {
+ // stop currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+}
+
+/**
+ * Opens and inits all sound sample files.
+ */
+void SoundManager::openSampleFiles(void) {
+ // Floppy and demo versions have no sample files
+ if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO)
+ return;
+
+ Common::File f;
+
+ if (_sampleIndex)
+ // already allocated
+ return;
+
+ // open sample index file in binary mode
+ if (f.open(SAMPLE_INDEX)) {
+ // get length of index file
+ f.seek(0, SEEK_END); // move to end of file
+ _sampleIndexLen = f.pos(); // get file pointer
+ f.seek(0, SEEK_SET); // back to beginning
+
+ if (_sampleIndex == NULL) {
+ // allocate a buffer for the indices
+ _sampleIndex = (uint32 *)malloc(_sampleIndexLen);
+
+ // make sure memory allocated
+ if (_sampleIndex == NULL) {
+ // disable samples if cannot alloc buffer for indices
+ // TODO: Disabled sound if we can't load the sample index?
+ return;
+ }
+ }
+
+ // load data
+ if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", SAMPLE_FILE);
+
+#ifdef SCUMM_BIG_ENDIAN
+ // Convert all ids from LE to native format
+ for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) {
+ _sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i);
+ }
+#endif
+
+ // close the file
+ f.close();
+
+ // convert file size to size in DWORDs
+ _sampleIndexLen /= sizeof(uint32);
+ } else
+ error("Cannot find file %s", SAMPLE_INDEX);
+
+ // open sample file in binary mode
+ if (!_sampleStream.open(SAMPLE_FILE))
+ error("Cannot find file %s", SAMPLE_FILE);
+
+/*
+ // gen length of the largest sample
+ sampleBuffer.size = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+*/
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h
new file mode 100644
index 0000000000..56618eeb8e
--- /dev/null
+++ b/engines/tinsel/sound.h
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Sound Driver data structures etc.
+ */
+
+#ifndef TINSEL_SOUND_H
+#define TINSEL_SOUND_H
+
+#include "common/file.h"
+#include "common/file.h"
+
+#include "sound/mixer.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+#define MAXSAMPVOL 127
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+class SoundManager {
+protected:
+
+ //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone
+
+ /** Sample handle */
+ Audio::SoundHandle _handle;
+
+ /** Sample index buffer and number of entries */
+ uint32 *_sampleIndex;
+
+ /** Number of entries in the sample index */
+ long _sampleIndexLen;
+
+ /** file stream for sample file */
+ Common::File _sampleStream;
+
+public:
+
+ SoundManager(TinselEngine *vm);
+ ~SoundManager();
+
+ bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0);
+ void stopAllSamples(void); // Stops any currently playing sample
+
+ bool sampleExists(int id);
+ bool sampleIsPlaying(void);
+
+ // TODO: Internal method, make this protected?
+ void openSampleFiles(void);
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SOUND_H
diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp
new file mode 100644
index 0000000000..abf5a880f6
--- /dev/null
+++ b/engines/tinsel/strres.cpp
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "common/file.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+#ifdef DEBUG
+// Diagnostic number
+int newestString;
+#endif
+
+// buffer for resource strings
+static uint8 *textBuffer = 0;
+
+// language resource string filenames
+static const char *languageFiles[] = {
+ "english.txt",
+ "french.txt",
+ "german.txt",
+ "italian.txt",
+ "spanish.txt"
+};
+
+// Set if we're handling 2-byte characters.
+bool bMultiByte = false;
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang) {
+ Common::File f;
+ uint32 textLen = 0; // length of buffer
+
+ if (textBuffer) {
+ // free the previous buffer
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+
+ // Try and open the specified language file. If it fails, and the language
+ // isn't English, try falling back on opening 'english.txt' - some foreign
+ // language versions reused it rather than their proper filename
+ if (!f.open(languageFiles[newLang])) {
+ if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH]))
+ error("Cannot find file %s", languageFiles[newLang]);
+ }
+
+ // Check whether the file is compressed or not - for compressed files the
+ // first long is the filelength and for uncompressed files it is the chunk
+ // identifier
+ textLen = f.readUint32LE();
+ if (f.ioFailed())
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) {
+ // the file is uncompressed
+
+ bMultiByte = (textLen == CHUNK_MBSTRING);
+
+ // get length of uncompressed file
+ textLen = f.size();
+ f.seek(0, SEEK_SET); // Set to beginning of file
+
+ if (textBuffer == NULL) {
+ // allocate a text buffer for the strings
+ textBuffer = (uint8 *)malloc(textLen);
+
+ // make sure memory allocated
+ assert(textBuffer);
+ }
+
+ // load data
+ if (f.read(textBuffer, textLen) != textLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ // close the file
+ f.close();
+ } else { // the file must be compressed
+ error("Compression handling has been removed!");
+ }
+}
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax) {
+#ifdef DEBUG
+ // For diagnostics
+ newestString = id;
+#endif
+
+ // base of string resource table
+ uint8 *pText = textBuffer;
+
+ // index into text resource file
+ uint32 index = 0;
+
+ // number of chunks to skip
+ int chunkSkip = id / STRINGS_PER_CHUNK;
+
+ // number of strings to skip when in the correct chunk
+ int strSkip = id % STRINGS_PER_CHUNK;
+
+ // length of string
+ int len;
+
+ // skip to the correct chunk
+ while (chunkSkip-- != 0) {
+ // make sure chunk id is correct
+ assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING);
+
+ if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) {
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! HIGH STRING !!");
+
+ // string does not exist
+ return 0;
+ }
+
+ // get index to next chunk
+ index = READ_LE_UINT32(pText + index + sizeof(uint32));
+ }
+
+ // skip over chunk id and offset
+ index += (2 * sizeof(uint32));
+
+ // pointer to strings
+ pText = pText + index;
+
+ // skip to the correct string
+ while (strSkip-- != 0) {
+ // skip to next string
+ pText += *pText + 1;
+ }
+
+ // get length of string
+ len = *pText;
+
+ if (len) {
+ // the string exists
+
+ // copy the string to the buffer
+ if (len < bufferMax) {
+ memcpy(pBuffer, pText + 1, len);
+
+ // null terminate
+ pBuffer[len] = 0;
+
+ // number of chars copied
+ return len + 1;
+ } else {
+ memcpy(pBuffer, pText + 1, bufferMax - 1);
+
+ // null terminate
+ pBuffer[bufferMax - 1] = 0;
+
+ // number of chars copied
+ return bufferMax;
+ }
+ }
+
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! NULL STRING !!");
+
+ // string does not exist
+ return 0;
+}
+
+void FreeTextBuffer() {
+ if (textBuffer) {
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h
new file mode 100644
index 0000000000..fac287492b
--- /dev/null
+++ b/engines/tinsel/strres.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#ifndef TINSEL_STRRES_H
+#define TINSEL_STRRES_H
+
+#include "common/scummsys.h"
+#include "tinsel/scn.h"
+
+namespace Tinsel {
+
+#define STRINGS_PER_CHUNK 64 // number of strings per chunk in the language text files
+#define FIRST_STR_ID 1 // id number of first string in string table
+#define MAX_STRING_SIZE 255 // maximum size of a string in the resource table
+#define MAX_STRRES_SIZE 300000 // maximum size of string resource file
+
+// Set if we're handling 2-byte characters.
+extern bool bMultiByte;
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang);
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax);
+
+/**
+ * Frees the text buffer allocated from ChangeLanguage()
+ */
+void FreeTextBuffer();
+
+} // end of namespace Tinsel
+
+#endif
+
diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp
new file mode 100644
index 0000000000..dab97f7fdf
--- /dev/null
+++ b/engines/tinsel/text.cpp
@@ -0,0 +1,279 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utilities.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h" // object plotting
+#include "tinsel/handle.h"
+#include "tinsel/sched.h" // process scheduler defines
+#include "tinsel/strres.h" // bMultiByte
+#include "tinsel/text.h" // text defines
+
+namespace Tinsel {
+
+/**
+ * Returns the length of one line of a string in pixels.
+ * @param szStr String
+ * @param pFont Which font to use for dimensions
+ */
+int StringLengthPix(char *szStr, const FONT *pFont) {
+ int strLen; // accumulated length of string
+ byte c;
+ SCNHANDLE hImg;
+
+ // while not end of string or end of line
+ for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg) {
+ // there is a IMAGE for this character
+ const IMAGE *pChar = (const IMAGE *)LockMem(hImg);
+
+ // add width of font bitmap
+ strLen += FROM_LE_16(pChar->imgWidth);
+ } else
+ // use width of space character
+ strLen += FROM_LE_32(pFont->spaceSize);
+
+ // finally add the inter-character spacing
+ strLen += FROM_LE_32(pFont->xSpacing);
+ }
+
+ // return length of line in pixels - minus inter-char spacing for last character
+ strLen -= FROM_LE_32(pFont->xSpacing);
+ return (strLen > 0) ? strLen : 0;
+}
+
+/**
+ * Returns the justified x start position of a line of text.
+ * @param szStr String to output
+ * @param xPos X position of string
+ * @param pFont Which font to use
+ * @param mode Mode flags for the string
+ */
+int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) {
+ if (mode & TXT_CENTRE) {
+ // centre justify the text
+
+ // adjust x positioning by half the length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont) / 2;
+ } else if (mode & TXT_RIGHT) {
+ // right justify the text
+
+ // adjust x positioning by length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont);
+ }
+
+ // return text line x start position
+ return xPos;
+}
+
+/**
+ * Main text outputting routine. If a object list is specified a
+ * multi-object is created for the whole text and a pointer to the head
+ * of the list is returned.
+ * @param pList Object list to add text to
+ * @param szStr String to output
+ * @param colour Colour for monochrome text
+ * @param xPos X position of string
+ * @param yPos Y position of string
+ * @param hFont Which font to use
+ * @param mode Mode flags for the string
+ */
+OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos,
+ SCNHANDLE hFont, int mode) {
+ int xJustify; // x position of text after justification
+ int yOffset; // offset to next line of text
+ OBJECT *pFirst; // head of multi-object text list
+ OBJECT *pChar = 0; // object ptr for the character
+ byte c;
+ SCNHANDLE hImg;
+ const IMAGE *pImg;
+
+ // make sure there is a linked list to add text to
+ assert(pList);
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ // init head of text list
+ pFirst = NULL;
+
+ // get image for capital W
+ assert(pFont->fontDef[(int)'W']);
+ pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W']));
+
+ // get height of capital W for offset to next line
+ yOffset = FROM_LE_16(pImg->imgHeight);
+
+ while (*szStr) {
+ // x justify the text according to the mode flags
+ xJustify = JustifyText(szStr, xPos, pFont, mode);
+
+ // repeat until end of string or end of line
+ while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg == 0) {
+ // no image for this character
+
+ // add font spacing for a space character
+ xJustify += FROM_LE_32(pFont->spaceSize);
+ } else { // printable character
+
+ int aniX, aniY; // char image animation offsets
+
+ OBJ_INIT oi;
+ oi.hObjImg = FROM_LE_32(pFont->fontInit.hObjImg);
+ oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags);
+ oi.objID = FROM_LE_32(pFont->fontInit.objID);
+ oi.objX = FROM_LE_32(pFont->fontInit.objX);
+ oi.objY = FROM_LE_32(pFont->fontInit.objY);
+ oi.objZ = FROM_LE_32(pFont->fontInit.objZ);
+
+ // allocate and init a character object
+ if (pFirst == NULL)
+ // first time - init head of list
+ pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+ else
+ // chain to multi-char list
+ pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+
+ // convert image handle to pointer
+ pImg = (const IMAGE *)LockMem(hImg);
+
+ // fill in character object
+ pChar->hImg = hImg; // image def
+ pChar->width = FROM_LE_16(pImg->imgWidth); // width of chars bitmap
+ pChar->height = FROM_LE_16(pImg->imgHeight); // height of chars bitmap
+ pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pChar->flags |= DMA_ABS;
+
+ // set characters colour - only effective for mono fonts
+ pChar->constant = colour;
+
+ // get Y animation offset
+ GetAniOffset(hImg, pChar->flags, &aniX, &aniY);
+
+ // set x position - ignore animation point
+ pChar->xPos = intToFrac(xJustify);
+
+ // set y position - adjust for animation point
+ pChar->yPos = intToFrac(yPos - aniY);
+
+ if (mode & TXT_SHADOW) {
+ // we want to shadow the character
+ OBJECT *pShad;
+
+ // allocate a object for the shadow and chain to multi-char list
+ pShad = pChar->pSlave = AllocObject();
+
+ // copy the character for a shadow
+ CopyObject(pShad, pChar);
+
+ // add shadow offsets to characters position
+ pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow));
+ pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow));
+
+ // shadow is behind the character
+ pShad->zPos--;
+
+ // shadow is always mono
+ pShad->flags = DMA_CNZ | DMA_CHANGED;
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pShad->flags |= DMA_ABS;
+
+ // shadow always uses first palette entry
+ // should really alloc a palette here also ????
+ pShad->constant = 1;
+
+ // add shadow to object list
+ InsertObject(pList, pShad);
+ }
+
+ // add character to object list
+ InsertObject(pList, pChar);
+
+ // move to end of list
+ if (pChar->pSlave)
+ pChar = pChar->pSlave;
+
+ // add character spacing
+ xJustify += FROM_LE_16(pImg->imgWidth);
+ }
+
+ // finally add the inter-character spacing
+ xJustify += FROM_LE_32(pFont->xSpacing);
+
+ // next character in string
+ ++szStr;
+ }
+
+ // adjust the text y position and add the inter-line spacing
+ yPos += yOffset + FROM_LE_32(pFont->ySpacing);
+
+ // check for newline
+ if (c == LF_CHAR)
+ // next character in string
+ ++szStr;
+ }
+
+ // return head of list
+ return pFirst;
+}
+
+/**
+ * Is there an image for this character in this font?
+ * @param hFont which font to use
+ * @param c character to test
+ */
+bool IsCharImage(SCNHANDLE hFont, char c) {
+ byte c2 = (byte)c;
+
+ // Inventory save game name editor needs to be more clever for
+ // multi-byte characters. This bodge will stop it erring.
+ if (bMultiByte && (c2 & 0x80))
+ return false;
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ return pFont->fontDef[c2] != 0;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h
new file mode 100644
index 0000000000..78998831a1
--- /dev/null
+++ b/engines/tinsel/text.h
@@ -0,0 +1,101 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TEXT_H // prevent multiple includes
+#define TINSEL_TEXT_H
+
+#include "tinsel/object.h" // object manager defines
+
+namespace Tinsel {
+
+/** text mode flags - defaults to left justify */
+enum {
+ TXT_CENTRE = 0x0001, //!< centre justify text
+ TXT_RIGHT = 0x0002, //!< right justify text
+ TXT_SHADOW = 0x0004, //!< shadow each character
+ TXT_ABSOLUTE = 0x0008 //!< position of text is absolute (only for object text)
+};
+
+/** maximum number of characters in a font */
+#define MAX_FONT_CHARS 256
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * Text font data structure.
+ * @note only the pointer is used so the size of fontDef[] is not important.
+ * It is currently set at 300 because it suited me for debugging.
+ */
+struct FONT {
+ int xSpacing; //!< x spacing between characters
+ int ySpacing; //!< y spacing between characters
+ int xShadow; //!< x shadow offset
+ int yShadow; //!< y shadow offset
+ int spaceSize; //!< x spacing to use for a space character
+ OBJ_INIT fontInit; //!< structure used to init text objects
+ SCNHANDLE fontDef[300]; //!< image handle array for all characters in the font
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** structure for passing the correct parameters to ObjectTextOut */
+struct TEXTOUT {
+ OBJECT *pList; //!< object list to add text to
+ char *szStr; //!< string to output
+ int colour; //!< colour for monochrome text
+ int xPos; //!< x position of string
+ int yPos; //!< y position of string
+ SCNHANDLE hFont; //!< which font to use
+ int mode; //!< mode flags for the string
+ int sleepTime; //!< sleep time between each character (if non-zero)
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Text Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *ObjectTextOut( // output a string of text
+ OBJECT *pList, // object list to add text to
+ char *szStr, // string to output
+ int colour, // colour for monochrome text
+ int xPos, // x position of string
+ int yPos, // y position of string
+ SCNHANDLE hFont, // which font to use
+ int mode); // mode flags for the string
+
+OBJECT *ObjectTextOutIndirect( // output a string of text
+ TEXTOUT *pText); // pointer to TextOut struct with all parameters
+
+bool IsCharImage( // Is there an image for this character in this font?
+ SCNHANDLE hFont, // which font to use
+ char c); // character to test
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TEXT_H
diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp
new file mode 100644
index 0000000000..c7b9d3708b
--- /dev/null
+++ b/engines/tinsel/timers.cpp
@@ -0,0 +1,192 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ *
+ * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed
+ * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use
+ * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS
+ */
+
+#include "tinsel/timers.h"
+#include "tinsel/dw.h"
+#include "tinsel/serializer.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define MAX_TIMERS 16
+
+struct TIMER {
+ int tno; /**< Timer number */
+ int ticks; /**< Tick count */
+ int secs; /**< Second count */
+ int delta; /**< Increment/decrement value */
+ bool frame; /**< If set, in ticks, otherwise in seconds */
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static TIMER timers[MAX_TIMERS];
+
+
+//--------------------------------------------------------
+
+/**
+ * Gets the current time in number of ticks.
+ *
+ * DOS timer ticks is the number of 54.9254ms since midnight. Converting the
+ * millisecond count won't give the exact same 'since midnight' count, but I
+ * figure that as long as the timing interval is more or less accurate, it
+ * shouldn't be a problem.
+ */
+
+uint32 DwGetCurrentTime() {
+ return g_system->getMillis() * 55 / 1000;
+}
+
+/**
+ * Resets all of the timer slots
+ */
+
+void RebootTimers(void) {
+ memset(timers, 0, sizeof(timers));
+}
+
+/**
+ * (Un)serialize the timer data for save/restore game.
+ */
+void syncTimerInfo(Serializer &s) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ s.syncAsSint32LE(timers[i].tno);
+ s.syncAsSint32LE(timers[i].ticks);
+ s.syncAsSint32LE(timers[i].secs);
+ s.syncAsSint32LE(timers[i].delta);
+ s.syncAsSint32LE(timers[i].frame);
+ }
+}
+
+/**
+ * Find the timer numbered thus, if one is thus numbered.
+ * @param num number of the timer
+ * @return the timer with the specified number, or NULL if there is none
+ */
+static TIMER *findTimer(int num) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (timers[i].tno == num)
+ return &timers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Find an empty timer slot.
+ */
+static TIMER *allocateTimer(int num) {
+ assert(num); // zero is not allowed as a timer number
+ assert(!findTimer(num)); // Allocating already existant timer
+
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno) {
+ timers[i].tno = num;
+ return &timers[i];
+ }
+ }
+
+ error("Too many timers");
+}
+
+/**
+ * Update all timers, as appropriate.
+ */
+void FettleTimers(void) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno)
+ continue;
+
+ timers[i].ticks += timers[i].delta; // Update tick value
+
+ if (timers[i].frame) {
+ if (timers[i].ticks < 0)
+ timers[i].ticks = 0; // Have reached zero
+ } else {
+ if (timers[i].ticks < 0) {
+ timers[i].ticks = ONE_SECOND;
+ timers[i].secs--;
+ if (timers[i].secs < 0)
+ timers[i].secs = 0; // Have reached zero
+ } else if (timers[i].ticks == ONE_SECOND) {
+ timers[i].ticks = 0;
+ timers[i].secs++; // Another second has passed
+ }
+ }
+ }
+}
+
+/**
+ * Start a timer up.
+ */
+void DwSetTimer(int num, int sval, bool up, bool frame) {
+ TIMER *pt;
+
+ assert(num); // zero is not allowed as a timer number
+
+ pt = findTimer(num);
+ if (pt == NULL)
+ pt = allocateTimer(num);
+
+ pt->delta = up ? 1 : -1; // Increment/decrement value
+ pt->frame = frame;
+
+ if (frame) {
+ pt->secs = 0;
+ pt->ticks = sval;
+ } else {
+ pt->secs = sval;
+ pt->ticks = 0;
+ }
+}
+
+/**
+ * Return the current count of a timer.
+ */
+int Timer(int num) {
+ TIMER *pt;
+
+ pt = findTimer(num);
+
+ if (pt == NULL)
+ return -1;
+
+ if (pt->frame)
+ return pt->ticks;
+ else
+ return pt->secs;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h
new file mode 100644
index 0000000000..75eb87ee2b
--- /dev/null
+++ b/engines/tinsel/timers.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_TIMERS_H // prevent multiple includes
+#define TINSEL_TIMERS_H
+
+#include "common/scummsys.h"
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+class Serializer;
+
+#define ONE_SECOND 24
+
+uint32 DwGetCurrentTime(void);
+
+void RebootTimers(void);
+
+void syncTimerInfo(Serializer &s);
+
+void FettleTimers(void);
+
+void DwSetTimer(int num, int sval, bool up, bool frame);
+
+int Timer(int num);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
new file mode 100644
index 0000000000..e8364e20dd
--- /dev/null
+++ b/engines/tinsel/tinlib.cpp
@@ -0,0 +1,2980 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Glitter library functions.
+ *
+ * In the main called only from PCODE.C
+ * Function names are the same as Glitter code function names.
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#define BODGE
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/coroutine.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+extern bool bHasRestarted; // Set after a restart
+
+// In DOS_MAIN.C
+// TODO/FIXME: From dos_main.c: "Only used on PSX so far"
+int clRunMode = 0;
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern void ChangePalette(SCNHANDLE hPal);
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+// in DOS_DW.C
+extern void SetHookScene(SCNHANDLE scene, int entrance, int transition);
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+extern void UnHookScene(void);
+extern void SuspendHook(void);
+extern void UnSuspendHook(void);
+
+// in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+bool DisableTagsIfEnabled(void);
+extern void setshowstring(void);
+
+// in PLAY.C
+extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+
+// in SCENE.C
+extern void setshowpos(void);
+
+#ifdef BODGE
+// In DOS_HAND.C
+bool ValidHandle(SCNHANDLE offset);
+
+// In SCENE.C
+SCNHANDLE GetSceneHandle(void);
+#endif
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bEnableF1;
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define JAP_TEXT_TIME (2*ONE_SECOND)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes *|
+\*----------------------------------------------------------------------*/
+
+enum LIB_CODE {
+ ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4,
+ ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10,
+ BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15,
+ CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20,
+ DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25,
+ DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31,
+ GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36,
+ INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42,
+ MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48,
+ PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54,
+ PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59,
+ SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65,
+ SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71,
+ STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79,
+ TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85,
+ WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91,
+ WHICHINVENTORY = 92,
+ ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97,
+ TALKAT, UNHOOKSCENE, WAITFRAME, DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103,
+ TALKATS = 104,
+ DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109,
+ NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113,
+ RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119,
+ HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// Saved cursor co-ordinates for control(on) to restore cursor position
+// as it was at control(off).
+// They are global so that movecursor(..) has a net effect if it
+// precedes control(on).
+static int controlX = 0, controlY = 0;
+
+static int offtype = 0; // used by control()
+static uint32 lastValue = 0; // used by dw_random()
+static int scrollCount = 0; // used by scroll()
+
+static bool NotPointedRunning = false; // Used in printobj and printobjPointed
+
+static COLORREF s_talkfontColor = 0;
+
+//----------------- FORWARD REFERENCES --------------------
+
+void resetidletime(void);
+void stopmidi(void);
+void stopsample(void);
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime);
+
+
+/**
+ * NOT A LIBRARY FUNCTION
+ *
+ * Poke supplied colours into the DAC queue.
+ */
+static void setTextPal(COLORREF col) {
+ s_talkfontColor = col;
+ UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor);
+}
+
+
+static int TextTime(char *pTstring) {
+ if (isJapanMode())
+ return JAP_TEXT_TIME;
+ else if (!speedText)
+ return strlen(pTstring) + ONE_SECOND;
+ else
+ return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100;
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+/**
+ * Set actor's attributes.
+ * - currently only the text colour.
+ */
+void actorattr(int actor, int r1, int g1, int b1) {
+ storeActorAttr(actor, r1, g1, b1);
+}
+
+/**
+ * Return the actor's direction.
+ */
+int actordirection(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorDirection(pActor);
+}
+
+/**
+ * Return the actor's scale.
+ */
+int actorscale(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorScale(pActor);
+}
+
+/**
+ * Returns the x or y position of an actor.
+ */
+int actorpos(int xory, int actor) {
+ int x, y;
+
+ GetActorPos(actor, &x, &y);
+ return (xory == ACTORXPOS) ? x : y;
+}
+
+/**
+ * Make all actors alive at the start of each scene.
+ */
+void actorson(void) {
+ setactorson();
+}
+
+/**
+ * Adds an icon to the conversation window.
+ */
+void addicon(int icon) {
+ AddToInventory(INV_CONV, icon, false);
+}
+
+/**
+ * Place the object in inventory 1 or 2.
+ */
+void addinv(int invno, int object) {
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number
+
+ AddToInventory(invno, object, false);
+}
+
+/**
+ * Define an actor's walk and stand reels for an auxilliary scale.
+ */
+void auxscale(int actor, int scale, SCNHANDLE *rp) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Can't set aux scale for a non-moving actor
+
+ int j;
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[scale-1][j] = *rp++;
+}
+
+/**
+ * Defines the background image for a scene.
+ */
+void background(SCNHANDLE bfilm) {
+ startupBackground(bfilm);
+}
+
+/**
+ * Sets focus of the scroll process.
+ */
+void camera(int actor) {
+ ScrollFocus(actor);
+}
+
+/**
+ * A CDPLAY() is imminent.
+ */
+void cdload(SCNHANDLE start, SCNHANDLE next) {
+ assert(start && next && start != next); // cdload() fault
+
+// TODO/FIXME
+// LoadExtraGraphData(start, next);
+}
+
+/**
+ * Clear the hooked scene (if any)
+ */
+
+void clearhookscene() {
+ SetHookScene(0, 0, 0);
+}
+
+/**
+ * Guess what.
+ */
+
+void closeinventory(void) {
+ KillInventory();
+}
+
+/**
+ * Turn off cursor and take control from player - and variations on the theme.
+ * OR Restore cursor and return control to the player.
+ */
+
+void control(int param) {
+ bEnableF1 = false;
+
+ switch (param) {
+ case CONTROL_STARTOFF:
+ GetControlToken(); // Take control
+ DisableTags(); // Switch off tags
+ DwHideCursor(); // Blank out cursor
+ offtype = param;
+ break;
+
+ case CONTROL_OFF:
+ case CONTROL_OFFV:
+ case CONTROL_OFFV2:
+ if (TestToken(TOKEN_CONTROL)) {
+ GetControlToken(); // Take control
+
+ DisableTags(); // Switch off tags
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ // There may be a button timing out
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+
+ if (offtype == CONTROL_STARTOFF)
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ offtype = param;
+
+ if (param == CONTROL_OFF)
+ DwHideCursor(); // Blank out cursor
+ else if (param == CONTROL_OFFV) {
+ UnHideCursor();
+ FreezeCursor();
+ } else if (param == CONTROL_OFFV2) {
+ UnHideCursor();
+ }
+ break;
+
+ case CONTROL_ON:
+ if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF)
+ SetCursorXY(controlX, controlY);// ... where it was
+
+ FreeControlToken(); // Release control
+
+ if (!InventoryActive())
+ EnableTags(); // Tags back on
+
+ RestoreMainCursor(); // Re-instate cursor...
+ }
+}
+
+/**
+ * Open or close the conversation window.
+ */
+
+void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) {
+ assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block
+
+ switch (fn) {
+ case CONV_END: // Close down conversation
+ CloseDownConv();
+ break;
+
+ case CONV_DEF: // Default (i.e. TOP of screen)
+ case CONV_BOTTOM: // BOTTOM of screen
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+
+ if (IsConvWindow())
+ break;
+
+ KillInventory();
+ convPos(fn);
+ ConvPoly(hp);
+ PopUpInventory(INV_CONV); // Conversation window
+ ConvAction(INV_OPENICON); // CONVERSATION event
+ break;
+ }
+}
+
+/**
+ * Add icon to conversation window's permanent default list.
+ */
+
+void convicon(int icon) {
+ AddIconToPermanentDefaultList(icon);
+}
+
+/**
+ * Returns the x or y position of the cursor.
+ */
+
+int cursorpos(int xory) {
+ int x, y;
+
+ GetCursorXY(&x, &y, true);
+ return (xory == CURSORXPOS) ? x : y;
+}
+
+/**
+ * Declare conversation window.
+ */
+
+void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ idec_convw(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare config strings.
+ */
+
+void dec_cstrings(SCNHANDLE *tp) {
+ setConfigStrings(tp);
+}
+
+/**
+ * Declare cursor's reels.
+ */
+
+void dec_cursor(SCNHANDLE bfilm) {
+ DwInitCursor(bfilm);
+}
+
+/**
+ * Declare the language flags.
+ */
+
+void dec_flags(SCNHANDLE hf) {
+ setFlagFilms(hf);
+}
+
+/**
+ * Declare inventory 1's parameters.
+ */
+
+void dec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv1(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare inventory 2's parameters.
+ */
+
+void dec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv2(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare the bits that the inventory windows are constructed from.
+ */
+
+void dec_invw(SCNHANDLE hf) {
+ setInvWinParts(hf);
+}
+
+/**
+ * Declare lead actor.
+ * - the actor's id, walk and stand reels for all the regular scales,
+ * and the tag text.
+ */
+
+void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) {
+ PMACTOR pActor; // Moving actor structure
+
+ Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged
+ setleadid(id); // Establish this as the lead
+ SetMover(id); // Establish as a moving actor
+
+ pActor = GetMover(id); // Get moving actor structure
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ pActor->TalkReels[i][j] = pActor->TalkReels[4][j];
+ }
+ }
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_tagfont(SCNHANDLE hf) {
+ TagFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_talkfont(SCNHANDLE hf) {
+ TalkFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Remove an icon from the conversation window.
+ */
+
+void delicon(int icon) {
+ RemFromInventory(INV_CONV, icon);
+}
+
+/**
+ * Delete the object from inventory 1 or 2.
+ */
+
+void delinv(int object) {
+ if (!RemFromInventory(INV_1, object)) // Remove from inventory 1...
+ RemFromInventory(INV_2, object); // ...or 2 (whichever)
+
+ DropItem(object); // Stop holding it
+}
+
+/**
+ * enablef1
+ */
+
+void enablef1(void) {
+ bEnableF1 = true;
+}
+
+/**
+ * fademidi(in/out)
+ */
+
+void fademidi(CORO_PARAM, int inout) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(inout == FM_IN || inout == FM_OUT);
+
+ // To prevent compiler complaining
+ if (inout == FM_IN || inout == FM_OUT)
+ CORO_SLEEP(1);
+ CORO_END_CODE;
+}
+
+/**
+ * Guess what.
+ */
+
+int getinvlimit(int invno) {
+ return InvGetLimit(invno);
+}
+
+/**
+ * Returns TRUE if the game has been restarted, FALSE if not.
+ */
+bool hasrestarted(void) {
+ return bHasRestarted;
+}
+
+/**
+ * Returns which object is currently held.
+ */
+
+int heldobject(void) {
+ return WhichItemHeld();
+}
+
+/**
+ * Removes a player from the screen, probably when he's about to be
+ * replaced by an animation.
+ *
+ * Not believed to work anymore! (hide() is not used).
+ */
+
+void hide(int actor) {
+ HideActor(actor);
+}
+
+/**
+ * hookscene(scene, entrance, transition)
+ */
+
+void hookscene(SCNHANDLE scene, int entrance, int transition) {
+ SetHookScene(scene, entrance, transition);
+}
+
+/**
+ * idletime
+ */
+
+int idletime(void) {
+ uint32 x;
+
+ x = getUserEventTime() / ONE_SECOND;
+
+ if (!TestToken(TOKEN_CONTROL))
+ resetidletime();
+
+ return (int)x;
+}
+
+/**
+ * invdepict
+ */
+void invdepict(int object, SCNHANDLE hFilm) {
+ invObjectFilm(object, hFilm);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int ininventory(int object) {
+ return (InventoryPos(object) != INV_NOICON);
+}
+
+/**
+ * Open an inventory.
+ */
+void inventory(int invno, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory
+
+ PopUpInventory(invno);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int inwhichinv(int object) {
+ if (WhichItemHeld() == object)
+ return 0;
+
+ if (IsInInventory(object, INV_1))
+ return 1;
+
+ if (IsInInventory(object, INV_2))
+ return 2;
+
+ return -1;
+}
+
+/**
+ * Kill an actor.
+ */
+void killactor(int actor) {
+ DisableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon off.
+ */
+void killblock(int block) {
+ DisableBlock(block);
+}
+
+/**
+ * Turn an exit off.
+ */
+void killexit(int exit) {
+ DisableExit(exit);
+}
+
+/**
+ * Turn a tag off.
+ */
+void killtag(int tagno) {
+ DisableTag(tagno);
+}
+
+/**
+ * Returns the left or top offset of the screen.
+ */
+int ltoffset(int lort) {
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ return (lort == LEFTOFFSET) ? Loffset : Toffset;
+}
+
+/**
+ * Set new cursor position.
+ */
+void movecursor(int x, int y) {
+ SetCursorXY(x, y);
+
+ controlX = x; // Save these values so that
+ controlY = y; // control(on) doesn't undo this
+}
+
+/**
+ * Triggers change to a new scene.
+ */
+void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+#ifdef BODGE
+ if (!ValidHandle(scene)) {
+ scene = GetSceneHandle();
+ entrance = 1;
+ }
+ assert(scene); // Non-existant first scene!
+#endif
+
+ SetNewScene(scene, entrance, transition);
+
+#if 1
+ // Prevent tags and cursor re-appearing
+ GetControl(CONTROL_STARTOFF);
+#endif
+
+ // Prevent code subsequent to this call running before scene changes
+ if (g_scheduler->getCurrentPID() != PID_MASTER_SCR)
+ CORO_KILL_SELF();
+ CORO_END_CODE;
+}
+
+/**
+ * Disable dynamic blocking for current scene.
+ */
+void noblocking(void) {
+ bNoBlocking = true;
+}
+
+/**
+ * Define a no-scroll boundary for the current scene.
+ */
+void noscroll(int x1, int y1, int x2, int y2) {
+ SetNoScroll(x1, y1, x2, y2);
+}
+
+/**
+ * Hold the specified object.
+ */
+void objectheld(int object) {
+ HoldItem(object);
+}
+
+/**
+ * Set the top left offset of the screen.
+ */
+void offset(int x, int y) {
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, x, y);
+}
+
+/**
+ * Play a film.
+ */
+void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // play(): Trying to play NULL film
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ // 7/4/95
+ if (!escOn)
+ myescEvent = GetEscEvents();
+
+ if (compit == 1) {
+ // Play to completion before returning
+ CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop));
+ } else if (compit == 2) {
+ error("play(): compit == 2 - please advise John");
+ } else {
+ // Kick off the play and return.
+ playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a midi file.
+ */
+void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) {
+ // FIXME: This is a workaround for the FIXME below
+ if (GetMidiVolume() == 0)
+ return;
+
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(loop == MIDI_DEF || loop == MIDI_LOOP);
+
+ PlayMidiSequence(hMidi, loop == MIDI_LOOP);
+
+ // FIXME: The following check messes up the script arguments when
+ // entering the secret door in the bookshelf in the library,
+ // leading to a crash, when the music volume is set to 0 (MidiPlaying()
+ // always false then).
+ //
+ // Why exactly this happens is unclear. An analysis of the involved
+ // script(s) might reveal more.
+ //
+ // Note: This check&sleep was added in DW v2. It was most likely added
+ // to ensure that the MIDI song started playing before the next opcode
+ // is executed.
+ if (!MidiPlaying())
+ CORO_SLEEP(1);
+
+ if (complete) {
+ while (MidiPlaying())
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ Audio::SoundHandle handle;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't play SFX if voice is already playing
+ if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType))
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+ return;
+ }
+
+ if (volSound != 0 && _vm->_sound->sampleExists(sample)) {
+ _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle);
+
+ if (complete) {
+ while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_mixer->stopHandle(_ctx->handle);
+ break;
+ }
+
+ CORO_SLEEP(1);
+ }
+ }
+ } else {
+ // Prevent Glitter lock-up
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not appropriate
+ if (_vm->_sound->sampleIsPlaying()) {
+ // return, but prevent Glitter lock-up
+ CORO_SLEEP(1);
+ return;
+ }
+
+ CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent));
+ CORO_END_CODE;
+}
+
+/**
+ * Trigger pre-loading of a scene's data.
+ */
+void preparescene(SCNHANDLE scene) {
+#ifdef BODGE
+ if (!ValidHandle(scene))
+ return;
+#endif
+}
+
+/**
+ * Print the given text at the given place for the given time.
+ *
+ * Print(....., h) -> hold = 1 (not used)
+ * Print(....., s) -> hold = 2 (sustain)
+ */
+void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int myleftEvent;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ bool bJapDoPrintText; // Bodge to get-around Japanese bodge
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->bSample = false;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ }
+
+ // Calculate display time
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ bJapDoPrintText = false;
+ if (time == 0) {
+ // This is a 'talky' print
+ _ctx->time = TextTime(tBufferAddr());
+
+ // Cut short-able if sustain was not set
+ _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents();
+ } else {
+ _ctx->time = time * ONE_SECOND;
+ _ctx->myleftEvent = 0;
+ if (isJapanMode())
+ bJapDoPrintText = true;
+ }
+
+ // Print the text
+ if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) {
+ int Loffset, Toffset; // Screen position
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // string produced NULL text
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ int shift;
+ shift = MultiRightmost(_ctx->pText) + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(_ctx->pText) - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -shift, 0);
+ shift = MultiLowest(_ctx->pText);
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift);
+ }
+
+ // Give up if nothing printed and no sample
+ if (_ctx->pText == NULL && !_ctx->bSample)
+ return;
+
+ // Leave it up until time runs out or whatever
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talky-print!
+ // Will be ignored if myleftevent happens to be 0!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->bSample && --_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->time-- <= 0)
+ break;
+ }
+
+ } while (1);
+
+ // Delete the text
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item);
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText);
+
+/**
+ * Print the given inventory object's name or whatever.
+ */
+void printobj(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, const int event) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int textx, texty;
+ int item;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(pinvo != 0); // printobj() may only be called from an object code block
+
+ if (text == (SCNHANDLE)-1) { // 'OFF'
+ NotPointedRunning = true;
+ return;
+ }
+ if (text == (SCNHANDLE)-2) { // 'ON'
+ NotPointedRunning = false;
+ return;
+ }
+
+ GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position..
+ _ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position
+
+ if (_ctx->item == INV_NOICON)
+ return;
+
+ if (event != POINTED) {
+ NotPointedRunning = true; // Get POINTED text to die
+ CORO_SLEEP(1); // Give it chance to
+ } else
+ NotPointedRunning = false; // There may have been an OFF without an ON
+
+ // Display the text and set it's Z position
+ if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) {
+ int xshift;
+
+ LoadStringRes(text, tBufferAddr(), TBUFSZ); // The text string
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // printobj() string produced NULL text
+ MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
+
+ // Don't go off the side of the screen
+ xshift = MultiLeftmost(_ctx->pText);
+ if (xshift < 0) {
+ MultiMoveRelXY(_ctx->pText, - xshift, 0);
+ _ctx->textx -= xshift;
+ }
+ xshift = MultiRightmost(_ctx->pText);
+ if (xshift > SCREEN_WIDTH) {
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ _ctx->textx += SCREEN_WIDTH - xshift;
+ }
+ } else
+ _ctx->pText = NULL;
+
+ if (event == POINTED) {
+ // FIXME: Is there ever an associated sound if in POINTED mode???
+ assert(!_vm->_sound->sampleExists(text));
+ CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item));
+ } else {
+ CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText);
+ }
+
+ // Delete the text, if haven't already
+ if (_ctx->pText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+
+ CORO_END_CODE;
+}
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Have to give way to non-POINTED-generated text
+ // and go away if the item gets picked up
+ int x, y;
+ do {
+ // Give up if this item gets picked up
+ if (WhichItemHeld() == pinvo->id)
+ break;
+
+ // Give way to non-POINTED-generated text
+ if (NotPointedRunning) {
+ // Delete the text, and wait for the all-clear
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText);
+ pText = NULL;
+ while (NotPointedRunning)
+ CORO_SLEEP(1);
+
+ GetCursorXY(&x, &y, false);
+ if (InvItem(&x, &y, false) != item)
+ break;
+
+ // Re-display in the same place
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, textx, texty, hTagFontHandle(), TXT_CENTRE);
+ assert(pText); // printobj() string produced NULL text
+ MultiSetZPosition(pText, Z_INV_ITEXT);
+ }
+
+ CORO_SLEEP(1);
+
+ // Carry on until the cursor leaves this icon
+ GetCursorXY(&x, &y, false);
+ } while (InvItemId(x, y) == pinvo->id);
+
+ CORO_END_CODE;
+}
+
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) {
+ CORO_BEGIN_CONTEXT;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+
+ int myleftEvent;
+ bool took_control;
+ int ticks;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ _ctx->myleftEvent = GetLeftEvents();
+ _ctx->took_control = GetControl(CONTROL_OFF);
+
+ // Display for a time, but abort if conversation gets hidden
+ if (isJapanMode())
+ _ctx->ticks = JAP_TEXT_TIME;
+ else if (pText)
+ _ctx->ticks = TextTime(tBufferAddr());
+ else
+ _ctx->ticks = 0;
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if left click - hardwired feature for talky-print!
+ // Abort if sample times out
+ // Abort if conversation hidden
+ if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid())
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ NotPointedRunning = false; // Let POINTED text back in
+
+ if (_ctx->took_control)
+ control(CONTROL_ON); // Free control if we took it
+
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Register the fact that this poly would like its tag displayed.
+ */
+void printtag(HPOLYGON hp, SCNHANDLE text) {
+ assert(hp != NOPOLY); // printtag() may only be called from a polygon code block
+
+ if (PolyTagState(hp) == TAG_OFF) {
+ SetPolyTagState(hp, TAG_ON);
+ SetPolyTagHandle(hp, text);
+ }
+}
+
+/**
+ * quitgame
+ */
+void quitgame(void) {
+ stopmidi();
+ stopsample();
+ _vm->quitFlag = true;
+}
+
+/**
+ * Return a random number between optional limits.
+ */
+int dw_random(int n1, int n2, int norpt) {
+ int i = 0;
+ uint32 value;
+
+ do {
+ value = n1 + _vm->getRandomNumber(n2 - n1);
+ } while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10));
+
+ lastValue = value;
+ return value;
+}
+
+/**
+ * resetidletime
+ */
+void resetidletime(void) {
+ resetUserEventTime();
+}
+
+/**
+ * restartgame
+ */
+void restartgame(void) {
+ stopmidi();
+ stopsample();
+ bRestart = true;
+}
+
+/**
+ * Restore saved scene.
+ */
+void restore_scene(bool bFade) {
+ UnSuspendHook();
+ PleaseRestoreScene(bFade);
+}
+
+/**
+ * runmode
+ */
+int runmode(void) {
+ return clRunMode;
+}
+
+/**
+ * sampleplaying
+ */
+bool sampleplaying(bool escOn, int myescEvent) {
+ // escape effects introduced 14/12/95 to fix
+ // while (sampleplaying()) pause;
+
+ if (escOn && myescEvent != GetEscEvents())
+ return false;
+
+ return _vm->_sound->sampleIsPlaying();
+}
+
+/**
+ * Save current scene.
+ */
+void save_scene(CORO_PARAM) {
+ PleaseSaveScene(coroParam);
+ SuspendHook();
+}
+
+/**
+ * scalingreels
+ */
+void scalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+
+ setscalingreels(actor, scale, direction, left, right, forward, away);
+}
+
+/**
+ * Return the icon that caused the CONVERSE event.
+ */
+
+int scanicon(void) {
+ return convIcon();
+}
+
+/**
+ * Scroll the screen to target co-ordinates.
+ */
+
+void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int mycount;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ } else {
+ _ctx->mycount = ++scrollCount;
+
+ ScrollTo(x, y, iter);
+
+ if (comp) {
+ int Loffset, Toffset;
+ do {
+ CORO_SLEEP(1);
+
+ // If escapable and ESCAPE is pressed...
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ break;
+ }
+
+ // give up if have been superseded
+ if (_ctx->mycount != scrollCount)
+ CORO_KILL_SELF();
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ } while (Loffset != x || Toffset != y);
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Un-kill an actor.
+ */
+void setactor(int actor) {
+ EnableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon on.
+ */
+
+void setblock(int blockno) {
+ EnableBlock(blockno);
+}
+
+/**
+ * Turn an exit on.
+ */
+
+void setexit(int exitno) {
+ EnableExit(exitno);
+}
+
+/**
+ * Guess what.
+ */
+void setinvlimit(int invno, int n) {
+ InvSetLimit(invno, n);
+}
+
+/**
+ * Guess what.
+ */
+void setinvsize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Guess what.
+ */
+void setlanguage(LANGUAGE lang) {
+ assert(lang == TXT_ENGLISH || lang == TXT_FRENCH
+ || lang == TXT_GERMAN || lang == TXT_ITALIAN
+ || lang == TXT_SPANISH); // ensure language is valid
+
+ ChangeLanguage(lang);
+}
+
+/**
+ * Set palette
+ */
+void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ ChangePalette(hPal);
+}
+
+/**
+ * Turn a tag on.
+ */
+void settag(int tagno) {
+ EnableTag(tagno);
+}
+
+/**
+ * Initialise a timer.
+ */
+void settimer(int timerno, int start, bool up, bool frame) {
+ DwSetTimer(timerno, start, up != 0, frame != 0);
+}
+
+#ifdef DEBUG
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showpos(void) {
+ setshowpos();
+}
+
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showstring(void) {
+ setshowstring();
+}
+#endif
+
+/**
+ * Special play - slow down associated actor's movement while the play
+ * is running. After the play, position the actor where the play left
+ * it and continue walking, if the actor still is.
+ */
+
+void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false);
+}
+
+/**
+ * (Re)Position an actor.
+ * If moving actor is not around yet in this scene, start it up.
+ */
+
+void stand(int actor, int x, int y, SCNHANDLE film) {
+ PMACTOR pActor; // Moving actor structure
+
+ pActor = GetMover(actor);
+ if (pActor) {
+ if (pActor->MActorState == NO_MACTOR) {
+ // create a moving actor process
+ MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor);
+
+ if (film == TF_NONE) {
+ SetMActorStanding(pActor);
+ } else {
+ switch (film) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else {
+ switch (film) {
+ case TF_NONE:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else if (actor == NULL_ACTOR) {
+ //
+ } else {
+ assert(film != 0); // Trying to play NULL film
+
+ // Kick off the play and return.
+ playFilm(film, x, y, actor, false, 0, false, 0, false);
+ }
+}
+
+/**
+ * Position the actor at the polygon's tag node.
+ */
+void standtag(int actor, HPOLYGON hp) {
+ SCNHANDLE film;
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // standtag() may only be called from a polygon code block
+
+ // Lead actor uses tag node film
+ film = getPolyFilm(hp);
+ getPolyNode(hp, &pnodex, &pnodey);
+ if (film && (actor == LEAD_ACTOR || actor == LeadId()))
+ stand(actor, pnodex, pnodey, film);
+ else
+ stand(actor, pnodex, pnodey, 0);
+}
+
+/**
+ * Kill a moving actor's walk.
+ */
+void stop(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Trying to stop a null actor
+
+ GetToken(pActor->actorToken); // Kill the walk process
+ pActor->stop = true; // Cause the actor to stop
+ FreeToken(pActor->actorToken);
+}
+
+void stopmidi(void) {
+ StopMidi(); // Stop any currently playing midi
+}
+
+void stopsample(void) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+}
+
+void subtitles(int onoff) {
+ assert (onoff == ST_ON || onoff == ST_OFF);
+
+ if (isJapanMode())
+ return; // Subtitles are always off in JAPAN version (?)
+
+ if (onoff == ST_ON)
+ bSubtitles = true;
+ else
+ bSubtitles = false;
+}
+
+/**
+ * Special walk.
+ * Walk into or out of a legal path.
+ */
+void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // For lead actor, lock out the user (if not already locked out)
+ if (actor == LeadId() || actor == LEAD_ACTOR)
+ _ctx->took_control = GetControl(CONTROL_OFFV2);
+ else
+ _ctx->took_control = false;
+
+ HPOLYGON hPath;
+
+ hPath = InPolygon(x1, y1, PATH);
+ if (hPath != NOPOLY) {
+ // Walking out of a path
+ stand(actor, x1, y1, 0);
+ } else {
+ hPath = InPolygon(x2, y2, PATH);
+ // One of them has to be in a path
+ assert(hPath != NOPOLY); //one co-ordinate must be in a legal path
+
+ // Walking into a path
+ stand(actor, x2, y2, 0); // Get path's characteristics
+ stand(actor, x1, y1, 0);
+ }
+
+ CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent));
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Define a tagged actor.
+ */
+
+void tagactor(int actor, SCNHANDLE text, int tp) {
+ Tag_Actor(actor, text, tp);
+}
+
+/**
+ * Text goes over actor's head while actor plays the talk reel.
+ */
+
+void FinishTalkingReel(PMACTOR pActor, int actor) {
+ if (pActor) {
+ SetMActorStanding(pActor);
+ AlterMActor(pActor, 0, AR_POPREEL);
+ } else {
+ setActorTalking(actor, false);
+ playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false);
+ }
+}
+
+void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int Loffset, Toffset; // Top left of display
+ int actor; // The speaking actor
+ PMACTOR pActor; // For moving actors
+ int myleftEvent;
+ int ticks;
+ bool bTookControl; // Set if this function takes control
+ bool bTookTags; // Set if this function disables tags
+ OBJECT *pText; // text object pointer
+ bool bSample; // Set if a sample is playing
+ bool bTalkReel; // Set while talk reel is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->Loffset = 0;
+ _ctx->Toffset = 0;
+ _ctx->ticks = 0;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ _ctx->myleftEvent = GetLeftEvents();
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ /*
+ * Find out which actor is talking
+ * and with which direction if no film supplied
+ */
+ TFTYPE direction;
+ switch (film) {
+ case TF_NONE:
+ case TF_UP:
+ case TF_DOWN:
+ case TF_LEFT:
+ case TF_RIGHT:
+ _ctx->actor = LeadId(); // If no film, actor is lead actor
+ direction = (TFTYPE)film;
+ break;
+
+ default:
+ _ctx->actor = extractActor(film);
+ assert(_ctx->actor); // talk() - no actor ID in the reel
+ direction = TF_BOGUS;
+ break;
+ }
+
+ /*
+ * Lock out the user (for lead actor, if not already locked out)
+ * May need to disable tags for other actors
+ */
+ if (_ctx->actor == LeadId())
+ _ctx->bTookControl = GetControl(CONTROL_OFF);
+ else
+ _ctx->bTookControl = false;
+ _ctx->bTookTags = DisableTagsIfEnabled();
+
+ /*
+ * Kick off the voice sample
+ */
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ /*
+ * Replace actor with the talk reel, saving the current one
+ */
+ _ctx->pActor = GetMover(_ctx->actor);
+ if (_ctx->pActor) {
+ if (direction != TF_BOGUS)
+ film = GetMactorTalkReel(_ctx->pActor, direction);
+ AlterMActor(_ctx->pActor, film, AR_PUSHREEL);
+ } else {
+ setActorTalking(_ctx->actor, true);
+ setActorTalkFilm(_ctx->actor, film);
+ playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false);
+ }
+ _ctx->bTalkReel = true;
+ CORO_SLEEP(1); // Allow the play to come in
+
+ /*
+ * Display the text.
+ */
+ _ctx->pText = NULL;
+ if (isJapanMode()) {
+ _ctx->ticks = JAP_TEXT_TIME;
+ } else if (bSubtitles || !_ctx->bSample) {
+ int aniX, aniY; // actor position
+ int xshift, yshift;
+ /*
+ * Work out where to display the text
+ */
+ PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset);
+ GetActorMidTop(_ctx->actor, &aniX, &aniY);
+ aniY -= _ctx->Toffset;
+
+ setTextPal(getActorTcol(_ctx->actor));
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // talk() string produced NULL text;
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * Set bottom of text just above the speaker's head
+ * But don't go off the top of the screen
+ */
+ yshift = aniY - MultiLowest(_ctx->pText) - 2; // Just above head
+ MultiMoveRelXY(_ctx->pText, 0, yshift); //
+ yshift = MultiHighest(_ctx->pText);
+ if (yshift < 4)
+ MultiMoveRelXY(_ctx->pText, 0, 4 - yshift); // Not off top
+
+ /*
+ * Don't go off the side of the screen
+ */
+ xshift = MultiRightmost(_ctx->pText) + 2;
+ if (xshift >= SCREEN_WIDTH) // Not off right
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ xshift = MultiLeftmost(_ctx->pText) - 1;
+ if (xshift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -xshift, 0);
+ /*
+ * Work out how long to talk.
+ * During this time, reposition the text if the screen scrolls.
+ */
+ _ctx->ticks = TextTime(tBufferAddr());
+ }
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ // Keep text in place if scrolling
+ if (_ctx->pText != NULL) {
+ int nLoff, nToff;
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) {
+ MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff);
+ _ctx->Loffset = nLoff;
+ _ctx->Toffset = nToff;
+ }
+ }
+
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talk!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Talk reel stops at end of speech
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+ _ctx->bTalkReel = false;
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ /*
+ * The talk is over now - dump the text
+ * Stop the sample
+ * Restore the actor's film or standing reel
+ */
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+ if (_ctx->bTalkReel)
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+
+ /*
+ * Restore user control and tags, as appropriate
+ * And, finally, release the talk token.
+ */
+ if (_ctx->bTookControl)
+ control(CONTROL_ON);
+ if (_ctx->bTookTags)
+ EnableTags();
+
+ CORO_END_CODE;
+}
+
+/**
+ * talkat(actor, x, y, text)
+ */
+void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, 0, escOn, myescEvent);
+}
+
+/**
+ * talkats(actor, x, y, text, sustain)
+ */
+void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ assert(sustain == 2);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode())
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, sustain, escOn, myescEvent);
+}
+
+/**
+ * Set talk font's palette entry.
+ */
+void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) {
+ if (isJapanMode())
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ setTextPal(RGB(r1, g1, b1));
+}
+
+/**
+ * Get a timer's current count.
+ */
+int timer(int timerno) {
+ return Timer(timerno);
+}
+
+/**
+ * topplay(film, x, y, actor, hold, complete)
+ */
+void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) {
+ play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true);
+}
+
+/**
+ * Open or close the 'top window'
+ */
+
+void topwindow(int bpos) {
+ assert(bpos == TW_START || bpos == TW_END);
+
+ switch (bpos) {
+ case TW_END:
+ KillInventory();
+ break;
+
+ case TW_START:
+ KillInventory();
+ PopUpConf(TOPWIN);
+ break;
+ }
+}
+
+/**
+ * unhookscene
+ */
+
+void unhookscene(void) {
+ UnHookScene();
+}
+
+/**
+ * Un-define an actor as tagged.
+ */
+
+void untagactor(int actor) {
+ UnTagActor(actor);
+}
+
+/**
+ * vibrate
+ */
+
+void vibrate(void) {
+}
+
+/**
+ * waitframe(int actor, int frameNumber)
+ */
+
+void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (getActorSteps(actor) < frameNumber) {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Return when a key pressed or button pushed.
+ */
+
+void waitkey(CORO_PARAM, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int startEvent;
+ int startX, startY;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (1) {
+ _ctx->startEvent = getUserEvents();
+ // Store cursor position
+ while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false))
+ CORO_SLEEP(1);
+
+ while (_ctx->startEvent == getUserEvents()) {
+ CORO_SLEEP(1);
+
+ // Not necessary to monitor escape as it's an event anyway
+
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false); // Store cursor position
+ if (curX != _ctx->startX || curY != _ctx->startY)
+ break;
+
+ if (IsConfWindow())
+ break;
+ }
+
+ if (!IsConfWindow())
+ return;
+
+ do {
+ CORO_SLEEP(1);
+ } while (IsConfWindow());
+
+ CORO_SLEEP(ONE_SECOND / 2); // Let it die down
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Pause for requested time.
+ */
+
+void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!frame)
+ time *= ONE_SECOND;
+
+ _ctx->time = time;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ } while (_ctx->time--);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ */
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ return;
+ }
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ GetToken(pActor->actorToken);
+ SetActorDest(pActor, x, y, igPath, film);
+ DontScrollCursor();
+
+ if (hold == 2) {
+ ;
+ } else {
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+ }
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ * Wait to see if its aborted or completed.
+ */
+void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int ticket;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+
+ CORO_SLEEP(ONE_SECOND);
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ // Briefly aquire token to kill off any other normal walk
+ GetToken(pActor->actorToken);
+ FreeToken(pActor->actorToken);
+
+ SetActorDest(pActor, x, y, false, film);
+ DontScrollCursor();
+
+ _ctx->ticket = GetActorTicket(pActor);
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (_ctx->ticket != GetActorTicket(pActor)) {
+ retVal = false;
+ return;
+ }
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+ }
+
+ int endx, endy;
+ GetMActorPosition(pActor, &endx, &endy);
+ retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Declare a moving actor.
+ */
+void walkingactor(uint32 id, SCNHANDLE *rp) {
+ PMACTOR pActor; // Moving actor structure
+
+ SetMover(id); // Establish as a moving actor
+ pActor = GetMover(id);
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ }
+ }
+}
+
+/**
+ * Walk a moving actor towards the polygon's tag, but return when the
+ * actor enters the polygon.
+ */
+
+void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int aniX, aniY; // cursor/actor position
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ do {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+
+ GetMActorPosition(pActor, &aniX, &aniY);
+ } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor));
+
+ FreeToken(pActor->actorToken);
+
+ CORO_END_CODE;
+}
+
+/**
+ * walktag(actor, reel, hold)
+ */
+
+void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+
+ // Adopt the tag-related reel
+ SCNHANDLE pfilm = getPolyFilm(hp);
+
+ switch (pfilm) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (actor == LEAD_ACTOR || actor == LeadId())
+ AlterMActor(pActor, pfilm, AR_NORMAL);
+ else
+ SetMActorStanding(pActor);
+ break;
+ }
+
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * whichinventory
+ */
+
+int whichinventory(void) {
+ return WhichInventoryOpen();
+}
+
+
+/**
+ * Subtract one less that the number of parameters from pp
+ * pp then points to the first parameter.
+ *
+ * If the library function has no return value:
+ * return -(the number of parameters) to pop them from the stack
+ *
+ * If the library function has a return value:
+ * return -(the number of parameters - 1) to pop most of them from
+ * the stack, and stick the return value in pp[0]
+ * @param operand Library function
+ * @param pp Top of parameter stack
+ */
+int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) {
+ debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent);
+ switch (operand) {
+ case ACTORATTR:
+ pp -= 3; // 4 parameters
+ actorattr(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case ACTORDIRECTION:
+ pp[0] = actordirection(pp[0]);
+ return 0;
+
+ case ACTORREF:
+ error("actorref isn't a real function!");
+
+ case ACTORSCALE:
+ pp[0] = actorscale(pp[0]);
+ return 0;
+
+ case ACTORSON:
+ actorson();
+ return 0;
+
+ case ACTORXPOS:
+ pp[0] = actorpos(ACTORXPOS, pp[0]);
+ return 0;
+
+ case ACTORYPOS:
+ pp[0] = actorpos(ACTORYPOS, pp[0]);
+ return 0;
+
+ case ADDICON:
+ addicon(pp[0]);
+ return -1;
+
+ case ADDINV1:
+ addinv(INV_1, pp[0]);
+ return -1;
+
+ case ADDINV2:
+ addinv(INV_2, pp[0]);
+ return -1;
+
+ case ADDOPENINV:
+ addinv(INV_OPEN, pp[0]);
+ return -1;
+
+ case AUXSCALE:
+ pp -= 13; // 14 parameters
+ auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2));
+ return -14;
+
+ case BACKGROUND:
+ background(pp[0]);
+ return -1;
+
+ case CAMERA:
+ camera(pp[0]);
+ return -1;
+
+ case CDLOAD:
+ pp -= 1; // 2 parameters
+ cdload(pp[0], pp[1]);
+ return -2;
+
+ case CDPLAY:
+ error("cdplay isn't a real function!");
+
+ case CLEARHOOKSCENE:
+ clearhookscene();
+ return 0;
+
+ case CLOSEINVENTORY:
+ closeinventory();
+ return 0;
+
+ case CONTROL:
+ control(pp[0]);
+ return -1;
+
+ case CONVERSATION:
+ conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -1;
+
+ case CONVICON:
+ convicon(pp[0]);
+ return -1;
+
+ case CURSORXPOS:
+ pp[0] = cursorpos(CURSORXPOS);
+ return 0;
+
+ case CURSORYPOS:
+ pp[0] = cursorpos(CURSORYPOS);
+ return 0;
+
+ case CUTSCENE:
+ error("cutscene isn't a real function!");
+
+ case DEC_CONVW:
+ pp -= 7; // 8 parameters
+ dec_convw(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_CSTRINGS:
+ pp -= 19; // 20 parameters
+ dec_cstrings((SCNHANDLE *)pp);
+ return -20;
+
+ case DEC_CURSOR:
+ dec_cursor(pp[0]);
+ return -1;
+
+ case DEC_FLAGS:
+ dec_flags(pp[0]);
+ return -1;
+
+ case DEC_INV1:
+ pp -= 7; // 8 parameters
+ dec_inv1(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INV2:
+ pp -= 7; // 8 parameters
+ dec_inv2(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INVW:
+ dec_invw(pp[0]);
+ return -1;
+
+ case DEC_LEAD:
+ pp -= 61; // 62 parameters
+ dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]);
+ return -62;
+
+ case DEC_TAGFONT:
+ dec_tagfont(pp[0]);
+ return -1;
+
+ case DEC_TALKFONT:
+ dec_talkfont(pp[0]);
+ return -1;
+
+ case DELICON:
+ delicon(pp[0]);
+ return -1;
+
+ case DELINV:
+ delinv(pp[0]);
+ return -1;
+
+ case EFFECTACTOR:
+ assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code
+
+ pp[0] = pic->actorid;
+ return 0;
+
+ case ENABLEF1:
+ enablef1();
+ return 0;
+
+ case EVENT:
+ pp[0] = pic->event;
+ return 0;
+
+ case FADEMIDI:
+ fademidi(coroParam, pp[0]);
+ return -1;
+
+ case FRAMEGRAB:
+ return -1;
+
+ case GETINVLIMIT:
+ pp[0] = getinvlimit(pp[0]);
+ return 0;
+
+ case HASRESTARTED:
+ pp[0] = hasrestarted();
+ return 0;
+
+ case HELDOBJECT:
+ pp[0] = heldobject();
+ return 0;
+
+ case HIDE:
+ hide(pp[0]);
+ return -1;
+
+ case HOOKSCENE:
+ pp -= 2; // 3 parameters
+ hookscene(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case IDLETIME:
+ pp[0] = idletime();
+ return 0;
+
+ case ININVENTORY:
+ pp[0] = ininventory(pp[0]);
+ return 0; // using return value
+
+ case INVDEPICT:
+ pp -= 1; // 2 parameters
+ invdepict(pp[0], pp[1]);
+ return -2;
+
+ case INVENTORY:
+ inventory(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case INWHICHINV:
+ pp[0] = inwhichinv(pp[0]);
+ return 0; // using return value
+
+ case KILLACTOR:
+ killactor(pp[0]);
+ return -1;
+
+ case KILLBLOCK:
+ killblock(pp[0]);
+ return -1;
+
+ case KILLEXIT:
+ killexit(pp[0]);
+ return -1;
+
+ case KILLTAG:
+ killtag(pp[0]);
+ return -1;
+
+ case LEFTOFFSET:
+ pp[0] = ltoffset(LEFTOFFSET);
+ return 0;
+
+ case MOVECURSOR:
+ pp -= 1; // 2 parameters
+ movecursor(pp[0], pp[1]);
+ return -2;
+
+ case NEWSCENE:
+ pp -= 2; // 3 parameters
+ if (*pResumeState == RES_2)
+ *pResumeState = RES_NOT;
+ else
+ newscene(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case NOBLOCKING:
+ noblocking();
+ return 0;
+
+ case NOSCROLL:
+ pp -= 3; // 4 parameters
+ noscroll(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case OBJECTHELD:
+ objectheld(pp[0]);
+ return -1;
+
+ case OFFSET:
+ pp -= 1; // 2 parameters
+ offset(pp[0], pp[1]);
+ return -2;
+
+ case PLAY:
+ pp -= 5; // 6 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false);
+ else
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false);
+ return -6;
+
+ case PLAYMIDI:
+ pp -= 2; // 3 parameters
+ playmidi(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case PLAYRTF:
+ error("playrtf only applies to cdi!");
+
+ case PLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case PREPARESCENE:
+ preparescene(pp[0]);
+ return -1;
+
+ case PRINT:
+ pp -= 5; // 6 parameters
+ /* pp[2] was intended to be attribute */
+ print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case PRINTOBJ:
+ printobj(coroParam, pp[0], pic->pinvo, pic->event);
+ return -1;
+
+ case PRINTTAG:
+ printtag(pic->hpoly, pp[0]);
+ return -1;
+
+ case QUITGAME:
+ quitgame();
+ return 0;
+
+ case RANDOM:
+ pp -= 2; // 3 parameters
+ pp[0] = dw_random(pp[0], pp[1], pp[2]);
+ return -2; // One holds return value
+
+ case RESETIDLETIME:
+ resetidletime();
+ return 0;
+
+ case RESTARTGAME:
+ restartgame();
+ return 0;
+
+ case RESTORE_CUT:
+ restore_scene(false);
+ return 0;
+
+ case RESTORE_SCENE:
+ restore_scene(true);
+ return 0;
+
+ case RUNMODE:
+ pp[0] = runmode();
+ return 0;
+
+ case SAMPLEPLAYING:
+ pp[0] = sampleplaying(pic->escOn, pic->myescEvent);
+ return 0;
+
+ case SAVE_SCENE:
+ if (*pResumeState == RES_1)
+ *pResumeState = RES_2;
+ else
+ save_scene(coroParam);
+ return 0;
+
+ case SCALINGREELS:
+ pp -= 6; // 7 parameters
+ scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SCANICON:
+ pp[0] = scanicon();
+ return 0;
+
+ case SCROLL:
+ pp -= 3; // 4 parameters
+ scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case SETACTOR:
+ setactor(pp[0]);
+ return -1;
+
+ case SETBLOCK:
+ setblock(pp[0]);
+ return -1;
+
+ case SETEXIT:
+ setexit(pp[0]);
+ return -1;
+
+ case SETINVLIMIT:
+ pp -= 1; // 2 parameters
+ setinvlimit(pp[0], pp[1]);
+ return -2;
+
+ case SETINVSIZE:
+ pp -= 6; // 7 parameters
+ setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SETLANGUAGE:
+ setlanguage((LANGUAGE)pp[0]);
+ return -1;
+
+ case SETPALETTE:
+ setpalette(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case SETTAG:
+ settag(pp[0]);
+ return -1;
+
+ case SETTIMER:
+ pp -= 3; // 4 parameters
+ settimer(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case SHOWPOS:
+#ifdef DEBUG
+ showpos();
+#endif
+ return 0;
+
+ case SHOWSTRING:
+#ifdef DEBUG
+ showstring();
+#endif
+ return 0;
+
+ case SPLAY:
+ pp -= 6; // 7 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent);
+ else
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent);
+ return -7;
+
+ case STAND:
+ pp -= 3; // 4 parameters
+ stand(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case STANDTAG:
+ standtag(pp[0], pic->hpoly);
+ return -1;
+
+ case STOP:
+ stop(pp[0]);
+ return -1;
+
+ case STOPMIDI:
+ stopmidi();
+ return 0;
+
+ case STOPSAMPLE:
+ stopsample();
+ return 0;
+
+ case SUBTITLES:
+ subtitles(pp[0]);
+ return -1;
+
+ case SWALK:
+ pp -= 5; // 6 parameters
+ swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TAGACTOR:
+ pp -= 2; // 3 parameters
+ tagactor(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case TALK:
+ pp -= 1; // 2 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent);
+ else
+ talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent);
+ return -2;
+
+ case TALKAT:
+ pp -= 3; // 4 parameters
+ talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case TALKATS:
+ pp -= 4; // 5 parameters
+ talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent);
+ return -5;
+
+ case TALKATTR:
+ pp -= 2; // 3 parameters
+ talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent);
+ return -3;
+
+ case TIMER:
+ pp[0] = timer(pp[0]);
+ return 0;
+
+ case TOPOFFSET:
+ pp[0] = ltoffset(TOPOFFSET);
+ return 0;
+
+ case TOPPLAY:
+ pp -= 5; // 6 parameters
+ topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TOPWINDOW:
+ topwindow(pp[0]);
+ return -1;
+
+ case TRYPLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case UNHOOKSCENE:
+ unhookscene();
+ return 0;
+
+ case UNTAGACTOR:
+ untagactor(pp[0]);
+ return -1;
+
+ case VIBRATE:
+ vibrate();
+ return 0;
+
+ case WAITKEY:
+ waitkey(coroParam, pic->escOn, pic->myescEvent);
+ return 0;
+
+ case WAITFRAME:
+ pp -= 1; // 2 parameters
+ waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WAITTIME:
+ pp -= 1; // 2 parameters
+ waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WALK:
+ pp -= 4; // 5 parameters
+ walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent);
+ return -5;
+
+ case WALKED: {
+ pp -= 3; // 4 parameters
+ bool tmp;
+ walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp);
+ if (!coroParam) {
+ // Only write the result to the stack if walked actually completed running.
+ pp[0] = tmp;
+ }
+ }
+ return -3;
+
+ case WALKINGACTOR:
+ pp -= 40; // 41 parameters
+ walkingactor(pp[0], (SCNHANDLE *)&pp[1]);
+ return -41;
+
+ case WALKPOLY:
+ pp -= 2; // 3 parameters
+ walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WALKTAG:
+ pp -= 2; // 3 parameters
+ walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WHICHINVENTORY:
+ pp[0] = whichinventory();
+ return 0;
+
+ default:
+ error("Unsupported library function");
+ }
+
+ error("Can't possibly get here");
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h
new file mode 100644
index 0000000000..001de70896
--- /dev/null
+++ b/engines/tinsel/tinlib.h
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TINLIB_H // prevent multiple includes
+#define TINSEL_TINLIB_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Library functions in TINLIB.C
+
+void control(int param);
+void stand(int actor, int x, int y, SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TINLIB_H
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
new file mode 100644
index 0000000000..1f56385283
--- /dev/null
+++ b/engines/tinsel/tinsel.cpp
@@ -0,0 +1,999 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/file.h"
+#include "common/savefile.h"
+#include "common/config-manager.h"
+#include "common/stream.h"
+
+#include "graphics/cursorman.h"
+
+#include "base/plugins.h"
+#include "base/version.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // MemoryInit
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/savescn.h"
+#include "tinsel/scn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In BG.CPP
+extern void SetDoFadeIn(bool tf);
+extern void DropBackground(void);
+
+// In CURSOR.CPP
+extern void CursorProcess(CORO_PARAM, const void *);
+
+// In INVENTORY.CPP
+extern void InventoryProcess(CORO_PARAM, const void *);
+
+// In SCENE.CPP
+extern void PrimeBackground();
+extern void NewScene(SCNHANDLE scene, int entry);
+extern SCNHANDLE GetSceneHandle(void);
+
+// In TIMER.CPP
+extern void FettleTimers(void);
+extern void RebootTimers(void);
+
+//----------------- FORWARD DECLARATIONS ---------------------
+void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bRestart = false;
+bool bHasRestarted = false;
+
+#ifdef DEBUG
+bool bFast; // set to make it go ludicrously fast
+#endif
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Scene {
+ SCNHANDLE scene; // Memory handle for scene
+ int entry; // Entrance number
+ int trans; // Transition - not yet used
+};
+
+static Scene NextScene = { 0, 0, 0 };
+static Scene HookScene = { 0, 0, 0 };
+static Scene DelayedScene = { 0, 0, 0 };
+
+static bool bHookSuspend = false;
+
+static PROCESS *pMouseProcess = 0;
+static PROCESS *pKeyboardProcess = 0;
+
+// Stack of pending mouse button events
+Common::List<Common::EventType> mouseButtons;
+
+// Stack of pending keypresses
+Common::List<Common::Event> keypresses;
+
+//----------------- LOCAL PROCEDURES --------------------
+
+/**
+ * Process to handle keypresses
+ */
+void KeyboardProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (true) {
+ if (keypresses.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // Get the next keyboard event off the stack
+ Common::Event evt = *keypresses.begin();
+ keypresses.erase(keypresses.begin());
+
+ // Switch for special keys
+ switch (evt.kbd.keycode) {
+ // Drag action
+ case Common::KEYCODE_LALT:
+ case Common::KEYCODE_RALT:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_RDSTART);
+ else
+ ProcessButEvent(BE_LDSTART);
+ } else {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_LDEND);
+ else
+ ProcessButEvent(BE_RDEND);
+ }
+ continue;
+
+ case Common::KEYCODE_LCTRL:
+ case Common::KEYCODE_RCTRL:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ ProcessKeyEvent(LOOK_KEY);
+ } else {
+ // Control key release
+ }
+ continue;
+
+ default:
+ break;
+ }
+
+ // At this point only key down events need processing
+ if (evt.type == Common::EVENT_KEYUP)
+ continue;
+
+ if (_vm->_keyHandler != NULL)
+ // Keyboard is hooked, so pass it on to that handler first
+ if (!_vm->_keyHandler(evt.kbd))
+ continue;
+
+ switch (evt.kbd.keycode) {
+ /*** SPACE = WALKTO ***/
+ case Common::KEYCODE_SPACE:
+ ProcessKeyEvent(WALKTO_KEY);
+ continue;
+
+ /*** RETURN = ACTION ***/
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ ProcessKeyEvent(ACTION_KEY);
+ continue;
+
+ /*** l = LOOK ***/
+ case Common::KEYCODE_l: // LOOK
+ ProcessKeyEvent(LOOK_KEY);
+ continue;
+
+ case Common::KEYCODE_ESCAPE:
+ // WORKAROUND: Check if any of the starting logo screens are active, and if so
+ // manually skip to the title screen, allowing them to be bypassed
+ {
+ int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0;
+ int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset;
+ if ((language == TXT_GERMAN) &&
+ ((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) {
+ // Skip to title screen
+ // It seems the German CD version uses scenes 25,26,27,17 for the intro,
+ // instead of 13,14,15,11; also, the title screen is 11 instead of 10
+ SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) {
+ // Skip to title screen
+ SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else {
+ // Not on an intro screen, so process the key normally
+ ProcessKeyEvent(ESC_KEY);
+ }
+ }
+ continue;
+
+#ifdef SLOW_RINCE_DOWN
+ case '>':
+ AddInterlude(1);
+ continue;
+ case '<':
+ AddInterlude(-1);
+ continue;
+#endif
+
+ case Common::KEYCODE_F1:
+ // Options dialog
+ ProcessKeyEvent(OPTION_KEY);
+ continue;
+ case Common::KEYCODE_F5:
+ // Save game
+ ProcessKeyEvent(SAVE_KEY);
+ continue;
+ case Common::KEYCODE_F7:
+ // Load game
+ ProcessKeyEvent(LOAD_KEY);
+ continue;
+ case Common::KEYCODE_q:
+ if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT))
+ ProcessKeyEvent(QUIT_KEY);
+ continue;
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ ProcessKeyEvent(PGUP_KEY);
+ continue;
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ ProcessKeyEvent(PGDN_KEY);
+ continue;
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ ProcessKeyEvent(HOME_KEY);
+ continue;
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ ProcessKeyEvent(END_KEY);
+ continue;
+ default:
+ ProcessKeyEvent(NOEVENT_KEY);
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Process to handle changes in the mouse buttons.
+ */
+void MouseProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ bool lastLWasDouble;
+ bool lastRWasDouble;
+ uint32 lastLeftClick, lastRightClick;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->lastLWasDouble = false;
+ _ctx->lastRWasDouble = false;
+ _ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime();
+
+ while (true) {
+ // FIXME: I'm still keeping the ctrl/Alt handling in the ProcessKeyEvent method.
+ // Need to make sure that this works correctly
+ //DragKeys();
+
+ if (mouseButtons.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // get next mouse button event
+ Common::EventType type = *mouseButtons.begin();
+ mouseButtons.erase(mouseButtons.begin());
+
+ switch (type) {
+ case Common::EVENT_LBUTTONDOWN:
+ // left button press
+ if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)dclickSpeed) {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left double click event
+ ProcessButEvent(BE_DLEFT);
+
+ _ctx->lastLWasDouble = true;
+ } else {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left single click event
+ ProcessButEvent(BE_SLEFT);
+
+ _ctx->lastLWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_LBUTTONUP:
+ // left button release
+
+ // update click timer
+ if (_ctx->lastLWasDouble == false)
+ _ctx->lastLeftClick = DwGetCurrentTime();
+ else
+ _ctx->lastLeftClick -= dclickSpeed;
+
+ // signal left drag end
+ ProcessButEvent(BE_LDEND);
+ break;
+
+ case Common::EVENT_RBUTTONDOWN:
+ // right button press
+
+ if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)dclickSpeed) {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right double click event
+ ProcessButEvent(BE_DRIGHT);
+
+ _ctx->lastRWasDouble = true;
+ } else {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right single click event
+ ProcessButEvent(BE_SRIGHT);
+
+ _ctx->lastRWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_RBUTTONUP:
+ // right button release
+
+ // update click timer
+ if (_ctx->lastRWasDouble == false)
+ _ctx->lastRightClick = DwGetCurrentTime();
+ else
+ _ctx->lastRightClick -= dclickSpeed;
+
+ // signal right drag end
+ ProcessButEvent(BE_RDEND);
+ break;
+
+ default:
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Run the master script.
+ * Continues between scenes, or until Interpret() returns.
+ */
+static void MasterScriptProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ _ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ CORO_END_CODE;
+}
+
+/**
+ * Store the facts pertaining to a scene change.
+ */
+
+void SetNewScene(SCNHANDLE scene, int entrance, int transition) {
+ if (HookScene.scene == 0 || bHookSuspend) {
+ // This scene comes next
+ NextScene.scene = scene;
+ NextScene.entry = entrance;
+ NextScene.trans = transition;
+ } else {
+ // This scene gets delayed
+ DelayedScene.scene = scene;
+ DelayedScene.entry = entrance;
+ DelayedScene.trans = transition;
+
+ // The hooked scene comes next
+ NextScene.scene = HookScene.scene;
+ NextScene.entry = HookScene.entry;
+ NextScene.trans = HookScene.trans;
+
+ HookScene.scene = 0;
+ }
+}
+
+void SetHookScene(SCNHANDLE scene, int entrance, int transition) {
+ assert(HookScene.scene == 0); // scene already hooked
+
+ HookScene.scene = scene;
+ HookScene.entry = entrance;
+ HookScene.trans = transition;
+}
+
+void UnHookScene(void) {
+ assert(DelayedScene.scene != 0); // no scene delayed
+
+ // The delayed scene can go now
+ NextScene.scene = DelayedScene.scene;
+ NextScene.entry = DelayedScene.entry;
+ NextScene.trans = DelayedScene.trans;
+
+ DelayedScene.scene = 0;
+}
+
+void SuspendHook(void) {
+ bHookSuspend = true;
+}
+
+void UnSuspendHook(void) {
+ bHookSuspend = false;
+}
+
+void syncSCdata(Serializer &s) {
+ s.syncAsUint32LE(HookScene.scene);
+ s.syncAsSint32LE(HookScene.entry);
+ s.syncAsSint32LE(HookScene.trans);
+
+ s.syncAsUint32LE(DelayedScene.scene);
+ s.syncAsSint32LE(DelayedScene.entry);
+ s.syncAsSint32LE(DelayedScene.trans);
+}
+
+
+//-----------------------------------------------------------------------
+
+static void RestoredProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get the stuff copied to process when it was created
+ _ctx->pic = *((INT_CONTEXT **)param);
+
+ _ctx->pic = RestoreInterpretContext(_ctx->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+void RestoreProcess(INT_CONTEXT *pic) {
+ g_scheduler->createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
+}
+
+void RestoreMasterProcess(INT_CONTEXT *pic) {
+ g_scheduler->createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
+}
+
+// FIXME: CountOut is used by ChangeScene
+static int CountOut = 1; // == 1 for immediate start of first scene
+
+/**
+ * If a scene restore is going on, just return (we don't update the
+ * screen during this time).
+ * If a scene change is required, 'order' the data for the new scene and
+ * start a fade out and a countdown.
+ * When the count expires, the screen will have faded. Ensure the scene |
+ * is loaded, clear the screen, and start the new scene.
+ */
+void ChangeScene() {
+
+ if (IsRestoringScene())
+ return;
+
+ if (NextScene.scene != 0) {
+ if (!CountOut) {
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ CountOut = 1;
+ break;
+
+ case TRANS_FADE:
+ default:
+ // Trigger pre-load and fade and start countdown
+ CountOut = COUNTOUT_COUNT;
+ FadeOutFast(NULL);
+ break;
+ }
+ } else if (--CountOut == 0) {
+ ClearScreen();
+
+ NewScene(NextScene.scene, NextScene.entry);
+ NextScene.scene = 0;
+
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ SetDoFadeIn(false);
+ break;
+
+ case TRANS_FADE:
+ default:
+ SetDoFadeIn(true);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * LoadBasicChunks
+ */
+
+void LoadBasicChunks(void) {
+ byte *cptr;
+ int numObjects;
+
+ // Allocate RAM for savescene data
+ InitialiseSs();
+
+ // CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value
+ // TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS);
+ RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511);
+
+ // CHUNK_TOTAL_GLOBALS seems to be missing in some versions.
+ // So if it is missing, set a reasonably high value for the number of globals.
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS);
+ RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512);
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS);
+ numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0;
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS);
+
+#ifdef SCUMM_BIG_ENDIAN
+ //convert to native endianness
+ INV_OBJECT *io = (INV_OBJECT *)cptr;
+ for (int i = 0; i < numObjects; i++, io++) {
+ io->id = FROM_LE_32(io->id);
+ io->hFilm = FROM_LE_32(io->hFilm);
+ io->hScript = FROM_LE_32(io->hScript);
+ io->attribute = FROM_LE_32(io->attribute);
+ }
+#endif
+
+ RegisterIcons(cptr, numObjects);
+
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY);
+ if (cptr != NULL)
+ MaxPolygons(*cptr);
+}
+
+//----------------- TinselEngine --------------------
+
+// Global pointer to engine
+TinselEngine *_vm;
+
+struct GameSettings {
+ const char *gameid;
+ const char *description;
+ byte id;
+ uint32 features;
+ const char *detectname;
+};
+
+static const GameSettings tinselSettings[] = {
+ {"tinsel", "Tinsel game", 0, 0, 0},
+
+ {NULL, NULL, 0, 0, NULL}
+};
+
+TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) :
+ Engine(syst), _gameDescription(gameDesc) {
+ _vm = this;
+
+ // Setup mixer
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+
+ const GameSettings *g;
+
+ const char *gameid = ConfMan.get("gameid").c_str();
+ for (g = tinselSettings; g->gameid; ++g)
+ if (!scumm_stricmp(g->gameid, gameid))
+ _gameId = g->id;
+
+ int cd_num = ConfMan.getInt("cdrom");
+ if (cd_num >= 0)
+ _system->openCD(cd_num);
+
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+ //bool adlib = (midiDriver == MD_ADLIB);
+
+ MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ if (native_mt32)
+ driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ _music = new MusicPlayer(driver);
+ //_music->setNativeMT32(native_mt32);
+ //_music->setAdlib(adlib);
+
+ _musicVolume = ConfMan.getInt("music_volume");
+
+ _sound = new SoundManager(this);
+
+ _mousePos.x = 0;
+ _mousePos.y = 0;
+ _keyHandler = NULL;
+ _dosPlayerDir = 0;
+ quitFlag = false;
+}
+
+TinselEngine::~TinselEngine() {
+ delete _sound;
+ delete _music;
+ delete _console;
+ FreeSs();
+ FreeTextBuffer();
+ FreeHandleTable();
+ FreeActors();
+ FreeObjectList();
+ FreeGlobals();
+ delete _scheduler;
+}
+
+int TinselEngine::init() {
+ // Initialize backend
+ _system->beginGFXTransaction();
+ initCommonGFX(false);
+ _system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+ _system->endGFXTransaction();
+
+ _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1);
+
+ g_system->getEventManager()->registerRandomSource(_random, "tinsel");
+
+ _console = new Console();
+
+ _scheduler = new Scheduler();
+
+ // init memory manager
+ MemoryInit();
+
+ // load user configuration
+ ReadConfig();
+
+#if 1
+ // FIXME: The following is taken from RestartGame().
+ // It may have to be adjusted a bit
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+#endif
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // TODO: More stuff from dos_main.c may have to be added here
+
+ // Set language - we'll be clever here and use the ScummVM language setting
+ language = TXT_ENGLISH;
+ switch (getLanguage()) {
+ case Common::FR_FRA:
+ language = TXT_FRENCH;
+ break;
+ case Common::DE_DEU:
+ language = TXT_GERMAN;
+ break;
+ case Common::IT_ITA:
+ language = TXT_ITALIAN;
+ break;
+ case Common::ES_ESP:
+ language = TXT_SPANISH;
+ break;
+ default:
+ language = TXT_ENGLISH;
+ }
+ ChangeLanguage(language);
+
+ // load in graphics info
+ SetupHandleTable();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ return 0;
+}
+
+Common::String TinselEngine::getSavegamePattern() const {
+ return _targetName + ".???";
+}
+
+Common::String TinselEngine::getSavegameFilename(int16 saveNum) const {
+ char filename[256];
+ snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum);
+ return filename;
+}
+
+#define GAME_FRAME_DELAY (1000 / ONE_SECOND)
+
+int TinselEngine::go() {
+ uint32 timerVal = 0;
+
+ // Continuous game processes
+ CreateConstProcesses();
+
+ // allow game to run in the background
+ //RestartBackgroundProcess(); // FIXME: is this still needed?
+
+ //dumpMusic(); // dumps all of the game's music in external XMIDI files
+
+#if 0
+ // Load game from specified slot, if any
+ // FIXME: Not working correctly right now
+ if (ConfMan.hasKey("save_slot")) {
+ getList();
+ RestoreGame(ConfMan.getInt("save_slot"));
+ }
+#endif
+
+ // Foreground loop
+
+ while (!quitFlag) {
+ assert(_console);
+ if (_console->isAttached())
+ _console->onFrame();
+
+ // Check for time to do next game cycle
+ if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) {
+ timerVal = g_system->getMillis();
+ AudioCD.updateCD();
+ NextGameCycle();
+ }
+
+ if (bRestart) {
+ RestartGame();
+ bRestart = false;
+ bHasRestarted = true; // Set restarted flag
+ }
+
+ // Save/Restore scene file transfers
+ ProcessSRQueue();
+
+#ifdef DEBUG
+ if (bFast)
+ continue; // run flat-out
+#endif
+ // Loop processing events while there are any pending
+ while (pollEvent());
+
+ g_system->delayMillis(10);
+ }
+
+ // Write configuration
+ WriteConfig();
+
+ return 0;
+}
+
+
+void TinselEngine::NextGameCycle(void) {
+ //
+ ChangeScene();
+
+ // Allow a user event for this schedule
+ ResetEcount();
+
+ // schedule process
+ _scheduler->schedule();
+
+ // redraw background
+ DrawBackgnd();
+
+ // Why waste resources on yet another process?
+ FettleTimers();
+}
+
+
+bool TinselEngine::pollEvent() {
+ Common::Event event;
+
+ if (!g_system->getEventManager()->pollEvent(event))
+ return false;
+
+ // Handle the various kind of events
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ quitFlag = true;
+ break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ // Add button to queue for the mouse process
+ mouseButtons.push_back(event.type);
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ case Common::EVENT_KEYUP:
+ ProcessKeyEvent(event);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/**
+ * Start the processes that continue between scenes.
+ */
+
+void TinselEngine::CreateConstProcesses(void) {
+ // Process to run the master script
+ _scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
+
+ // Processes to run the cursor and inventory,
+ _scheduler->createProcess(PID_CURSOR, CursorProcess, NULL, 0);
+ _scheduler->createProcess(PID_INVENTORY, InventoryProcess, NULL, 0);
+}
+
+/**
+ * Restart the game
+ */
+
+void TinselEngine::RestartGame(void) {
+ HoldItem(INV_NOICON); // Holding nothing
+
+ DropBackground(); // No background
+
+ // Ditches existing infrastructure background
+ PrimeBackground();
+
+ // Next scene change won't need to fade out
+ // -> reset the count used by ChangeScene
+ CountOut = 1;
+
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+
+ // remove keyboard, mouse and joystick drivers
+ ChopDrivers();
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ // Continuous game processes
+ CreateConstProcesses();
+}
+
+/**
+ * Init palette and object managers, scheduler, keyboard and mouse.
+ */
+
+void TinselEngine::RestartDrivers(void) {
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // init the process scheduler
+ _scheduler->reset();
+
+ // init the event handlers
+ pMouseProcess = _scheduler->createProcess(PID_MOUSE, MouseProcess, NULL, 0);
+ pKeyboardProcess = _scheduler->createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);
+
+ // open MIDI files
+ OpenMidiFiles();
+
+ // open sample files (only if mixer is ready)
+ if (_mixer->isReady()) {
+ _sound->openSampleFiles();
+ }
+
+ // Set midi volume
+ SetMidiVolume(volMidi);
+}
+
+/**
+ * Remove keyboard, mouse and joystick drivers.
+ */
+
+void TinselEngine::ChopDrivers(void) {
+ // remove sound driver
+ StopMidi();
+ _sound->stopAllSamples();
+ DeleteMidiBuffer();
+
+ // remove event drivers
+ _scheduler->killProcess(pMouseProcess);
+ _scheduler->killProcess(pKeyboardProcess);
+}
+
+/**
+ * Process a keyboard event
+ */
+
+void TinselEngine::ProcessKeyEvent(const Common::Event &event) {
+
+ // Handle any special keys immediately
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if ((event.kbd.flags == Common::KBD_CTRL) && (event.type == Common::EVENT_KEYDOWN)) {
+ // Activate the debugger
+ assert(_console);
+ _console->attach();
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Check for movement keys
+ int idx = 0;
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ idx = MSK_UP;
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ idx = MSK_DOWN;
+ break;
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ idx = MSK_LEFT;
+ break;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ idx = MSK_RIGHT;
+ break;
+ default:
+ break;
+ }
+ if (idx != 0) {
+ if (event.type == Common::EVENT_KEYDOWN)
+ _dosPlayerDir |= idx;
+ else
+ _dosPlayerDir &= ~idx;
+ return;
+ }
+
+ // All other keypresses add to the queue for processing in KeyboardProcess
+ keypresses.push_back(event);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
new file mode 100644
index 0000000000..9ffadfe8c1
--- /dev/null
+++ b/engines/tinsel/tinsel.h
@@ -0,0 +1,143 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_H
+#define TINSEL_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/util.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+
+#include "engines/engine.h"
+#include "tinsel/debugger.h"
+#include "tinsel/graphics.h"
+#include "tinsel/sound.h"
+
+namespace Tinsel {
+
+class MusicPlayer;
+class Scheduler;
+class SoundManager;
+
+enum TinselGameID {
+ GID_DW1 = 0,
+ GID_DW2 = 1
+};
+
+enum TinselGameFeatures {
+ GF_DEMO = 1 << 0,
+ GF_CD = 1 << 1,
+ GF_FLOPPY = 1 << 2,
+ GF_SCNFILES = 1 << 3
+};
+
+enum TinselEngineVersion {
+ TINSEL_V1 = 1 << 0,
+ TINSEL_V2 = 1 << 1
+};
+
+struct TinselGameDescription;
+
+enum TinselKeyDirection {
+ MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8,
+ MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN
+};
+
+typedef bool (*KEYFPTR)(const Common::KeyState &);
+
+class TinselEngine : public ::Engine {
+ int _gameId;
+ Common::KeyState _keyPressed;
+ Common::RandomSource _random;
+ Graphics::Surface _screenSurface;
+ Common::Point _mousePos;
+ uint8 _dosPlayerDir;
+ Console *_console;
+ Scheduler *_scheduler;
+
+protected:
+
+ int init();
+ int go();
+
+public:
+ TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc);
+ virtual ~TinselEngine();
+ int getGameId() {
+ return _gameId;
+ }
+
+ const TinselGameDescription *_gameDescription;
+ uint32 getGameID() const;
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ uint16 getVersion() const;
+ Common::Platform getPlatform() const;
+ bool quitFlag;
+
+ SoundManager *_sound;
+ MusicPlayer *_music;
+
+ KEYFPTR _keyHandler;
+private:
+ //MusicPlayer *_music;
+ int _musicVolume;
+
+ void NextGameCycle(void);
+ void CreateConstProcesses(void);
+ void RestartGame(void);
+ void RestartDrivers(void);
+ void ChopDrivers(void);
+ void ProcessKeyEvent(const Common::Event &event);
+ bool pollEvent();
+
+public:
+ const Common::String getTargetName() const { return _targetName; }
+ Common::String getSavegamePattern() const;
+ Common::String getSavegameFilename(int16 saveNum) const;
+ Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
+ Graphics::Surface &screen() { return _screenSurface; }
+
+ Common::Point getMousePosition() const { return _mousePos; }
+ void setMousePosition(const Common::Point &pt) {
+ g_system->warpMouse(pt.x, pt.y);
+ _mousePos = pt;
+ }
+ void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; }
+ int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); }
+ uint8 getKeyDirection() const { return _dosPlayerDir; }
+};
+
+// Global reference to the TinselEngine object
+extern TinselEngine *_vm;
+
+} // End of namespace Tinsel
+
+#endif /* TINSEL_H */
diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp
new file mode 100644
index 0000000000..0bdac0d6eb
--- /dev/null
+++ b/engines/tinsel/token.cpp
@@ -0,0 +1,129 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#include "common/util.h"
+
+#include "tinsel/sched.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Token {
+ PROCESS *proc;
+};
+
+static Token tokens[NUMTOKENS];
+
+
+/**
+ * Release all tokens held by this process, and kill the process.
+ */
+static void TerminateProcess(PROCESS *tProc) {
+
+ // Release tokens held by the process
+ for (int i = 0; i < NUMTOKENS; i++) {
+ if (tokens[i].proc == tProc) {
+ tokens[i].proc = NULL;
+ }
+ }
+
+ // Kill the process
+ g_scheduler->killProcess(tProc);
+}
+
+/**
+ * Gain control of the CONTROL token if it is free.
+ */
+void GetControlToken() {
+ const int which = TOKEN_CONTROL;
+
+ if (tokens[which].proc == NULL) {
+ tokens[which].proc = g_scheduler->getCurrentProcess();
+ }
+}
+
+/**
+ * Release control of the CONTROL token.
+ */
+void FreeControlToken() {
+ // Allow anyone to free TOKEN_CONTROL
+ tokens[TOKEN_CONTROL].proc = NULL;
+}
+
+
+/**
+ * Gain control of a token. If the requested token is out of range, or
+ * is already held by the calling process, then the calling process
+ * will be killed off.
+ *
+ * Otherwise, the calling process will gain the token. If the token was
+ * held by another process, then the previous holder is killed off.
+ */
+void GetToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ if (tokens[which].proc != NULL) {
+ assert(tokens[which].proc != g_scheduler->getCurrentProcess());
+ TerminateProcess(tokens[which].proc);
+ }
+
+ tokens[which].proc = g_scheduler->getCurrentProcess();
+}
+
+/**
+ * Release control of a token. If the requested token is not owned by
+ * the calling process, then the calling process will be killed off.
+ */
+void FreeToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ assert(tokens[which].proc == g_scheduler->getCurrentProcess()); // we'd have been killed if some other proc had taken this token
+
+ tokens[which].proc = NULL;
+}
+
+/**
+ * If it's a valid token and it's free, returns true.
+ */
+bool TestToken(int which) {
+ if (which < 0 || which >= NUMTOKENS)
+ return false;
+
+ return (tokens[which].proc == NULL);
+}
+
+/**
+ * Call at the start of each scene.
+ */
+void FreeAllTokens(void) {
+ for (int i = 0; i < NUMTOKENS; i++) {
+ tokens[i].proc = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h
new file mode 100644
index 0000000000..4ab4775bfb
--- /dev/null
+++ b/engines/tinsel/token.h
@@ -0,0 +1,57 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_TOKEN_H
+#define TINSEL_TOKEN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Fixed tokens
+
+enum {
+ TOKEN_CONTROL = 0,
+ TOKEN_LEAD, // = TOKEN_CONTROL + 1
+ TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS,
+
+ NUMTOKENS // = TOKEN_LEFT_BUT + 1
+};
+
+// Token functions
+
+void GetControlToken();
+void FreeControlToken();
+
+void GetToken(int which);
+void FreeToken(int which);
+
+void FreeAllTokens(void);
+bool TestToken(int which);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TOKEN_H
diff --git a/engines/touche/midi.cpp b/engines/touche/midi.cpp
index 14cb85912a..d77dbf5bfa 100644
--- a/engines/touche/midi.cpp
+++ b/engines/touche/midi.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/stream.h"
#include "sound/midiparser.h"
@@ -31,9 +32,8 @@
namespace Touche {
-MidiPlayer::MidiPlayer(MidiDriver *driver, bool nativeMT32)
- : _driver(driver), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0), _nativeMT32(nativeMT32) {
- assert(_driver);
+MidiPlayer::MidiPlayer()
+ : _driver(0), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0) {
memset(_channelsTable, 0, sizeof(_channelsTable));
memset(_channelsVolume, 0, sizeof(_channelsVolume));
open();
@@ -92,6 +92,9 @@ void MidiPlayer::setVolume(int volume) {
}
int MidiPlayer::open() {
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ _nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+ _driver = MidiDriver::createMidi(midiDriver);
int ret = _driver->open();
if (ret == 0) {
_parser = MidiParser::createParser_SMF();
@@ -107,6 +110,7 @@ void MidiPlayer::close() {
_mutex.lock();
_driver->setTimerCallback(NULL, NULL);
_driver->close();
+ delete _driver;
_driver = 0;
_parser->setMidiDriver(NULL);
delete _parser;
diff --git a/engines/touche/midi.h b/engines/touche/midi.h
index 3b128593db..a518a4bb29 100644
--- a/engines/touche/midi.h
+++ b/engines/touche/midi.h
@@ -46,7 +46,7 @@ public:
NUM_CHANNELS = 16
};
- MidiPlayer(MidiDriver *driver, bool nativeMT32);
+ MidiPlayer();
~MidiPlayer();
void play(Common::ReadStream &stream, int size, bool loop = false);
diff --git a/engines/touche/saveload.cpp b/engines/touche/saveload.cpp
index eb647a1b42..4fcf6e114d 100644
--- a/engines/touche/saveload.cpp
+++ b/engines/touche/saveload.cpp
@@ -200,9 +200,6 @@ static void saveOrLoad(S &s, ProgramPointData &data) {
saveOrLoad(s, data.order);
}
-template <class S, class A>
-static void saveOrLoadCommonArray(S &s, A &array);
-
template <class A>
static void saveOrLoadCommonArray(Common::WriteStream &stream, A &array) {
uint count = array.size();
diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp
index 6520fb5e4a..ac8e8a786a 100644
--- a/engines/touche/touche.cpp
+++ b/engines/touche/touche.cpp
@@ -91,10 +91,7 @@ int ToucheEngine::init() {
setupOpcodes();
- int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
- bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
- _midiPlayer = new MidiPlayer(driver, native_mt32);
+ _midiPlayer = new MidiPlayer;
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));