diff options
315 files changed, 39623 insertions, 3834 deletions
@@ -6,6 +6,11 @@ ScummVM Team Max Horn Eugene Sandulenko + Retired Project Leaders + ----------------------- + Vincent Hamm - ScummVM co-founder, Original Cruise/CinE author + Ludvig Strigeus - Original ScummVM and SimonVM author + Engine Teams ------------ SCUMM: @@ -94,10 +99,19 @@ ScummVM Team SAGA: Torbjorn Andersson + Sven Hesse Filippos Karapetis Andrew Kurushin Eugene Sandulenko + Tinsel;: + Torbjorn Andersson + Paul Gilbert + Sven Hesse + Max Horn + Filippos Karapetis + Joost Peters + Touche: Gregory Montoir @@ -176,12 +190,10 @@ ScummVM Team Ralph Brorsen - Help with GUI implementation Jamieson Christian - iMUSE, MIDI, all things musical Ruediger Hanke - Port: MorphOS - Vincent Hamm - ScummVM co-founder, Original Cruise/CinE author Felix Jakschitsch - Zak256 reverse engineering Mutwin Kraus - Original MacOS porter Peter Moraliyski - Port: GP32 Jeremy Newman - Former webmaster - Ludvig Strigeus - Original ScummVM and SimonVM author Lionel Ulmer - Port: X11 Won Star - Former GP32 porter @@ -1,6 +1,10 @@ For a more comprehensive changelog for the latest experimental SVN code, see: http://scummvm.sourceforge.net/daily/ChangeLog +0.13.0 (????-??-??) + New Games: + - Added support for Discworld. + 0.12.0 (????-??-??) New Games: - Added support for The Legend of Kyrandia: Book Two: Hand of Fate @@ -19,11 +23,19 @@ For a more comprehensive changelog for the latest experimental SVN code, see: - Plugged numerous memory leaks in all engines (part of GSoC'08 task) AGOS: + - Fixed crashes during certain music in Amiga versions of Elvira 1 and + Simon the Sorcerer 1. - Fixed palette issues in Amiga versions of Simon the Sorcerer 1. + Queen: + - Speech is played at the correct sample rate. (It used to be pitched a bit + too low.) + SCUMM: - Rewrote parts of Digital iMUSE, fixing some bugs. - Rewrote the internal timer code, fixing some speed issues in e.g. COMI. + - Improved support for sound effects in Amiga version of Zak McKracken. + - Added support for mixed Adlib/MIDI mode in Monkey Island 1 (Floppy). 0.11.1 (2008-02-29) SCUMM: @@ -675,9 +687,10 @@ For a more comprehensive changelog for the latest experimental SVN code, see: OS X. - Loading COMI savegames for disk 2 doesn't anymore require disk 1 first. - Rewritten iMUSE engine, and many Music fixes (exp. Monkey Island 2). -- Support for music in Humongous games and simon2dos/simon2talkie (XMIDI +- Support for music in DOS versions of Humongous Entertainment games and + Simon the Sorcerer 2 (XMIDI format). +- Support for music in floppy demo of Simon the Sorcerer 1 (Proprietary format). -- Support for music in simon1demo (Proprietary format). - Complete music support for Simon the Sorcerer 2. - Improved music and sound support in Zak256. - Added Aspect Ratio option. diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h index 8125ad7d95..97de40a2fc 100644 --- a/backends/fs/abstract-fs.h +++ b/backends/fs/abstract-fs.h @@ -64,7 +64,7 @@ protected: * * @param name String containing the name of the child to create a new node. */ - virtual AbstractFilesystemNode *getChild(const String &name) const = 0; + virtual AbstractFilesystemNode *getChild(const Common::String &name) const = 0; /** * The parent node of this directory. @@ -100,7 +100,7 @@ public: * * @note By default, this method returns the value of getName(). */ - virtual String getDisplayName() const { return getName(); } + virtual Common::String getDisplayName() const { return getName(); } /** * Returns the last component of the path pointed by this FilesystemNode. @@ -111,12 +111,12 @@ public: * * @note This method is very architecture dependent, please check the concrete implementation for more information. */ - virtual String getName() const = 0; + virtual Common::String getName() const = 0; /** * Returns the 'path' of the current node, usable in fopen(). */ - virtual String getPath() const = 0; + virtual Common::String getPath() const = 0; /** * Indicates whether this path refers to a directory or not. diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp index 5cde32c851..10782a9057 100644 --- a/backends/fs/posix/posix-fs.cpp +++ b/backends/fs/posix/posix-fs.cpp @@ -42,8 +42,8 @@ */ class POSIXFilesystemNode : public AbstractFilesystemNode { protected: - String _displayName; - String _path; + Common::String _displayName; + Common::String _path; bool _isDirectory; bool _isValid; @@ -59,17 +59,17 @@ public: * @param path String with the path the new node should point to. * @param verify true if the isValid and isDirectory flags should be verified during the construction. */ - POSIXFilesystemNode(const String &path, bool verify); + POSIXFilesystemNode(const Common::String &path, bool verify); virtual bool exists() const { return access(_path.c_str(), F_OK) == 0; } - virtual String getDisplayName() const { return _displayName; } - virtual String getName() const { return _displayName; } - virtual String getPath() const { return _path; } + virtual Common::String getDisplayName() const { return _displayName; } + virtual Common::String getName() const { return _displayName; } + virtual Common::String getPath() const { return _path; } virtual bool isDirectory() const { return _isDirectory; } virtual bool isReadable() const { return access(_path.c_str(), R_OK) == 0; } virtual bool isWritable() const { return access(_path.c_str(), W_OK) == 0; } - virtual AbstractFilesystemNode *getChild(const String &n) const; + virtual AbstractFilesystemNode *getChild(const Common::String &n) const; virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; virtual AbstractFilesystemNode *getParent() const; @@ -119,7 +119,7 @@ POSIXFilesystemNode::POSIXFilesystemNode() { _isDirectory = true; } -POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) { +POSIXFilesystemNode::POSIXFilesystemNode(const Common::String &p, bool verify) { assert(p.size() > 0); // Expand "~/" to the value of the HOME env variable @@ -142,12 +142,12 @@ POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) { } } -AbstractFilesystemNode *POSIXFilesystemNode::getChild(const String &n) const { +AbstractFilesystemNode *POSIXFilesystemNode::getChild(const Common::String &n) const { // FIXME: Pretty lame implementation! We do no error checking to speak // of, do not check if this is a special node, etc. assert(_isDirectory); - String newPath(_path); + Common::String newPath(_path); if (_path.lastChar() != '/') newPath += '/'; newPath += n; @@ -175,7 +175,7 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo continue; } - String newPath(_path); + Common::String newPath(_path); if (newPath.lastChar() != '/') newPath += '/'; newPath += dp->d_name; @@ -236,7 +236,7 @@ AbstractFilesystemNode *POSIXFilesystemNode::getParent() const { const char *start = _path.c_str(); const char *end = lastPathComponent(_path); - return new POSIXFilesystemNode(String(start, end - start), true); + return new POSIXFilesystemNode(Common::String(start, end - start), true); } #endif //#if defined(UNIX) diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp index cbb93e8cd6..ac2f521e21 100644 --- a/backends/fs/windows/windows-fs.cpp +++ b/backends/fs/windows/windows-fs.cpp @@ -245,7 +245,7 @@ WindowsFilesystemNode::WindowsFilesystemNode(const String &p, const bool current _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0); _isValid = true; // Add a trailing slash, if necessary. - if (_path.lastChar() != '\\') { + if (_isDirectory && _path.lastChar() != '\\') { _path += '\\'; } } diff --git a/backends/platform/PalmOS/Src/be_base.cpp b/backends/platform/PalmOS/Src/be_base.cpp index afb3f15bae..32e68bde9f 100644 --- a/backends/platform/PalmOS/Src/be_base.cpp +++ b/backends/platform/PalmOS/Src/be_base.cpp @@ -30,6 +30,9 @@ #include "backends/timer/default/default-timer.h" #include "sound/mixer.h" +#define DEFAULT_SAVE_PATH "/PALM/Programs/ScummVM/Saved" + + OSystem_PalmBase::OSystem_PalmBase() { _overlayVisible = false; @@ -100,7 +103,7 @@ void OSystem_PalmBase::initBackend() { // Create the savefile manager, if none exists yet (we check for this to // allow subclasses to provide their own). if (_saveMgr == 0) { - _saveMgr = new DefaultSaveFileManager(); + _saveMgr = new DefaultSaveFileManager(DEFAULT_SAVE_PATH); } // Create and hook up the mixer, if none exists yet (we check for this to diff --git a/backends/platform/gp2x/gp2x.cpp b/backends/platform/gp2x/gp2x.cpp index c138f6c54d..e5f062ed35 100644 --- a/backends/platform/gp2x/gp2x.cpp +++ b/backends/platform/gp2x/gp2x.cpp @@ -219,7 +219,7 @@ void OSystem_GP2X::initBackend() { // Create the savefile manager, if none exists yet (we check for this to // allow subclasses to provide their own). if (_savefile == 0) { - _savefile = new DefaultSaveFileManager(); + _savefile = new DefaultSaveFileManager(savePath); } // Create and hook up the mixer, if none exists yet (we check for this to diff --git a/backends/platform/iphone/blit_arm.s b/backends/platform/iphone/blit_arm.s index ae31fdcce4..417f3741cf 100644 --- a/backends/platform/iphone/blit_arm.s +++ b/backends/platform/iphone/blit_arm.s @@ -36,47 +36,47 @@ _blitLandscapeScreenRect16bpp: @ r3 = h @ <> = _screenWidth @ <> = _screenHeight - MOV r12,r13 - STMFD r13!,{r4-r11,r14} - LDMFD r12,{r12,r14} @ r12 = _screenWidth + mov r12,r13 + stmfd r13!,{r4-r11,r14} + ldmfd r12,{r12,r14} @ r12 = _screenWidth @ r14 = _screenHeight - ADD r14,r14,r3 @ r14 = _screenHeight + h - MVN r11,#0 - MLA r11,r3,r12,r11 @ r11= _screenWidth*h-1 - ADD r12,r12,r12 + add r14,r14,r3 @ r14 = _screenHeight + h + mvn r11,#0 + mla r11,r3,r12,r11 @ r11= _screenWidth*h-1 + add r12,r12,r12 xloop: - SUBS r4,r3,#5 @ r4 = y = h - BLE thin + subs r4,r3,#5 @ r4 = y = h + ble thin yloop: - LDRH r5, [r1],r12 @ r5 = *src src += _screenWidth - LDRH r6, [r1],r12 @ r6 = *src src += _screenWidth - LDRH r7, [r1],r12 @ r7 = *src src += _screenWidth - LDRH r8, [r1],r12 @ r8 = *src src += _screenWidth - LDRH r9, [r1],r12 @ r9 = *src src += _screenWidth - LDRH r10,[r1],r12 @ r10= *src src += _screenWidth - SUBS r4,r4,#6 - STRH r5, [r0],#2 @ *dst++ = r5 - STRH r6, [r0],#2 @ *dst++ = r6 - STRH r7, [r0],#2 @ *dst++ = r7 - STRH r8, [r0],#2 @ *dst++ = r8 - STRH r9, [r0],#2 @ *dst++ = r9 - STRH r10,[r0],#2 @ *dst++ = r10 - BGT yloop + ldrh r5, [r1],r12 @ r5 = *src src += _screenWidth + ldrh r6, [r1],r12 @ r6 = *src src += _screenWidth + ldrh r7, [r1],r12 @ r7 = *src src += _screenWidth + ldrh r8, [r1],r12 @ r8 = *src src += _screenWidth + ldrh r9, [r1],r12 @ r9 = *src src += _screenWidth + ldrh r10,[r1],r12 @ r10= *src src += _screenWidth + subs r4,r4,#6 + strh r5, [r0],#2 @ *dst++ = r5 + strh r6, [r0],#2 @ *dst++ = r6 + strh r7, [r0],#2 @ *dst++ = r7 + strh r8, [r0],#2 @ *dst++ = r8 + strh r9, [r0],#2 @ *dst++ = r9 + strh r10,[r0],#2 @ *dst++ = r10 + bgt yloop thin: - ADDS r4,r4,#5 - BEQ lineend + adds r4,r4,#5 + beq lineend thin_loop: - LDRH r5,[r1],r12 @ r5 = *src src += _screenWidth - SUBS r4,r4,#1 - STRH r5,[r0],#2 @ *dst++ = r5 - BGT thin_loop + ldrh r5,[r1],r12 @ r5 = *src src += _screenWidth + subs r4,r4,#1 + strh r5,[r0],#2 @ *dst++ = r5 + bgt thin_loop lineend: - SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h - SUB r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h - SUBS r2,r2,#1 - BGT xloop + sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h + sub r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h + subs r2,r2,#1 + bgt xloop - LDMFD r13!,{r4-r11,PC} + ldmfd r13!,{r4-r11,PC} _blitLandscapeScreenRect8bpp: @ r0 = dst @@ -86,55 +86,55 @@ _blitLandscapeScreenRect8bpp: @ <> = _palette @ <> = _screenWidth @ <> = _screenHeight - MOV r12,r13 - STMFD r13!,{r4-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11 = _palette + mov r12,r13 + stmfd r13!,{r4-r11,r14} + ldmfd r12,{r11,r12,r14} @ r11 = _palette @ r12 = _screenWidth @ r14 = _screenHeight - ADD r14,r14,r3 @ r14 = _screenHeight + h - MVN r6,#0 - MLA r6,r3,r12,r6 @ r6 = _screenWidth*h-1 + add r14,r14,r3 @ r14 = _screenHeight + h + mvn r6,#0 + mla r6,r3,r12,r6 @ r6 = _screenWidth*h-1 xloop8: - MOV r4,r3 @ r4 = y = h - SUBS r4,r3,#4 @ r4 = y = h - BLE thin8 + mov r4,r3 @ r4 = y = h + subs r4,r3,#4 @ r4 = y = h + ble thin8 yloop8: - LDRB r5, [r1],r12 @ r5 = *src src += _screenWidth - LDRB r7, [r1],r12 @ r7 = *src src += _screenWidth - LDRB r8, [r1],r12 @ r8 = *src src += _screenWidth - LDRB r9, [r1],r12 @ r9 = *src src += _screenWidth - LDRB r10,[r1],r12 @ r10= *src src += _screenWidth - ADD r5, r5, r5 - ADD r7, r7, r7 - ADD r8, r8, r8 - ADD r9, r9, r9 - ADD r10,r10,r10 - LDRH r5, [r11,r5] - LDRH r7, [r11,r7] - LDRH r8, [r11,r8] - LDRH r9, [r11,r9] - LDRH r10,[r11,r10] - SUBS r4,r4,#5 - STRH r5, [r0],#2 @ *dst++ = r5 - STRH r7, [r0],#2 @ *dst++ = r7 - STRH r8, [r0],#2 @ *dst++ = r8 - STRH r9, [r0],#2 @ *dst++ = r9 - STRH r10,[r0],#2 @ *dst++ = r10 - BGT yloop8 + ldrb r5, [r1],r12 @ r5 = *src src += _screenWidth + ldrb r7, [r1],r12 @ r7 = *src src += _screenWidth + ldrb r8, [r1],r12 @ r8 = *src src += _screenWidth + ldrb r9, [r1],r12 @ r9 = *src src += _screenWidth + ldrb r10,[r1],r12 @ r10= *src src += _screenWidth + add r5, r5, r5 + add r7, r7, r7 + add r8, r8, r8 + add r9, r9, r9 + add r10,r10,r10 + ldrh r5, [r11,r5] + ldrh r7, [r11,r7] + ldrh r8, [r11,r8] + ldrh r9, [r11,r9] + ldrh r10,[r11,r10] + subs r4,r4,#5 + strh r5, [r0],#2 @ *dst++ = r5 + strh r7, [r0],#2 @ *dst++ = r7 + strh r8, [r0],#2 @ *dst++ = r8 + strh r9, [r0],#2 @ *dst++ = r9 + strh r10,[r0],#2 @ *dst++ = r10 + bgt yloop8 thin8: - ADDS r4,r4,#4 - BEQ lineend8 + adds r4,r4,#4 + beq lineend8 thin_loop8: - LDRB r5,[r1],r12 @ r5 = *src src += _screenWidth - ADD r5,r5,r5 - LDRH r5,[r11,r5] - SUBS r4,r4,#1 - STRH r5,[r0],#2 @ *dst++ = r5 - BGT thin_loop8 + ldrb r5,[r1],r12 @ r5 = *src src += _screenWidth + add r5,r5,r5 + ldrh r5,[r11,r5] + subs r4,r4,#1 + strh r5,[r0],#2 @ *dst++ = r5 + bgt thin_loop8 lineend8: - SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h - SUB r1,r1,r6 @ src += 1-_screenWidth*h - SUBS r2,r2,#1 - BGT xloop8 + sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h + sub r1,r1,r6 @ src += 1-_screenWidth*h + subs r2,r2,#1 + bgt xloop8 - LDMFD r13!,{r4-r11,PC} + ldmfd r13!,{r4-r11,PC} diff --git a/backends/platform/iphone/iphone_keyboard.h b/backends/platform/iphone/iphone_keyboard.h index 17a3836efd..6d381d561d 100644 --- a/backends/platform/iphone/iphone_keyboard.h +++ b/backends/platform/iphone/iphone_keyboard.h @@ -26,11 +26,7 @@ #import <UIKit/UIKit.h> #import <UIKit/UITextView.h> -@protocol KeyboardInputProtocol -- (void)handleKeyPress:(unichar)c; -@end - -@interface SoftKeyboard : UIKeyboard<KeyboardInputProtocol> { +@interface SoftKeyboard : UIView { id inputDelegate; UITextView* inputView; } diff --git a/backends/platform/iphone/iphone_keyboard.m b/backends/platform/iphone/iphone_keyboard.m index dc2d417746..bd4948e30a 100644 --- a/backends/platform/iphone/iphone_keyboard.m +++ b/backends/platform/iphone/iphone_keyboard.m @@ -25,19 +25,6 @@ #import "iphone_keyboard.h" -// Override settings of the default keyboard implementation -@implementation UIKeyboardImpl (DisableFeatures) - -- (BOOL)autoCapitalizationPreference { - return false; -} - -- (BOOL)autoCorrectionPreference { - return false; -} - -@end - @implementation TextInputHandler - (id)initWithKeyboard:(SoftKeyboard*)keyboard; { @@ -67,7 +54,8 @@ @implementation SoftKeyboard - (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; + //self = [super initWithFrame:frame]; + self = [super initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 0.0f)]; inputDelegate = nil; inputView = [[TextInputHandler alloc] initWithKeyboard:self]; return self; diff --git a/backends/platform/iphone/iphone_main.m b/backends/platform/iphone/iphone_main.m index f7f5667bb5..b01e9f3f34 100644 --- a/backends/platform/iphone/iphone_main.m +++ b/backends/platform/iphone/iphone_main.m @@ -46,9 +46,14 @@ int main(int argc, char** argv) { gArgc = argc; gArgv = argv; - [[NSAutoreleasePool alloc] init]; - - return UIApplicationMain(argc, argv, [iPhoneMain class]); + NSAutoreleasePool *autoreleasePool = [ + [ NSAutoreleasePool alloc ] init + ]; + + UIApplicationUseLegacyEvents(1); + int returnCode = UIApplicationMain(argc, argv, [iPhoneMain class]); + [ autoreleasePool release ]; + return returnCode; } @implementation iPhoneMain @@ -74,7 +79,10 @@ int main(int argc, char** argv) { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // hide the status bar [UIHardware _setStatusBarHeight:0.0f]; - [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + //[self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + + //[self setStatusBarStyle:UIStatusBarStyleBlackTranslucent animated:NO]; + [self setStatusBarHidden:YES animated:YES]; _window = [[UIWindow alloc] initWithContentRect: [UIHardware fullScreenApplicationContentRect]]; [_window retain]; @@ -96,7 +104,7 @@ int main(int argc, char** argv) { - (void)applicationResume:(GSEventRef)event { [self removeApplicationBadge]; [UIHardware _setStatusBarHeight:0.0f]; - [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + [self setStatusBarHidden:YES animated:YES]; [_view applicationResume]; } diff --git a/backends/platform/iphone/iphone_video.h b/backends/platform/iphone/iphone_video.h index 615b2e5345..6e4b446926 100644 --- a/backends/platform/iphone/iphone_video.h +++ b/backends/platform/iphone/iphone_video.h @@ -27,12 +27,11 @@ #define _IPHONE_VIDEO__H #import <UIKit/UIKit.h> -#import <UIKit/UIView-Geometry.h> #import <GraphicsServices/GraphicsServices.h> #import <Foundation/Foundation.h> #import <CoreSurface/CoreSurface.h> -#import <LayerKit/LKLayer.h> +#import <QuartzCore/QuartzCore.h> #import "iphone_keyboard.h" @interface iPhoneView : UIView @@ -41,7 +40,7 @@ NSMutableArray* _events; NSLock* _lock; SoftKeyboard* _keyboardView; - LKLayer* _screenLayer; + CALayer* _screenLayer; int _fullWidth; int _fullHeight; diff --git a/backends/platform/iphone/iphone_video.m b/backends/platform/iphone/iphone_video.m index 6c6944045e..89f159c1d9 100644 --- a/backends/platform/iphone/iphone_video.m +++ b/backends/platform/iphone/iphone_video.m @@ -31,8 +31,8 @@ #import <GraphicsServices/GraphicsServices.h> #import <Foundation/Foundation.h> #import <CoreSurface/CoreSurface.h> -#import <LayerKit/LKLayer.h> #import <UIKit/UIKeyboardLayoutQWERTY.h> +#import <QuartzCore/QuartzCore.h> static iPhoneView *sharedInstance = nil; static int _width = 0; @@ -53,8 +53,8 @@ void iPhone_updateScreen() { } void iPhone_updateScreenRect(int x1, int y1, int x2, int y2) { - NSRect rect = NSMakeRect(x1, y1, x2, y2); - [sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO]; + //CGRect rect = CGRectMake(x1, y1, x2, y2); + //[sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO]; } void iPhone_lockSurface() { @@ -146,9 +146,9 @@ bool getLocalMouseCoords(CGPoint *point) { } - (void)updateScreenRect:(id)rect { - NSRect nsRect = [rect rectValue]; - CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height); - [sharedInstance setNeedsDisplayInRect: cgRect]; + // NSRect nsRect = [rect rectValue]; + // CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height); + // [sharedInstance setNeedsDisplayInRect: cgRect]; } - (void)initSurface { @@ -178,7 +178,7 @@ bool getLocalMouseCoords(CGPoint *point) { //printf("Surface created.\n"); CoreSurfaceBufferLock(_screenSurface, 3); - LKLayer* screenLayer = [[LKLayer layer] retain]; + CALayer* screenLayer = [[CALayer layer] retain]; if (_keyboardView != nil) { [_keyboardView removeFromSuperview]; @@ -213,7 +213,7 @@ bool getLocalMouseCoords(CGPoint *point) { _keyboardView = [[SoftKeyboard alloc] initWithFrame:keyFrame]; [_keyboardView setInputDelegate:self]; } - + [self addSubview:[_keyboardView inputView]]; [self addSubview: _keyboardView]; [[_keyboardView inputView] becomeFirstResponder]; @@ -283,11 +283,13 @@ bool getLocalMouseCoords(CGPoint *point) { } - (void)mouseDown:(GSEvent*)event { - struct CGPoint point = GSEventGetLocationInWindow(event); - + //printf("mouseDown()\n"); + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDown], @"type", @@ -298,12 +300,18 @@ bool getLocalMouseCoords(CGPoint *point) { ]; } +- (void)touchesBegan { + //printf("touchesBegan()\n"); +} + - (void)mouseUp:(GSEvent*)event { - struct CGPoint point = GSEventGetLocationInWindow(event); - + //printf("mouseUp()\n"); + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseUp], @"type", @@ -316,11 +324,12 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseDragged:(GSEvent*)event { //printf("mouseDragged()\n"); - struct CGPoint point = GSEventGetLocationInWindow(event); - + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDragged], @"type", @@ -333,19 +342,21 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseEntered:(GSEvent*)event { //printf("mouseEntered()\n"); - // struct CGPoint point = GSEventGetLocationInWindow(event); - // - // if (!getLocalMouseCoords(&point)) - // return; - // - // [self addEvent: - // [[NSDictionary alloc] initWithObjectsAndKeys: - // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", - // [NSNumber numberWithFloat:point.x], @"x", - // [NSNumber numberWithFloat:point.y], @"y", - // nil - // ] - // ]; + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + + + if (!getLocalMouseCoords(&point)) + return; + + [self addEvent: + [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", + [NSNumber numberWithFloat:point.x], @"x", + [NSNumber numberWithFloat:point.y], @"y", + nil + ] + ]; } - (void)mouseExited:(GSEvent*)event { @@ -361,19 +372,19 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseMoved:(GSEvent*)event { //printf("mouseMoved()\n"); - struct CGPoint point = GSEventGetLocationInWindow(event); - - if (!getLocalMouseCoords(&point)) - return; - - [self addEvent: - [[NSDictionary alloc] initWithObjectsAndKeys: - [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", - [NSNumber numberWithFloat:point.x], @"x", - [NSNumber numberWithFloat:point.y], @"y", - nil - ] - ]; + // struct CGPoint point = GSEventGetLocationInWindow(event); + // + // if (!getLocalMouseCoords(&point)) + // return; + // + // [self addEvent: + // [[NSDictionary alloc] initWithObjectsAndKeys: + // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", + // [NSNumber numberWithFloat:point.x], @"x", + // [NSNumber numberWithFloat:point.y], @"y", + // nil + // ] + // ]; } - (void)handleKeyPress:(unichar)c { @@ -391,7 +402,7 @@ bool getLocalMouseCoords(CGPoint *point) { return TRUE; } -- (int)swipe:(UIViewSwipeDirection)num withEvent:(GSEvent*)event { +- (int)swipe:(int)num withEvent:(GSEvent*)event { //printf("swipe: %i\n", num); [self addEvent: diff --git a/backends/platform/iphone/osys_iphone.cpp b/backends/platform/iphone/osys_iphone.cpp index 4cb90d35b9..3d5571cf3a 100644 --- a/backends/platform/iphone/osys_iphone.cpp +++ b/backends/platform/iphone/osys_iphone.cpp @@ -25,8 +25,6 @@ #if defined(IPHONE_BACKEND) -#include <CoreGraphics/CGDirectDisplay.h> -#include <CoreSurface/CoreSurface.h> #include <unistd.h> #include <pthread.h> @@ -41,10 +39,15 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" #include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "gui/message.h" #include "osys_iphone.h" #include "blit_arm.h" +#include <sys/time.h> + +#include <CoreGraphics/CGDirectDisplay.h> +#include <CoreSurface/CoreSurface.h> const OSystem::GraphicsMode OSystem_IPHONE::s_supportedGraphicsModes[] = { {0, 0, 0} @@ -85,13 +88,13 @@ int OSystem_IPHONE::timerHandler(int t) { } void OSystem_IPHONE::initBackend() { - _savefile = new DefaultSaveFileManager(); - _mixer = new Audio::Mixer(); + _savefile = new DefaultSaveFileManager(SCUMMVM_SAVE_PATH); _timer = new DefaultTimerManager(); gettimeofday(&_startTime, NULL); - setSoundCallback(Audio::Mixer::mixCallback, _mixer); + setupMixer(); + setTimerCallback(&OSystem_IPHONE::timerHandler, 10); OSystem::initBackend(); @@ -871,7 +874,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) { suspendLoop(); break; - case kInputKeyPressed: + case kInputKeyPressed: { int keyPressed = (int)xUnit; int ascii = keyPressed; //printf("key: %i\n", keyPressed); @@ -932,6 +935,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) { event.kbd.ascii = _queuedInputEvent.kbd.ascii = ascii; _needEventRestPeriod = true; break; + } case kInputSwipe: { Common::KeyCode keycode = Common::KEYCODE_INVALID; @@ -1088,10 +1092,21 @@ void OSystem_IPHONE::AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBuf AudioQueueStop(s_AudioQueue.queue, false); } -bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { +void OSystem_IPHONE::mixCallback(void *sys, byte *samples, int len) { + OSystem_IPHONE *this_ = (OSystem_IPHONE *)sys; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(samples, len); +} + +void OSystem_IPHONE::setupMixer() { //printf("setSoundCallback()\n"); - s_soundCallback = proc; - s_soundParam = param; + _mixer = new Audio::MixerImpl(this); + + s_soundCallback = mixCallback; + s_soundParam = this; + s_AudioQueue.dataFormat.mSampleRate = AUDIO_SAMPLE_RATE; s_AudioQueue.dataFormat.mFormatID = kAudioFormatLinearPCM; @@ -1105,7 +1120,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { if (AudioQueueNewOutput(&s_AudioQueue.dataFormat, AQBufferCallback, &s_AudioQueue, 0, kCFRunLoopCommonModes, 0, &s_AudioQueue.queue)) { printf("Couldn't set the AudioQueue callback!\n"); - return false; + _mixer->setReady(false); + return; } uint32 bufferBytes = s_AudioQueue.frameCount * s_AudioQueue.dataFormat.mBytesPerFrame; @@ -1113,7 +1129,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { for (int i = 0; i < AUDIO_BUFFERS; i++) { if (AudioQueueAllocateBuffer(s_AudioQueue.queue, bufferBytes, &s_AudioQueue.buffers[i])) { printf("Error allocating AudioQueue buffer!\n"); - return false; + _mixer->setReady(false); + return; } AQBufferCallback(&s_AudioQueue, s_AudioQueue.queue, s_AudioQueue.buffers[i]); @@ -1122,10 +1139,12 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { AudioQueueSetParameter(s_AudioQueue.queue, kAudioQueueParam_Volume, 1.0); if (AudioQueueStart(s_AudioQueue.queue, NULL)) { printf("Error starting the AudioQueue!\n"); - return false; + _mixer->setReady(false); + return; } - - return true; + + _mixer->setOutputRate(AUDIO_SAMPLE_RATE); + _mixer->setReady(true); } int OSystem_IPHONE::getOutputSampleRate() const { @@ -1146,6 +1165,11 @@ void OSystem_IPHONE::setTimerCallback(TimerProc callback, int interval) { void OSystem_IPHONE::quit() { } +void OSystem_IPHONE::getTimeAndDate(struct tm &t) const { + time_t curTime = time(0); + t = *localtime(&curTime); +} + void OSystem_IPHONE::setWindowCaption(const char *caption) { } @@ -1169,17 +1193,7 @@ OSystem *OSystem_IPHONE_create() { } const char* OSystem_IPHONE::getConfigPath() { - if (s_is113OrHigher) - return SCUMMVM_PREFS_PATH; - else - return SCUMMVM_OLD_PREFS_PATH; -} - -const char* OSystem_IPHONE::getSavePath() { - if (s_is113OrHigher) - return SCUMMVM_SAVE_PATH; - else - return SCUMMVM_OLD_SAVE_PATH; + return SCUMMVM_PREFS_PATH; } void OSystem_IPHONE::migrateApp() { @@ -1193,7 +1207,7 @@ void OSystem_IPHONE::migrateApp() { if (!file.exists()) { system("mkdir " SCUMMVM_ROOT_PATH); system("mkdir " SCUMMVM_SAVE_PATH); - + // Copy over the prefs file system("cp " SCUMMVM_OLD_PREFS_PATH " " SCUMMVM_PREFS_PATH); @@ -1207,24 +1221,24 @@ void OSystem_IPHONE::migrateApp() { void iphone_main(int argc, char *argv[]) { - OSystem_IPHONE::migrateApp(); - - // Redirect stdout and stderr if we're launching from the Springboard. - if (argc == 2 && strcmp(argv[1], "--launchedFromSB") == 0) { - FILE *newfp = fopen("/tmp/scummvm.log", "a"); - if (newfp != NULL) { - fclose(stdout); - fclose(stderr); - *stdout = *newfp; - *stderr = *newfp; - setbuf(stdout, NULL); - setbuf(stderr, NULL); - - //extern int gDebugLevel; - //gDebugLevel = 10; - } + //OSystem_IPHONE::migrateApp(); + + FILE *newfp = fopen("/var/mobile/.scummvm.log", "a"); + if (newfp != NULL) { + fclose(stdout); + fclose(stderr); + *stdout = *newfp; + *stderr = *newfp; + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + //extern int gDebugLevel; + //gDebugLevel = 10; } + system("mkdir " SCUMMVM_ROOT_PATH); + system("mkdir " SCUMMVM_SAVE_PATH); + g_system = OSystem_IPHONE_create(); assert(g_system); diff --git a/backends/platform/iphone/osys_iphone.h b/backends/platform/iphone/osys_iphone.h index a01cad480a..c058686c8c 100644 --- a/backends/platform/iphone/osys_iphone.h +++ b/backends/platform/iphone/osys_iphone.h @@ -29,6 +29,8 @@ #include "iphone_common.h" #include "common/system.h" #include "common/events.h" +#include "sound/mixer_intern.h" +#include "backends/fs/posix/posix-fs-factory.h" #include <AudioToolbox/AudioQueue.h> @@ -62,7 +64,7 @@ protected: static bool s_is113OrHigher; Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; Common::TimerManager *_timer; Graphics::Surface _framebuffer; @@ -152,12 +154,16 @@ public: virtual void unlockMutex(MutexRef mutex); virtual void deleteMutex(MutexRef mutex); - virtual bool setSoundCallback(SoundProc proc, void *param); + static void mixCallback(void *sys, byte *samples, int len); + virtual void setupMixer(void); virtual int getOutputSampleRate() const; virtual void setTimerCallback(TimerProc callback, int interval); virtual void quit(); + FilesystemFactory *getFilesystemFactory() { return &POSIXFilesystemFactory::instance(); } + virtual void getTimeAndDate(struct tm &t) const; + virtual void setWindowCaption(const char *caption); virtual Common::SaveFileManager *getSavefileManager(); @@ -166,7 +172,6 @@ public: static void migrateApp(); static const char* getConfigPath(); - static const char* getSavePath(); protected: inline void addDirtyRect(int16 x1, int16 y1, int16 w, int16 h); diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 76ac91c282..75bac0f536 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -26,6 +26,11 @@ #include "backends/platform/sdl/sdl.h" #include "common/config-manager.h" #include "common/events.h" +#include "common/file.h" +#if defined(WIN32) && defined(ARRAYSIZE) +// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h +#undef ARRAYSIZE +#endif #include "common/util.h" #include "backends/saves/default/default-saves.h" @@ -40,6 +45,7 @@ #define SAMPLES_PER_SEC 22050 //#define SAMPLES_PER_SEC 44100 + /* * Include header files needed for the getFilesystemFactory() method. */ @@ -52,6 +58,18 @@ #endif +#if defined(UNIX) +#ifdef MACOSX +#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences" +#else +#define DEFAULT_CONFIG_FILE ".scummvmrc" +#endif +#else +#define DEFAULT_CONFIG_FILE "scummvm.ini" +#endif + + + static Uint32 timer_handler(Uint32 interval, void *param) { ((DefaultTimerManager *)param)->handler(); return interval; @@ -170,6 +188,10 @@ OSystem_SDL::OSystem_SDL() _joystick(0), _currentShakePos(0), _newShakePos(0), _paletteDirtyStart(0), _paletteDirtyEnd(0), +#ifdef MIXER_DOUBLE_BUFFERING + _soundMutex(0), _soundCond(0), _soundThread(0), + _soundThreadIsRunning(false), _soundThreadShouldQuit(false), +#endif _savefile(0), _mixer(0), _timer(0), @@ -241,6 +263,92 @@ FilesystemFactory *OSystem_SDL::getFilesystemFactory() { #endif } +static Common::String getDefaultConfigFileName() { + char configFile[MAXPATHLEN]; +#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) + OSVERSIONINFO win32OsVersion; + ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO)); + win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&win32OsVersion); + // Check for non-9X version of Windows. + if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) { + // Use the Application Data directory of the user profile. + if (win32OsVersion.dwMajorVersion >= 5) { + if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile))) + error("Unable to access application data directory"); + } else { + if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile))) + error("Unable to access user profile directory"); + + strcat(configFile, "\\Application Data"); + CreateDirectory(configFile, NULL); + } + + strcat(configFile, "\\ScummVM"); + CreateDirectory(configFile, NULL); + strcat(configFile, "\\" DEFAULT_CONFIG_FILE); + + if (fopen(configFile, "r") == NULL) { + // Check windows directory + char oldConfigFile[MAXPATHLEN]; + GetWindowsDirectory(oldConfigFile, MAXPATHLEN); + strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE); + if (fopen(oldConfigFile, "r")) { + printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n"); + printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n"); + printf("file from the old default location:\n"); + printf("%s\n", oldConfigFile); + printf("to the new default location:\n"); + printf("%s\n\n", configFile); + strcpy(configFile, oldConfigFile); + } + } + } else { + // Check windows directory + GetWindowsDirectory(configFile, MAXPATHLEN); + strcat(configFile, "\\" DEFAULT_CONFIG_FILE); + } +#elif defined(UNIX) + // On UNIX type systems, by default we store the config file inside + // to the HOME directory of the user. + // + // GP2X is Linux based but Home dir can be read only so do not use + // it and put the config in the executable dir. + // + // On the iPhone, the home dir of the user when you launch the app + // from the Springboard, is /. Which we don't want. + const char *home = getenv("HOME"); + if (home != NULL && strlen(home) < MAXPATHLEN) + snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE); + else + strcpy(configFile, DEFAULT_CONFIG_FILE); +#else + strcpy(configFile, DEFAULT_CONFIG_FILE); +#endif + + return configFile; +} + +Common::SeekableReadStream *OSystem_SDL::openConfigFileForReading() { + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + +Common::WriteStream *OSystem_SDL::openConfigFileForWriting() { + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + void OSystem_SDL::setWindowCaption(const char *caption) { SDL_WM_SetCaption(caption, caption); } diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 4ad588f5f5..1c1381ec5c 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -210,6 +210,9 @@ public: virtual Common::SaveFileManager *getSavefileManager(); virtual FilesystemFactory *getFilesystemFactory(); + virtual Common::SeekableReadStream *openConfigFileForReading(); + virtual Common::WriteStream *openConfigFileForWriting(); + protected: bool _inited; diff --git a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl index 5d85fc03a2..12e5f8f0c4 100644 --- a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl +++ b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl @@ -2,12 +2,11 @@ ################################################################################################################## @WorkingEngines = qw( - scumm agos sky queen gob saga - kyra lure agi + scumm agos sky queen gob saga drascula + kyra lure agi touche parallaction ); @TestingEngines = qw( - cine cruise touche parallaction - drascula igor made m4 + cruise igor made m4 cine ); @BrokenEngines = qw( sword1 @@ -29,21 +28,21 @@ ); # these are normally enabled for each variation - $DefaultFeatures = qw(zlib tremor); - #$DefaultFeatures = qw(zlib mad tremor); + #$DefaultFeatures = qw(zlib,mad); + $DefaultFeatures = qw(zlib,mad,tremor); - # you can use these below for speed & clarity or override with custom settings - $DefaultTopMacros = " - MACRO USE_ZLIB // LIB:zlib.lib - //MACRO USE_MAD // LIB:libmad.lib - MACRO USE_TREMOR // LIB:libtremor.lib - "; + # you can use these below for speed & clarity or override with custom settings + $DefaultTopMacros = " + MACRO USE_ZLIB // LIB:zlib.lib + MACRO USE_MAD // LIB:libmad.lib + MACRO USE_TREMOR // LIB:libtremor.lib + "; - $DefaultBottomMacros = " - MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib - MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib - "; + $DefaultBottomMacros = " + MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib + MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib + "; ################################################################################################################## ## diff --git a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in index 3fea916e43..8daf76138c 100644 --- a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in @@ -50,7 +50,7 @@ LANG SC END EPOCSTACKSIZE 80000 -EPOCHEAPSIZE 3000000 64000000 +EPOCHEAPSIZE 5000000 64000000 START BITMAP ScummVM.mbm TARGETPATH \Resource\Apps diff --git a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in index 0013d061ca..cf3d0c1d7b 100644 --- a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in +++ b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in @@ -51,7 +51,7 @@ LANG SC END EPOCSTACKSIZE 80000 -EPOCHEAPSIZE 3000000 64000000 +EPOCHEAPSIZE 5000000 64000000 START BITMAP ScummVM.mbm TARGETPATH \Resource\Apps diff --git a/backends/platform/symbian/src/SymbianActions.cpp b/backends/platform/symbian/src/SymbianActions.cpp index 8fc35e9f8d..da127eaec6 100644 --- a/backends/platform/symbian/src/SymbianActions.cpp +++ b/backends/platform/symbian/src/SymbianActions.cpp @@ -140,6 +140,8 @@ void SymbianActions::initInstanceGame() { bool is_touche = (gameid == "touche"); bool is_agi = (gameid == "agi"); bool is_parallaction = (gameid == "parallaction"); + bool is_lure = (gameid == "lure"); + bool is_feeble = (gameid == "feeble"); Actions::initInstanceGame(); @@ -175,7 +177,7 @@ void SymbianActions::initInstanceGame() { // Skip text if (!is_cine && !is_parallaction) _action_enabled[ACTION_SKIP_TEXT] = true; - if (is_simon || is_sky || is_sword2 || is_queen || is_sword1 || is_gob || is_saga || is_kyra || is_touche) + if (is_simon || is_sky || is_sword2 || is_queen || is_sword1 || is_gob || is_saga || is_kyra || is_touche || is_lure || is_feeble) _key_action[ACTION_SKIP_TEXT].setKey(Common::KEYCODE_ESCAPE, Common::KEYCODE_ESCAPE); // Escape key else { _key_action[ACTION_SKIP_TEXT].setKey(SDLK_PERIOD); diff --git a/backends/platform/symbian/src/SymbianOS.cpp b/backends/platform/symbian/src/SymbianOS.cpp index 660b0c69ed..0ce44d1704 100644 --- a/backends/platform/symbian/src/SymbianOS.cpp +++ b/backends/platform/symbian/src/SymbianOS.cpp @@ -30,6 +30,7 @@ #include "backends/platform/symbian/src/SymbianActions.h" #include "common/config-manager.h" #include "common/events.h" +#include "common/file.h" #include "gui/Actions.h" #include "gui/Key.h" #include "gui/message.h" @@ -42,6 +43,10 @@ #define SAMPLES_PER_SEC 16000 #endif + +#define DEFAULT_CONFIG_FILE "scummvm.ini" + + #define KInputBufferLength 128 // Symbian libc file functionality in order to provide shared file handles struct TSymbianFileEntry { @@ -122,6 +127,34 @@ FilesystemFactory *OSystem_SDL_Symbian::getFilesystemFactory() { return &SymbianFilesystemFactory::instance(); } +static Common::String getDefaultConfigFileName() { + char configFile[MAXPATHLEN]; + strcpy(configFile, Symbian::GetExecutablePath()); + strcat(configFile, DEFAULT_CONFIG_FILE); + return configFile; +} + +Common::SeekableReadStream *OSystem_SDL_Symbian::openConfigFileForReading() { + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + +Common::WriteStream *OSystem_SDL_Symbian::openConfigFileForWriting() { + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + + OSystem_SDL_Symbian::zoneDesc OSystem_SDL_Symbian::_zones[TOTAL_ZONES] = { { 0, 0, 320, 145 }, { 0, 145, 150, 55 }, @@ -617,9 +650,13 @@ bool symbian_feof(FILE* handle) { long int symbian_ftell(FILE* handle) { TInt pos = 0; + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); - ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(ESeekCurrent, pos); - + entry->iFileHandle.Seek(ESeekCurrent, pos); + if(entry->iInputPos != KErrNotFound) + { + pos+=(entry->iInputPos - entry->iInputBufferLen); + } return pos; } @@ -627,6 +664,7 @@ int symbian_fseek(FILE* handle, long int offset, int whence) { TSeek seekMode = ESeekStart; TInt pos = offset; + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); switch(whence) { case SEEK_SET: @@ -634,6 +672,9 @@ int symbian_fseek(FILE* handle, long int offset, int whence) { break; case SEEK_CUR: seekMode = ESeekCurrent; + if(entry->iInputPos != KErrNotFound) { + pos+=(entry->iInputPos - entry->iInputBufferLen); + } break; case SEEK_END: seekMode = ESeekEnd; @@ -641,9 +682,9 @@ int symbian_fseek(FILE* handle, long int offset, int whence) { } - ((TSymbianFileEntry*)(handle))->iInputPos = KErrNotFound; + entry->iInputPos = KErrNotFound; - return ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(seekMode, pos); + return entry->iFileHandle.Seek(seekMode, pos); } void symbian_clearerr(FILE* /*handle*/) { diff --git a/backends/platform/symbian/src/SymbianOS.h b/backends/platform/symbian/src/SymbianOS.h index 71d24f6286..68a6fb492f 100644 --- a/backends/platform/symbian/src/SymbianOS.h +++ b/backends/platform/symbian/src/SymbianOS.h @@ -71,6 +71,9 @@ protected: static void symbianMixCallback(void *s, byte *samples, int len); virtual FilesystemFactory *getFilesystemFactory(); + + virtual Common::SeekableReadStream *openConfigFileForReading(); + virtual Common::WriteStream *openConfigFileForWriting(); public: // vibration support #ifdef USE_VIBRA_SE_PXXX diff --git a/backends/platform/symbian/src/main_features.inl b/backends/platform/symbian/src/main_features.inl index f572ddb3dd..30bbbea52c 100644 --- a/backends/platform/symbian/src/main_features.inl +++ b/backends/platform/symbian/src/main_features.inl @@ -27,62 +27,61 @@ // we want a list of supported engines visible in the program, // because we also release special builds with only one engine -#ifndef DISABLE_SCUMM +#ifdef ENABLE_SCUMM "SCUMM " #endif -#ifndef DISABLE_AGOS +#ifdef ENABLE_AGOS "AGOS " #endif -#ifndef DISABLE_SKY +#ifdef ENABLE_SKY "Sky " #endif -#ifndef DISABLE_QUEEN +#ifdef ENABLE_QUEEN "Queen " #endif -#ifndef DISABLE_GOB +#ifdef ENABLE_GOB "Gob " #endif -#ifndef DISABLE_SAGA +#ifdef ENABLE_SAGA "Saga " #endif -#ifndef DISABLE_KYRA +#ifdef ENABLE_KYRA "Kyra " #endif -#ifndef DISABLE_SWORD1 +#ifdef ENABLE_SWORD1 "Sword1 " #endif -#ifndef DISABLE_SWORD2 +#ifdef ENABLE_SWORD2 "Sword2 " #endif -#ifndef DISABLE_CINE +#ifdef ENABLE_CINE "Cine " #endif -#ifndef DISABLE_LURE +#ifdef ENABLE_LURE "Lure " #endif -#ifndef DISABLE_AGI +#ifdef ENABLE_AGI "AGI " #endif -#ifndef DISABLE_TOUCHE +#ifdef ENABLE_TOUCHE "Touche " #endif -#ifndef DISABLE_DRASCULA +#ifdef ENABLE_DRASCULA "Drascula " #endif -#ifndef DISABLE_IGOR +#ifdef ENABLE_IGOR "Igor " #endif -#ifndef DISABLE_PARALLACTION +#ifdef ENABLE_PARALLACTION "Parallaction " #endif -#ifndef DISABLE_CRUISE +#ifdef ENABLE_CRUISE "Cruise " #endif -#ifndef DISABLE_MADE +#ifdef ENABLE_MADE "MADE " #endif - -#ifndef DISABLE_M4 +#ifdef ENABLE_M4 "M4 " #endif diff --git a/backends/platform/symbian/src/portdefs.h b/backends/platform/symbian/src/portdefs.h index 06a4cf374c..4577824b33 100644 --- a/backends/platform/symbian/src/portdefs.h +++ b/backends/platform/symbian/src/portdefs.h @@ -157,5 +157,6 @@ void inline *scumm_bsearch(const void *key, const void *base, size_t nmemb, size namespace Symbian { extern void FatalError(const char *msg); extern char* GetExecutablePath(); +#define DYNAMIC_MODULES 1 } #endif diff --git a/backends/plugins/win32/win32-provider.cpp b/backends/plugins/win32/win32-provider.cpp index 64636d8096..b8fdd3d802 100644 --- a/backends/plugins/win32/win32-provider.cpp +++ b/backends/plugins/win32/win32-provider.cpp @@ -50,21 +50,14 @@ protected: virtual VoidFunc findSymbol(const char *symbol) { #ifndef _WIN32_WCE - void *func = (void *)GetProcAddress((HMODULE)_dlHandle, symbol); + FARPROC func = GetProcAddress((HMODULE)_dlHandle, symbol); #else - void *func = (void *)GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol)); + FARPROC func = GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol)); #endif if (!func) debug("Failed loading symbol '%s' from plugin '%s'", symbol, _filename.c_str()); - // FIXME HACK: This is a HACK to circumvent a clash between the ISO C++ - // standard and POSIX: ISO C++ disallows casting between function pointers - // and data pointers, but dlsym always returns a void pointer. For details, - // see e.g. <http://www.trilithium.com/johan/2004/12/problem-with-dlsym/>. - assert(sizeof(VoidFunc) == sizeof(func)); - VoidFunc tmp; - memcpy(&tmp, &func, sizeof(VoidFunc)); - return tmp; + return (void (*)())func; } public: diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp index 21bc56e441..dc5e8adca7 100644 --- a/backends/saves/default/default-saves.cpp +++ b/backends/saves/default/default-saves.cpp @@ -41,79 +41,42 @@ #include <sys/stat.h> #endif - -class StdioSaveFile : public Common::InSaveFile, public Common::OutSaveFile { -private: - FILE *fh; -public: - StdioSaveFile(const char *filename, bool saveOrLoad) { - fh = ::fopen(filename, (saveOrLoad? "wb" : "rb")); - } - ~StdioSaveFile() { - if (fh) - ::fclose(fh); - } - - bool eos() const { return feof(fh) != 0; } - bool ioFailed() const { return ferror(fh) != 0; } - void clearIOFailed() { clearerr(fh); } - - bool isOpen() const { return fh != 0; } - - uint32 read(void *dataPtr, uint32 dataSize) { - assert(fh); - return fread(dataPtr, 1, dataSize, fh); - } - uint32 write(const void *dataPtr, uint32 dataSize) { - assert(fh); - return fwrite(dataPtr, 1, dataSize, fh); - } - - uint32 pos() const { - assert(fh); - return ftell(fh); - } - uint32 size() const { - assert(fh); - uint32 oldPos = ftell(fh); - fseek(fh, 0, SEEK_END); - uint32 length = ftell(fh); - fseek(fh, oldPos, SEEK_SET); - return length; - } - - void seek(int32 offs, int whence = SEEK_SET) { - assert(fh); - fseek(fh, offs, whence); - } -}; - -static void join_paths(const char *filename, const char *directory, - char *buf, int bufsize) { - buf[bufsize-1] = '\0'; - strncpy(buf, directory, bufsize-1); - -#ifdef WIN32 - // Fix for Win98 issue related with game directory pointing to root drive ex. "c:\" - if ((buf[0] != 0) && (buf[1] == ':') && (buf[2] == '\\') && (buf[3] == 0)) { - buf[2] = 0; - } +#ifdef UNIX +#ifdef MACOSX +#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames" +#else +#define DEFAULT_SAVE_PATH ".scummvm" #endif - - const int dirLen = strlen(buf); - - if (dirLen > 0) { -#if defined(__MORPHOS__) || defined(__amigaos4__) - if (buf[dirLen-1] != ':' && buf[dirLen-1] != '/') +#elif defined(__SYMBIAN32__) +#define DEFAULT_SAVE_PATH "Savegames" #endif -#if !defined(__GP32__) - strncat(buf, "/", bufsize-1); // prevent double / -#endif +DefaultSaveFileManager::DefaultSaveFileManager() { + // Register default savepath + // TODO: Remove this code here, and instead leave setting the + // default savepath to the ports using this class. +#ifdef DEFAULT_SAVE_PATH + Common::String savePath; +#if defined(UNIX) && !defined(IPHONE) + const char *home = getenv("HOME"); + if (home && *home && strlen(home) < MAXPATHLEN) { + savePath = home; + savePath += "/" DEFAULT_SAVE_PATH; + ConfMan.registerDefault("savepath", savePath); } - strncat(buf, filename, bufsize-1); +#elif defined(__SYMBIAN32__) + savePath = Symbian::GetExecutablePath(); + savePath += DEFAULT_SAVE_PATH "\\"; + ConfMan.registerDefault("savepath", savePath); +#endif +#endif // #ifdef DEFAULT_SAVE_PATH +} + +DefaultSaveFileManager::DefaultSaveFileManager(const Common::String &defaultSavepath) { + ConfMan.registerDefault("savepath", defaultSavepath); } + Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) { FilesystemNode savePath(getSavePath()); FSList savefiles; @@ -129,7 +92,8 @@ Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) { return results; } -void DefaultSaveFileManager::checkPath(const Common::String &path) { +void DefaultSaveFileManager::checkPath(const FilesystemNode &dir) { + const Common::String path = dir.getPath(); clearError(); #if defined(UNIX) || defined(__SYMBIAN32__) @@ -196,23 +160,27 @@ void DefaultSaveFileManager::checkPath(const Common::String &path) { setError(SFM_DIR_NOTDIR, "The given savepath is not a directory: "+path); } } +#else + if (!dir.exists()) { + // TODO: We could try to mkdir the directory here; or rather, we could + // add a mkdir method to FilesystemNode and invoke that here. + setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+path); + } else if (!dir.isDirectory()) { + setError(SFM_DIR_NOTDIR, "The given savepath is not a directory: "+path); + } #endif } Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename) { // Ensure that the savepath is valid. If not, generate an appropriate error. - char buf[256]; - Common::String savePath = getSavePath(); + FilesystemNode savePath(getSavePath()); checkPath(savePath); if (getError() == SFM_NO_ERROR) { - join_paths(filename, savePath.c_str(), buf, sizeof(buf)); - StdioSaveFile *sf = new StdioSaveFile(buf, false); + FilesystemNode file = savePath.getChild(filename); - if (!sf->isOpen()) { - delete sf; - sf = 0; - } + // Open the file for reading + Common::SeekableReadStream *sf = file.openForReading(); return wrapInSaveFile(sf); } else { @@ -222,18 +190,14 @@ Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename) Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename) { // Ensure that the savepath is valid. If not, generate an appropriate error. - char buf[256]; - Common::String savePath = getSavePath(); + FilesystemNode savePath(getSavePath()); checkPath(savePath); if (getError() == SFM_NO_ERROR) { - join_paths(filename, savePath.c_str(), buf, sizeof(buf)); - StdioSaveFile *sf = new StdioSaveFile(buf, true); + FilesystemNode file = savePath.getChild(filename); - if (!sf->isOpen()) { - delete sf; - sf = 0; - } + // Open the file for saving + Common::WriteStream *sf = file.openForWriting(); return wrapOutSaveFile(sf); } else { @@ -242,18 +206,19 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename) } bool DefaultSaveFileManager::removeSavefile(const char *filename) { - char buf[256]; clearError(); - Common::String filenameStr; - join_paths(filename, getSavePath().c_str(), buf, sizeof(buf)); - if (remove(buf) != 0) { + FilesystemNode savePath(getSavePath()); + FilesystemNode file = savePath.getChild(filename); + + // TODO: Add new method FilesystemNode::remove() + if (remove(file.getPath().c_str()) != 0) { #ifndef _WIN32_WCE if (errno == EACCES) - setError(SFM_DIR_ACCESS, "Search or write permission denied: "+filenameStr); + setError(SFM_DIR_ACCESS, "Search or write permission denied: "+file.getName()); if (errno == ENOENT) - setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+filenameStr); + setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+file.getName()); #endif return false; } else { diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h index f3e0ec5b35..97845c4623 100644 --- a/backends/saves/default/default-saves.h +++ b/backends/saves/default/default-saves.h @@ -34,6 +34,9 @@ */ class DefaultSaveFileManager : public Common::SaveFileManager { public: + DefaultSaveFileManager(); + DefaultSaveFileManager(const Common::String &defaultSavepath); + virtual Common::StringList listSavefiles(const char *pattern); virtual Common::InSaveFile *openForLoading(const char *filename); virtual Common::OutSaveFile *openForSaving(const char *filename); @@ -51,7 +54,7 @@ protected: * Checks the given path for read access, existence, etc. * Sets the internal error and error message accordingly. */ - void checkPath(const Common::String &path); + void checkPath(const FilesystemNode &dir); }; #endif diff --git a/base/commandLine.cpp b/base/commandLine.cpp index b9fd4ecfb7..84f2013ae9 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -34,22 +34,6 @@ #include "sound/mididrv.h" -#ifdef IPHONE -#include "backends/platform/iphone/osys_iphone.h" -#endif - -#ifdef UNIX -#ifdef MACOSX -#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames" -#else -#define DEFAULT_SAVE_PATH ".scummvm" -#endif -#elif defined(__SYMBIAN32__) -#define DEFAULT_SAVE_PATH "Savegames" -#elif defined(PALMOS_MODE) -#define DEFAULT_SAVE_PATH "/PALM/Programs/ScummVM/Saved" -#endif - #define DETECTOR_TESTING_HACK namespace Base { @@ -181,9 +165,6 @@ void registerDefaults() { // Game specific ConfMan.registerDefault("path", ""); - ConfMan.registerDefault("savepath", ""); - -// ConfMan.registerDefault("amiga", false); ConfMan.registerDefault("platform", Common::kPlatformPC); ConfMan.registerDefault("language", "en"); ConfMan.registerDefault("subtitles", false); @@ -216,27 +197,6 @@ void registerDefaults() { ConfMan.registerDefault("alsa_port", "65:0"); #endif - // Register default savepath -#ifdef DEFAULT_SAVE_PATH - char savePath[MAXPATHLEN]; -#if defined(UNIX) && !defined(IPHONE) - const char *home = getenv("HOME"); - if (home && *home && strlen(home) < MAXPATHLEN) { - snprintf(savePath, MAXPATHLEN, "%s/%s", home, DEFAULT_SAVE_PATH); - ConfMan.registerDefault("savepath", savePath); - } -#elif defined(__SYMBIAN32__) - strcpy(savePath, Symbian::GetExecutablePath()); - strcat(savePath, DEFAULT_SAVE_PATH); - ConfMan.registerDefault("savepath", savePath); -#elif defined (IPHONE) - ConfMan.registerDefault("savepath", OSystem_IPHONE::getSavePath()); - -#elif defined(PALMOS_MODE) - ConfMan.registerDefault("savepath", DEFAULT_SAVE_PATH); -#endif -#endif // #ifdef DEFAULT_SAVE_PATH - ConfMan.registerDefault("record_mode", "none"); ConfMan.registerDefault("record_file_name", "record.bin"); ConfMan.registerDefault("record_temp_file_name", "record.tmp"); @@ -684,9 +644,9 @@ static void runDetectorTest() { failure++; } else if (candidates.size() > 1) { if (gameidDiffers) { - printf(" FAILURE: Multiple games detected, some/all with wrong gameid\n"); + printf(" WARNING: Multiple games detected, some/all with wrong gameid\n"); } else { - printf(" FAILURE: Multiple games detected, but all have the same gameid\n"); + printf(" WARNING: Multiple games detected, but all have the same gameid\n"); } failure++; } else if (gameidDiffers) { diff --git a/base/game.h b/base/game.h index b068c300ce..d81f2afb8a 100644 --- a/base/game.h +++ b/base/game.h @@ -92,6 +92,10 @@ public: const Common::String &description() const { return getVal("description"); } Common::Language language() const { return contains("language") ? Common::parseLanguage(getVal("language")) : Common::UNK_LANG; } Common::Platform platform() const { return contains("platform") ? Common::parsePlatform(getVal("platform")) : Common::kPlatformUnknown; } + + const Common::String &preferredtarget() const { + return contains("preferredtarget") ? getVal("preferredtarget") : getVal("gameid"); + } }; /** List of games. */ diff --git a/base/main.cpp b/base/main.cpp index bb7a17b901..4283b6cacf 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -111,35 +111,8 @@ static const EnginePlugin *detectPlugin() { // TODO: specify the possible return values here static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::String &edebuglevels) { - Common::String gameDataPath(ConfMan.get("path")); - if (gameDataPath.empty()) { - } else if (gameDataPath.lastChar() != '/' -#if defined(__MORPHOS__) || defined(__amigaos4__) - && gameDataPath.lastChar() != ':' -#endif - && gameDataPath.lastChar() != '\\') { - gameDataPath += '/'; - ConfMan.set("path", gameDataPath, Common::ConfigManager::kTransientDomain); - } - - // We add the game "path" to the file search path via File::addDefaultDirectory(), - // so that MD5-based detection will be able to properly find files with mixed case - // filenames. - // FIXME/TODO: Fingolfin still doesn't like this; if those MD5-based detectors used - // FSNodes instead of File::open, they wouldn't have to do this. - Common::String path; - if (ConfMan.hasKey("path")) { - path = ConfMan.get("path"); - FilesystemNode dir(path); - if (!dir.isDirectory()) { - warning("Game directory does not exist (%s)", path.c_str()); - return 0; - } - } else { - path = "."; - warning("No path was provided. Assuming the data files are in the current directory"); - } - Common::File::addDefaultDirectory(path); + // Query the game data path, for messages + Common::String path = ConfMan.hasKey("path") ? ConfMan.get("path") : "."; // Create the game engine Engine *engine = 0; @@ -182,15 +155,14 @@ static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::St system.setWindowCaption(caption.c_str()); } - if (ConfMan.hasKey("path")) - Common::File::addDefaultDirectory(ConfMan.get("path")); - else - Common::File::addDefaultDirectory("."); + // Add the game path to the directory search list + Common::File::addDefaultDirectory(path); // Add extrapath (if any) to the directory search list if (ConfMan.hasKey("extrapath")) Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath")); + // If a second extrapath is specified on the app domain level, add that as well. if (ConfMan.hasKey("extrapath", Common::ConfigManager::kApplicationDomain)) Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath", Common::ConfigManager::kApplicationDomain)); diff --git a/base/plugins.cpp b/base/plugins.cpp index dcd394495f..216c6ef1af 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -140,6 +140,9 @@ public: #if PLUGIN_ENABLED_STATIC(SWORD2) LINK_PLUGIN(SWORD2) #endif + #if PLUGIN_ENABLED_STATIC(TINSEL) + LINK_PLUGIN(TINSEL) + #endif #if PLUGIN_ENABLED_STATIC(TOUCHE) LINK_PLUGIN(TOUCHE) #endif diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp index 4387bd199e..522b24163e 100644 --- a/common/advancedDetector.cpp +++ b/common/advancedDetector.cpp @@ -34,7 +34,11 @@ namespace Common { -using namespace AdvancedDetector; +/** + * A list of pointers to ADGameDescription structs (or subclasses thereof). + */ +typedef Array<const ADGameDescription*> ADGameDescList; + /** * Detect games in specified directory. @@ -48,7 +52,7 @@ using namespace AdvancedDetector; * @param platform restrict results to specified platform only * @return list of ADGameDescription (or subclass) pointers corresponding to matched games */ -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); /** @@ -90,6 +94,8 @@ static void upgradeTargetIfNecessary(const Common::ADParams ¶ms) { warning("Target upgraded from %s to %s", o->from, o->to); + // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching + // undefined target adds launcher entry" if (ConfMan.hasKey("id_came_from_command_line")) { warning("Target came from command line. Skipping save"); } else { @@ -194,7 +200,7 @@ static void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription * } GameList AdvancedMetaEngine::detectGames(const FSList &fslist) const { - ADGameDescList matches = detectGame(&fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); + ADGameDescList matches = detectGame(fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); GameList detectedGames; // Use fallback detector if there were no matches by other means @@ -233,7 +239,21 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c Common::String gameid = ConfMan.get("gameid"); - ADGameDescList matches = detectGame(0, params, language, platform, extra); + Common::String path; + if (ConfMan.hasKey("path")) { + path = ConfMan.get("path"); + } else { + path = "."; + warning("No path was provided. Assuming the data files are in the current directory"); + } + FilesystemNode dir(path); + FSList files; + if (!dir.isDirectory() || !dir.getChildren(files, FilesystemNode::kListAll)) { + warning("Game data path does not exist or is not a directory (%s)", path.c_str()); + return kNoGameDataFoundError; + } + + ADGameDescList matches = detectGame(files, params, language, platform, extra); if (params.singleid == NULL) { for (uint i = 0; i < matches.size(); i++) { @@ -268,10 +288,10 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c return kNoError; } -typedef HashMap<String, bool> StringSet; -typedef HashMap<String, int32> IntMap; +typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet; +typedef HashMap<String, int32, IgnoreCase_Hash, IgnoreCase_EqualTo> IntMap; -static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { +static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) { // TODO: This message should be cleaned up / made more specific. // For example, we should specify at least which engine triggered this. // @@ -287,96 +307,77 @@ static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { printf("\n"); } -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { - StringSet filesList; +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { + StringMap allFiles; + + StringSet detectFiles; StringMap filesMD5; IntMap filesSize; - IntMap allFiles; - - File testFile; - String tstr; - - uint i; - char md5str[32+1]; - - bool fileMissing; const ADGameFileDescription *fileDesc; const ADGameDescription *g; const byte *descPtr; debug(3, "Starting detection"); - // First we compose list of files which we need MD5s for + // First we compose an efficient to query set of all files in fslist. + // Includes nifty stuff like removing trailing dots and ignoring case. + for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (file->isDirectory()) + continue; + + String tstr = file->getName(); + + // Strip any trailing dot + if (tstr.lastChar() == '.') + tstr.deleteLastChar(); + + allFiles[tstr] = file->getPath(); // Record the presence of this file + } + + // Compute the set of files for which we need MD5s for. I.e. files which are + // included in some ADGameDescription *and* present in fslist. for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = String(fileDesc->fileName); - tstr.toLowercase(); - filesList[tstr] = true; + String tstr = fileDesc->fileName; + if (allFiles.contains(tstr)) + detectFiles[tstr] = true; } } - // TODO/FIXME: Fingolfin says: It's not good that we have two different code paths here, - // one using a FSList, one using File::open, as that will lead to discrepancies and subtle - // problems caused by those. - if (fslist != 0) { - // Get the information of the existing files - for (FSList::const_iterator file = fslist->begin(); file != fslist->end(); ++file) { - if (file->isDirectory()) continue; - tstr = file->getName(); - tstr.toLowercase(); - - // Strip any trailing dot - if (tstr.lastChar() == '.') - tstr.deleteLastChar(); - - allFiles[tstr] = true; - - debug(3, "+ %s", tstr.c_str()); - - if (!filesList.contains(tstr)) continue; + // Get the information for all detection files, if they exist + for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) { + String fname = file->_key; - if (!md5_file_string(*file, md5str, params.md5Bytes)) - continue; - filesMD5[tstr] = md5str; + debug(3, "+ %s", fname.c_str()); - debug(3, "> %s: %s", tstr.c_str(), md5str); + char md5str[32+1]; + if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes)) + continue; + filesMD5[fname] = md5str; - if (testFile.open(file->getPath())) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - } - } - } else { - // Get the information of the requested files - for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) { - tstr = file->_key; + debug(3, "> %s: %s", fname.c_str(), md5str); - debug(3, "+ %s", tstr.c_str()); - if (!filesMD5.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - - if (md5_file_string(file->_key.c_str(), md5str, params.md5Bytes)) { - filesMD5[tstr] = md5str; - debug(3, "> %s: %s", tstr.c_str(), md5str); - } - } - } + File testFile; + if (testFile.open(allFiles[fname])) { + filesSize[fname] = (int32)testFile.size(); + testFile.close(); } } + ADGameDescList matched; int maxFilesMatched = 0; // MD5 based matching + uint i; for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) { g = (const ADGameDescription *)descPtr; - fileMissing = false; + bool fileMissing = false; // Do not even bother to look at entries which do not have matching // language and platform (if specified). @@ -385,32 +386,28 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p continue; } - if ((params.flags & kADFlagUseExtraAsHint) && extra != "" && g->extra != extra) + if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; // Try to match all files for this game for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = fileDesc->fileName; - tstr.toLowercase(); + String tstr = fileDesc->fileName; if (!filesMD5.contains(tstr)) { fileMissing = true; break; } - if (fileDesc->md5 != NULL) { - if (fileDesc->md5 != filesMD5[tstr]) { - debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); - fileMissing = true; - break; - } + + if (fileDesc->md5 != NULL && fileDesc->md5 != filesMD5[tstr]) { + debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); + fileMissing = true; + break; } - if (fileDesc->fileSize != -1) { - if (fileDesc->fileSize != filesSize[tstr]) { - debug(3, "Size Mismatch. Skipping"); - fileMissing = true; - break; - } + if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSize[tstr]) { + debug(3, "Size Mismatch. Skipping"); + fileMissing = true; + break; } debug(3, "Matched file: %s", tstr.c_str()); @@ -448,85 +445,68 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p } } - // We've found a match - if (!matched.empty()) - return matched; - - if (!filesMD5.empty()) - reportUnknown(filesMD5, filesSize); - - // Filename based fallback - if (params.fileBasedFallback != 0) { - const ADFileBasedFallback *ptr = params.fileBasedFallback; - const char* const* filenames = 0; - - // First we create list of files required for detection. - // The filenames can be different than the MD5 based match ones. - for (; ptr->desc; ptr++) { - filenames = ptr->filenames; - for (; *filenames; filenames++) { - tstr = String(*filenames); - tstr.toLowercase(); - - if (!allFiles.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - allFiles[tstr] = true; - testFile.close(); - } - } - } - } - - // Then we perform the actual filename matching. If there are - // several matches, only the one with the maximum numbers of - // files is considered. - int maxNumMatchedFiles = 0; - const ADGameDescription *matchedDesc = 0; - - ptr = params.fileBasedFallback; + // We didn't find a match + if (matched.empty()) { + if (!filesMD5.empty()) + reportUnknown(filesMD5, filesSize); + + // Filename based fallback + if (params.fileBasedFallback != 0) + matched = detectGameFilebased(allFiles, params); + } - for (; ptr->desc; ptr++) { - const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; - int numMatchedFiles = 0; - fileMissing = false; + return matched; +} - filenames = ptr->filenames; - for (; *filenames; filenames++) { - if (fileMissing) { - continue; - } +/** + * Check for each ADFileBasedFallback record whether all files listed + * in it are present. If multiple pass this test, we pick the one with + * the maximal number of matching files. In case of a tie, the entry + * coming first in the list is chosen. + */ +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms) { + const ADFileBasedFallback *ptr; + const char* const* filenames; - tstr = String(*filenames); - tstr.toLowercase(); + int maxNumMatchedFiles = 0; + const ADGameDescription *matchedDesc = 0; - debug(3, "++ %s", *filenames); - if (!allFiles.contains(tstr)) { - fileMissing = true; - continue; - } + for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) { + const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; + int numMatchedFiles = 0; + bool fileMissing = false; - numMatchedFiles++; + for (filenames = ptr->filenames; *filenames; ++filenames) { + debug(3, "++ %s", *filenames); + if (!allFiles.contains(*filenames)) { + fileMissing = true; + break; } - if (!fileMissing) - debug(4, "Matched: %s", agdesc->gameid); + numMatchedFiles++; + } - if (!fileMissing && numMatchedFiles > maxNumMatchedFiles) { + if (!fileMissing) { + debug(4, "Matched: %s", agdesc->gameid); + + if (numMatchedFiles > maxNumMatchedFiles) { matchedDesc = agdesc; maxNumMatchedFiles = numMatchedFiles; - + debug(4, "and overriden"); } } + } - if (matchedDesc) { // We got a match - matched.push_back(matchedDesc); - if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { - printf("Your game version has been detected using filename matching as a\n"); - printf("variant of %s.\n", matchedDesc->gameid); - printf("If this is an original and unmodified version, please report any\n"); - printf("information previously printed by ScummVM to the team.\n"); - } + ADGameDescList matched; + + if (matchedDesc) { // We got a match + matched.push_back(matchedDesc); + if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { + printf("Your game version has been detected using filename matching as a\n"); + printf("variant of %s.\n", matchedDesc->gameid); + printf("If this is an original and unmodified version, please report any\n"); + printf("information previously printed by ScummVM to the team.\n"); } } diff --git a/common/advancedDetector.h b/common/advancedDetector.h index 48b9e213d7..40f5823d1b 100644 --- a/common/advancedDetector.h +++ b/common/advancedDetector.h @@ -69,11 +69,6 @@ struct ADGameDescription { }; /** - * A list of pointers to ADGameDescription structs (or subclasses thereof). - */ -typedef Array<const ADGameDescription*> ADGameDescList; - -/** * End marker for a table of ADGameDescription structs. Use this to * terminate a list to be passed to the AdvancedDetector API. */ diff --git a/common/algorithm.h b/common/algorithm.h index beae34245f..3b6c63d55c 100644 --- a/common/algorithm.h +++ b/common/algorithm.h @@ -29,6 +29,11 @@ namespace Common { +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + */ template<class In, class Out> Out copy(In first, In last, Out dst) { while (first != last) @@ -36,6 +41,13 @@ Out copy(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst - (last - first), dst). + * It requires the range [dst - (last - first), dst) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy copy_backward copies the data from the end to the beginning. + */ template<class In, class Out> Out copy_backward(In first, In last, Out dst) { while (first != last) @@ -43,6 +55,15 @@ Out copy_backward(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy or copy_backward it does not copy all data. It only copies + * a data element when operator() of the op parameter returns true for the + * passed data element. + */ template<class In, class Out, class Op> Out copy_if(In first, In last, Out dst, Op op) { while (first != last) { @@ -76,6 +97,9 @@ char *set_to(char *first, char *last, Value val) { return last; } +/** + * Sets all elements in the range [first, last) to val. + */ template<class In, class Value> In set_to(In first, In last, Value val) { while (first != last) @@ -83,6 +107,10 @@ In set_to(In first, In last, Value val) { return first; } +/** + * Finds the first data value in the range [first, last) matching v. + * For data comperance it uses operator == of the data elements. + */ template<class In, class T> In find(In first, In last, const T &v) { while (first != last) { @@ -93,6 +121,10 @@ In find(In first, In last, const T &v) { return last; } +/** + * Finds the first data value in the range [first, last) for which + * the specified predicate p returns true. + */ template<class In, class Pred> In find_if(In first, In last, Pred p) { while (first != last) { @@ -103,15 +135,22 @@ In find_if(In first, In last, Pred p) { return last; } +/** + * Applies the function f on all elements of the range [first, last). + * The processing order is from beginning to end. + */ template<class In, class Op> Op for_each(In first, In last, Op f) { while (first != last) f(*first++); return f; } -// Simple sort function, modeled after std::sort. -// Use it like this: sort(container.begin(), container.end()). -// Also work on plain old int arrays etc. +/** + * Simple sort function, modeled after std::sort. + * Use it like this: sort(container.begin(), container.end()). + * Also works on plain old i.e. int arrays etc. For comperance + * operator < is used. + */ template<class T> void sort(T first, T last) { if (first == last) @@ -131,8 +170,13 @@ void sort(T first, T last) { } } -// Using this with: Common::Less from common/func.h -// will give the same results as the function above. +/** + * Simple sort function, modeled after std::sort. + * It compares data with the given comparator object comp. + * + * Note: Using this with: Common::Less from common/func.h + * will give the same results as the plain sort function. + */ template<class T, class StrictWeakOrdering> void sort(T first, T last, StrictWeakOrdering comp) { if (first == last) diff --git a/common/config-file.cpp b/common/config-file.cpp index fe827e32dc..9f54c9ddde 100644 --- a/common/config-file.cpp +++ b/common/config-file.cpp @@ -58,7 +58,7 @@ void ConfigFile::clear() { bool ConfigFile::loadFromFile(const String &filename) { File file; - if (file.open(filename, File::kFileReadMode)) + if (file.open(filename)) return loadFromStream(file); else return false; @@ -171,8 +171,8 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { } bool ConfigFile::saveToFile(const String &filename) { - File file; - if (file.open(filename, File::kFileWriteMode)) + DumpFile file; + if (file.open(filename)) return saveToStream(file); else return false; diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 59855cf6c9..a424c4f6c7 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -23,38 +23,13 @@ * */ -#if defined(WIN32) -#include <windows.h> -// winnt.h defines ARRAYSIZE, but we want our own one... -#undef ARRAYSIZE -#endif - #include "common/config-manager.h" #include "common/file.h" #include "common/util.h" +#include "common/system.h" DECLARE_SINGLETON(Common::ConfigManager); -#ifdef __PLAYSTATION2__ -#include "backends/platform/ps2/systemps2.h" -#endif - -#ifdef IPHONE -#include "backends/platform/iphone/osys_iphone.h" -#endif - -#if defined(UNIX) -#ifdef MACOSX -#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences" -#else -#define DEFAULT_CONFIG_FILE ".scummvmrc" -#endif -#else -#define DEFAULT_CONFIG_FILE "scummvm.ini" -#endif - -#define MAXLINELEN 256 - static bool isValidDomainName(const Common::String &domName) { const char *p = domName.c_str(); while (*p && (isalnum(*p) || *p == '-' || *p == '_')) @@ -85,242 +60,180 @@ ConfigManager::ConfigManager() void ConfigManager::loadDefaultConfigFile() { - char configFile[MAXPATHLEN]; - // GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir. - // On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want. -#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE) - const char *home = getenv("HOME"); - if (home != NULL && strlen(home) < MAXPATHLEN) - snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE); - else - strcpy(configFile, DEFAULT_CONFIG_FILE); -#else - #if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) - OSVERSIONINFO win32OsVersion; - ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO)); - win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&win32OsVersion); - // Check for non-9X version of Windows. - if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) { - // Use the Application Data directory of the user profile. - if (win32OsVersion.dwMajorVersion >= 5) { - if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile))) - error("Unable to access application data directory"); - } else { - if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile))) - error("Unable to access user profile directory"); + // Open the default config file + SeekableReadStream *stream = g_system->openConfigFileForReading(); + _filename.clear(); // clear the filename to indicate that we are using the default config file - strcat(configFile, "\\Application Data"); - CreateDirectory(configFile, NULL); - } + // ... load it ... + assert(stream); + loadFromStream(*stream); + + // ... and close it again. + delete stream; - strcat(configFile, "\\ScummVM"); - CreateDirectory(configFile, NULL); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - - if (fopen(configFile, "r") == NULL) { - // Check windows directory - char oldConfigFile[MAXPATHLEN]; - GetWindowsDirectory(oldConfigFile, MAXPATHLEN); - strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE); - if (fopen(oldConfigFile, "r")) { - printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n"); - printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n"); - printf("file from the old default location:\n"); - printf("%s\n", oldConfigFile); - printf("to the new default location:\n"); - printf("%s\n\n", configFile); - strcpy(configFile, oldConfigFile); - } - } - } else { - // Check windows directory - GetWindowsDirectory(configFile, MAXPATHLEN); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - } - - #elif defined(PALMOS_MODE) - strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE); - #elif defined(IPHONE) - strcpy(configFile, OSystem_IPHONE::getConfigPath()); - #elif defined(__PLAYSTATION2__) - ((OSystem_PS2*)g_system)->makeConfigPath(configFile); - #elif defined(__PSP__) - strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE); - #elif defined (__SYMBIAN32__) - strcpy(configFile, Symbian::GetExecutablePath()); - strcat(configFile, DEFAULT_CONFIG_FILE); - #else - strcpy(configFile, DEFAULT_CONFIG_FILE); - #endif -#endif - - loadConfigFile(configFile); flushToDisk(); } void ConfigManager::loadConfigFile(const String &filename) { - _appDomain.clear(); - _gameDomains.clear(); - _transientDomain.clear(); - _filename = filename; - _domainSaveOrder.clear(); - loadFile(_filename); - printf("Using configuration file: %s\n", _filename.c_str()); -} -void ConfigManager::loadFile(const String &filename) { File cfg_file; - if (!cfg_file.open(filename)) { printf("Creating configuration file: %s\n", filename.c_str()); } else { - String domain; - String comment; - int lineno = 0; - - // TODO: Detect if a domain occurs multiple times (or likewise, if - // a key occurs multiple times inside one domain). - - while (!cfg_file.eof() && !cfg_file.ioFailed()) { - lineno++; - - // Read a line - String line; - while (line.lastChar() != '\n') { - char buf[MAXLINELEN]; - if (!cfg_file.readLine_NEW(buf, MAXLINELEN)) - break; - line += buf; + printf("Using configuration file: %s\n", _filename.c_str()); + loadFromStream(cfg_file); + } +} + +void ConfigManager::loadFromStream(SeekableReadStream &stream) { + String domain; + String comment; + int lineno = 0; + + _appDomain.clear(); + _gameDomains.clear(); + _transientDomain.clear(); + _domainSaveOrder.clear(); + + // TODO: Detect if a domain occurs multiple times (or likewise, if + // a key occurs multiple times inside one domain). + + while (!stream.eos() && !stream.ioFailed()) { + lineno++; + + // Read a line + String line; + while (line.lastChar() != '\n') { + char buf[256]; + if (!stream.readLine_NEW(buf, 256)) + break; + line += buf; + } + + if (line.size() == 0) { + // Do nothing + } else if (line[0] == '#') { + // Accumulate comments here. Once we encounter either the start + // of a new domain, or a key-value-pair, we associate the value + // of the 'comment' variable with that entity. + comment += line; + } else if (line[0] == '[') { + // It's a new domain which begins here. + const char *p = line.c_str() + 1; + // Get the domain name, and check whether it's valid (that + // is, verify that it only consists of alphanumerics, + // dashes and underscores). + while (*p && (isalnum(*p) || *p == '-' || *p == '_')) + p++; + + if (*p == '\0') + error("Config file buggy: missing ] in line %d", lineno); + else if (*p != ']') + error("Config file buggy: Invalid character '%c' occured in section name in line %d", *p, lineno); + + domain = String(line.c_str() + 1, p); + + // Store domain comment + if (domain == kApplicationDomain) { + _appDomain.setDomainComment(comment); + } else { + _gameDomains[domain].setDomainComment(comment); + } + comment.clear(); + + _domainSaveOrder.push_back(domain); + } else { + // This line should be a line with a 'key=value' pair, or an empty one. + + // Skip leading whitespaces + const char *t = line.c_str(); + while (isspace(*t)) + t++; + + // Skip empty lines / lines with only whitespace + if (*t == 0) + continue; + + // If no domain has been set, this config file is invalid! + if (domain.empty()) { + error("Config file buggy: Key/value pair found outside a domain in line %d", lineno); } - if (line.size() == 0) { - // Do nothing - } else if (line[0] == '#') { - // Accumulate comments here. Once we encounter either the start - // of a new domain, or a key-value-pair, we associate the value - // of the 'comment' variable with that entity. - comment += line; - } else if (line[0] == '[') { - // It's a new domain which begins here. - const char *p = line.c_str() + 1; - // Get the domain name, and check whether it's valid (that - // is, verify that it only consists of alphanumerics, - // dashes and underscores). - while (*p && (isalnum(*p) || *p == '-' || *p == '_')) - p++; - - switch (*p) { - case '\0': - error("Config file buggy: missing ] in line %d", lineno); - break; - case ']': - domain = String(line.c_str() + 1, p - (line.c_str() + 1)); - //domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes - break; - default: - error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno); - } - - // Store domain comment - if (domain == kApplicationDomain) { - _appDomain.setDomainComment(comment); - } else { - _gameDomains[domain].setDomainComment(comment); - } - comment.clear(); - - _domainSaveOrder.push_back(domain); + // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. + const char *p = strchr(t, '='); + if (!p) + error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); + + // Extract the key/value pair + String key(t, p); + String value(p + 1); + + // Trim of spaces + key.trim(); + value.trim(); + + // Finally, store the key/value pair in the active domain + set(key, value, domain); + + // Store comment + if (domain == kApplicationDomain) { + _appDomain.setKVComment(key, comment); } else { - // This line should be a line with a 'key=value' pair, or an empty one. - - // Skip leading whitespaces - const char *t = line.c_str(); - while (isspace(*t)) - t++; - - // Skip empty lines / lines with only whitespace - if (*t == 0) - continue; - - // If no domain has been set, this config file is invalid! - if (domain.empty()) { - error("Config file buggy: Key/value pair found outside a domain in line %d", lineno); - } - - // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. - const char *p = strchr(t, '='); - if (!p) - error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); - - // Trim spaces before the '=' to obtain the key - const char *p2 = p; - while (p2 > t && isspace(*(p2-1))) - p2--; - String key(t, p2 - t); - - // Skip spaces after the '=' - t = p + 1; - while (isspace(*t)) - t++; - - // Trim trailing spaces - p2 = t + strlen(t); - while (p2 > t && isspace(*(p2-1))) - p2--; - - String value(t, p2 - t); - - // Finally, store the key/value pair in the active domain - set(key, value, domain); - - // Store comment - if (domain == kApplicationDomain) { - _appDomain.setKVComment(key, comment); - } else { - _gameDomains[domain].setKVComment(key, comment); - } - comment.clear(); + _gameDomains[domain].setKVComment(key, comment); } + comment.clear(); } } } void ConfigManager::flushToDisk() { #ifndef __DC__ - File cfg_file; + WriteStream *stream; -// TODO -// if (!willwrite) -// return; - - if (!cfg_file.open(_filename, File::kFileWriteMode)) { - warning("Unable to write configuration file: %s", _filename.c_str()); + if (_filename.empty()) { + // Write to the default config file + stream = g_system->openConfigFileForWriting(); + if (!stream) // If writing to the config file is not possible, do nothing + return; } else { - // First write the domains in _domainSaveOrder, in that order. - // Note: It's possible for _domainSaveOrder to list domains which - // are not present anymore. - StringList::const_iterator i; - for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) { - if (kApplicationDomain == *i) { - writeDomain(cfg_file, *i, _appDomain); - } else if (_gameDomains.contains(*i)) { - writeDomain(cfg_file, *i, _gameDomains[*i]); - } + DumpFile *dump = new DumpFile(); + assert(dump); + + if (!dump->open(_filename)) { + warning("Unable to write configuration file: %s", _filename.c_str()); + delete dump; + return; } + + stream = dump; + } - DomainMap::const_iterator d; + // First write the domains in _domainSaveOrder, in that order. + // Note: It's possible for _domainSaveOrder to list domains which + // are not present anymore. + StringList::const_iterator i; + for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) { + if (kApplicationDomain == *i) { + writeDomain(*stream, *i, _appDomain); + } else if (_gameDomains.contains(*i)) { + writeDomain(*stream, *i, _gameDomains[*i]); + } + } + DomainMap::const_iterator d; - // Now write the domains which haven't been written yet - if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end()) - writeDomain(cfg_file, kApplicationDomain, _appDomain); - for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) { - if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end()) - writeDomain(cfg_file, d->_key, d->_value); - } + + // Now write the domains which haven't been written yet + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end()) + writeDomain(*stream, kApplicationDomain, _appDomain); + for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) { + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end()) + writeDomain(*stream, d->_key, d->_value); } + + delete stream; + #endif // !__DC__ } @@ -328,6 +241,12 @@ void ConfigManager::writeDomain(WriteStream &stream, const String &name, const D if (domain.empty()) return; // Don't bother writing empty domains. + // WORKAROUND: Fix for bug #1972625 "ALL: On-the-fly targets are + // written to the config file": Do not save domains that came from + // the command line + if (domain.contains("id_came_from_command_line")) + return; + String comment; // Write domain comment (if any) @@ -642,6 +561,10 @@ void ConfigManager::addGameDomain(const String &domName) { // the given name already exists? _gameDomains[domName]; + + // Add it to the _domainSaveOrder, if it's not already in there + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end()) + _domainSaveOrder.push_back(domName); } void ConfigManager::removeGameDomain(const String &domName) { diff --git a/common/config-manager.h b/common/config-manager.h index bebb59b539..9e5b88a073 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -36,7 +36,7 @@ namespace Common { class WriteStream; - +class SeekableReadStream; /** * The (singleton) configuration manager, used to query & set configuration @@ -144,19 +144,11 @@ public: bool hasGameDomain(const String &domName) const; const DomainMap & getGameDomains() const { return _gameDomains; } -/* - TODO: Callback/change notification system - typedef void (*ConfigCallback)(const ConstString &key, void *refCon); - - void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString) - void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString) -*/ - private: friend class Singleton<SingletonBaseType>; ConfigManager(); - void loadFile(const String &filename); + void loadFromStream(SeekableReadStream &stream); void writeDomain(WriteStream &stream, const String &name, const Domain &domain); Domain _transientDomain; diff --git a/common/file.cpp b/common/file.cpp index a1ea1aff77..fb837b9499 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -104,7 +104,7 @@ //#define fgets(str, size, file) DS::std_fgets(str, size, file) // not used //#define getc(handle) DS::std_getc(handle) // not used //#define getcwd(dir, dunno) DS::std_getcwd(dir, dunno) // not used - //#define ferror(handle) DS::std_ferror(handle) // not used + #define ferror(handle) DS::std_ferror(handle) #endif @@ -273,26 +273,14 @@ File::File() : _handle(0), _ioFailed(false) { } -//#define DEBUG_FILE_REFCOUNT - File::~File() { -#ifdef DEBUG_FILE_REFCOUNT - warning("File::~File on file '%s'", _name.c_str()); -#endif close(); } -bool File::open(const String &filename, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); - - if (filename.empty()) { - error("File::open: No filename was specified"); - } - - if (_handle) { - error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str()); - } +bool File::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); _name.clear(); clearIOFailed(); @@ -300,32 +288,29 @@ bool File::open(const String &filename, AccessMode mode) { String fname(filename); fname.toLowercase(); - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - if (mode == kFileWriteMode) { - _handle = fopenNoCase(filename, "", modeStr); - } else if (_filesMap && _filesMap->contains(fname)) { + if (_filesMap && _filesMap->contains(fname)) { fname = (*_filesMap)[fname]; debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); + _handle = fopen(fname.c_str(), "rb"); } else if (_filesMap && _filesMap->contains(fname + ".")) { // WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails" // sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot) fname = (*_filesMap)[fname + "."]; debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); + _handle = fopen(fname.c_str(), "rb"); } else { if (_defaultDirectories) { // Try all default directories StringIntMap::const_iterator x(_defaultDirectories->begin()); for (; _handle == NULL && x != _defaultDirectories->end(); ++x) { - _handle = fopenNoCase(filename, x->_key, modeStr); + _handle = fopenNoCase(filename, x->_key, "rb"); } } // Last resort: try the current directory if (_handle == NULL) - _handle = fopenNoCase(filename, "", modeStr); + _handle = fopenNoCase(filename, "", "rb"); // Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file. #if defined(MACOSX) || defined(IPHONE) @@ -335,7 +320,7 @@ bool File::open(const String &filename, AccessMode mode) { if (fileUrl) { UInt8 buf[256]; if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) { - _handle = fopen((char *)buf, modeStr); + _handle = fopen((char *)buf, "rb"); } CFRelease(fileUrl); } @@ -345,26 +330,15 @@ bool File::open(const String &filename, AccessMode mode) { } - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); - return false; - } - + if (_handle == NULL) + debug(2, "File %s not opened", filename.c_str()); + else + _name = filename; - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + return _handle != NULL; } -bool File::open(const FilesystemNode &node, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); +bool File::open(const FilesystemNode &node) { if (!node.exists()) { warning("File::open: Trying to open a FilesystemNode which does not exist"); @@ -389,25 +363,14 @@ bool File::open(const FilesystemNode &node, AccessMode mode) { clearIOFailed(); _name.clear(); - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - - _handle = fopen(node.getPath().c_str(), modeStr); + _handle = fopen(node.getPath().c_str(), "rb"); - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); - return false; - } - - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif + if (_handle == NULL) + debug(2, "File %s not found", filename.c_str()); + else + _name = filename; - return true; + return _handle != NULL; } bool File::exists(const String &filename) { @@ -438,7 +401,7 @@ bool File::exists(const String &filename) { //Try opening the file inside the local directory as a last resort File tmp; - return tmp.open(filename, kFileReadMode); + return tmp.open(filename); } void File::close() { @@ -462,28 +425,19 @@ void File::clearIOFailed() { } bool File::eof() const { - if (_handle == NULL) { - error("File::eof: File is not open!"); - return false; - } + assert(_handle); return feof((FILE *)_handle) != 0; } uint32 File::pos() const { - if (_handle == NULL) { - error("File::pos: File is not open!"); - return 0; - } + assert(_handle); return ftell((FILE *)_handle); } uint32 File::size() const { - if (_handle == NULL) { - error("File::size: File is not open!"); - return 0; - } + assert(_handle); uint32 oldPos = ftell((FILE *)_handle); fseek((FILE *)_handle, 0, SEEK_END); @@ -494,10 +448,7 @@ uint32 File::size() const { } void File::seek(int32 offs, int whence) { - if (_handle == NULL) { - error("File::seek: File is not open!"); - return; - } + assert(_handle); if (fseek((FILE *)_handle, offs, whence) != 0) clearerr((FILE *)_handle); @@ -507,10 +458,7 @@ uint32 File::read(void *ptr, uint32 len) { byte *ptr2 = (byte *)ptr; uint32 real_len; - if (_handle == NULL) { - error("File::read: File is not open!"); - return 0; - } + assert(_handle); if (len == 0) return 0; @@ -523,20 +471,91 @@ uint32 File::read(void *ptr, uint32 len) { return real_len; } -uint32 File::write(const void *ptr, uint32 len) { - if (_handle == NULL) { - error("File::write: File is not open!"); - return 0; - } + +DumpFile::DumpFile() : _handle(0) { +} + +DumpFile::~DumpFile() { + close(); +} + +bool DumpFile::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); + + String fname(filename); + fname.toLowercase(); + + _handle = fopenNoCase(filename, "", "wb"); + + if (_handle == NULL) + debug(2, "Failed to open '%s' for writing", filename.c_str()); + + return _handle != NULL; +} + +bool DumpFile::open(const FilesystemNode &node) { + assert(!_handle); + + if (node.isDirectory()) { + warning("File::open: Trying to open a FilesystemNode which is a directory"); + return false; + } /*else if (!node.isReadable() && mode == kFileReadMode) { + warning("File::open: Trying to open an unreadable FilesystemNode object for reading"); + return false; + } else if (!node.isWritable() && mode == kFileWriteMode) { + warning("File::open: Trying to open an unwritable FilesystemNode object for writing"); + return false; + }*/ + + _handle = fopen(node.getPath().c_str(), "wb"); + + if (_handle == NULL) + debug(2, "File %s not found", node.getName().c_str()); + + return _handle != NULL; +} + +void DumpFile::close() { + if (_handle) + fclose((FILE *)_handle); + _handle = NULL; +} + +bool DumpFile::isOpen() const { + return _handle != NULL; +} + +bool DumpFile::ioFailed() const { + assert(_handle); + return ferror((FILE *)_handle) != 0; +} + +void DumpFile::clearIOFailed() { + assert(_handle); + clearerr((FILE *)_handle); +} + +bool DumpFile::eof() const { + assert(_handle); + return feof((FILE *)_handle) != 0; +} + +uint32 DumpFile::write(const void *ptr, uint32 len) { + assert(_handle); if (len == 0) return 0; - if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) { - _ioFailed = true; - } + return (uint32)fwrite(ptr, 1, len, (FILE *)_handle); +} - return len; +void DumpFile::flush() { + assert(_handle); + // TODO: Should check the return value of fflush, and if it is non-zero, + // check errno and set an error flag. + fflush((FILE *)_handle); } + } // End of namespace Common diff --git a/common/file.h b/common/file.h index 8a69318128..3adeb6ff36 100644 --- a/common/file.h +++ b/common/file.h @@ -27,6 +27,7 @@ #define COMMON_FILE_H #include "common/scummsys.h" +#include "common/noncopyable.h" #include "common/str.h" #include "common/stream.h" @@ -34,7 +35,10 @@ class FilesystemNode; namespace Common { -class File : public SeekableReadStream, public WriteStream { +/** + * TODO: vital to document this core class properly!!! For both users and implementors + */ +class File : public SeekableReadStream, public NonCopyable { protected: /** File handle to the actual file; 0 if no file is open. */ void *_handle; @@ -45,19 +49,7 @@ protected: /** The name of this file, for debugging. */ String _name; -private: - // Disallow copying File objects. There is not strict reason for this, - // except that so far we never had real need for such a feature, and - // code that accidentally copied File objects tended to break in strange - // ways. - File(const File &f); - File &operator =(const File &f); - public: - enum AccessMode { - kFileReadMode = 1, - kFileWriteMode = 2 - }; static void addDefaultDirectory(const String &directory); static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = ""); @@ -80,8 +72,8 @@ public: */ static bool exists(const String &filename); - virtual bool open(const String &filename, AccessMode mode = kFileReadMode); - virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode); + virtual bool open(const String &filename); + virtual bool open(const FilesystemNode &node); virtual void close(); @@ -114,9 +106,54 @@ public: virtual uint32 size() const; void seek(int32 offs, int whence = SEEK_SET); uint32 read(void *dataPtr, uint32 dataSize); - uint32 write(const void *dataPtr, uint32 dataSize); }; + +/** + * TODO: document this class + * + * Some design ideas: + * - automatically drop all files into dumps/ dir? Might not be desired in all cases + */ +class DumpFile : public WriteStream, public NonCopyable { +protected: + /** File handle to the actual file; 0 if no file is open. */ + void *_handle; + +public: + DumpFile(); + virtual ~DumpFile(); + + virtual bool open(const String &filename); + virtual bool open(const FilesystemNode &node); + + virtual void close(); + + /** + * Checks if the object opened a file successfully. + * + * @return: true if any file is opened, false otherwise. + */ + bool isOpen() const; + + + bool ioFailed() const; + void clearIOFailed(); + bool eos() const { return eof(); } + + /** + * Checks for end of file. + * + * @return: true if the end of file is reached, false otherwise. + */ + virtual bool eof() const; + + virtual uint32 write(const void *dataPtr, uint32 dataSize); + + virtual void flush(); +}; + + } // End of namespace Common #endif diff --git a/common/fs.cpp b/common/fs.cpp index 7d803dacd4..3f585c6038 100644 --- a/common/fs.cpp +++ b/common/fs.cpp @@ -23,10 +23,13 @@ */ #include "common/util.h" +#include "common/file.h" #include "common/system.h" #include "backends/fs/abstract-fs.h" #include "backends/fs/fs-factory.h" +//namespace Common { + FilesystemNode::FilesystemNode() { } @@ -170,3 +173,41 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h return !results.empty(); } + +Common::SeekableReadStream *FilesystemNode::openForReading() { + if (_realNode == 0) + return 0; +#if 0 + return _realNode->openForReading(); +#else + // FIXME: Until we support openForReading in AbstractFilesystemNode, + // we just use Common::File. + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(*this)) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} + +Common::WriteStream *FilesystemNode::openForWriting() { + if (_realNode == 0) + return 0; +#if 0 + return _realNode->openForWriting(); +#else + // FIXME: Until we support openForWriting in AbstractFilesystemNode, + // we just use Common::DumpFile. + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(*this)) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} + +//} // End of namespace Common diff --git a/common/fs.h b/common/fs.h index ed7355cc00..972e0d86af 100644 --- a/common/fs.h +++ b/common/fs.h @@ -29,10 +29,18 @@ #include "common/ptr.h" #include "common/str.h" +class AbstractFilesystemNode; + +namespace Common { + class SeekableReadStream; + class WriteStream; +} + //namespace Common { class FilesystemNode; -class AbstractFilesystemNode; +//class SeekableReadStream; +//class WriteStream; /** * List of multiple file system nodes. E.g. the contents of a given directory. @@ -49,22 +57,6 @@ class FSList : public Common::Array<FilesystemNode> {}; * To this end, we abstract away from paths; implementations can be based on * paths (and it's left to them whether / or \ or : is the path separator :-); * but it is also possible to use inodes or vrefs (MacOS 9) or anything else. - * - * NOTE: Backends still have to provide a way to extract a path from a FSIntern - * - * You may ask now: "isn't this cheating? Why do we go through all this when we use - * a path in the end anyway?!?". - * Well, for once as long as we don't provide our own file open/read/write API, we - * still have to use fopen(). Since all our targets already support fopen(), it should - * be possible to get a fopen() compatible string for any file system node. - * - * Secondly, with this abstraction layer, we still avoid a lot of complications based on - * differences in FS roots, different path separators, or even systems with no real - * paths (MacOS 9 doesn't even have the notion of a "current directory"). - * And if we ever want to support devices with no FS in the classical sense (Palm...), - * we can build upon this. - * - * This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs. */ class FilesystemNode { private: @@ -108,9 +100,9 @@ public: bool operator<(const FilesystemNode& node) const; /** - * Indicates whether the object referred by this path exists in the filesystem or not. + * Indicates whether the object referred by this node exists in the filesystem or not. * - * @return bool true if the path exists, false otherwise. + * @return bool true if the node exists, false otherwise. */ virtual bool exists() const; @@ -168,7 +160,7 @@ public: FilesystemNode getParent() const; /** - * Indicates whether the path refers to a directory or not. + * Indicates whether the node refers to a directory or not. * * @todo Currently we assume that a node that is not a directory * automatically is a file (ignoring things like symlinks or pipes). @@ -179,28 +171,28 @@ public: virtual bool isDirectory() const; /** - * Indicates whether the object referred by this path can be read from or not. + * Indicates whether the object referred by this node can be read from or not. * - * If the path refers to a directory, readability implies being able to read + * If the node refers to a directory, readability implies being able to read * and list the directory entries. * - * If the path refers to a file, readability implies being able to read the + * If the node refers to a file, readability implies being able to read the * contents of the file. * - * @return bool true if the object can be read, false otherwise. + * @return true if the object can be read, false otherwise. */ virtual bool isReadable() const; /** - * Indicates whether the object referred by this path can be written to or not. + * Indicates whether the object referred by this node can be written to or not. * - * If the path refers to a directory, writability implies being able to modify + * If the node refers to a directory, writability implies being able to modify * the directory entry (i.e. rename the directory, remove it or write files inside of it). * - * If the path refers to a file, writability implies being able to write data + * If the node refers to a file, writability implies being able to write data * to the file. * - * @return bool true if the object can be written to, false otherwise. + * @return true if the object can be written to, false otherwise. */ virtual bool isWritable() const; @@ -221,6 +213,25 @@ public: * @return true if matches could be found, false otherwise. */ virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const; + + + /** + * Creates a SeekableReadStream instance corresponding to the file + * referred by this node. This assumes that the node actually refers + * to a readable file. If this is not the case, 0 is returned. + * + * @return pointer to the stream object, 0 in case of a failure + */ + virtual Common::SeekableReadStream *openForReading(); + + /** + * Creates a WriteStream instance corresponding to the file + * referred by this node. This assumes that the node actually refers + * to a readable file. If this is not the case, 0 is returned. + * + * @return pointer to the stream object, 0 in case of a failure + */ + virtual Common::WriteStream *openForWriting(); }; //} // End of namespace Common diff --git a/common/func.h b/common/func.h index 95df96123a..6aa5b76ed4 100644 --- a/common/func.h +++ b/common/func.h @@ -29,12 +29,18 @@ namespace Common { +/** + * Generic unary function. + */ template<class Arg, class Result> struct UnaryFunction { typedef Arg ArgumenType; typedef Result ResultType; }; +/** + * Generic binary function. + */ template<class Arg1, class Arg2, class Result> struct BinaryFunction { typedef Arg1 FirstArgumentType; @@ -42,16 +48,25 @@ struct BinaryFunction { typedef Result ResultType; }; +/** + * Predicate to check for equallity of two data elements. + */ template<class T> struct EqualTo : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x == y; } }; +/** + * Predicate to check for x being less than y. + */ template<class T> struct Less : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x < y; } }; +/** + * Predicate to check for x being greater than y. + */ template<class T> struct Greater : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x > y; } @@ -63,15 +78,19 @@ private: Op _op; typename Op::FirstArgumentType _arg1; public: - Binder1st(const Op &op, const typename Op::FirstArgumentType &arg1) : _op(op), _arg1(arg1) {} + Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {} typename Op::ResultType operator()(typename Op::SecondArgumentType v) const { return _op(_arg1, v); } }; -template<class Op, class T> -inline Binder1st<Op> bind1st(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder1st<Op> bind1st(const Op &op, typename Op::FirstArgumentType t) { return Binder1st<Op>(op, t); } @@ -81,15 +100,19 @@ private: Op _op; typename Op::SecondArgumentType _arg2; public: - Binder2nd(const Op &op, const typename Op::SecondArgumentType &arg2) : _op(op), _arg2(arg2) {} + Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {} typename Op::ResultType operator()(typename Op::FirstArgumentType v) const { return _op(v, _arg2); } }; -template<class Op, class T> -inline Binder2nd<Op> bind2nd(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder2nd<Op> bind2nd(const Op &op, typename Op::SecondArgumentType t) { return Binder2nd<Op>(op, t); } @@ -119,18 +142,24 @@ public: } }; +/** + * Creates an unary function object from a function pointer. + */ template<class Arg, class Result> inline PointerToUnaryFunc<Arg, Result> ptr_fun(Result (*func)(Arg)) { return PointerToUnaryFunc<Arg, Result>(func); } +/** + * Creates an binary function object from a function pointer. + */ template<class Arg1, class Arg2, class Result> inline PointerToBinaryFunc<Arg1, Arg2, Result> ptr_fun(Result (*func)(Arg1, Arg2)) { return PointerToBinaryFunc<Arg1, Arg2, Result>(func); } template<class Result, class T> -class MemFunc0 : public UnaryFunction<T*, Result> { +class MemFunc0 : public UnaryFunction<T *, Result> { private: Result (T::*_func)(); public: @@ -143,20 +172,20 @@ public: }; template<class Result, class T> -class ConstMemFunc0 : public UnaryFunction<T*, Result> { +class ConstMemFunc0 : public UnaryFunction<T *, Result> { private: Result (T::*_func)() const; public: typedef Result (T::*FuncType)() const; ConstMemFunc0(const FuncType &func) : _func(func) {} - Result operator()(T *v) const { + Result operator()(const T *v) const { return (v->*_func)(); } }; template<class Result, class Arg, class T> -class MemFunc1 : public BinaryFunction<T*, Arg, Result> { +class MemFunc1 : public BinaryFunction<T *, Arg, Result> { private: Result (T::*_func)(Arg); public: @@ -169,40 +198,166 @@ public: }; template<class Result, class Arg, class T> -class ConstMemFunc1 : public BinaryFunction<T*, Arg, Result> { +class ConstMemFunc1 : public BinaryFunction<T *, Arg, Result> { private: Result (T::*_func)(Arg) const; public: typedef Result (T::*FuncType)(Arg) const; ConstMemFunc1(const FuncType &func) : _func(func) {} - Result operator()(T *v1, Arg v2) const { + Result operator()(const T *v1, Arg v2) const { return (v1->*_func)(v2); } }; +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template<class Result, class T> inline MemFunc0<Result, T> mem_fun(Result (T::*f)()) { return MemFunc0<Result, T>(f); } +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template<class Result, class T> inline ConstMemFunc0<Result, T> mem_fun(Result (T::*f)() const) { return ConstMemFunc0<Result, T>(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template<class Result, class Arg, class T> inline MemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg)) { return MemFunc1<Result, Arg, T>(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template<class Result, class Arg, class T> inline ConstMemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg) const) { return ConstMemFunc1<Result, Arg, T>(f); } +template<class Result, class T> +class MemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)(); +public: + typedef Result (T::*FuncType)(); + + MemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class T> +class ConstMemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)() const; +public: + typedef Result (T::*FuncType)() const; + + ConstMemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(const T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class Arg, class T> +class MemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg); +public: + typedef Result (T::*FuncType)(Arg); + + MemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +template<class Result, class Arg, class T> +class ConstMemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg) const; +public: + typedef Result (T::*FuncType)(Arg) const; + + ConstMemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(const T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline MemFuncRef0<Result, T> mem_fun_ref(Result (T::*f)()) { + return MemFuncRef0<Result, T>(f); +} + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline ConstMemFuncRef0<Result, T> mem_fun_Ref(Result (T::*f)() const) { + return ConstMemFuncRef0<Result, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline MemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg)) { + return MemFuncRef1<Result, Arg, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline ConstMemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg) const) { + return ConstMemFuncRef1<Result, Arg, T>(f); +} + // functor code +/** + * Generic functor object for function objects without parameters. + * + * @see Functor1 + */ template<class Res> struct Functor0 { virtual ~Functor0() {} @@ -211,6 +366,18 @@ struct Functor0 { virtual Res operator()() const = 0; }; +/** + * Functor object for a class member function without parameter. + * + * Example creation: + * + * Foo bar; + * Functor0Men<void, Foo> myFunctor(&bar, &Foo::myFunc); + * + * Example usage: + * + * myFunctor(); + */ template<class Res, class T> class Functor0Mem : public Functor0<Res> { public: @@ -218,7 +385,7 @@ public: Functor0Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()() const { return (_t->*_func)(); } @@ -227,6 +394,38 @@ private: const FuncType _func; }; +/** + * Generic functor object for unary function objects. + * + * A typical usage for an unary function object is for executing opcodes + * in a script interpreter. To achieve that one can create an Common::Array + * object with 'Functor1<Arg, Res> *' as type. Now after the right engine version + * has been determined and the opcode table to use is found one could easily + * add the opcode implementations like this: + * + * Common::Array<Functor1<ScriptState, void> *> opcodeTable; + * opcodeTable[0] = new Functor1Mem<ScriptState, void, MyEngine_v1>(&myEngine, &MyEngine_v1::o1_foo); + * opcodeTable[1] = new Functor1Mem<ScriptState, void, MyEngine_v2>(&myEngine, &MyEngine_v2::o2_foo); + * // unimplemented/unused opcode + * opcodeTable[2] = 0; + * etc. + * + * This makes it easy to add member functions of different classes as + * opcode functions to the function table. Since with the generic + * Functor1<ScriptState, void> object the only requirement for an + * function to be used is 'ScriptState' as argument and 'void' as return + * value. + * + * Now for calling the opcodes one has simple to do: + * if (opcodeTable[opcodeNum] && opcodeTable[opcodeNum]->isValid()) + * (*opcodeTable[opcodeNum])(scriptState); + * else + * warning("Unimplemented opcode %d", opcodeNum); + * + * If you want to see an real world example check the kyra engine. + * Files: engines/kyra/script.cpp and .h and engine/kyra/script_*.cpp + * are interesting for that matter. + */ template<class Arg, class Res> struct Functor1 : public Common::UnaryFunction<Arg, Res> { virtual ~Functor1() {} @@ -235,6 +434,13 @@ struct Functor1 : public Common::UnaryFunction<Arg, Res> { virtual Res operator()(Arg) const = 0; }; +/** + * Functor object for an unary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take one parameter though. + * + * @see Functor0Men + */ template<class Arg, class Res, class T> class Functor1Mem : public Functor1<Arg, Res> { public: @@ -242,7 +448,7 @@ public: Functor1Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg v1) const { return (_t->*_func)(v1); } @@ -251,6 +457,11 @@ private: const FuncType _func; }; +/** + * Generic functor object for binary function objects. + * + * @see Functor1 + */ template<class Arg1, class Arg2, class Res> struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> { virtual ~Functor2() {} @@ -259,6 +470,13 @@ struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> { virtual Res operator()(Arg1, Arg2) const = 0; }; +/** + * Functor object for a binary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take two parameter though. + * + * @see Functor0Men + */ template<class Arg1, class Arg2, class Res, class T> class Functor2Mem : public Functor2<Arg1, Arg2, Res> { public: @@ -266,7 +484,7 @@ public: Functor2Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg1 v1, Arg2 v2) const { return (_t->*_func)(v1, v2); } diff --git a/common/hashmap.h b/common/hashmap.h index 1bae44e98e..69f367de97 100644 --- a/common/hashmap.h +++ b/common/hashmap.h @@ -65,8 +65,12 @@ // on every system we support, so we should get rid of this. // The solution should be to write a simple placement new // on our own. + +// Symbian does not have <new> but the new operator +#if !defined(__SYMBIAN32__) #include <new> #endif +#endif namespace Common { diff --git a/common/ptr.h b/common/ptr.h index eea3c39882..c6fcaa4f75 100644 --- a/common/ptr.h +++ b/common/ptr.h @@ -121,7 +121,7 @@ public: ~SharedPtr() { decRef(); } - SharedPtr &operator =(const SharedPtr &r) { + SharedPtr &operator=(const SharedPtr &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -134,7 +134,7 @@ public: } template<class T2> - SharedPtr &operator =(const SharedPtr<T2> &r) { + SharedPtr &operator=(const SharedPtr<T2> &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -146,8 +146,8 @@ public: return *this; } - ValueType &operator *() const { assert(_pointer); return *_pointer; } - Pointer operator ->() const { assert(_pointer); return _pointer; } + ValueType &operator*() const { assert(_pointer); return *_pointer; } + Pointer operator->() const { assert(_pointer); return _pointer; } /** * Returns the plain pointer value. Be sure you know what you @@ -171,6 +171,16 @@ public: bool unique() const { return refCount() == 1; } /** + * Resets the SharedPtr object to a NULL pointer. + */ + void reset() { + decRef(); + _deletion = 0; + _refCount = 0; + _pointer = 0; + } + + /** * Returns the number of references to the assigned pointer. * This should just be used for debugging purposes. */ @@ -199,12 +209,12 @@ private: } // end of namespace Common template<class T1, class T2> -bool operator ==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { +bool operator==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { return l.get() == r.get(); } template<class T1, class T2> -bool operator !=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { +bool operator!=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { return l.get() != r.get(); } diff --git a/common/rect.h b/common/rect.h index f71124434a..dcf1c8b421 100644 --- a/common/rect.h +++ b/common/rect.h @@ -177,6 +177,10 @@ struct Rect { clip(Rect(0, 0, maxw, maxh)); } + bool isEmpty() const { + return (left >= right || top >= bottom); + } + bool isValidRect() const { return (left <= right && top <= bottom); } diff --git a/common/savefile.h b/common/savefile.h index f30ddfc160..d44f946d48 100644 --- a/common/savefile.h +++ b/common/savefile.h @@ -39,27 +39,14 @@ namespace Common { * That typically means "save games", but also includes things like the * IQ points in Indy3. */ -class InSaveFile : public SeekableReadStream {}; +typedef SeekableReadStream InSaveFile; /** * A class which allows game engines to save game state data. * That typically means "save games", but also includes things like the * IQ points in Indy3. */ -class OutSaveFile : public WriteStream { -public: - /** - * Close this savefile, to be called right before destruction of this - * savefile. The idea is that this ways, I/O errors that occur - * during closing/flushing of the file can still be handled by the - * game engine. - * - * By default, this just flushes the stream. - */ - virtual void finalize() { - flush(); - } -}; +typedef WriteStream OutSaveFile; /** diff --git a/common/str.cpp b/common/str.cpp index a2e6e0c66d..5f8d4ffb7e 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -111,6 +111,74 @@ String::~String() { decRefCount(_extern._refCount); } +void String::makeUnique() { + ensureCapacity(_len, true); +} + +/** + * Ensure that enough storage is available to store at least new_len + * characters plus a null byte. In addition, if we currently share + * the storage with another string, unshare it, so that we can safely + * write to the storage. + */ +void String::ensureCapacity(uint32 new_len, bool keep_old) { + bool isShared; + uint32 curCapacity, newCapacity; + char *newStorage; + int *oldRefCount = _extern._refCount; + + if (isStorageIntern()) { + isShared = false; + curCapacity = _builtinCapacity - 1; + } else { + isShared = (oldRefCount && *oldRefCount > 1); + curCapacity = _extern._capacity; + } + + // Special case: If there is enough space, and we do not share + // the storage, then there is nothing to do. + if (!isShared && new_len <= curCapacity) + return; + + if (isShared && new_len <= _builtinCapacity - 1) { + // We share the storage, but there is enough internal storage: Use that. + newStorage = _storage; + newCapacity = _builtinCapacity - 1; + } else { + // We need to allocate storage on the heap! + + // Compute a suitable new capacity limit + newCapacity = computeCapacity(new_len); + + // Allocate new storage + newStorage = (char *)malloc(newCapacity+1); + assert(newStorage); + } + + // Copy old data if needed, elsewise reset the new storage. + if (keep_old) { + assert(_len <= newCapacity); + memcpy(newStorage, _str, _len + 1); + } else { + _len = 0; + newStorage[0] = 0; + } + + // Release hold on the old storage ... + decRefCount(oldRefCount); + + // ... in favor of the new storage + _str = newStorage; + + if (!isStorageIntern()) { + // Set the ref count & capacity if we use an external storage. + // It is important to do this *after* copying any old content, + // else we would override data that has not yet been copied! + _extern._refCount = 0; + _extern._capacity = newCapacity; + } +} + void String::incRefCount() const { assert(!isStorageIntern()); if (_extern._refCount == 0) { @@ -170,7 +238,8 @@ String &String::operator =(const String &str) { } String& String::operator =(char c) { - ensureCapacity(1, false); + decRefCount(_extern._refCount); + _str = _storage; _len = 1; _str[0] = c; _str[1] = 0; @@ -253,10 +322,7 @@ void String::deleteLastChar() { void String::deleteChar(uint32 p) { assert(p < _len); - // Call ensureCapacity to make sure we actually *own* the storage - // to which _str points to -- we wouldn't want to modify a storage - // which other string objects are sharing, after all. - ensureCapacity(_len, true); + makeUnique(); while (p++ < _len) _str[p-1] = _str[p]; _len--; @@ -273,7 +339,7 @@ void String::clear() { void String::setChar(char c, uint32 p) { assert(p <= _len); - ensureCapacity(_len, true); + makeUnique(); _str[p] = c; } @@ -288,78 +354,36 @@ void String::insertChar(char c, uint32 p) { } void String::toLowercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = tolower(_str[i]); } void String::toUppercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = toupper(_str[i]); } -/** - * Ensure that enough storage is available to store at least new_len - * characters plus a null byte. In addition, if we currently share - * the storage with another string, unshare it, so that we can safely - * write to the storage. - */ -void String::ensureCapacity(uint32 new_len, bool keep_old) { - bool isShared; - uint32 curCapacity, newCapacity; - char *newStorage; - int *oldRefCount = _extern._refCount; - - if (isStorageIntern()) { - isShared = false; - curCapacity = _builtinCapacity - 1; - } else { - isShared = (oldRefCount && *oldRefCount > 1); - curCapacity = _extern._capacity; - } - - // Special case: If there is enough space, and we do not share - // the storage, then there is nothing to do. - if (!isShared && new_len <= curCapacity) +void String::trim() { + if (_len == 0) return; - if (isShared && new_len <= _builtinCapacity - 1) { - // We share the storage, but there is enough internal storage: Use that. - newStorage = _storage; - newCapacity = _builtinCapacity - 1; - } else { - // We need to allocate storage on the heap! - - // Compute a suitable new capacity limit - newCapacity = computeCapacity(new_len); + makeUnique(); - // Allocate new storage - newStorage = (char *)malloc(newCapacity+1); - assert(newStorage); - } - - // Copy old data if needed, elsewise reset the new storage. - if (keep_old) { - assert(_len <= newCapacity); - memcpy(newStorage, _str, _len + 1); - } else { - _len = 0; - newStorage[0] = 0; - } - - // Release hold on the old storage ... - decRefCount(oldRefCount); + // Trim trailing whitespace + while (_len >= 1 && isspace(_str[_len-1])) + _len--; + _str[_len] = 0; - // ... in favor of the new storage - _str = newStorage; + // Trim leading whitespace + char *t = _str; + while (isspace(*t)) + t++; - if (!isStorageIntern()) { - // Set the ref count & capacity if we use an external storage. - // It is important to do this *after* copying any old content, - // else we would override data that has not yet been copied! - _extern._refCount = 0; - _extern._capacity = newCapacity; + if (t != _str) { + _len -= t - _str; + memmove(_str, t, _len + 1); } } diff --git a/common/str.h b/common/str.h index ae9cb992b6..3479fee8e4 100644 --- a/common/str.h +++ b/common/str.h @@ -177,6 +177,8 @@ public: void toLowercase(); void toUppercase(); + void trim(); + uint hash() const; public: @@ -200,6 +202,7 @@ public: } protected: + void makeUnique(); void ensureCapacity(uint32 new_len, bool keep_old); void incRefCount() const; void decRefCount(int *oldRefCount); diff --git a/common/stream.cpp b/common/stream.cpp index 61166fd451..e06cc28415 100644 --- a/common/stream.cpp +++ b/common/stream.cpp @@ -242,4 +242,83 @@ void SeekableSubReadStream::seek(int32 offset, int whence) { _parentStream->seek(_pos); } +BufferedReadStream::BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream) + : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), + _pos(0), + _bufSize(0), + _realBufSize(bufSize) { + + assert(parentStream); + _buf = new byte[bufSize]; + assert(_buf); +} + +BufferedReadStream::~BufferedReadStream() { + if (_disposeParentStream) + delete _parentStream; + delete _buf; +} + +uint32 BufferedReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 alreadyRead = 0; + const uint32 bufBytesLeft = _bufSize - _pos; + + // Check whether the data left in the buffer suffices.... + if (dataSize > bufBytesLeft) { + // Nope, we need to read more data + + // First, flush the buffer, if it is non-empty + if (0 < bufBytesLeft) { + memcpy(dataPtr, _buf + _pos, bufBytesLeft); + _pos = _bufSize; + alreadyRead += bufBytesLeft; + dataPtr = (byte *)dataPtr + bufBytesLeft; + dataSize -= bufBytesLeft; + } + + // At this point the buffer is empty. Now if the read request + // exceeds the buffer size, just satisfy it directly. + if (dataSize > _bufSize) + return alreadyRead + _parentStream->read(dataPtr, dataSize); + + // Refill the buffer. + // If we didn't read as many bytes as requested, the reason + // is EOF or an error. In that case we truncate the buffer + // size, as well as the number of bytes we are going to + // return to the caller. + _bufSize = _parentStream->read(_buf, _realBufSize); + _pos = 0; + if (dataSize > _bufSize) + dataSize = _bufSize; + } + + // Satisfy the request from the buffer + memcpy(dataPtr, _buf + _pos, dataSize); + _pos += dataSize; + return alreadyRead + dataSize; +} + +BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream) + : BufferedReadStream(parentStream, bufSize, disposeParentStream), + _parentStream(parentStream) { +} + +void BufferedSeekableReadStream::seek(int32 offset, int whence) { + // If it is a "local" seek, we may get away with "seeking" around + // in the buffer only. + // Note: We could try to handle SEEK_END and SEEK_SET, too, but + // since they are rarely used, it seems not worth the effort. + if (whence == SEEK_CUR && (int)_pos + offset >= 0 && _pos + offset <= _bufSize) { + _pos += offset; + } else { + // Seek was not local enough, so we reset the buffer and + // just seeks normally in the parent stream. + if (whence == SEEK_CUR) + offset -= (_bufSize - _pos); + _pos = _bufSize; + _parentStream->seek(offset, whence); + } +} + } // End of namespace Common diff --git a/common/stream.h b/common/stream.h index 4cf5fae114..01a946e685 100644 --- a/common/stream.h +++ b/common/stream.h @@ -78,6 +78,22 @@ public: */ virtual void flush() {} + /** + * Finalize and close this stream. To be called right before this + * stream instance is deleted. The goal here is to enable calling + * code to detect and handle I/O errors which might occur when + * closing (and this flushing, if buffered) the stream. + * + * After this method has been called, no further writes may be + * peformed on the stream. Calling ioFailed() is allowed. + * + * By default, this just flushes the stream. + */ + virtual void finalize() { + flush(); + } + + // The remaining methods all have default implementations; subclasses // need not (and should not) overload them. @@ -350,15 +366,17 @@ public: class SubReadStream : virtual public ReadStream { protected: ReadStream *_parentStream; + bool _disposeParentStream; uint32 _pos; uint32 _end; - bool _disposeParentStream; public: SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false) : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), _pos(0), - _end(end), - _disposeParentStream(disposeParentStream) {} + _end(end) { + assert(parentStream); + } ~SubReadStream() { if (_disposeParentStream) delete _parentStream; } @@ -414,6 +432,48 @@ public: } }; +/** + * Wrapper class which adds buffering to any given ReadStream. + * Users can specify how big the buffer should be, and whether the + * wrapped stream should be disposed when the wrapper is disposed. + */ +class BufferedReadStream : virtual public ReadStream { +protected: + ReadStream *_parentStream; + bool _disposeParentStream; + byte *_buf; + uint32 _pos; + uint32 _bufSize; + uint32 _realBufSize; + +public: + BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + ~BufferedReadStream(); + + virtual bool eos() const { return (_pos == _bufSize) && _parentStream->eos(); } + virtual bool ioFailed() const { return _parentStream->ioFailed(); } + virtual void clearIOFailed() { _parentStream->clearIOFailed(); } + + virtual uint32 read(void *dataPtr, uint32 dataSize); +}; + +/** + * Wrapper class which adds buffering to any given SeekableReadStream. + * @see BufferedReadStream + */ +class BufferedSeekableReadStream : public BufferedReadStream, public SeekableReadStream { +protected: + SeekableReadStream *_parentStream; +public: + BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + + virtual uint32 pos() const { return _parentStream->pos() - (_bufSize - _pos); } + virtual uint32 size() const { return _parentStream->size(); } + + virtual void seek(int32 offset, int whence = SEEK_SET); +}; + + /** * Simple memory based 'stream', which implements the ReadStream interface for @@ -514,9 +574,9 @@ public: uint32 size() const { return _bufSize; } }; -/** +/** * A sort of hybrid between MemoryWriteStream and Array classes. A stream - * that grows as it's written to. + * that grows as it's written to. */ class MemoryWriteStreamDynamic : public Common::WriteStream { private: diff --git a/common/system.cpp b/common/system.cpp index 8d528258f4..e3f81a69b6 100644 --- a/common/system.cpp +++ b/common/system.cpp @@ -29,6 +29,10 @@ #include "common/config-manager.h" #include "common/system.h" #include "common/timer.h" +#if defined(WIN32) && defined(ARRAYSIZE) +// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h +#undef ARRAYSIZE +#endif #include "common/util.h" #include "graphics/colormasks.h" @@ -121,3 +125,70 @@ void OSystem::clearScreen() { memset(screen->pixels, 0, screen->h * screen->pitch); unlockScreen(); } + + +/* +FIXME: The config file loading code below needs to be cleaned up. + Port specific variants should be pushed into the respective ports. + + Ideally, the default OSystem::openConfigFileForReading/Writing methods + should be removed completely. +*/ + +#include "common/file.h" + +#ifdef __PLAYSTATION2__ +#include "backends/platform/ps2/systemps2.h" +#endif + +#ifdef IPHONE +#include "backends/platform/iphone/osys_iphone.h" +#endif + + +#if defined(UNIX) +#define DEFAULT_CONFIG_FILE ".scummvmrc" +#else +#define DEFAULT_CONFIG_FILE "scummvm.ini" +#endif + +static Common::String getDefaultConfigFileName() { + char configFile[MAXPATHLEN]; +#if defined(PALMOS_MODE) + strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE); +#elif defined(IPHONE) + strcpy(configFile, OSystem_IPHONE::getConfigPath()); +#elif defined(__PLAYSTATION2__) + ((OSystem_PS2*)g_system)->makeConfigPath(configFile); +#elif defined(__PSP__) + strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE); +#else + strcpy(configFile, DEFAULT_CONFIG_FILE); +#endif + + return configFile; +} + +Common::SeekableReadStream *OSystem::openConfigFileForReading() { + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + +Common::WriteStream *OSystem::openConfigFileForWriting() { +#ifdef __DC__ + return 0; +#else + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} diff --git a/common/system.h b/common/system.h index b895a5cfba..501d0802fd 100644 --- a/common/system.h +++ b/common/system.h @@ -44,6 +44,8 @@ namespace Common { class EventManager; class SaveFileManager; class TimerManager; + class SeekableReadStream; + class WriteStream; } class FilesystemFactory; @@ -900,10 +902,25 @@ public: /** * Returns the FilesystemFactory object, depending on the current architecture. * - * @return FilesystemFactory* The specific factory for the current architecture. + * @return the FSNode factory for the current architecture */ virtual FilesystemFactory *getFilesystemFactory() = 0; + /** + * Open the default config file for reading, by returning a suitable + * ReadStream instance. It is the callers responsiblity to delete + * the stream after use. + */ + virtual Common::SeekableReadStream *openConfigFileForReading(); + + /** + * Open the default config file for writing, by returning a suitable + * WriteStream instance. It is the callers responsiblity to delete + * the stream after use. + * + * May return 0 to indicate that writing to config file is not possible. + */ + virtual Common::WriteStream *openConfigFileForWriting(); /** * Return String which is used for backend-specific addition to theme diff --git a/common/unarj.cpp b/common/unarj.cpp index f3ac20c285..da88c11fc9 100644 --- a/common/unarj.cpp +++ b/common/unarj.cpp @@ -231,7 +231,7 @@ ArjHeader *ArjFile::readHeader() { } -bool ArjFile::open(const Common::String &filename, AccessMode mode) { +bool ArjFile::open(const Common::String &filename) { if (_isOpen) error("Attempt to open another instance of archive"); diff --git a/common/unarj.h b/common/unarj.h index b015999671..c8965968f6 100644 --- a/common/unarj.h +++ b/common/unarj.h @@ -110,7 +110,7 @@ public: void registerArchive(const String &filename); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); void close(); uint32 read(void *dataPtr, uint32 dataSize); @@ -100,6 +100,7 @@ add_engine saga "SAGA" yes add_engine sky "Beneath a Steel Sky" yes add_engine sword1 "Broken Sword 1" yes add_engine sword2 "Broken Sword 2" yes +add_engine tinsel "Tinsel" no add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes _endian=unknown @@ -810,6 +811,10 @@ iphone) _host_os=iphone _host_cpu=arm ;; +neuros) + _host_os=linux + _host_cpu=arm + ;; *) if test -z "$_host"; then guessed_host=`$_srcdir/config.guess` @@ -1122,6 +1127,19 @@ if test -n "$_host"; then _backend="gp2x" _build_hq_scalers="no" ;; + neuros) + echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes" + DEFINES="$DEFINES -DUNIX" + _endian=little + _need_memalign=yes + add_line_to_config_h "#define NEUROS" + type_1_byte='char' + type_2_byte='short' + type_4_byte='int' + _backend='null' + _build_hq_scalers="no" + _mt32emu="no" + ;; ppc-amigaos) echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes" _endian=big @@ -1628,7 +1646,7 @@ for engine in $_engines; do fi # Save the settings - defname="ENABLE_`echo $engine | tr [a-z] [A-Z]`" + defname="ENABLE_`echo $engine | tr '[a-z]' '[A-Z]'`" if test "$isbuilt" = "no" ; then add_line_to_config_mk "# $defname" else diff --git a/dists/engine-data/kyra.dat b/dists/engine-data/kyra.dat Binary files differindex 7171707e8b..5cb5373f45 100644 --- a/dists/engine-data/kyra.dat +++ b/dists/engine-data/kyra.dat diff --git a/dists/msvc7/kyra.vcproj b/dists/msvc7/kyra.vcproj index d547288d21..2e0e8669e9 100644 --- a/dists/msvc7/kyra.vcproj +++ b/dists/msvc7/kyra.vcproj @@ -193,6 +193,12 @@ RelativePath="..\..\engines\kyra\kyra_v2.h"> </File> <File + RelativePath="..\..\engines\kyra\lol.cpp"> + </File> + <File + RelativePath="..\..\engines\kyra\lol.h"> + </File> + <File RelativePath="..\..\engines\kyra\resource.cpp"> </File> <File @@ -244,6 +250,12 @@ RelativePath="..\..\engines\kyra\screen_lok.h"> </File> <File + RelativePath="..\..\engines\kyra\screen_lol.cpp"> + </File> + <File + RelativePath="..\..\engines\kyra\screen_lol.h"> + </File> + <File RelativePath="..\..\engines\kyra\screen_mr.cpp"> </File> <File diff --git a/dists/msvc7/parallaction.vcproj b/dists/msvc7/parallaction.vcproj index 9dfb575c6b..2dac8737c2 100644 --- a/dists/msvc7/parallaction.vcproj +++ b/dists/msvc7/parallaction.vcproj @@ -148,6 +148,12 @@ RelativePath="..\..\engines\parallaction\graphics.h"> </File> <File + RelativePath="..\..\engines\parallaction\gui.cpp"> + </File> + <File + RelativePath="..\..\engines\parallaction\gui.h"> + </File> + <File RelativePath="..\..\engines\parallaction\gui_br.cpp"> </File> <File diff --git a/dists/msvc7/scummvm.sln b/dists/msvc7/scummvm.sln index e754435a8f..24880538c6 100644 --- a/dists/msvc7/scummvm.sln +++ b/dists/msvc7/scummvm.sln @@ -60,6 +60,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -146,6 +148,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc7/scummvm.vcproj b/dists/msvc7/scummvm.vcproj index 1d195fd938..884cd58a5a 100644 --- a/dists/msvc7/scummvm.vcproj +++ b/dists/msvc7/scummvm.vcproj @@ -21,7 +21,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="TRUE" ExceptionHandling="TRUE" BasicRuntimeChecks="3" @@ -38,7 +38,7 @@ Name="VCCustomBuildTool"/> <Tool Name="VCLinkerTool" - AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib" + AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib" OutputFile="$(OutDir)/scummvm.exe" LinkIncremental="2" IgnoreDefaultLibraryNames="libc.lib;libcmt.lib" @@ -76,7 +76,7 @@ InlineFunctionExpansion="1" OmitFramePointers="TRUE" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" StringPooling="TRUE" MinimalRebuild="FALSE" ExceptionHandling="TRUE" diff --git a/dists/msvc71/kyra.vcproj b/dists/msvc71/kyra.vcproj index f37b1d85fc..efc37c251e 100644 --- a/dists/msvc71/kyra.vcproj +++ b/dists/msvc71/kyra.vcproj @@ -207,6 +207,12 @@ RelativePath="..\..\engines\kyra\kyra_v2.h"> </File> <File + RelativePath="..\..\engines\kyra\lol.cpp"> + </File> + <File + RelativePath="..\..\engines\kyra\lol.h"> + </File> + <File RelativePath="..\..\engines\kyra\resource.cpp"> </File> <File @@ -258,6 +264,12 @@ RelativePath="..\..\engines\kyra\screen_lok.h"> </File> <File + RelativePath="..\..\engines\kyra\screen_lol.cpp"> + </File> + <File + RelativePath="..\..\engines\kyra\screen_lol.h"> + </File> + <File RelativePath="..\..\engines\kyra\screen_mr.cpp"> </File> <File diff --git a/dists/msvc71/parallaction.vcproj b/dists/msvc71/parallaction.vcproj index 6030b030a8..c72fb68234 100644 --- a/dists/msvc71/parallaction.vcproj +++ b/dists/msvc71/parallaction.vcproj @@ -162,6 +162,12 @@ RelativePath="..\..\engines\parallaction\graphics.h"> </File> <File + RelativePath="..\..\engines\parallaction\gui.cpp"> + </File> + <File + RelativePath="..\..\engines\parallaction\gui.h"> + </File> + <File RelativePath="..\..\engines\parallaction\gui_br.cpp"> </File> <File diff --git a/dists/msvc71/scummvm.sln b/dists/msvc71/scummvm.sln index 71bd6a9219..0845fb04ba 100644 --- a/dists/msvc71/scummvm.sln +++ b/dists/msvc71/scummvm.sln @@ -98,6 +98,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29 ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -184,6 +188,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc71/scummvm.vcproj b/dists/msvc71/scummvm.vcproj index 5817c91d45..6234c89dd5 100644 --- a/dists/msvc71/scummvm.vcproj +++ b/dists/msvc71/scummvm.vcproj @@ -21,7 +21,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="TRUE" ExceptionHandling="TRUE" BasicRuntimeChecks="3" @@ -38,7 +38,7 @@ Name="VCCustomBuildTool"/> <Tool Name="VCLinkerTool" - AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib" + AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib" OutputFile="$(OutDir)/scummvm.exe" LinkIncremental="2" IgnoreDefaultLibraryNames="libc.lib;libcmt.lib" @@ -82,7 +82,7 @@ InlineFunctionExpansion="1" OmitFramePointers="TRUE" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" StringPooling="TRUE" MinimalRebuild="FALSE" ExceptionHandling="TRUE" diff --git a/dists/msvc8/kyra.vcproj b/dists/msvc8/kyra.vcproj index c99abdc5bc..00f7749f4d 100644 --- a/dists/msvc8/kyra.vcproj +++ b/dists/msvc8/kyra.vcproj @@ -289,6 +289,14 @@ > </File> <File + RelativePath="..\..\engines\kyra\lol.cpp" + > + </File> + <File + RelativePath="..\..\engines\kyra\lol.h" + > + </File> + <File RelativePath="..\..\engines\kyra\resource.cpp" > </File> @@ -357,6 +365,14 @@ > </File> <File + RelativePath="..\..\engines\kyra\screen_lol.cpp" + > + </File> + <File + RelativePath="..\..\engines\kyra\screen_lol.h" + > + </File> + <File RelativePath="..\..\engines\kyra\screen_mr.cpp" > </File> diff --git a/dists/msvc8/parallaction.vcproj b/dists/msvc8/parallaction.vcproj index c5721bd30f..e268fe1e6b 100644 --- a/dists/msvc8/parallaction.vcproj +++ b/dists/msvc8/parallaction.vcproj @@ -229,6 +229,14 @@ > </File> <File + RelativePath="..\..\engines\parallaction\gui.cpp" + > + </File> + <File + RelativePath="..\..\engines\parallaction\gui.h" + > + </File> + <File RelativePath="..\..\engines\parallaction\gui_br.cpp" > </File> diff --git a/dists/msvc8/scummvm.sln b/dists/msvc8/scummvm.sln index d3621cbe45..8138c77a2b 100644 --- a/dists/msvc8/scummvm.sln +++ b/dists/msvc8/scummvm.sln @@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -147,6 +149,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc8/scummvm.vcproj b/dists/msvc8/scummvm.vcproj index b144dc0b79..8b10a22eb6 100644 --- a/dists/msvc8/scummvm.vcproj +++ b/dists/msvc8/scummvm.vcproj @@ -42,7 +42,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="true" ExceptionHandling="1" BasicRuntimeChecks="3" @@ -68,7 +68,7 @@ /> <Tool Name="VCLinkerTool" - AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib" + AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib" OutputFile="$(OutDir)/scummvm.exe" LinkIncremental="2" IgnoreDefaultLibraryNames="libc.lib;libcmt.lib" @@ -133,7 +133,7 @@ InlineFunctionExpansion="1" OmitFramePointers="true" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" StringPooling="true" MinimalRebuild="false" ExceptionHandling="1" diff --git a/dists/msvc8/tinsel.vcproj b/dists/msvc8/tinsel.vcproj new file mode 100644 index 0000000000..cb6ba0c2e8 --- /dev/null +++ b/dists/msvc8/tinsel.vcproj @@ -0,0 +1,486 @@ +<?xml version="1.0" encoding="windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="tinsel" + ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + RootNamespace="tinsel" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="tinsel_Debug" + IntermediateDirectory="tinsel_Debug" + ConfigurationType="4" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="0" + AdditionalIncludeDirectories="../..;../../engines" + PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + MinimalRebuild="true" + ExceptionHandling="1" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + BufferSecurityCheck="true" + EnableFunctionLevelLinking="true" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="false" + SuppressStartupBanner="false" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="tinsel_Release" + IntermediateDirectory="tinsel_Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="3" + InlineFunctionExpansion="2" + OmitFramePointers="true" + AdditionalIncludeDirectories="../../;../../engines" + PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + StringPooling="true" + ExceptionHandling="1" + RuntimeLibrary="0" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="false" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="true" + DebugInformationFormat="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\..\engines\tinsel\actors.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\actors.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\bg.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\coroutine.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\detection.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dw.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\effect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\film.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\mareels.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pdisplay.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pid.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\play.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\saveload.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\serializer.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/dists/msvc9/kyra.vcproj b/dists/msvc9/kyra.vcproj index f56a2733b5..ba1b9eb949 100644 --- a/dists/msvc9/kyra.vcproj +++ b/dists/msvc9/kyra.vcproj @@ -290,6 +290,14 @@ > </File> <File + RelativePath="..\..\engines\kyra\lol.cpp" + > + </File> + <File + RelativePath="..\..\engines\kyra\lol.h" + > + </File> + <File RelativePath="..\..\engines\kyra\resource.cpp" > </File> @@ -358,6 +366,14 @@ > </File> <File + RelativePath="..\..\engines\kyra\screen_lol.cpp" + > + </File> + <File + RelativePath="..\..\engines\kyra\screen_lol.h" + > + </File> + <File RelativePath="..\..\engines\kyra\screen_mr.cpp" > </File> diff --git a/dists/msvc9/parallaction.vcproj b/dists/msvc9/parallaction.vcproj index d3117b6b0c..f901976c37 100644 --- a/dists/msvc9/parallaction.vcproj +++ b/dists/msvc9/parallaction.vcproj @@ -230,6 +230,14 @@ > </File> <File + RelativePath="..\..\engines\parallaction\gui.cpp" + > + </File> + <File + RelativePath="..\..\engines\parallaction\gui.h" + > + </File> + <File RelativePath="..\..\engines\parallaction\gui_br.cpp" > </File> diff --git a/dists/msvc9/scummvm.sln b/dists/msvc9/scummvm.sln index a880f0acb6..72bfaaa8c4 100644 --- a/dists/msvc9/scummvm.sln +++ b/dists/msvc9/scummvm.sln @@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -147,6 +149,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc9/scummvm.vcproj b/dists/msvc9/scummvm.vcproj index 0c10158fdc..31270652df 100644 --- a/dists/msvc9/scummvm.vcproj +++ b/dists/msvc9/scummvm.vcproj @@ -43,7 +43,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="true" ExceptionHandling="1" BasicRuntimeChecks="3" @@ -69,7 +69,7 @@ /> <Tool Name="VCLinkerTool" - AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib" + AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib" OutputFile="$(OutDir)/scummvm.exe" LinkIncremental="2" IgnoreDefaultLibraryNames="libc.lib;libcmt.lib" @@ -136,7 +136,7 @@ InlineFunctionExpansion="1" OmitFramePointers="true" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" StringPooling="true" MinimalRebuild="false" ExceptionHandling="1" diff --git a/dists/msvc9/tinsel.vcproj b/dists/msvc9/tinsel.vcproj new file mode 100644 index 0000000000..7623290e80 --- /dev/null +++ b/dists/msvc9/tinsel.vcproj @@ -0,0 +1,487 @@ +<?xml version="1.0" encoding="windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="tinsel" + ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + RootNamespace="tinsel" + Keyword="Win32Proj" + TargetFrameworkVersion="131072" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="tinsel_Debug" + IntermediateDirectory="tinsel_Debug" + ConfigurationType="4" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="0" + AdditionalIncludeDirectories="../..;../../engines" + PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + MinimalRebuild="true" + ExceptionHandling="1" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + BufferSecurityCheck="true" + EnableFunctionLevelLinking="true" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="false" + SuppressStartupBanner="false" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="tinsel_Release" + IntermediateDirectory="tinsel_Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="3" + InlineFunctionExpansion="2" + OmitFramePointers="true" + AdditionalIncludeDirectories="../../;../../engines" + PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + StringPooling="true" + ExceptionHandling="1" + RuntimeLibrary="0" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="false" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="true" + DebugInformationFormat="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\..\engines\tinsel\actors.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\actors.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\bg.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\coroutine.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\detection.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dw.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\effect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\film.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\mareels.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pdisplay.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pid.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\play.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\saveload.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\serializer.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 6e04ca284b..7d03156bb6 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -577,6 +577,8 @@ int AGOSEngine::init() { _midiEnabled = true; + } else { + _driver = NULL; } // allocate buffers @@ -880,7 +882,8 @@ AGOSEngine::~AGOSEngine() { delete _gameFile; _midi.close(); - delete _driver; + if (_driver) + delete _driver; AudioCD.destroy(); 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/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/cine/anim.cpp b/engines/cine/anim.cpp index 10f4aa55b5..eb820804a6 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -511,14 +511,15 @@ int emptyAnimSpace(int start = 0) { /*! \brief Load SPL data into animDataTable * \param resourceName SPL filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded SPL data (-1 if error) */ -void loadSpl(const char *resourceName, int16 idx) { +int loadSpl(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry; if (foundFileIdx < 0) { - return; + return -1; } byte *dataPtr = readBundleFile(foundFileIdx); @@ -528,12 +529,15 @@ void loadSpl(const char *resourceName, int16 idx) { animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName); free(dataPtr); + return entry + 1; } /*! \brief Load 1bpp mask * \param resourceName Mask filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded mask */ -void loadMsk(const char *resourceName) { +int loadMsk(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); @@ -544,20 +548,23 @@ void loadMsk(const char *resourceName) { loadAnimHeader(animHeader, readS); ptr = dataPtr + 0x16; + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { - entry = emptyAnimSpace(entry); - assert(entry >= 0); animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName); ptr += animHeader.frameWidth * animHeader.frameHeight; } free(dataPtr); + return entry; } /*! \brief Load animation * \param resourceName Animation filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded animation */ -void loadAni(const char *resourceName) { +int loadAni(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); @@ -571,10 +578,10 @@ void loadAni(const char *resourceName) { transparentColor = getAnimTransparentColor(resourceName); - for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { - entry = emptyAnimSpace(entry); - assert(entry >= 0); + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); + for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { // special case transparency handling if (!strcmp(resourceName, "L2202.ANI")) { transparentColor = i < 2 ? 0 : 7; @@ -587,6 +594,7 @@ void loadAni(const char *resourceName) { } free(dataPtr); + return entry; } /*! \brief Decode 16 color image with palette @@ -642,13 +650,14 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) { /*! \brief Load image set * \param resourceName Image set filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded image set */ -void loadSet(const char *resourceName, int16 idx) { +int loadSet(const char *resourceName, int16 idx) { AnimHeader2Struct header2; uint16 numSpriteInAnim; int16 foundFileIdx = findFileInBundle(resourceName); - int16 entry = idx >= 0 ? idx : 0; + int16 entry; byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr; int type; @@ -661,6 +670,9 @@ void loadSet(const char *resourceName, int16 idx) { startOfDataPtr = ptr + numSpriteInAnim * 0x10; + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); + for (int16 i = 0; i < numSpriteInAnim; i++, entry++) { Common::MemoryReadStream readS(ptr, 0x10); @@ -674,9 +686,6 @@ void loadSet(const char *resourceName, int16 idx) { ptr += 0x10; - entry = idx < 0 ? emptyAnimSpace(entry) : idx + i; - assert(entry >= 0); - dataPtr = startOfDataPtr + header2.field_0; if (header2.type == 1) { @@ -693,175 +702,126 @@ void loadSet(const char *resourceName, int16 idx) { } free(origDataPtr); + return entry; } /*! \brief Load SEQ data into animDataTable * \param resourceName SEQ data filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded SEQ data */ -void loadSeq(const char *resourceName, int16 idx) { +int loadSeq(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); byte *dataPtr = readBundleFile(foundFileIdx); int entry = idx < 0 ? emptyAnimSpace() : idx; animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName); free(dataPtr); + return entry + 1; } -void loadResource(const char *resourceName) { - /* byte isMask = 0; */ - /* byte isSpl = 0; */ - +/*! \brief Load a resource into animDataTable + * \param resourceName Resource's filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded resource (-1 if error) + * \todo Implement loading of all resource types + */ +int loadResource(const char *resourceName, int16 idx) { + int result = -1; // Return an error by default if (strstr(resourceName, ".SPL")) { - loadSpl(resourceName, -1); - return; + result = loadSpl(resourceName, idx); } else if (strstr(resourceName, ".MSK")) { - loadMsk(resourceName); - return; + result = loadMsk(resourceName, idx); } else if (strstr(resourceName, ".ANI")) { - loadAni(resourceName); - return; + result = loadAni(resourceName, idx); } else if (strstr(resourceName, ".ANM")) { - loadAni(resourceName); - return; + result = loadAni(resourceName, idx); } else if (strstr(resourceName, ".SET")) { - loadSet(resourceName, -1); - return; + result = loadSet(resourceName, idx); } else if (strstr(resourceName, ".SEQ")) { - loadSeq(resourceName, -1); - return; - } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure - g_cine->quitGame(); - return; - } - - error("loadResource: Cannot determine type for '%s'", resourceName); -} - -/*! \todo There seems to be some additional resource file that is not loaded - */ -void loadAbs(const char *resourceName, uint16 idx) { - /* byte isMask = 0; */ - /* byte isSpl = 0; */ - - if (strstr(resourceName, ".SET")) { - loadSet(resourceName, idx); - return; + result = loadSeq(resourceName, idx); } else if (strstr(resourceName, ".H32")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; - } else if (strstr(resourceName, ".SEQ")) { - loadSeq(resourceName, idx); - return; - } else if (strstr(resourceName, ".SPL")) { - loadSpl(resourceName, idx); - return; + warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx); } else if (strstr(resourceName, ".AMI")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; - } else if (strstr(resourceName, ".ANI")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; + warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx); + } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure + g_cine->quitGame(); + } else { + error("loadResource: Cannot determine type for '%s'", resourceName); } - error("loadAbs: Cannot determine type for '%s'", resourceName); + return result; } /*! \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) { - int16 currentAnim, foundFileIdx; - int8 isMask = 0, isSpl = 0; - byte *dataPtr, *ptr; - char *animName, part[256]; - byte transparentColor = 0; - AnimData *currentPtr; - AnimHeaderStruct animHeader; - +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) { + int16 currentAnim, foundFileIdx, frame; + char *animName, part[256], name[10]; uint16 width, height, bpp, var1; - int16 frame; - char name[10]; - int type; 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(); + currentAnim = 0; + while (currentAnim < NUM_MAX_ANIMDATA) { + // 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) { + currentAnim++; // Jump over the invalid entry continue; } + // Alright, the animation entry looks to be valid so let's start handling it... if (strcmp(currentPartName, name)) { closePart(); loadPart(name); } animName = partBuffer[foundFileIdx].partName; - ptr = dataPtr = readBundleFile(foundFileIdx); - - isSpl = (strstr(animName, ".SPL")) ? 1 : 0; - isMask = (strstr(animName, ".MSK")) ? 1 : 0; - - if (isSpl) { - width = (uint16) partBuffer[foundFileIdx].unpackedSize; - height = 1; - frame = 0; - type = ANIM_RAW; - } else { - Common::MemoryReadStream readS(ptr, 0x16); - loadAnimHeader(animHeader, readS); - ptr += 0x16; - - width = animHeader.frameWidth; - height = animHeader.frameHeight; - - if (isMask) { - 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; - } - } - } - - ptr += frame * width * height; - currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor); - free(dataPtr); + loadRelatedPalette(animName); // Is this for Future Wars only? + const int16 prevAnim = currentAnim; + currentAnim = loadResource(animName, currentAnim); + assert(currentAnim > prevAnim); // Make sure we advance forward } 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..317654064b 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; @@ -99,9 +154,8 @@ extern AnimData animDataTable[NUM_MAX_ANIMDATA]; 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); +int loadResource(const char *resourceName, int16 idx = -1); +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 b10211282f..fddca078e5 100644 --- a/engines/cine/bg_list.cpp +++ b/engines/cine/bg_list.cpp @@ -83,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(); 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.h b/engines/cine/cine.h index 06f2dfd982..eaae555812 100644 --- a/engines/cine/cine.h +++ b/engines/cine/cine.h @@ -98,7 +98,13 @@ public: 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(); diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp index 1f868ccb75..cbddf0fc59 100644 --- a/engines/cine/gfx.cpp +++ b/engines/cine/gfx.cpp @@ -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 @@ -1247,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 */ @@ -1270,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 547379f02d..deac4fd57f 100644 --- a/engines/cine/main_loop.cpp +++ b/engines/cine/main_loop.cpp @@ -175,6 +175,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; byte di; @@ -187,7 +201,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { errorVar = 0; - addScriptToList0(bootScriptIdx); + addScriptToGlobalScripts(bootScriptIdx); menuVar = 0; @@ -235,12 +249,17 @@ void CineEngine::mainLoop(int bootScriptIdx) { } } - 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.h b/engines/cine/object.h index 103b2f50ba..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]; 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/script.h b/engines/cine/script.h index fcd21990fa..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(); @@ -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 54a4976000..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++) { @@ -1273,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; } @@ -1399,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; @@ -1568,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; @@ -1746,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); @@ -1820,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) { @@ -1831,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) { @@ -1842,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) { } //////////////////////////////////// @@ -2380,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; } @@ -2530,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; } @@ -2945,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 78b6c55564..0289a2a0bc 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. */ @@ -636,7 +636,7 @@ int FWScript::o2_loadAbs() { const char *param2 = getNextString(); debugC(5, kCineDebugScript, "Line: %d: loadABS(%d,%s)", _line, param1, param2); - loadAbs(param2, param1); + loadResource(param2, param1); return 0; } diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index b70201ce99..c490756403 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -187,6 +187,15 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { frame = ABS((int16)(objectTable[it->objIdx].frame)); part = objectTable[it->objIdx].part; + // Additional case for negative frame values in Operation Stealth + if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) { + if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) { + return it->objIdx; + } else { + continue; + } + } + if (it->type == 0) { threshold = animDataTable[frame]._var1; } else { @@ -199,16 +208,19 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { xdif = x - objX; ydif = y - objY; - if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif < 0) || (ydif >= height) || !animDataTable[frame].data()) { + if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) { continue; } if (g_cine->getGameType() == Cine::GType_OS) { + // This test isn't present in Operation Stealth's PC version's disassembly + // but removing it makes things crash sometimes (e.g. when selecting a verb + // and moving the mouse cursor around the floor in the airport's bathroom). if (xdif >= width) { continue; } - if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != part) { + if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) { return it->objIdx; } else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) { return it->objIdx; @@ -227,6 +239,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]; @@ -244,21 +393,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) { @@ -281,7 +552,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(); @@ -297,128 +568,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 - * \todo Add support for loading the zoneQuery table (Operation Stealth specific) - */ -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(); @@ -428,7 +581,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; @@ -462,29 +617,294 @@ 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; + } +} + +bool loadObjectTable(Common::SeekableReadStream &in) { + in.readUint16BE(); // Entry count + in.readUint16BE(); // Entry size + + 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(); +} + +bool loadZoneData(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneData[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadCommandVariables(Common::SeekableReadStream &in) { + for (int i = 0; i < 4; i++) { + commandVar3[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +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(); +} + +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); + } + return !in.ioFailed(); +} + +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); + } + return !in.ioFailed(); +} + +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); + } + return !in.ioFailed(); +} + +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(); +} + +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; + + // 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; + } + + // 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]); + } + + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } + + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } + } + + 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); + } + + // TODO: Add current music loading and playing here + // TODO: Palette handling? + + 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"); + } + + return !in.ioFailed(); +} + +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; // At savefile position 0x0000: - currentDisk = fHandle->readUint16BE(); + currentDisk = in.readUint16BE(); // At 0x0002: - fHandle->read(currentPartName, 13); + in.read(currentPartName, 13); // At 0x000F: - fHandle->read(currentDatName, 13); + in.read(currentDatName, 13); // At 0x001C: - saveVar2 = fHandle->readSint16BE(); + saveVar2 = in.readSint16BE(); // At 0x001E: - fHandle->read(currentPrcName, 13); + in.read(currentPrcName, 13); // At 0x002B: - fHandle->read(currentRelName, 13); + in.read(currentRelName, 13); // At 0x0038: - fHandle->read(currentMsgName, 13); + in.read(currentMsgName, 13); // At 0x0045: - fHandle->read(bgName, 13); + in.read(bgName, 13); // At 0x0052: - fHandle->read(currentCtName, 13); + in.read(currentCtName, 13); checkDataDisk(currentDisk); @@ -509,118 +929,69 @@ bool CineEngine::makeLoad(char *saveName) { } // At 0x005F: - fHandle->readUint16BE(); - // At 0x0061: - fHandle->readUint16BE(); + loadObjectTable(in); - // At 0x0063: - for (i = 0; i < 255; i++) { - // At 0x0063 + i * 32 + 0: - objectTable[i].x = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 2: - objectTable[i].y = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 4: - objectTable[i].mask = fHandle->readUint16BE(); - // At 0x0063 + i * 32 + 6: - objectTable[i].frame = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 8: - objectTable[i].costume = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 10: - fHandle->read(objectTable[i].name, 20); - // At 0x0063 + i * 32 + 30: - objectTable[i].part = fHandle->readUint16BE(); - } - - // At 0x2043 (i.e. 0x0063 + 255 * 32): - renderer->restorePalette(*fHandle); + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + globalVars.load(in, NUM_MAX_VAR); // At 0x2281 (i.e. 0x2083 + 255 * 2): - for (i = 0; i < 16; i++) { - // At 0x2281 + i * 2: - zoneData[i] = fHandle->readUint16BE(); - } + loadZoneData(in); // At 0x22A1 (i.e. 0x2281 + 16 * 2): - for (i = 0; i < 4; i++) { - // At 0x22A1 + i * 2: - commandVar3[i] = fHandle->readUint16BE(); - } + loadCommandVariables(in); // At 0x22A9 (i.e. 0x22A1 + 4 * 2): - fHandle->read(commandBuffer, 0x50); + in.read(commandBuffer, 0x50); renderer->setCommand(commandBuffer); // At 0x22F9 (i.e. 0x22A9 + 0x50): - renderer->_cmdY = fHandle->readUint16BE(); + renderer->_cmdY = in.readUint16BE(); // At 0x22FB: - bgVar0 = fHandle->readUint16BE(); + bgVar0 = in.readUint16BE(); // At 0x22FD: - allowPlayerInput = fHandle->readUint16BE(); + allowPlayerInput = in.readUint16BE(); // At 0x22FF: - playerCommand = fHandle->readSint16BE(); + playerCommand = in.readSint16BE(); // At 0x2301: - commandVar1 = fHandle->readSint16BE(); + commandVar1 = in.readSint16BE(); // At 0x2303: - isDrawCommandEnabled = fHandle->readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); // At 0x2305: - var5 = fHandle->readUint16BE(); + var5 = in.readUint16BE(); // At 0x2307: - var4 = fHandle->readUint16BE(); + var4 = in.readUint16BE(); // At 0x2309: - var3 = fHandle->readUint16BE(); + var3 = in.readUint16BE(); // At 0x230B: - var2 = fHandle->readUint16BE(); + var2 = in.readUint16BE(); // At 0x230D: - commandVar2 = fHandle->readSint16BE(); + commandVar2 = in.readSint16BE(); // At 0x230F: - renderer->_messageBg = fHandle->readUint16BE(); + renderer->_messageBg = in.readUint16BE(); // At 0x2311: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2313: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2315: - loadResourcesFromSave(*fHandle, broken); - - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); - } - - loadBgIncrustFromSave(*fHandle); + loadResourcesFromSave(in, saveGameFormat); - delete fHandle; + 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; @@ -630,137 +1001,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -/*! \todo Add support for saving the zoneQuery table (Operation Stealth specific) - */ -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); } @@ -901,6 +1274,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 @@ -1567,7 +2023,7 @@ void checkForPendingDataLoad(void) { // fixes a crash when failing copy protection in Amiga or Atari ST // versions of Future Wars. if (loadPrcOk) { - addScriptToList0(1); + 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). @@ -1740,6 +2196,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele const int8 *ptr2; int16 di; + 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); + // In the original an error string is set and 0 is returned if the following doesn't hold assert(ptr); @@ -1845,6 +2304,19 @@ void processSeqListElement(SeqListElement &element) { 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++; diff --git a/engines/cine/various.h b/engines/cine/various.h index 3befde7eb8..5f24d502be 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; 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/drascula.cpp b/engines/drascula/drascula.cpp index 1cbe2ae0e1..7c843892b6 100644 --- a/engines/drascula/drascula.cpp +++ b/engines/drascula/drascula.cpp @@ -457,13 +457,13 @@ bool DrasculaEngine::runCurrentChapter() { playMusic(roomMusic); } + delay(25); updateEvents(); if (menuScreen == 0 && takeObject == 1) checkObjects(); if (rightMouseButton == 1 && menuScreen == 1) { - delay(100); if (currentChapter == 2) loadPic(menuBackground, backSurface); else @@ -473,7 +473,6 @@ bool DrasculaEngine::runCurrentChapter() { updateEvents(); } if (rightMouseButton == 1 && menuScreen == 0) { - delay(100); characterMoved = 0; if (trackProtagonist == 2) trackProtagonist = 1; @@ -491,10 +490,8 @@ bool DrasculaEngine::runCurrentChapter() { } if (leftMouseButton == 1 && menuBar == 1) { - delay(100); selectVerbFromBar(); } else if (leftMouseButton == 1 && takeObject == 0) { - delay(100); if (verify1()) return true; } else if (leftMouseButton == 1 && takeObject == 1) { @@ -987,9 +984,14 @@ char ***DrasculaEngine::loadTexts(Common::File &in) { } void DrasculaEngine::freeTexts(char ***ptr) { + if (!ptr) + return; + for (int lang = 0; lang < _numLangs; lang++) { - free(ptr[lang][0] - DATAALIGNMENT); - free(ptr[lang]); + if (ptr[lang]) { + free(ptr[lang][0] - DATAALIGNMENT); + free(ptr[lang]); + } } free(ptr); } diff --git a/engines/drascula/interface.cpp b/engines/drascula/interface.cpp index 6e86788007..ef1d1cc7a3 100644 --- a/engines/drascula/interface.cpp +++ b/engines/drascula/interface.cpp @@ -114,7 +114,8 @@ void DrasculaEngine::clearMenu() { } void DrasculaEngine::enterName() { - Common::KeyCode key; + Common::KeyCode key, prevkey = Common::KEYCODE_INVALID; + int counter = 0; int v = 0, h = 0; char select2[23]; strcpy(select2, " "); @@ -123,17 +124,25 @@ void DrasculaEngine::enterName() { copyBackground(115, 14, 115, 14, 176, 9, bgSurface, screenSurface); print_abc(select2, 117, 15); updateScreen(); + _system->delayMillis(100); + key = getScan(); - delay(70); - if (key != 0) { + + // Artifically decrease repeat rate. + // Part of bug fix#2017432 DRASCULA: Typing is slow when you save a game + // Alternative is to roll our own event loop + if (key == prevkey) + if (++counter == 3) { + counter = 0; + prevkey = Common::KEYCODE_INVALID; + } + + if (key != 0 && key != prevkey) { + prevkey = key; if (key >= 0 && key <= 0xFF && isalpha(key)) select2[v] = tolower(key); - else if ((key == Common::KEYCODE_LCTRL) || (key == Common::KEYCODE_RCTRL)) - select2[v] = '\164'; - else if (key >= Common::KEYCODE_0 && key <= Common::KEYCODE_9) + else if ((key >= Common::KEYCODE_0 && key <= Common::KEYCODE_9) || key == Common::KEYCODE_SPACE) select2[v] = key; - else if (key == Common::KEYCODE_SPACE) - select2[v] = '\167'; else if (key == Common::KEYCODE_ESCAPE) break; else if (key == Common::KEYCODE_RETURN) { diff --git a/engines/drascula/sound.cpp b/engines/drascula/sound.cpp index 840d6c7cb5..2eb40e2e30 100644 --- a/engines/drascula/sound.cpp +++ b/engines/drascula/sound.cpp @@ -37,23 +37,25 @@ void DrasculaEngine::updateVolume(Audio::Mixer::SoundType soundType, int prevVol } void DrasculaEngine::volumeControls() { - int masterVolume, voiceVolume, musicVolume; - copyRect(1, 56, 73, 63, 177, 97, tableSurface, screenSurface); updateScreen(73, 63, 73, 63, 177, 97, screenSurface); - masterVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4); - voiceVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4); - musicVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4); - for (;;) { + int masterVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16), 0, 15); + int voiceVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16), 0, 15); + int musicVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16), 0, 15); + + int masterVolumeY = 72 + 61 - masterVolume * 4; + int voiceVolumeY = 72 + 61 - voiceVolume * 4; + int musicVolumeY = 72 + 61 - musicVolume * 4; + updateRoom(); copyRect(1, 56, 73, 63, 177, 97, tableSurface, screenSurface); - copyBackground(183, 56, 82, masterVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4), tableSurface, screenSurface); - copyBackground(183, 56, 138, voiceVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4), tableSurface, screenSurface); - copyBackground(183, 56, 194, musicVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4), tableSurface, screenSurface); + copyBackground(183, 56, 82, masterVolumeY, 39, 2 + masterVolume * 4, tableSurface, screenSurface); + copyBackground(183, 56, 138, voiceVolumeY, 39, 2 + voiceVolume * 4, tableSurface, screenSurface); + copyBackground(183, 56, 194, musicVolumeY, 39, 2 + musicVolume * 4, tableSurface, screenSurface); setCursorTable(); @@ -68,18 +70,15 @@ void DrasculaEngine::volumeControls() { if (leftMouseButton == 1) { delay(100); if (mouseX > 80 && mouseX < 121) { - updateVolume(Audio::Mixer::kPlainSoundType, mouseY); - masterVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4); + updateVolume(Audio::Mixer::kPlainSoundType, masterVolumeY); } if (mouseX > 136 && mouseX < 178) { - updateVolume(Audio::Mixer::kSFXSoundType, mouseY); - voiceVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4); + updateVolume(Audio::Mixer::kSFXSoundType, voiceVolumeY); } if (mouseX > 192 && mouseX < 233) { - updateVolume(Audio::Mixer::kMusicSoundType, mouseY); - musicVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4); + updateVolume(Audio::Mixer::kMusicSoundType, musicVolumeY); } } 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/gob.cpp b/engines/gob/gob.cpp index a8cbfa0550..d64ce3c9cc 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -141,6 +141,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 842a1dc59f..485389f990 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, @@ -227,6 +232,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/inter.cpp b/engines/gob/inter.cpp index c0a1bfc21a..4973bd756d 100644 --- a/engines/gob/inter.cpp +++ b/engines/gob/inter.cpp @@ -296,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/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/kyra/detection.cpp b/engines/kyra/detection.cpp index fce1e93bc2..f8aa44be63 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" @@ -55,6 +56,7 @@ namespace { #define KYRA2_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA2) #define KYRA2_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA2) #define KYRA2_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_KYRA2) +#define KYRA2_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, false, false, Kyra::GI_KYRA2) #define KYRA2_CD_DEMO_FLAGS FLAGS(true, false, true, false, false, false, Kyra::GI_KYRA2) #define KYRA2_DEMO_FLAGS FLAGS(true, false, false, false, false, false, Kyra::GI_KYRA2) #define KYRA2_TOWNS_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA2) @@ -64,6 +66,11 @@ namespace { #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) +#define LOL_PC98_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_LOL) +#define LOL_PC98_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_LOL) +#define LOL_DEMO_FLAGS FLAGS(true, false, false, false, false, false, Kyra::GI_KYRA2) + const KYRAGameDescription adGameDescs[] = { { { @@ -207,7 +214,7 @@ const KYRAGameDescription adGameDescs[] = { { // FM-Towns version { "kyra1", - 0, + "CD", { { "EMC.PAK", 0, "a046bb0b422061aab8e4c4689400343a", -1 }, { "TWMUSIC.PAK", 0, "e53bca3a3e3fb49107d59463ec387a59", -1 }, @@ -215,14 +222,14 @@ const KYRAGameDescription adGameDescs[] = { }, Common::EN_ANY, Common::kPlatformFMTowns, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA1_TOWNS_FLAGS }, { { "kyra1", - 0, + "CD", { { "JMC.PAK", 0, "9c5707a2a478e8167e44283246612d2c", -1 }, { "TWMUSIC.PAK", 0, "e53bca3a3e3fb49107d59463ec387a59", -1 }, @@ -230,7 +237,7 @@ const KYRAGameDescription adGameDescs[] = { }, Common::JA_JPN, Common::kPlatformFMTowns, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA1_TOWNS_SJIS_FLAGS }, @@ -418,6 +425,41 @@ const KYRAGameDescription adGameDescs[] = { KYRA2_CD_FLAGS }, + // Italian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3" + { // CD version + { + "kyra2", + "CD", + AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"), + Common::IT_ITA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY) + }, + { + { + "kyra2", + "CD", + AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"), + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY) + }, + { + { + "kyra2", + "CD", + AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"), + Common::FR_FRA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY) + }, + { // Interactive Demo { "kyra2", @@ -454,11 +496,11 @@ const KYRAGameDescription adGameDescs[] = { KYRA2_CD_DEMO_FLAGS }, - { // Non-Interactive Demo + { // Non-Interactive Demos { "kyra2", "Demo", - AD_ENTRY1("GENERAL.PAK", "35825783e5b60755fd520360079f9c15"), + AD_ENTRY1("VOC.PAK", "ecb3561b63749158172bf21528cf5f45"), Common::EN_ANY, Common::kPlatformPC, Common::ADGF_DEMO @@ -469,44 +511,44 @@ const KYRAGameDescription adGameDescs[] = { { // FM-Towns { "kyra2", - 0, + "CD", AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), Common::EN_ANY, Common::kPlatformFMTowns, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA2_TOWNS_FLAGS }, { { "kyra2", - 0, + "CD", AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), Common::JA_JPN, Common::kPlatformFMTowns, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA2_TOWNS_SJIS_FLAGS }, { // PC-9821 { "kyra2", - 0, + "CD", AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), Common::EN_ANY, Common::kPlatformPC98, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA2_TOWNS_FLAGS }, { { "kyra2", - 0, + "CD", AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), Common::JA_JPN, Common::kPlatformPC98, - Common::ADGF_NO_FLAGS + Common::ADGF_CD }, KYRA2_TOWNS_SJIS_FLAGS }, @@ -700,6 +742,151 @@ const KYRAGameDescription adGameDescs[] = { }, 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 + }, + + { + { + "lol", + "CD", + { + { "GENERAL.PAK", 0, "9e4bab499b7ea9337b91ac29fcba6d13", -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, "9e4bab499b7ea9337b91ac29fcba6d13", -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, "9e4bab499b7ea9337b91ac29fcba6d13", -1 }, + { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, + { 0, 0, 0, 0 } + }, + Common::FR_FRA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + LOL_CD_FLAGS + }, + + /*{ + { + "lol", + 0, + { + { "GENERAL.PAK", 0, "3fe6539b9b09084c0984eaf7170464e9", -1 }, + { "MUS.PAK", 0, "008dc69d8cbcdb6bae30e270fab26e76", -1 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, + Common::kPlatformPC98, + Common::ADGF_NO_FLAGS + }, + LOL_PC98_FLAGS + }, + + { + { + "lol", + 0, + { + { "GENERAL.PAK", 0, "3fe6539b9b09084c0984eaf7170464e9", -1 }, + { "MUS.PAK", 0, "008dc69d8cbcdb6bae30e270fab26e76", -1 }, + { 0, 0, 0, 0 } + }, + Common::JA_JPN, + Common::kPlatformPC98, + Common::ADGF_NO_FLAGS + }, + LOL_PC98_SJIS_FLAGS + },*/ + + { + { + "lol", + "Demo", + { + { "GENERAL.PAK", 0, "e94863d86c4597a2d581d05481c152ba", -1 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + LOL_DEMO_FLAGS + }, + { AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) } }; @@ -707,6 +894,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 } }; @@ -779,6 +967,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_lok.cpp b/engines/kyra/gui_lok.cpp index 092aaedb23..818d2f9b4e 100644 --- a/engines/kyra/gui_lok.cpp +++ b/engines/kyra/gui_lok.cpp @@ -526,7 +526,7 @@ int GUI_LoK::resumeGame(Button *button) { void GUI_LoK::setupSavegames(Menu &menu, int num) { Common::InSaveFile *in; - static char savenames[5][31]; + static char savenames[5][35]; uint8 startSlot; assert(num <= 5); @@ -545,7 +545,8 @@ void GUI_LoK::setupSavegames(Menu &menu, int num) { KyraEngine_v1::SaveHeader header; for (int i = startSlot; i < num && uint(_savegameOffset + i) < _saveSlots.size(); i++) { if ((in = _vm->openSaveForReading(_vm->getSavegameFilename(_saveSlots[i + _savegameOffset]), header))) { - strncpy(savenames[i], header.description.c_str(), 31); + strncpy(savenames[i], header.description.c_str(), ARRAYSIZE(savenames[0])); + savenames[i][34] = 0; menu.item[i].itemString = savenames[i]; menu.item[i].enabled = 1; menu.item[i].saveSlot = _saveSlots[i + _savegameOffset]; @@ -669,7 +670,7 @@ void GUI_LoK::updateSavegameString() { length = strlen(_savegameName); if (_keyPressed.ascii > 31 && _keyPressed.ascii < 127) { - if (length < 31) { + if (length < ARRAYSIZE(_savegameName)-1) { _savegameName[length] = _keyPressed.ascii; _savegameName[length+1] = 0; redrawTextfield(); diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h index 49081c7ae2..16b7ef9183 100644 --- a/engines/kyra/gui_lok.h +++ b/engines/kyra/gui_lok.h @@ -162,7 +162,7 @@ private: bool _menuRestoreScreen; uint8 _toplevelMenu; int _savegameOffset; - char _savegameName[31]; + char _savegameName[35]; const char *_specialSavegameString; Common::KeyState _keyPressed; int8 _mouseWheel; diff --git a/engines/kyra/gui_v2.cpp b/engines/kyra/gui_v2.cpp index 11f430040c..a7ae2a6c44 100644 --- a/engines/kyra/gui_v2.cpp +++ b/engines/kyra/gui_v2.cpp @@ -456,6 +456,7 @@ void GUI_v2::setupSavegameNames(Menu &menu, int num) { for (int i = startSlot; i < num && uint(_savegameOffset + i) < _saveSlots.size(); ++i) { if ((in = _vm->openSaveForReading(_vm->getSavegameFilename(_saveSlots[i + _savegameOffset]), header)) != 0) { strncpy(getTableString(menu.item[i].itemId), header.description.c_str(), 80); + getTableString(menu.item[i].itemId)[79] = 0; menu.item[i].saveSlot = _saveSlots[i + _savegameOffset]; menu.item[i].enabled = true; delete in; diff --git a/engines/kyra/gui_v2.h b/engines/kyra/gui_v2.h index 161752627b..60b7f0ab86 100644 --- a/engines/kyra/gui_v2.h +++ b/engines/kyra/gui_v2.h @@ -188,7 +188,7 @@ protected: // save menu bool _noSaveProcess; int _saveSlot; - char _saveDescription[0x50]; + char _saveDescription[0x51]; int saveMenu(Button *caller); int clickSaveSlot(Button *caller); diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp index ac69272ef4..fd14e8c909 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) { @@ -251,7 +251,7 @@ int KyraEngine_HoF::init() { _abortIntroFlag = false; if (_sequenceStrings) { - for (int i = 0; i < 33; i++) + for (int i = 0; i < MIN(33, _sequenceStringsSize); i++) _sequenceStringsDuration[i] = (int) strlen(_sequenceStrings[i]) * 8; } @@ -278,7 +278,10 @@ int KyraEngine_HoF::go() { seq_showStarcraftLogo(); if (_flags.isDemo && !_flags.isTalkie) { - seq_playSequences(kSequenceDemoVirgin, kSequenceDemoFisher); + if (_flags.gameID == GI_LOL) + seq_playSequences(kSequenceLolDemoScene1, kSequenceLolDemoScene6); + else + seq_playSequences(kSequenceDemoVirgin, kSequenceDemoFisher); _menuChoice = 4; } else { seq_playSequences(kSequenceVirgin, kSequenceZanfaun); diff --git a/engines/kyra/kyra_hof.h b/engines/kyra/kyra_hof.h index 866dd55d16..279e9e35a6 100644 --- a/engines/kyra/kyra_hof.h +++ b/engines/kyra/kyra_hof.h @@ -97,6 +97,20 @@ enum kNestedSequencesDemo { kSequenceDemoDig }; +enum kSequencesLolDemo { + kSequenceLolDemoScene1 = 0, + kSequenceLolDemoText1, + kSequenceLolDemoScene2, + kSequenceLolDemoText2, + kSequenceLolDemoScene3, + kSequenceLolDemoText3, + kSequenceLolDemoScene4, + kSequenceLolDemoText4, + kSequenceLolDemoScene5, + kSequenceLolDemoText5, + kSequenceLolDemoScene6 +}; + class WSAMovie_v2; class KyraEngine_HoF; class TextDisplayer_HoF; @@ -242,6 +256,14 @@ protected: int seq_demoBail(WSAMovie_v2 *wsaObj, int x, int y, int frm); int seq_demoDig(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene1(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene2(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene3(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene4(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene5(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoText5(WSAMovie_v2 *wsaObj, int x, int y, int frm); + int seq_lolDemoScene6(WSAMovie_v2 *wsaObj, int x, int y, int frm); + void seq_sequenceCommand(int command); void seq_loadNestedSequence(int wsaNum, int seqNum); void seq_nestedSequenceFrame(int command, int wsaNum); @@ -264,7 +286,7 @@ protected: WSAMovie_v2 * wsa, int firstframe, int lastframe, int wsaXpos, int wsaYpos); void seq_finaleActorScreen(); void seq_displayScrollText(uint8 *data, const ScreenDim *d, int tempPage1, int tempPage2, int speed, int step, Screen::FontId fid1, Screen::FontId fid2, const uint8 *shapeData = 0, const char *const *specialData = 0); - void seq_scrollPage(); + void seq_scrollPage(int bottom, int top); void seq_showStarcraftLogo(); void seq_init(); diff --git a/engines/kyra/kyra_lok.cpp b/engines/kyra/kyra_lok.cpp index afd164958c..f668ae8401 100644 --- a/engines/kyra/kyra_lok.cpp +++ b/engines/kyra/kyra_lok.cpp @@ -173,7 +173,8 @@ int KyraEngine_LoK::init() { initStaticResource(); - _sound->setSoundList(&_soundData[kMusicIntro]); + if (_soundData) + _sound->setSoundList(&_soundData[kMusicIntro]); _trackMap = _dosTrackMap; _trackMapSize = _dosTrackMapSize; @@ -316,7 +317,8 @@ void KyraEngine_LoK::startup() { debugC(9, kDebugLevelMain, "KyraEngine_LoK::startup()"); static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 }; _screen->setTextColorMap(colorMap); - _sound->setSoundList(&_soundData[kMusicIngame]); + if (_soundData) + _sound->setSoundList(&_soundData[kMusicIngame]); _sound->loadSoundFile(0); // _screen->setFont(Screen::FID_6_FNT); _screen->setAnimBlockPtr(3750); diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index 6121f6979c..f3014911fc 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -112,7 +112,7 @@ int KyraEngine_v1::init() { _sound = new SoundTownsPC98_v2(this, _mixer); } else if (_flags.platform == Common::kPlatformPC98) { if (_flags.gameID == GI_KYRA1) - _sound = new SoundTowns/*SoundPC98*/(this, _mixer); + _sound = new SoundPC98(this, _mixer); else _sound = new SoundTownsPC98_v2(this, _mixer); } else if (midiDriver == MD_ADLIB) { @@ -150,6 +150,16 @@ int KyraEngine_v1::init() { _res = new Resource(this); assert(_res); _res->reset(); + + if (_flags.isDemo) { + // HACK: check whether this is the HOF demo or the LOL demo. + // The LOL demo needs to be detected and run as KyraEngine_HoF, + // but the static resource loader and the sequence player will + // need correct IDs. + if (_res->exists("scene1.cps")) + _flags.gameID = GI_LOL; + } + _staticres = new StaticResource(this); assert(_staticres); if (!_staticres->init()) diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index 8dd95c79b9..6d61bd997f 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -34,8 +34,8 @@ #include "kyra/script.h" namespace Common { -class InSaveFile; -class OutSaveFile; +class SeekableReadStream; +class WriteStream; } // end of namespace Common class KyraMetaEngine; @@ -64,7 +64,8 @@ struct GameFlags { enum { GI_KYRA1 = 0, GI_KYRA2 = 1, - GI_KYRA3 = 2 + GI_KYRA3 = 2, + GI_LOL = 4 }; struct AudioDataStruct { @@ -293,10 +294,10 @@ protected: kRSHEIoError = 3 }; - static kReadSaveHeaderError readSaveHeader(Common::InSaveFile *file, SaveHeader &header); + static kReadSaveHeaderError readSaveHeader(Common::SeekableReadStream *file, SaveHeader &header); - Common::InSaveFile *openSaveForReading(const char *filename, SaveHeader &header); - Common::OutSaveFile *openSaveForWriting(const char *filename, const char *saveName) const; + Common::SeekableReadStream *openSaveForReading(const char *filename, SaveHeader &header); + Common::WriteStream *openSaveForWriting(const char *filename, const char *saveName) const; }; } // End of namespace Kyra 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..5d3c5ff715 100644 --- a/engines/kyra/resource.cpp +++ b/engines/kyra/resource.cpp @@ -99,6 +99,8 @@ bool Resource::reset() { loadFileList("FILEDATA.FDT"); return true; + } else if (_vm->game() == GI_LOL) { + return true; } FSList fslist; @@ -1120,7 +1122,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 +1187,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/saveload.cpp b/engines/kyra/saveload.cpp index 147c774a52..0dc7cf2c02 100644 --- a/engines/kyra/saveload.cpp +++ b/engines/kyra/saveload.cpp @@ -95,6 +95,9 @@ KyraEngine_v1::kReadSaveHeaderError KyraEngine_v1::readSaveHeader(Common::InSave if (header.version <= 8) { char buffer[31]; in->read(buffer, 31); + // WORKAROUND: Old savegames could contain a missing termination 0 at the + // end so we manually add it. + buffer[30] = 0; header.description = buffer; } else { header.description = ""; diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp index 6e7a88b1a3..0cde066cc0 100644 --- a/engines/kyra/screen.cpp +++ b/engines/kyra/screen.cpp @@ -380,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; } @@ -448,6 +410,64 @@ 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; + + const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256) * 3; + for (int i = 0; i < colors; ++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; + const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256) * 3; + for (int i = 0; i < colors; ++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; @@ -459,7 +479,7 @@ void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) { void Screen::setScreenPalette(const uint8 *palData) { debugC(9, kDebugLevelScreen, "Screen::setScreenPalette(%p)", (const void *)palData); - int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256); + const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256); if (palData != _screenPalette) memcpy(_screenPalette, palData, colors*3); @@ -2442,7 +2462,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_hof.cpp b/engines/kyra/script_hof.cpp index 94f160d11f..e3a8bf95bc 100644 --- a/engines/kyra/script_hof.cpp +++ b/engines/kyra/script_hof.cpp @@ -1492,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 4b82232049..9b215b2c03 100644 --- a/engines/kyra/script_tim.cpp +++ b/engines/kyra/script_tim.cpp @@ -26,12 +26,14 @@ #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 } #define cmd_return(n) cmd_return_##n @@ -39,32 +41,32 @@ TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _s // 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)), @@ -80,6 +82,19 @@ TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _s _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) { @@ -139,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; @@ -175,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) { _currentTim->procFunc = _currentFunc; break; + case 22: + cur.loopIp = 0; + break; + default: break; } @@ -201,6 +225,205 @@ 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 from file '%s'", cmd, _currentTim->filename); @@ -212,11 +435,14 @@ int TIMInterpreter::execCommand(int cmd, const uint16 *param) { 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; @@ -230,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); @@ -247,6 +513,89 @@ 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))); + _vm->sound()->loadSoundFile(file); + 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) @@ -256,17 +605,23 @@ 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) 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; + } + return (*(*_currentTim->opcodes)[opcode])(_currentTim, param); } diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h index 39a1d90a44..68ef23fd6c 100644 --- a/engines/kyra/script_tim.h +++ b/engines/kyra/script_tim.h @@ -30,9 +30,12 @@ #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; @@ -52,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; @@ -63,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; } @@ -74,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; @@ -85,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 *); @@ -98,8 +145,20 @@ 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); diff --git a/engines/kyra/sequences_hof.cpp b/engines/kyra/sequences_hof.cpp index 7e0220af8a..b42374f44f 100644 --- a/engines/kyra/sequences_hof.cpp +++ b/engines/kyra/sequences_hof.cpp @@ -50,7 +50,7 @@ void KyraEngine_HoF::seq_playSequences(int startSeq, int endSeq) { _sound->setSoundList(&_soundData[(startSeq > kSequenceZanfaun) ? kMusicFinale : kMusicIntro]); _sound->loadSoundFile(0); - _screen->_charWidth = -2; + _screen->_charWidth = (_flags.gameID == GI_LOL) ? 0 : -2; memset(_activeWSA, 0, sizeof(ActiveWSA) * 8); for (int i = 0; i < 8; ++i) @@ -300,8 +300,8 @@ void KyraEngine_HoF::seq_playSequences(int startSeq, int endSeq) { _eventList.clear(); seqNum = kSequenceFirates; } - } else if (seqNum == kSequenceDemoFisher && !(_abortIntroFlag || skipFlag())) { - seqNum = kSequenceDemoVirgin; + } else if (seqNum == endSeq && !(_abortIntroFlag || skipFlag())) { + seqNum = 0; } if (_menuChoice) { @@ -1722,7 +1722,7 @@ int KyraEngine_HoF::seq_demoFisher(WSAMovie_v2 *wsaObj, int x, int y, int frm) { _seqScrollTextCounter = 0; } - seq_scrollPage(); + seq_scrollPage(24, 144); _seqFrameCounter++; if (_seqFrameCounter < 0x256 || _seqFrameCounter > 0x31c) { if (_seqFrameCounter < 0x174 || _seqFrameCounter > 0x1d7) { @@ -1740,7 +1740,7 @@ int KyraEngine_HoF::seq_demoFisher(WSAMovie_v2 *wsaObj, int x, int y, int frm) { } } else { - seq_scrollPage(); + seq_scrollPage(24, 144); } return 0; } @@ -1796,6 +1796,182 @@ int KyraEngine_HoF::seq_demoDig(WSAMovie_v2 *wsaObj, int x, int y, int frm) { return frm; } +int KyraEngine_HoF::seq_lolDemoScene1(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + uint8 *tmpPal = _screen->getPalette(2); + + if (!(_seqFrameCounter % 100)) { + if (_seqFrameCounter == 0) { + _sound->haltTrack(); + _sound->playTrack(6); + } + memcpy(tmpPal, _screen->getPalette(0), 0x300); + for (int i = 3; i < 0x300; i++) { + tmpPal[i] = ((int)tmpPal[i] * 120) / 64; + if (tmpPal[i] > 0x3f) + tmpPal[i] = 0x3f; + } + seq_playTalkText(_rnd.getRandomBit()); + _screen->setScreenPalette(tmpPal); + _screen->updateScreen(); + delay(8); + } else { + _screen->setScreenPalette(_screen->getPalette(0)); + _screen->updateScreen(); + if (_seqFrameCounter == 40) + seq_playTalkText(3); + } + + _seqFrameCounter++; + return frm; +} + +int KyraEngine_HoF::seq_lolDemoScene2(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + switch (_seqFrameCounter - 17) { + case 0: + _seqFrameDelay = 8; + break; + case 3: + case 6: + case 9: + seq_playTalkText(8); + break; + case 15: + seq_playTalkText(9); + break; + case 18: + seq_playTalkText(2); + break; + default: + break; + } + _seqFrameCounter++; + return frm; +} + +int KyraEngine_HoF::seq_lolDemoScene3(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + if (_seqFrameCounter == 1) + seq_playTalkText(6); + else if (frm == 26) + seq_playTalkText(7); + + _seqFrameCounter++; + return frm; +} + +int KyraEngine_HoF::seq_lolDemoScene4(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + switch (_seqFrameCounter) { + case 11: + case 14: + case 17: + case 20: + seq_playTalkText(8); + break; + case 22: + seq_playTalkText(11); + break; + case 24: + seq_playTalkText(8); + break; + case 30: + seq_playTalkText(15); + break; + case 34: + seq_playTalkText(14); + break; + case 38: + seq_playTalkText(13); + break; + case 42: + seq_playTalkText(12); + break; + default: + break; + } + + _seqFrameCounter++; + return frm; +} + +int KyraEngine_HoF::seq_lolDemoScene5(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + switch (_seqFrameCounter++) { + case 0: + case 4: + case 6: + case 8: + case 10: + case 14: + case 16: + case 18: + case 20: + case 22: + case 24: + case 26: + case 28: + case 30: + seq_playTalkText(15); + break; + case 32: + seq_playTalkText(16); + break; + case 42: + seq_playTalkText(6); + break; + default: + break; + } + return frm; +} + +int KyraEngine_HoF::seq_lolDemoText5(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + if (_seqFrameCounter++ == 100) + seq_playTalkText(5); + return frm; +} + +int KyraEngine_HoF::seq_lolDemoScene6(WSAMovie_v2 *wsaObj, int x, int y, int frm) { + while (_seqScrollTextCounter < 0x122) { + _seqEndTime = _system->getMillis() + 6 * _tickLength; + if (!_seqFrameCounter) { + _screen->loadBitmap("adtext.cps", 4, 4, 0); + _screen->loadBitmap("adtext2.cps", 6, 6, 0); + _screen->copyPageMemory(6, 0, 4, 64000, 1024); + _screen->copyPageMemory(6, 1023, 6, 0, 64000); + _seqScrollTextCounter = 0; + } + + if (_seqFrameCounter % 175) { + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + uint8 *tmpPal = _screen->getPalette(2); + memcpy(tmpPal, _screen->getPalette(0), 0x300); + for (int i = 3; i < 0x300; i++) { + tmpPal[i] = ((int)tmpPal[i] * 120) / 64; + if (tmpPal[i] > 0x3f) + tmpPal[i] = 0x3f; + } + seq_playTalkText(_rnd.getRandomBit()); + _screen->setScreenPalette(tmpPal); + _screen->updateScreen(); + delay(8); + } + + if (_seqFrameCounter == 40 || _seqFrameCounter == 80 || _seqFrameCounter == 150 || _seqFrameCounter == 300) + seq_playTalkText(3); + + _screen->copyPage(12, 2); + seq_scrollPage(70, 130); + _screen->copyPage(2, 0); + _screen->updateScreen(); + _seqFrameCounter++; + if (_seqFrameCounter < 128 || _seqFrameCounter > 207) + _seqScrollTextCounter++; + delayUntil(_seqEndTime); + } + _screen->copyPage(2, 12); + + return 0; +} + uint32 KyraEngine_HoF::seq_activeTextsTimeLeft() { uint32 res = 0; @@ -1892,16 +2068,14 @@ void KyraEngine_HoF::seq_sequenceCommand(int command) { switch (command) { case 0: memset(pal, 0, 0x300); - _screen->fadePalette(pal, 16); + _screen->fadePalette(pal, 36); memcpy (_screen->getPalette(0), pal, 0x300); memcpy (_screen->getPalette(1), pal, 0x300); break; case 1: memset(pal, 0x3F, 0x300); - //////////XXX - //////////Unused anyway (at least by fm-towns intro/outro) - + seq_playTalkText(_rnd.getRandomBit()); _screen->fadePalette(pal, 16); memcpy (_screen->getPalette(0), pal, 0x300); memcpy (_screen->getPalette(1), pal, 0x300); @@ -2575,32 +2749,34 @@ void KyraEngine_HoF::seq_displayScrollText(uint8 *data, const ScreenDim *d, int delete[] textData; } -void KyraEngine_HoF::seq_scrollPage() { +void KyraEngine_HoF::seq_scrollPage(int bottom, int top) { int dstY, dstH, srcH; static const ScreenDim d = { 0x00, 0x00, 0x28, 0x320, 0xFF, 0xFE, 0x00, 0x00 }; - if (_seqScrollTextCounter - 143 < 0) { - dstY = 144 - _seqScrollTextCounter; + if (_seqScrollTextCounter - (top - 1) < 0) { + dstY = top - _seqScrollTextCounter; dstH = _seqScrollTextCounter; srcH = 0; } else { dstY = 0; - srcH = _seqScrollTextCounter - 144; - dstH = (400 - srcH <= 144) ? 400 - srcH : 144; + srcH = _seqScrollTextCounter - top; + dstH = (400 - srcH <= top) ? 400 - srcH : top; } if (dstH > 0) { - for (int i = 0; i < 4; i++) { - const ItemAnimData_v1 *def = &_demoAnimData[i]; - ActiveItemAnim *a = &_activeItemAnim[i]; - - _screen->fillRect(12, def->y - 8, 28, def->y + 8, 0, 4); - _screen->drawShape(4, getShapePtr(def->itemIndex + def->frames[a->currentFrame]), 12, def->y - 8, 0, 0); - if(_seqFrameCounter % 2 == 0) - a->currentFrame = ++a->currentFrame % 20; + if (_demoAnimData) { + for (int i = 0; i < 4; i++) { + const ItemAnimData_v1 *def = &_demoAnimData[i]; + ActiveItemAnim *a = &_activeItemAnim[i]; + + _screen->fillRect(12, def->y - 8, 28, def->y + 8, 0, 4); + _screen->drawShape(4, getShapePtr(def->itemIndex + def->frames[a->currentFrame]), 12, def->y - 8, 0, 0); + if(_seqFrameCounter % 2 == 0) + a->currentFrame = ++a->currentFrame % 20; + } } - _screen->copyRegionEx(4, 0, srcH, 2, 2, dstY + 24, 320, dstH, &d); + _screen->copyRegionEx(4, 0, srcH, 2, 2, dstY + bottom, 320, dstH, &d); } } @@ -2656,6 +2832,10 @@ void KyraEngine_HoF::seq_init() { _res->loadFileList(_sequencePakList, _sequencePakListSize); int numShp = -1; + + if (_flags.gameID == GI_LOL) + return; + if (_flags.isDemo && !_flags.isTalkie) { _demoAnimData = _staticres->loadShapeAnimData_v1(k2SeqplayShapeAnimData, _itemAnimDataSize); uint8 *shp = _res->fileData("icons.shp", 0); diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index 5068268d99..073639e4ca 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -104,6 +104,11 @@ int32 Sound::voicePlay(const char *file, bool isSfx) { fileSize = 0; } + if (!audioStream) { + warning("Couldn't load sound file '%s'", file); + return 0; + } + _soundChannels[h].file = file; _mixer->playInputStream(isSfx ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType, &_soundChannels[h].channelHandle, audioStream); @@ -202,8 +207,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); } @@ -323,7 +328,17 @@ struct DeleterArray { void SoundMidiPC::loadSoundFile(uint file) { Common::StackLock lock(_mutex); - Common::String filename = fileListEntry(file); + internalLoadFile(fileListEntry(file)); +} + +void SoundMidiPC::loadSoundFile(Common::String file) { + Common::StackLock lock(_mutex); + + internalLoadFile(file); +} + +void SoundMidiPC::internalLoadFile(Common::String file) { + Common::String filename = file; filename += "."; filename += _useC55 ? "C55" : "XMI"; diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index cebfdf491f..e5294eb15d 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -120,6 +120,12 @@ public: virtual void loadSoundFile(uint file) = 0; /** + * Load a sound file for playing music + * and sound effects from. + */ + virtual void loadSoundFile(Common::String file) = 0; + + /** * Plays the specified track. * * @param track track number @@ -215,8 +221,6 @@ protected: int _musicEnabled; bool _sfxEnabled; - int _currentTheme; - KyraEngine_v1 *_vm; Audio::Mixer *_mixer; @@ -260,6 +264,7 @@ public: void process(); void loadSoundFile(uint file); + void loadSoundFile(Common::String file); void playTrack(uint8 track); void haltTrack(); @@ -269,6 +274,8 @@ public: void beginFadeOut(); private: + void internalLoadFile(Common::String file); + void play(uint8 track); void unk1(); @@ -280,7 +287,8 @@ private: uint8 _trackEntries[500]; uint8 *_soundDataPtr; int _sfxPlayingSound; - uint _soundFileLoaded; + + Common::String _soundFileLoaded; uint8 _sfxPriority; uint8 _sfxFourthByteOfSong; @@ -316,6 +324,7 @@ public: void updateVolumeSettings(); void loadSoundFile(uint file); + void loadSoundFile(Common::String file); void playTrack(uint8 track); void haltTrack(); @@ -343,6 +352,7 @@ public: bool isMT32() const { return _nativeMT32; } private: + void internalLoadFile(Common::String file); void updateChannelVolume(uint8 vol); static void onTimer(void *data); @@ -397,6 +407,7 @@ public: void process(); void loadSoundFile(uint file); + void loadSoundFile(Common::String) {} void playTrack(uint8 track); void haltTrack(); @@ -454,6 +465,7 @@ public: void process() {} void loadSoundFile(uint file) {} + void loadSoundFile(Common::String) {} void playTrack(uint8 track); void haltTrack(); @@ -480,6 +492,7 @@ public: void process(); void loadSoundFile(uint file) {} + void loadSoundFile(Common::String) {} void playTrack(uint8 track); void haltTrack(); @@ -513,6 +526,7 @@ public: void setSoundList(const AudioDataStruct * list) { _music->setSoundList(list); _sfx->setSoundList(list); } bool hasSoundFile(uint file) const { return _music->hasSoundFile(file) && _sfx->hasSoundFile(file); } void loadSoundFile(uint file) { _music->loadSoundFile(file); _sfx->loadSoundFile(file); } + void loadSoundFile(Common::String file) { _music->loadSoundFile(file); _sfx->loadSoundFile(file); } void playTrack(uint8 track) { _music->playTrack(track); } void haltTrack() { _music->haltTrack(); } diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index 68a2f0be9c..62551d2b09 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,12 +2220,12 @@ 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); _sfxPlayingSound = -1; - _soundFileLoaded = (uint)-1; + _soundFileLoaded.clear(); if (_v2) { // TODO: Figure out if Kyra 2 uses sound triggers at all. @@ -2262,7 +2269,7 @@ void SoundAdlibPC::playTrack(uint8 track) { // sync for each loop. To avoid that, we declare that all four // of the song channels have to jump "in sync". - if (track == 4 && scumm_stricmp(fileListEntry(_soundFileLoaded), "KYRA1B") == 0) + if (track == 4 && _soundFileLoaded.equalsIgnoreCase("KYRA1B.ADL")) _driver->setSyncJumpMask(0x000F); else _driver->setSyncJumpMask(0); @@ -2341,6 +2348,15 @@ void SoundAdlibPC::beginFadeOut() { } void SoundAdlibPC::loadSoundFile(uint file) { + internalLoadFile(fileListEntry(file)); +} + +void SoundAdlibPC::loadSoundFile(Common::String file) { + internalLoadFile(file); +} + +void SoundAdlibPC::internalLoadFile(Common::String file) { + file += ".ADL"; if (_soundFileLoaded == file) return; @@ -2349,12 +2365,9 @@ void SoundAdlibPC::loadSoundFile(uint file) { uint8 *file_data = 0; uint32 file_size = 0; - char filename[25]; - sprintf(filename, "%s.ADL", fileListEntry(file)); - - file_data = _vm->resource()->fileData(filename, &file_size); + file_data = _vm->resource()->fileData(file.c_str(), &file_size); if (!file_data) { - warning("Couldn't find music file: '%s'", filename); + warning("Couldn't find music file: '%s'", file.c_str()); return; } diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp index 0f2b916c9d..ec1962a58f 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -2363,7 +2363,7 @@ TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) : _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; + _baserate = (486202500.0 / (double)getRate()) / 10368.0; } TownsPC98_OpnDriver::~TownsPC98_OpnDriver() { diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index c05795dacd..7d7f0cd4b8 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" @@ -42,7 +43,7 @@ namespace Kyra { -#define RESFILE_VERSION 28 +#define RESFILE_VERSION 31 bool StaticResource::checkKyraDat() { Common::File kyraDat; @@ -278,6 +279,16 @@ bool StaticResource::init() { { 0, 0, 0 } }; + static const FilenameTable lolStaticRes[] = { + // Demo Sequence Player + { k2SeqplayPakFiles, kStringList, "S_PAKFILES.TXT" }, + { k2SeqplayStrings, kLanguageList, "S_STRINGS." }, + { k2SeqplaySfxFiles, kStringList, "S_SFXFILES.TXT" }, + { k2SeqplaySeqData, k2SeqData, "S_DATA.SEQ" }, + { k2SeqplayIntroTracks, kStringList, "S_INTRO.TRA" }, + { 0, 0, 0 } + }; + if (_vm->game() == GI_KYRA1) { _builtIn = 0; _filenameTable = kyra1StaticRes; @@ -287,8 +298,13 @@ bool StaticResource::init() { } else if (_vm->game() == GI_KYRA3) { _builtIn = 0; _filenameTable = kyra3StaticRes; + } else if (_vm->game() == GI_LOL) { + if (!_vm->gameFlags().isDemo) + return true; + _builtIn = 0; + _filenameTable = lolStaticRes; } else { - error("unknown game ID"); + error("StaticResource: Unknown game ID"); } char errorBuffer[100]; @@ -917,6 +933,8 @@ const char *StaticResource::getFilename(const char *name) { filename += ".K2"; else if (_vm->gameFlags().gameID == GI_KYRA3) filename += ".K3"; + else if (_vm->gameFlags().gameID == GI_LOL) + filename += ".LOL"; if (_vm->gameFlags().isTalkie && _vm->gameFlags().gameID != GI_KYRA3) filename += ".CD"; @@ -1034,10 +1052,8 @@ 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 }, @@ -1051,21 +1067,20 @@ void KyraEngine_LoK::initStaticResource() { { 0, 0, 0, 0} }; -#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*/; - + _soundData = soundData_PC98; + else + _soundData = 0; } void KyraEngine_LoK::loadMouseShapes() { @@ -1263,11 +1278,9 @@ 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 }, @@ -1281,20 +1294,18 @@ void KyraEngine_HoF::initStaticResource() { { fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 } }; -#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*/; + _soundData = soundData_PC98; // setup sequence data _sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize); @@ -1333,8 +1344,17 @@ void KyraEngine_HoF::initStaticResource() { &KyraEngine_HoF::seq_demoDig, 0 }; - _callbackS = (_flags.isDemo && !_flags.isTalkie) ? hofDemoSequenceCallbacks : hofSequenceCallbacks; - _callbackN = (_flags.isDemo && !_flags.isTalkie) ? hofDemoNestedSequenceCallbacks : hofNestedSequenceCallbacks; + static const SeqProc lolDemoSequenceCallbacks[] = { + &KyraEngine_HoF::seq_lolDemoScene1, 0, &KyraEngine_HoF::seq_lolDemoScene2, 0, + &KyraEngine_HoF::seq_lolDemoScene3, 0, &KyraEngine_HoF::seq_lolDemoScene4, 0, + &KyraEngine_HoF::seq_lolDemoScene5, &KyraEngine_HoF::seq_lolDemoText5, + &KyraEngine_HoF::seq_lolDemoScene6, 0 + }; + + static const SeqProc lolDemoNestedSequenceCallbacks[] = { 0 }; + + _callbackS = _flags.gameID == GI_LOL ? lolDemoSequenceCallbacks : ((_flags.isDemo && !_flags.isTalkie) ? hofDemoSequenceCallbacks : hofSequenceCallbacks); + _callbackN = _flags.gameID == GI_LOL ? lolDemoNestedSequenceCallbacks : ((_flags.isDemo && !_flags.isTalkie) ? hofDemoNestedSequenceCallbacks : hofNestedSequenceCallbacks); } void KyraEngine_MR::initStaticResource() { @@ -2236,5 +2256,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/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp index fab92dada9..bbd8bc9ca7 100644 --- a/engines/parallaction/balloons.cpp +++ b/engines/parallaction/balloons.cpp @@ -23,6 +23,8 @@ * */ +#include "common/util.h" + #include "parallaction/graphics.h" #include "parallaction/parallaction.h" @@ -76,6 +78,7 @@ class BalloonManager_ns : public BalloonManager { 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); @@ -149,12 +152,12 @@ int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w int16 w, h; - _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); int id = createBalloon(w+5, h, winding, 1); Balloon *balloon = &_intBalloons[id]; - _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + 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); @@ -169,12 +172,12 @@ int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textC int16 w, h; - _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); int id = createBalloon(w+5, h, winding, 1); Balloon *balloon = &_intBalloons[id]; - _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + 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); @@ -193,7 +196,7 @@ int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textC void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) { Balloon *balloon = getBalloon(id); balloon->surface->fillRect(balloon->innerBox, 1); - _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); } @@ -201,11 +204,11 @@ int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) { int16 w, h; - _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &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]; - _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH); + 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); @@ -242,8 +245,109 @@ void BalloonManager_ns::freeBalloons() { _numBalloons = 0; } +// TODO: get rid of parseNextToken from here. Use the +// StringTokenizer instead. +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); + } + +} +// TODO: get rid of parseNextToken from here. Use the +// StringTokenizer instead. +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; +} @@ -259,17 +363,36 @@ class BalloonManager_br : public BalloonManager { uint _numBalloons; - Frames *_leftBalloon; - Frames *_rightBalloon; 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); @@ -315,11 +438,11 @@ int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w Balloon *balloon = &_intBalloons[id]; if (winding == 0) { - src = _leftBalloon; + src = _rightBalloon; srcFrame = 0; } else if (winding == 1) { - src = _rightBalloon; + src = _leftBalloon; srcFrame = 0; } @@ -328,12 +451,12 @@ int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w balloon->surface = expandBalloon(src, srcFrame); src->getRect(srcFrame, balloon->box); -// drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + 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->x = x + balloon->box.left; + balloon->obj->y = y + balloon->box.top; balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; _numBalloons++; @@ -351,11 +474,11 @@ int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textC Balloon *balloon = &_intBalloons[id]; if (winding == 0) { - src = _leftBalloon; + src = _rightBalloon; srcFrame = id; } else if (winding == 1) { - src = _rightBalloon; + src = _leftBalloon; srcFrame = 0; } @@ -364,12 +487,12 @@ int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textC balloon->surface = expandBalloon(src, srcFrame); src->getRect(srcFrame, balloon->box); -// drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + 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 = 0; - balloon->obj->y = 10; + balloon->obj->x = balloon->box.left; + balloon->obj->y = balloon->box.top; balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; if (id > 0) { @@ -434,6 +557,155 @@ void BalloonManager_br::cacheAnims() { } } + +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) { } @@ -453,4 +725,6 @@ void Parallaction::setupBalloonManager() { } } + + } // namespace Parallaction diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp index ed60a193ce..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,7 +312,7 @@ void Parallaction_ns::_c_endComment(void *param) { g_system->delayMillis(20); } - _input->waitUntilLeftClick(); + _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; } @@ -467,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) { @@ -596,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/detection.cpp b/engines/parallaction/detection.cpp index da94e0c5d7..531bd8ee16 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 96491bf084..4f2343be64 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,6 +41,25 @@ 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 { @@ -78,6 +97,7 @@ class DialogueManager { bool _isKeyDown; uint16 _downKey; + BalloonPositions _ballonPos; public: DialogueManager(Parallaction *vm, ZonePtr z); @@ -112,6 +132,15 @@ protected: }; 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; @@ -168,16 +197,16 @@ bool DialogueManager::displayAnswers() { if (_askPassword) { resetPassword(); // _vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3); - int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); + 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, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); + 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, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); + 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; @@ -189,8 +218,8 @@ bool DialogueManager::displayAnswers() { bool DialogueManager::displayQuestion() { if (!scumm_stricmp(_q->_text, "NULL")) return false; - _vm->_balloonMan->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); return true; @@ -361,9 +390,6 @@ void DialogueManager::run() { break; case DIALOGUE_OVER: - if (_cmdList) { - _vm->_cmdExec->run(*_cmdList); - } break; default: @@ -383,6 +409,10 @@ void Parallaction::exitDialogueMode() { debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()"); _input->_inputMode = Input::kInputModeGame; + if (_dialogueMan->_cmdList) { + _vm->_cmdExec->run(*_dialogueMan->_cmdList); + } + // 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). diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h index 694d4efa6d..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" @@ -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); @@ -234,15 +250,33 @@ 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); @@ -254,6 +288,11 @@ public: 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 ee1e111139..11f67a2d58 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" @@ -90,49 +91,40 @@ 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() { @@ -141,45 +133,63 @@ DosDisk_br::~DosDisk_br() { 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 new GfxObj(0, createSprites(stream), name); + 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); } @@ -192,6 +202,7 @@ GfxObj* 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,20 +238,32 @@ 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); } 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) { @@ -247,13 +273,15 @@ void genSlidePath(char *path, 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 GfxObj(0, new SurfaceToFrames(surf), name); @@ -283,17 +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::File stream; - if (!stream.open(path)) { - sprintf(path, "%s/ani/%s.ani", _partPath, name); - if (!stream.open(path)) { - errorFileNotFound(path); + 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; + stream.open(node); return createSprites(stream); } @@ -305,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]; @@ -328,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]; @@ -350,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 @@ -363,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 @@ -380,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; @@ -410,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; @@ -483,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); @@ -548,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; } 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; @@ -576,13 +639,7 @@ GfxObj* AmigaDisk_br::loadStatic(const char* name) { 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); @@ -606,32 +663,163 @@ 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); } 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 new GfxObj(0, 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(); + 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/exec.h b/engines/parallaction/exec.h index 887d6be526..22e75744f1 100644 --- a/engines/parallaction/exec.h +++ b/engines/parallaction/exec.h @@ -47,14 +47,30 @@ 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) @@ -143,23 +159,23 @@ public: ~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; @@ -183,7 +199,7 @@ protected: DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); @@ -208,7 +224,6 @@ class ProgramExec_br : public ProgramExec_ns { protected: 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); @@ -228,7 +243,6 @@ protected: DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif); DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); public: void init(); diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp index edb832cffc..0b7400f0f7 100644 --- a/engines/parallaction/exec_br.cpp +++ b/engines/parallaction/exec_br.cpp @@ -61,8 +61,6 @@ namespace Parallaction { #define INST_STOP 30 #define INST_ENDSCRIPT 31 - - #define SetOpcodeTable(x) table = &x; typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1; @@ -73,6 +71,8 @@ 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); @@ -122,11 +122,19 @@ DECLARE_COMMAND_OPCODE(location) { 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); + } } @@ -165,12 +173,13 @@ DECLARE_COMMAND_OPCODE(call) { 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) { @@ -194,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) { DECLARE_COMMAND_OPCODE(onmouse) { - _vm->_input->showCursor(true); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); } DECLARE_COMMAND_OPCODE(offmouse) { - _vm->_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); } @@ -365,13 +374,6 @@ DECLARE_INSTRUCTION_OPCODE(set) { } -DECLARE_INSTRUCTION_OPCODE(loop) { - InstructionPtr inst = *_ctxt.inst; - - _ctxt.program->_loopCounter = inst->_opB.getRValue(); - _ctxt.program->_loopStart = _ctxt.inst; -} - DECLARE_INSTRUCTION_OPCODE(inc) { InstructionPtr inst = *_ctxt.inst; @@ -495,16 +497,6 @@ DECLARE_INSTRUCTION_OPCODE(stop) { warning("Parallaction_br::instOp_stop not yet implemented"); } -DECLARE_INSTRUCTION_OPCODE(endscript) { - if ((_ctxt.anim->_flags & kFlagsLooping) == 0) { - _ctxt.anim->_flags &= ~kFlagsActing; - _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim); - _ctxt.program->_status = kProgramDone; - } - _ctxt.program->_ip = _ctxt.program->_instructions.begin(); - - _ctxt.suspend = true; -} void CommandExec_br::init() { Common::Array<const Opcode*> *table = 0; @@ -576,7 +568,7 @@ void ProgramExec_br::init() { 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); @@ -602,6 +594,7 @@ void ProgramExec_br::init() { } ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) { + _instructionNames = _instructionNamesRes_br; } ProgramExec_br::~ProgramExec_br() { diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp index 913e7ae648..2235c4e98e 100644 --- a/engines/parallaction/exec_ns.cpp +++ b/engines/parallaction/exec_ns.cpp @@ -61,7 +61,7 @@ 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) { @@ -81,13 +81,13 @@ DECLARE_INSTRUCTION_OPCODE(loop) { InstructionPtr inst = *_ctxt.inst; _ctxt.program->_loopCounter = inst->_opB.getRValue(); - _ctxt.program->_loopStart = _ctxt.inst; + _ctxt.program->_loopStart = _ctxt.ip; } DECLARE_INSTRUCTION_OPCODE(endloop) { if (--_ctxt.program->_loopCounter > 0) { - _ctxt.inst = _ctxt.program->_loopStart; + _ctxt.ip = _ctxt.program->_loopStart; } } @@ -97,7 +97,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) { if (inst->_flags & kInstMod) { // mod int16 _bx = (_si > 0 ? _si : -_si); - if (_modCounter % _bx != 0) return; + if (_ctxt.modCounter % _bx != 0) return; _si = (_si > 0 ? 1 : -1); } @@ -142,8 +142,8 @@ DECLARE_INSTRUCTION_OPCODE(put) { _vm->_gfx->patchBackground(v18, x, y, mask); } -DECLARE_INSTRUCTION_OPCODE(null) { - +DECLARE_INSTRUCTION_OPCODE(show) { + _ctxt.suspend = true; } DECLARE_INSTRUCTION_OPCODE(invalid) { @@ -156,8 +156,10 @@ DECLARE_INSTRUCTION_OPCODE(call) { DECLARE_INSTRUCTION_OPCODE(wait) { - if (_engineFlags & kEngineWalking) + if (_engineFlags & kEngineWalking) { + _ctxt.ip--; _ctxt.suspend = true; + } } @@ -186,8 +188,8 @@ DECLARE_INSTRUCTION_OPCODE(endscript) { _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim); _ctxt.program->_status = kProgramDone; } - _ctxt.program->_ip = _ctxt.program->_instructions.begin(); + _ctxt.ip = _ctxt.program->_instructions.begin(); _ctxt.suspend = true; } @@ -332,35 +334,40 @@ void Parallaction_ns::drawAnimations() { 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; + + // 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(); - if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0)) { + if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0)) { - int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1); - if (v18->_flags & kFlagsNoMasked) + 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); } @@ -372,14 +379,41 @@ void Parallaction_ns::drawAnimations() { 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; + + } + script->_ip = _ctxt.ip; + +} void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) { if (_engineFlags & kEnginePauseJobs) { return; } - debugC(9, kDebugExec, "runScripts"); - for (ProgramList::iterator it = first; it != last; it++) { AnimationPtr a = (*it)->_anim; @@ -390,31 +424,8 @@ void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator if ((a->_flags & kFlagsActing) == 0) continue; - InstructionList::iterator inst = (*it)->_ip; - while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) { - - (*it)->_status = kProgramRunning; - - debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]); - - _ctxt.inst = inst; - _ctxt.anim = AnimationPtr(a); - _ctxt.program = *it; - _ctxt.suspend = false; - - (*_opcodes[(*inst)->_index])(); + runScript(*it, a); - inst = _ctxt.inst; // handles endloop correctly - - if (_ctxt.suspend) - goto label1; - - inst++; - } - - (*it)->_ip = ++inst; - -label1: if (a->_flags & kFlagsCharacter) a->_z = a->_top + a->height(); } @@ -424,24 +435,19 @@ label1: return; } -void CommandExec::run(CommandList& list, ZonePtr z) { - if (list.size() == 0) { - debugC(3, kDebugExec, "runCommands: nothing to do"); - return; - } - - debugC(3, kDebugExec, "runCommands starting"); +void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) { uint32 useFlags = 0; bool useLocalFlags; - CommandList::iterator it = list.begin(); - for ( ; it != list.end(); it++) { + _ctxt.suspend = false; + + for ( ; first != last; first++) { if (_vm->quit()) break; - CommandPtr cmd = *it; + CommandPtr cmd = *first; if (_vm->quit()) break; @@ -462,16 +468,65 @@ void CommandExec::run(CommandList& list, ZonePtr z) { if (!onMatch || !offMatch) continue; - _ctxt.z = z; + _ctxt.z = _execZone; _ctxt.cmd = cmd; (*_opcodes[cmd->_id])(); + + if (_ctxt.suspend) { + createSuspendList(++first, last); + return; + } + } + +} + +void CommandExec::run(CommandList& list, ZonePtr z) { + if (list.size() == 0) { + debugC(3, kDebugExec, "runCommands: nothing to do"); + return; } + _execZone = z; + + debugC(3, kDebugExec, "runCommands starting"); + runList(list.begin(), list.end()); debugC(3, kDebugExec, "runCommands completed"); +} - return; +void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) { + if (first == last) { + return; + } + + debugC(3, kDebugExec, "CommandExec::createSuspendList()"); + + _suspendedCtxt.valid = true; + _suspendedCtxt.first = first; + _suspendedCtxt.last = last; + _suspendedCtxt.zone = _execZone; +} +void CommandExec::cleanSuspendedList() { + debugC(3, kDebugExec, "CommandExec::cleanSuspended()"); + + _suspendedCtxt.valid = false; + _suspendedCtxt.first = _suspendedCtxt.last; + _suspendedCtxt.zone = nullZonePtr; +} + +void CommandExec::runSuspended() { + if (_engineFlags & kEngineWalking) { + return; + } + + if (_suspendedCtxt.valid) { + debugC(3, kDebugExec, "CommandExec::runSuspended()"); + + _execZone = _suspendedCtxt.zone; + runList(_suspendedCtxt.first, _suspendedCtxt.last); + cleanSuspendedList(); + } } CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) { @@ -486,35 +541,69 @@ 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); - _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); + } 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) { @@ -526,8 +615,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; @@ -718,7 +807,7 @@ void ProgramExec_ns::init() { 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); @@ -733,6 +822,7 @@ void ProgramExec_ns::init() { } ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) { + _instructionNames = _instructionNamesRes_ns; } ProgramExec_ns::~ProgramExec_ns() { 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 e8250ac8fd..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(kGfxObjNormal), _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 { @@ -209,63 +209,6 @@ void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, cons 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; - - 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); - } - - } - - drawText(font, surf, rx, ry, token, color); - - rx += tokenWidth + blankWidth; - linewidth += blankWidth; - - text = Common::ltrim(text); - } - -} - #if 0 void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { @@ -348,8 +291,8 @@ void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 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 (_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; @@ -365,13 +308,13 @@ void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 } } else { - if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) { + 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); + byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i); if (z >= v) *d = *s; } diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp index 9b9ea8605a..c19d6ae5e5 100644 --- a/engines/parallaction/graphics.cpp +++ b/engines/parallaction/graphics.cpp @@ -153,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++) { @@ -262,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 @@ -307,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); @@ -348,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"); @@ -395,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); + 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; + 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; @@ -466,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++) { @@ -484,7 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) { d++; } - d += (_backgroundInfo.bg.pitch - r.width()); + d += (_backgroundInfo->bg.pitch - r.width()); } } @@ -674,49 +707,6 @@ void Gfx::drawLabels() { } -void Gfx::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; -} - void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) { @@ -734,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); } @@ -754,6 +744,8 @@ Gfx::Gfx(Parallaction* vm) : _screenX = 0; _screenY = 0; + _backgroundInfo = 0; + _halfbrite = false; _hbCircleRadius = 0; @@ -769,12 +761,22 @@ 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; @@ -829,23 +831,29 @@ void Gfx::freeItems() { _numItems = 0; } -void Gfx::freeBackground() { - _backgroundInfo.free(); -} - -void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) { - - freeBackground(); +void Gfx::setBackground(uint type, BackgroundInfo *info) { + delete _backgroundInfo; + _backgroundInfo = info; if (type == kBackgroundLocation) { - _disk->loadScenery(_backgroundInfo, name, mask, path); - setPalette(_backgroundInfo.palette); - _palette.clone(_backgroundInfo.palette); + // 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); + } + } + + 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 a7242ba6f4..23b4569c6a 100644 --- a/engines/parallaction/graphics.h +++ b/engines/parallaction/graphics.h @@ -261,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); @@ -435,7 +436,7 @@ struct BackgroundInfo { return LAYER_FOREGROUND; } - void free() { + ~BackgroundInfo() { bg.free(); mask.free(); path.free(); @@ -471,6 +472,9 @@ typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common:: class Gfx { +protected: + Parallaction* _vm; + public: Disk *_disk; VarMap _vars; @@ -496,7 +500,6 @@ public: void freeLabels(); // dialogue balloons - void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height); GfxObj* registerBalloon(Frames *frames, const char *text); void destroyBalloons(); @@ -508,8 +511,8 @@ public: 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); @@ -550,18 +553,23 @@ public: 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); @@ -592,7 +600,6 @@ public: // 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); 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 391459a12f..3315433762 100644 --- a/engines/parallaction/gui_br.cpp +++ b/engines/parallaction/gui_br.cpp @@ -25,185 +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: - _quit = true; - _vm->quitGame(); - 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; + } + // wrap the surface into the suitable Frames adapter + return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); + } -int Parallaction_br::guiShowMenu() { - // TODO: filter menu entries according to progress in game + enum MenuOptions { + kMenuPart0 = 0, + kMenuPart1 = 1, + kMenuPart2 = 2, + kMenuPart3 = 3, + kMenuPart4 = 4, + kMenuLoadGame = 5, + kMenuQuit = 6 + }; #define NUM_MENULINES 7 GfxObj *_lines[NUM_MENULINES]; - const char *menuStrings[NUM_MENULINES] = { - "SEE INTRO", - "NEW GAME", - "SAVED GAME", - "EXIT TO DOS", - "PART 2", - "PART 3", - "PART 4" - }; + static const char *_menuStrings[NUM_MENULINES]; + static const MenuOptions _options[NUM_MENULINES]; - MenuOptions options[NUM_MENULINES] = { - kMenuPart0, - kMenuPart1, - kMenuLoadGame, - kMenuQuit, - kMenuPart2, - kMenuPart3, - kMenuPart4 - }; + int _availItems; + int _selection; - _gfx->clearScreen(); - _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0); - if (getPlatform() == Common::kPlatformPC) { - _gfx->_backgroundInfo.x = 20; - _gfx->_backgroundInfo.y = 50; + void cleanup() { + _vm->_system->showMouse(false); + _vm->hideDialogueStuff(); + + for (int i = 0; i < _availItems; i++) { + delete _lines[i]; + } } - int availItems = 4 + _progress; + void performChoice(int selectedItem) { + switch (selectedItem) { + case kMenuQuit: + _engineFlags |= kEngineQuit; + break; - // TODO: keep track of and destroy menu item frames/surfaces + case kMenuLoadGame: + warning("loadgame not yet implemented"); + break; - int i; - for (i = 0; i < availItems; i++) { - _lines[i] = new GfxObj(0, guiRenderMenuItem(menuStrings[i]), "MenuItem"); - uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF); - _gfx->setItemFrame(id, 0); + default: + _vm->startPart(selectedItem); + } } - int selectedItem = -1; - - setMousePointer(0); - - uint32 event; - Common::Point p; - while (true) { +public: + MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) { + } - _input->readInput(); + virtual MenuInputState* run() { - event = _input->getLastButtonEvent(); - if ((event == kMouseLeftUp) && selectedItem >= 0) - break; + int event = _vm->_input->getLastButtonEvent(); + if ((event == kMouseLeftUp) && _selection >= 0) { + cleanup(); + performChoice(_options[_selection]); + return 0; + } - _input->getCursorPos(p); + 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); - 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 9c48586dbc..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,313 +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) { + } + + 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 *newGameMsg[] = { - "NUOVO GIOCO", - "NEUF JEU", - "NEW GAME", - "NEUES SPIEL" +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"; + } }; -const char *loadGameMsg[] = { - "GIOCO SALVATO", - "JEU SAUVE'", - "SAVED GAME", - "SPIEL GESPEICHERT" +class SplashInputState1_NS : public SplashInputState_NS { + +public: + SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) { + _slideName = "minintro"; + _timeOut = 2000; + _nextState = "chooselanguage"; + } }; -#define BLOCK_WIDTH 16 -#define BLOCK_HEIGHT 24 +class ChooseLanguageInputState_NS : public MenuInputState { + #define BLOCK_WIDTH 16 + #define BLOCK_HEIGHT 24 -#define BLOCK_X 112 -#define BLOCK_Y 130 + #define BLOCK_X 112 + #define BLOCK_Y 130 -#define BLOCK_SELECTION_X (BLOCK_X-1) -#define BLOCK_SELECTION_Y (BLOCK_Y-1) + #define BLOCK_SELECTION_X (BLOCK_X-1) + #define BLOCK_SELECTION_Y (BLOCK_Y-1) -#define BLOCK_X_OFFSET (BLOCK_WIDTH+1) -#define BLOCK_Y_OFFSET 9 + #define BLOCK_X_OFFSET (BLOCK_WIDTH+1) + #define BLOCK_Y_OFFSET 9 -// destination slots for code blocks -// -#define SLOT_X 61 -#define SLOT_Y 64 -#define SLOT_WIDTH (BLOCK_WIDTH+2) + // destination slots for code blocks + // + #define SLOT_X 61 + #define SLOT_Y 64 + #define SLOT_WIDTH (BLOCK_WIDTH+2) -#define PASSWORD_LEN 6 + int _language; + bool _allowChoice; + Common::String _nextState; -#define CHAR_DINO 0 -#define CHAR_DONNA 1 -#define CHAR_DOUGH 2 + static const Common::Rect _dosLanguageSelectBlocks[4]; + static const Common::Rect _amigaLanguageSelectBlocks[4]; + const Common::Rect *_blocks; -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 -}; + Parallaction_ns *_vm; -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 -}; +public: + ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) { + _allowChoice = false; + _nextState = "selectgame"; -static const char *_charStartLocation[] = { - "test.dino", - "test.donna", - "test.dough" + 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; + } + } + + _blocks = _amigaLanguageSelectBlocks; + } else { + _blocks = _dosLanguageSelectBlocks; + } + + _language = -1; + _allowChoice = true; + } + + virtual MenuInputState* run() { + if (!_allowChoice) { + _vm->setInternLanguage(_language); + return _helper->getState(_nextState); + } + + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } + + Common::Point p; + _vm->_input->getCursorPos(p); + + 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; + } + + virtual void enter() { + if (!_allowChoice) { + return; + } + + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + + // user can choose language in this version + _vm->showSlide("lingua"); + + uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1); + _vm->_gfx->showLabel(id, 60, 30); + + _vm->setArrowCursor(); + } }; -enum { - NEW_GAME, - LOAD_GAME +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 }; -enum { - START_DEMO, - START_INTRO, - GAME_LOADED, - SELECT_CHARACTER +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 }; -void Parallaction_ns::guiStart() { +class SelectGameInputState_NS : public MenuInputState { - _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); + int _choice, _oldChoice; + Common::String _nextState[2]; - guiSplash(); + uint _labels[2]; - _language = guiChooseLanguage(); - _disk->setLanguage(_language); + Parallaction_ns *_vm; - int event; + static const char *newGameMsg[4]; + static const char *loadGameMsg[4]; - if (getFeatures() & GF_DEMO) { - event = START_DEMO; - } else { - if (guiSelectGame() == NEW_GAME) { - event = guiNewGame(); - } else { - event = loadGame() ? GAME_LOADED : START_INTRO; - } +public: + SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) { + _choice = 0; + _oldChoice = -1; + + _nextState[0] = "newgame"; + _nextState[1] = "loadgame"; } - switch (event) { - case START_DEMO: - strcpy(_location._name, "fognedemo.dough"); - break; - case START_INTRO: - strcpy(_location._name, "fogne.dough"); - break; + virtual MenuInputState *run() { + int event = _vm->_input->getLastButtonEvent(); + + if (event == kMouseLeftUp) { + _vm->_gfx->freeLabels(); + return _helper->getState(_nextState[_choice]); + } - case GAME_LOADED: - // nothing to do here - return; + Common::Point p; + _vm->_input->getCursorPos(p); + _choice = (p.x > 160) ? 1 : 0; - case SELECT_CHARACTER: - selectStartLocation(); - break; + if (_choice != _oldChoice) { + if (_oldChoice != -1) + _vm->_gfx->hideLabel(_labels[_oldChoice]); + if (_choice != -1) + _vm->_gfx->showLabel(_labels[_choice], 60, 30); + + _oldChoice = _choice; + } + + return this; } - return; -} + virtual void enter() { + _vm->showSlide("restore"); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); -void Parallaction_ns::selectStartLocation() { - _inTestResult = false; + _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1); + _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1); + } - int character = guiSelectCharacter(); - if (character == -1) - error("invalid character selected from menu screen"); +}; - scheduleLocationSwitch(_charStartLocation[character]); -} +const char *SelectGameInputState_NS::newGameMsg[4] = { + "NUOVO GIOCO", + "NEUF JEU", + "NEW GAME", + "NEUES SPIEL" +}; +const char *SelectGameInputState_NS::loadGameMsg[4] = { + "GIOCO SALVATO", + "JEU SAUVE'", + "SAVED GAME", + "SPIEL GESPEICHERT" +}; -void Parallaction_ns::guiSplash() { - showSlide("intro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); - showSlide("minintro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); -} +class LoadGameInputState_NS : public MenuInputState { + bool _result; + Parallaction_ns *_vm; -int Parallaction_ns::guiNewGame() { +public: + LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { } - const char **v14 = introMsg3; + virtual MenuInputState* run() { + if (!_result) { + _vm->scheduleLocationSwitch("fogne.dough"); + } + return 0; + } - _disk->selectArchive("disk1"); + virtual void enter() { + _result = _vm->loadGame(); + } +}; - setBackground("test", NULL, NULL); - _gfx->updateScreen(); - 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); +class NewGameInputState_NS : public MenuInputState { + Parallaction_ns *_vm; - _input->showCursor(false); + static const char *introMsg3[4]; - _gfx->updateScreen(); +public: + NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) { + } - _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp); - uint32 event = _input->getLastButtonEvent(); + virtual MenuInputState* run() { + int event = _vm->_input->getLastButtonEvent(); - _input->showCursor(true); + if (event == kMouseLeftUp || event == kMouseRightUp) { + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + _vm->_gfx->freeLabels(); - _gfx->freeLabels(); + if (event == kMouseLeftUp) { + _vm->scheduleLocationSwitch("fogne.dough"); + return 0; + } - if (event != kMouseRightUp) { - return START_INTRO; - } + return _helper->getState("selectcharacter"); + } - return SELECT_CHARACTER; -} + return this; + } -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 + 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); + } }; -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 *NewGameInputState_NS::introMsg3[4] = { + "PRESS LEFT MOUSE BUTTON", + "TO SEE INTRO", + "PRESS RIGHT MOUSE BUTTON", + "TO START" }; -uint16 Parallaction_ns::guiChooseLanguage() { - const Common::Rect *blocks; +class StartDemoInputState_NS : public MenuInputState { + Parallaction_ns *_vm; - 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 - } - } +public: + StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) { + } - blocks = _amigaLanguageSelectBlocks; - } else { - blocks = _dosLanguageSelectBlocks; + virtual MenuInputState* run() { + _vm->scheduleLocationSwitch("fognedemo.dough"); + return 0; } - // user can choose language in dos version - showSlide("lingua"); + virtual void enter() { + _vm->_input->setMouseState(MOUSE_DISABLED); + } +}; - uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1); - _gfx->showLabel(id, 60, 30); +class SelectCharacterInputState_NS : public MenuInputState { - setArrowCursor(); + #define PASSWORD_LEN 6 - Common::Point p; + #define CHAR_DINO 0 + #define CHAR_DONNA 1 + #define CHAR_DOUGH 2 - int selection = -1; - while (selection == -1) { - _input->waitUntilLeftClick(); - _input->getCursorPos(p); - for (uint16 i = 0; i < 4; i++) { - if (blocks[i].contains(p)) { + 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++; } - _gfx->updateScreen(); - g_system->delayMillis(30); + if (_len == PASSWORD_LEN) { + _state = _fail ? FAIL : SUCCESS; + } } - _gfx->freeLabels(); + 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; + } + + 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)."); + } + + _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"); + + _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" +}; - return _si ? LOAD_GAME : NEW_GAME; -} -static const Common::Rect codeSelectBlocks[9] = { +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 @@ -350,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 ), @@ -363,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 d625199e2b..19747c007e 100644 --- a/engines/parallaction/input.cpp +++ b/engines/parallaction/input.cpp @@ -36,25 +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; - _lastKeyDownAscii = -1; + _hasKeyPressEvent = false; Common::EventManager *eventMan = _vm->_system->getEventManager(); while (eventMan->pollEvent(e)) { switch (e.type) { case Common::EVENT_KEYDOWN: - _lastKeyDownAscii = e.kbd.ascii; + _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: @@ -83,7 +81,7 @@ uint16 Input::readInput() { case Common::EVENT_RTL: case Common::EVENT_QUIT: _vm->_quit = true; - return KeyDown; + return; default: break; @@ -95,13 +93,13 @@ uint16 Input::readInput() { if (_vm->_debugger->isAttached()) _vm->_debugger->onFrame(); - return KeyDown; + return; } bool Input::getLastKeyDown(uint16 &ascii) { - ascii = _lastKeyDownAscii; - return (_lastKeyDownAscii != -1); + ascii = _keyPressed.ascii; + return (_hasKeyPressEvent); } // FIXME: see comment for readInput() @@ -128,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(); + readInput(); - debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)", - !_mouseHidden, - (_engineFlags & kEngineBlockInput) == 0, - (_engineFlags & kEngineWalking) == 0, - (_engineFlags & kEngineChangeLocation) == 0 - ); - - 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->hideDialogueStuff(); - _vm->_gfx->setHalfbriteMode(false); - - _inputMode = kInputModeGame; -} InputData* Input::updateInput() { @@ -193,15 +163,18 @@ InputData* Input::updateInput() { switch (_inputMode) { case kInputModeComment: - updateCommentInput(); + case kInputModeDialogue: + case kInputModeMenu: + readInput(); break; case kInputModeGame: updateGameInput(); break; - case kInputModeDialogue: + case kInputModeInventory: readInput(); + updateInventoryInput(); break; } @@ -230,38 +203,45 @@ void Input::stopHovering() { _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; } @@ -275,16 +255,17 @@ bool Input::translateGameInput() { _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(); @@ -293,31 +274,40 @@ 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); @@ -326,25 +316,61 @@ bool Input::translateInventoryInput() { _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 f260352dba..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" @@ -47,51 +49,65 @@ struct InputData { 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; - int32 _lastKeyDownAscii; - bool _mouseHidden; ZonePtr _hoverZone; + void enterInventoryMode(); + void exitInventoryMode(); + public: enum { kInputModeGame = 0, kInputModeComment = 1, - kInputModeDialogue = 2 + 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; } @@ -99,15 +115,20 @@ public: int _inputMode; InventoryItem _activeItem; - uint16 readInput(); + void readInput(); InputData* updateInput(); void trackMouse(ZonePtr z); - void waitUntilLeftClick(); void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1); uint32 getLastButtonEvent() { return _mouseButtons; } bool getLastKeyDown(uint16 &ascii); 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 fb867f5285..9d44422541 100644 --- a/engines/parallaction/module.mk +++ b/engines/parallaction/module.mk @@ -14,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 66025cf0f7..c387484de7 100644 --- a/engines/parallaction/objects.cpp +++ b/engines/parallaction/objects.cpp @@ -60,14 +60,14 @@ Animation::~Animation() { 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(); } @@ -81,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]; diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h index 19835da9d0..7e7a811ba6 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 }; @@ -256,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; @@ -264,6 +279,8 @@ struct TypeData { DoorData *door; HearData *hear; MergeData *merge; + // BRA specific field + PathData *path; TypeData() { get = NULL; @@ -272,6 +289,7 @@ struct TypeData { door = NULL; hear = NULL; merge = NULL; + path = NULL; } }; @@ -432,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 2845fcb94b..6a61087804 100644 --- a/engines/parallaction/parallaction.cpp +++ b/engines/parallaction/parallaction.cpp @@ -98,6 +98,10 @@ Parallaction::~Parallaction() { freeCharacter(); destroyInventory(); + cleanupGui(); + + delete _comboArrow; + delete _localFlagNames; delete _gfx; delete _soundMan; @@ -139,6 +143,8 @@ int Parallaction::init() { _debugger = new Debugger(this); + _menuHelper = 0; + setupBalloonManager(); syncSoundSettings(); @@ -155,7 +161,7 @@ void Parallaction::clearSet(OpcodeSet &opcodes) { void Parallaction::updateView() { - if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) { + if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) { return; } @@ -259,10 +265,9 @@ void Parallaction::freeLocation() { _localFlagNames->clear(); - _location._walkNodes.clear(); + _location._walkPoints.clear(); _gfx->clearGfxObjects(kGfxObjNormal); - freeBackground(); _location._programs.clear(); freeZones(); @@ -282,15 +287,17 @@ 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; } @@ -301,46 +308,11 @@ void Parallaction::showLocationComment(const char *text, bool end) { void Parallaction::processInput(InputData *data) { + if (!data) { + return; + } switch (data->_event) { - case kEvAction: - debugC(2, kDebugInput, "processInput: kEvAction"); - _input->stopHovering(); - pauseJobs(); - runZone(data->_zone); - resumeJobs(); - break; - - case kEvOpenInventory: - _input->stopHovering(); - 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: - _quit = true; - _vm->quitGame(); - break; case kEvSaveGame: _input->stopHovering(); @@ -365,20 +337,19 @@ void Parallaction::runGame() { if (_vm->quit()) return; - if (_input->_inputMode == Input::kInputModeDialogue) { - runDialogueFrame(); - } else { - if (data->_event != kEvNone) { - processInput(data); - } + runGuiFrame(); + runDialogueFrame(); + runCommentFrame(); - if (_vm->quit()) - return; + if (_vm->quit()) + return; + if (_input->_inputMode == Input::kInputModeGame) { + processInput(data); runPendingZones(); - if (_vm->quit()) - return; + if (_vm->quit()) + return; if (_engineFlags & kEngineChangeLocation) { changeLocation(_location._name); @@ -393,7 +364,7 @@ void Parallaction::runGame() { if (_char._ani->gfxobj) { _char._ani->gfxobj->z = _char._ani->_z; } - walk(_char); + _char._walker->walk(); drawAnimations(); } @@ -429,11 +400,10 @@ void Parallaction::doLocationEnterTransition() { _programExec->runScripts(_location._programs.begin(), _location._programs.end()); drawAnimations(); - + showLocationComment(_location._comment, false); _gfx->updateScreen(); - showLocationComment(_location._comment, false); - _input->waitUntilLeftClick(); + _input->waitForButtonEvent(kMouseLeftUp); _balloonMan->freeBalloons(); // fades maximum intensity palette towards approximation of main palette @@ -543,7 +513,7 @@ 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; @@ -562,24 +532,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; } diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h index e9897b67c3..2a32904d63 100644 --- a/engines/parallaction/parallaction.h +++ b/engines/parallaction/parallaction.h @@ -100,10 +100,8 @@ enum { enum EngineFlags { kEnginePauseJobs = (1 << 1), - kEngineInventory = (1 << 2), kEngineWalking = (1 << 3), kEngineChangeLocation = (1 << 4), - kEngineBlockInput = (1 << 5), kEngineDragging = (1 << 6), kEngineTransformedDonna = (1 << 7), @@ -113,12 +111,6 @@ enum EngineFlags { enum { kEvNone = 0, - kEvAction = 3, - kEvOpenInventory = 4, - kEvCloseInventory = 5, - kEvHoverInventory = 6, - kEvWalk = 7, - kEvQuitGame = 1000, kEvSaveGame = 2000, kEvLoadGame = 4000 }; @@ -163,6 +155,7 @@ class Gfx; class SoundMan; class Input; class DialogueManager; +class MenuInputHelper; struct Location { @@ -183,7 +176,7 @@ struct Location { char _soundFile[50]; // NS specific - WalkNodeList _walkNodes; + PointList _walkPoints; char _slideText[2][MAX_TOKEN_LEN]; // BRA specific @@ -204,10 +197,13 @@ struct Character { GfxObj *_head; GfxObj *_talk; GfxObj *_objs; - PathBuilder _builder; - WalkNodeList *_walkPath; + 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); @@ -271,10 +267,6 @@ public: virtual void syncSoundSettings(); - void finalizeWalk(Character &character); - int16 selectWalkFrame(Character &character, const Common::Point& pos, const WalkNodePtr to); - void clipMove(Common::Point& pos, const Common::Point& to); - ZonePtr findZone(const char *name); ZonePtr hitZone(uint32 type, uint16 x, uint16 y); uint16 runZone(ZonePtr z); @@ -339,6 +331,7 @@ public: Common::RandomSource _rnd; Debugger *_debugger; + Frames *_comboArrow; protected: // data @@ -366,8 +359,6 @@ protected: // members void displayComment(ExamineData *data); - void checkDoor(const Common::Point &foot); - void freeCharacter(); int16 pickupItem(ZonePtr z); @@ -382,18 +373,18 @@ public: 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 walk(Character &character) = 0; virtual void drawAnimations() = 0; void beep(); ZonePtr _zoneTrap; + PathBuilder* getPathBuilder(Character *ch); public: // const char **_zoneFlagNamesRes; @@ -429,6 +420,17 @@ public: 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(); }; @@ -487,12 +489,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; @@ -504,17 +512,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); @@ -527,7 +532,6 @@ private: static byte _resMouseArrow[256]; byte *_mouseArrow; - Frames *_mouseComposedArrow; static const Callable _dosCallables[25]; static const Callable _amigaCallables[25]; @@ -547,9 +551,6 @@ private: ZonePtr _moveSarcExaZones[5]; AnimationPtr _rightHandAnim; - bool _inTestResult; - - // common callables void _c_play_boogie(void*); void _c_startIntro(void*); @@ -586,7 +587,6 @@ private: const Callable *_callables; protected: - void walk(Character &character); void drawAnimations(); void parseLocation(const char *filename); @@ -594,15 +594,9 @@ protected: 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(); }; @@ -646,7 +640,8 @@ public: int32 _counters[32]; uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES]; - + void startPart(uint part); + void setArrowCursor(); private: LocationParser_br *_locationParser; ProgramParser_br *_programParser; @@ -655,17 +650,14 @@ private: void initFonts(); void freeFonts(); - 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; @@ -676,10 +668,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]; diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp index 0c8058e4bd..f4329edf74 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 { @@ -99,6 +124,7 @@ Parallaction_br::~Parallaction_br() { delete _dougCursor; delete _donnaCursor; + delete _mouseArrow; } void Parallaction_br::callFunction(uint index, void* parm) { @@ -109,16 +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 (quit() == 0) { - guiStart(); - - if (quit()) - return _eventMan->shouldRTL(); - // initCharacter(); _input->_inputMode = Input::kInputModeGame; @@ -152,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? @@ -159,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)); @@ -180,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"); + } } @@ -195,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); @@ -209,6 +237,8 @@ void Parallaction_br::startPart() { void Parallaction_br::runPendingZones() { ZonePtr z; + _cmdExec->runSuspended(); + if (_activeZone) { z = _activeZone; // speak Zone or sound _activeZone = nullZonePtr; @@ -236,7 +266,10 @@ void Parallaction_br::changeLocation(char *location) { // free open location stuff clearSubtitles(); freeBackground(); - _gfx->clearGfxObjects(kGfxObjNormal | kGfxObjCharacter); + _gfx->clearGfxObjects(kGfxObjNormal); + _gfx->freeLabels(); + _subtitle[0] = _subtitle[1] = -1; + _location._programs.clear(); _location._animations.remove(_char._ani); @@ -248,12 +281,17 @@ void Parallaction_br::changeLocation(char *location) { // free(_location._comment); // _location._comment = 0; -// _location._commands.clear(); -// _location._aCommands.clear(); - + _location._commands.clear(); + _location._aCommands.clear(); // load new location parseLocation(location); + + // 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(); _cmdExec->run(_location._aCommands); @@ -305,30 +343,46 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) { void Parallaction_br::changeCharacter(const char *name) { - printf("changeCharacter(%s)\n", 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._ani->gfxobj = _gfx->loadAnim(name); - _char._ani->gfxobj->setFlags(kGfxObjCharacter); - _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 2f7c3dc572..32c4991a15 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 @@ -165,7 +166,6 @@ Parallaction_ns::~Parallaction_ns() { delete _locationParser; delete _programParser; - delete _mouseComposedArrow; _location._animations.remove(_char._ani); @@ -182,7 +182,7 @@ void Parallaction_ns::freeFonts() { } void Parallaction_ns::initCursors() { - _mouseComposedArrow = _disk->loadPointer("pointer"); + _comboArrow = _disk->loadPointer("pointer"); _mouseArrow = _resMouseArrow; } @@ -195,36 +195,16 @@ void Parallaction_ns::setArrowCursor() { _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; - - const InventoryItem *item = getInventoryItem(pos); - if (item->_index == 0) - return; - - _input->_activeItem._id = item->_id; +void Parallaction_ns::setInventoryCursor(ItemName name) { + assert(name > 0); - 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); - } @@ -247,24 +227,19 @@ int Parallaction_ns::go() { _gameToLoad = -1; } if (_gameToLoad == -1) { - guiStart(); - } else { + startGui(); + } else { _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); - _language = guiChooseLanguage(); - _disk->setLanguage(_language); + + _menuHelper = new MenuInputHelper; + assert(_menuHelper); + + new ChooseLanguageInputState_NS(this, _menuHelper); doLoadGame(_gameToLoad); } + + startGui(); - - if (quit()) - return _eventMan->shouldRTL(); - - changeLocation(_location._name); - - if (quit()) - return _eventMan->shouldRTL(); - - _input->_inputMode = Input::kInputModeGame; while (!quit()) { runGame(); } @@ -296,8 +271,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() { @@ -314,6 +295,9 @@ 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); _input->stopHovering(); @@ -321,9 +305,7 @@ void Parallaction_ns::changeLocation(char *location) { _zoneTrap = nullZonePtr; - if (_engineFlags & kEngineBlockInput) { - setArrowCursor(); - } + setArrowCursor(); _gfx->showGfxObj(_char._ani->gfxobj, false); _location._animations.remove(_char._ani); @@ -337,7 +319,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(); } @@ -381,10 +365,9 @@ void Parallaction_ns::changeLocation(char *location) { if (_location._hasSound) _soundMan->playSfx(_location._soundFile, 0, true); - debugC(1, kDebugExec, "changeLocation() done"); - - return; + _input->setMouseState(oldMouseState); + debugC(1, kDebugExec, "changeLocation() done"); } diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp index 6de0a7d7f5..97caa9dc6c 100644 --- a/engines/parallaction/parser.cpp +++ b/engines/parallaction/parser.cpp @@ -28,7 +28,10 @@ namespace Parallaction { -char _tokens[20][MAX_TOKEN_LEN]; +#define MAX_TOKENS 50 + +int _numTokens; +char _tokens[MAX_TOKENS][MAX_TOKEN_LEN]; Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {} @@ -65,9 +68,11 @@ char *Script::readLine(char *buf, size_t bufSize) { void Script::clearTokens() { - for (uint16 i = 0; i < 20; i++) + for (uint16 i = 0; i < MAX_TOKENS; i++) _tokens[i][0] = '\0'; + _numTokens = 0; + return; } @@ -151,12 +156,14 @@ char *parseNextToken(char *s, char *tok, uint16 count, const char *brk, bool ign uint16 Script::fillTokens(char* line) { uint16 i = 0; - while (strlen(line) > 0 && i < 20) { + while (strlen(line) > 0 && i < MAX_TOKENS) { line = parseNextToken(line, _tokens[i], MAX_TOKEN_LEN, " \t\n"); line = Common::ltrim(line); i++; } + _numTokens = i; + return i; } @@ -243,4 +250,185 @@ void Parser::parseStatement() { } +#define BLOCK_BASE 100 + +class StatementDef { +protected: + Common::String makeLineFromTokens() { + Common::String space(" "); + Common::String newLine("\n"); + Common::String text; + for (int i = 0; i < _numTokens; i++) + text += (Common::String(_tokens[i]) + space); + text.deleteLastChar(); + text += newLine; + return text; + } + +public: + uint _score; + const char* _name; + + + StatementDef(uint score, const char *name) : _score(score), _name(name) { } + virtual ~StatementDef() { } + + virtual Common::String makeLine(Script &script) = 0; + +}; + + +class SimpleStatementDef : public StatementDef { + +public: + SimpleStatementDef(uint score, const char *name) : StatementDef(score, name) { } + + Common::String makeLine(Script &script) { + return makeLineFromTokens(); + } + +}; + + + +class BlockStatementDef : public StatementDef { + + const char* _ending1; + const char* _ending2; + +public: + BlockStatementDef(uint score, const char *name, const char *ending1, const char *ending2 = 0) : StatementDef(score, name), _ending1(ending1), + _ending2(ending2) { } + + Common::String makeLine(Script &script) { + Common::String text = makeLineFromTokens(); + bool end; + do { + script.readLineToken(true); + text += makeLineFromTokens(); + end = !scumm_stricmp(_ending1, _tokens[0]) || (_ending2 && !scumm_stricmp(_ending2, _tokens[0])); + } while (!end); + return text; + } + +}; + +class CommentStatementDef : public StatementDef { + + Common::String parseComment(Script &script) { + Common::String result; + char buf[401]; + + do { + script.readLine(buf, 400); + buf[strlen(buf)-1] = '\0'; + if (!scumm_stricmp(buf, "endtext")) + break; + result += Common::String(buf) + "\n"; + } while (true); + result += "endtext\n"; + return result; + } + +public: + CommentStatementDef(uint score, const char *name) : StatementDef(score, name) { } + + Common::String makeLine(Script &script) { + Common::String text = makeLineFromTokens(); + text += parseComment(script); + return text; + } + +}; + + + + +PreProcessor::PreProcessor() { + _defs.push_back(new SimpleStatementDef(1, "disk" )); + _defs.push_back(new SimpleStatementDef(2, "location" )); + _defs.push_back(new SimpleStatementDef(3, "localflags" )); + _defs.push_back(new SimpleStatementDef(4, "flags" )); + _defs.push_back(new SimpleStatementDef(5, "zeta" )); + _defs.push_back(new SimpleStatementDef(6, "music" )); + _defs.push_back(new SimpleStatementDef(7, "sound" )); + _defs.push_back(new SimpleStatementDef(8, "mask" )); + _defs.push_back(new SimpleStatementDef(9, "path" )); + _defs.push_back(new SimpleStatementDef(10, "character" )); + _defs.push_back(new CommentStatementDef(11, "comment" )); + _defs.push_back(new CommentStatementDef(12, "endcomment" )); + _defs.push_back(new BlockStatementDef(13, "ifchar", "endif" )); + _defs.push_back(new BlockStatementDef(BLOCK_BASE, "zone", "endanimation", "endzone" )); + _defs.push_back(new BlockStatementDef(BLOCK_BASE, "animation", "endanimation", "endzone" )); + _defs.push_back(new BlockStatementDef(1000, "commands", "endcommands" )); + _defs.push_back(new BlockStatementDef(1001, "acommands", "endcommands" )); + _defs.push_back(new BlockStatementDef(1002, "escape", "endcommands" )); + _defs.push_back(new SimpleStatementDef(2000, "endlocation")); +} + +PreProcessor::~PreProcessor() { + DefList::iterator it = _defs.begin(); + for (; it != _defs.end(); it++) { + delete *it; + } +} + +StatementDef* PreProcessor::findDef(const char* name) { + DefList::iterator it = _defs.begin(); + for (; it != _defs.end(); it++) { + if (!scumm_stricmp((*it)->_name, name)) { + return *it; + } + } + return 0; +} + + + +uint PreProcessor::getDefScore(StatementDef* def) { + if (def->_score == BLOCK_BASE) { + _numZones++; + return (_numZones + BLOCK_BASE); + } + return def->_score; +} + + +void PreProcessor::preprocessScript(Script &script, StatementList &list) { + _numZones = 0; + Common::String text; + do { + script.readLineToken(false); + if (_numTokens == 0) + break; + + StatementDef *def = findDef(_tokens[0]); + assert(def); + + text = def->makeLine(script); + int score = getDefScore(def); + list.push_back(StatementListNode(score, def->_name, text)); + } while (true); + Common::sort(list.begin(), list.end()); +} + + + + +void testPreprocessing(Parallaction *vm, const char *filename) { + Script *script = vm->_disk->loadLocation(filename); + StatementList list; + PreProcessor pp; + pp.preprocessScript(*script, list); + delete script; + Common::DumpFile dump; + dump.open(filename); + StatementList::iterator it = list.begin(); + for ( ; it != list.end(); it++) { + dump.write((*it)._text.c_str(), (*it)._text.size()); + } + dump.close(); +} + + } // namespace Parallaction diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h index 1541fb89b2..7e7937fb19 100644 --- a/engines/parallaction/parser.h +++ b/engines/parallaction/parser.h @@ -35,6 +35,7 @@ namespace Parallaction { char *parseNextToken(char *s, char *tok, uint16 count, const char *brk, bool ignoreQuotes = false); #define MAX_TOKEN_LEN 50 +extern int _numTokens; extern char _tokens[][MAX_TOKEN_LEN]; class Script { @@ -63,6 +64,7 @@ typedef Common::Functor0<void> Opcode; typedef Common::Array<const Opcode*> OpcodeSet; + class Parser { public: @@ -95,6 +97,7 @@ public: class Parallaction_ns; class Parallaction_br; + class LocationParser_ns { protected: @@ -128,6 +131,7 @@ protected: // BRA specific int numZones; + BackgroundInfo *info; char *bgName; char *maskName; char *pathName; @@ -171,7 +175,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 +196,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(); @@ -239,6 +243,23 @@ public: }; +/* + TODO: adapt the parser to effectively use the + statement list provided by preprocessor as its + input, instead of relying on the current Script + class. + + This would need a major rewrite of the parsing + system! + + parseNextToken could then be sealed into the + PreProcessor class forever, together with the + _tokens[] and _numTokens stuff, now dangling as + global objects. + + NS balloons code should be dealt with before, + though. +*/ class LocationParser_br : public LocationParser_ns { protected: @@ -284,6 +305,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) { } @@ -307,6 +331,7 @@ protected: Parser *_parser; Parallaction_ns *_vm; + Script *_script; ProgramPtr _program; @@ -397,6 +422,117 @@ public: }; + +/* + This simple stream is temporarily needed to hook the + preprocessor output to the parser. It will go away + when the parser is rewritten to fully exploit the + statement list provided by the preprocessor. +*/ + +class ReadStringStream : public Common::ReadStream { + + char *_text; + uint32 _pos; + uint32 _size; + +public: + ReadStringStream(const Common::String &text) { + _text = new char[text.size() + 1]; + strcpy(_text, text.c_str()); + _size = text.size(); + _pos = 0; + } + + ~ReadStringStream() { + delete []_text; + } + + uint32 read(void *buffer, uint32 size) { + if (_pos + size > _size) { + size = _size - _pos; + } + memcpy(buffer, _text + _pos, size); + _pos += size; + return size; + } + + bool eos() const { + return _pos == _size; + } + +}; + + +/* + Demented as it may sound, the success of a parsing operation in the + original BRA depends on what has been parsed before. The game features + an innovative chaos system that involves the parser and the very game + engine, in order to inflict the user an unforgettable game experience. + + Ok, now for the serious stuff. + + The PreProcessor implemented here fixes the location scripts before + they are fed to the parser. It tries to do so by a preliminary scan + of the text file, during which a score is assigned to each statement + (more on this later). When the whole file has been analyzed, the + statements are sorted according to their score, to create a parsable + sequence. + + For parsing, the statements in location scripts can be conveniently + divided into 3 groups: + + * location definitions + * element definitions + * start-up commands + + Since the parsing of element definitions requires location parameters + to be set, location definitions should be encountered first in the + script. Start-up commands in turn may reference elements, so they can + be parsed last. The first goal is to make sure the parser gets these + three sets in this order. + + Location definitions must also be presented in a certain sequence, + because resource files are not fully self-describing. In short, some + critical game data in contained in certain files, that must obviously + be read before any other can be analyzed. This is the second goal. + + TODO: some words about actual implementation. +*/ + +class StatementDef; + +struct StatementListNode { + int _score; + Common::String _name; + Common::String _text; + + StatementListNode(int score, const Common::String &name, const Common::String &text) : _score(score), _name(name), _text(text) { } + + bool operator<(const StatementListNode& node) const { + return _score < node._score; + } +}; +typedef Common::List<StatementListNode> StatementList; + + +class PreProcessor { + typedef Common::List<StatementDef*> DefList; + + int _numZones; + DefList _defs; + + StatementDef* findDef(const char* name); + uint getDefScore(StatementDef*); + +public: + PreProcessor(); + ~PreProcessor(); + void preprocessScript(Script &script, StatementList &list); +}; + + + } // namespace Parallaction #endif diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp index defc917a72..dee08485fb 100644 --- a/engines/parallaction/parser_br.cpp +++ b/engines/parallaction/parser_br.cpp @@ -25,6 +25,7 @@ #include "parallaction/parallaction.h" + #include "parallaction/sound.h" namespace Parallaction { @@ -104,6 +105,7 @@ namespace Parallaction { #define INST_ENDIF 30 #define INST_STOP 31 + const char *_zoneTypeNamesRes_br[] = { "examine", "door", @@ -442,7 +444,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 +466,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 +752,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 +1046,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 +1054,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 @@ -1107,18 +1170,40 @@ void ProgramParser_br::init() { INSTRUCTION_PARSER(endscript); } + +/* + Ancillary routine to support hooking preprocessor and + parser. +*/ +Common::ReadStream *getStream(StatementList &list) { + Common::String text; + StatementList::iterator it = list.begin(); + for ( ; it != list.end(); it++) { + text += (*it)._text; + } + return new ReadStringStream(text); +} + void LocationParser_br::parse(Script *script) { + PreProcessor pp; + StatementList list; + pp.preprocessScript(*script, list); + Script *script2 = new Script(getStream(list), true); + ctxt.numZones = 0; ctxt.bgName = 0; ctxt.maskName = 0; ctxt.pathName = 0; ctxt.characterName = 0; + ctxt.info = new BackgroundInfo; + + LocationParser_ns::parse(script2); - LocationParser_ns::parse(script); + _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName); + _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info); + _vm->_pathBuffer = &ctxt.info->path; - _vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName); - _vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path; if (ctxt.characterName) { _vm->changeCharacter(ctxt.characterName); @@ -1129,6 +1214,7 @@ void LocationParser_br::parse(Script *script) { free(ctxt.pathName); free(ctxt.characterName); + delete script2; } } // namespace Parallaction diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp index 2f4d2df776..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); } @@ -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 @@ -1397,7 +1392,7 @@ void LocationParser_ns::parseZone(ZoneList &list, char *name) { list.push_front(z); _parser->pushTables(&_locationZoneParsers, _locationZoneStmt); - + return; } diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp index a717b615e6..bf8f423fd5 100644 --- a/engines/parallaction/walk.cpp +++ b/engines/parallaction/walk.cpp @@ -28,26 +28,42 @@ namespace Parallaction { + +#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; @@ -56,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; @@ -84,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; @@ -97,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); @@ -118,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; } @@ -202,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) { @@ -228,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; @@ -239,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; @@ -253,21 +245,21 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { return 1; } -void Parallaction::clipMove(Common::Point& pos, const Common::Point& to) { +void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) { - if ((pos.x < to.x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) { + 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 > to.x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) { + 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 < to.y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) { + 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 > to.y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) { + 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; } @@ -275,85 +267,81 @@ void Parallaction::clipMove(Common::Point& pos, const Common::Point& to) { } -void Parallaction::checkDoor(const Common::Point &foot) { +void PathWalker_NS::checkDoor(const Common::Point &foot) { - ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y); + ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); if (z) { if ((z->_flags & kFlagsClosed) == 0) { - _location._startPosition = z->u.door->_startPos; - _location._startFrame = z->u.door->_startFrame; - scheduleLocationSwitch(z->u.door->_location); - _zoneTrap = nullZonePtr; + _vm->_location._startPosition = z->u.door->_startPos; + _vm->_location._startFrame = z->u.door->_startFrame; + _vm->scheduleLocationSwitch(z->u.door->_location); + _vm->_zoneTrap = nullZonePtr; } else { - _cmdExec->run(z->_commands, z); + _vm->_cmdExec->run(z->_commands, z); } } - z = hitZone(kZoneTrap, foot.x, foot.y); + z = _vm->hitZone(kZoneTrap, foot.x, foot.y); if (z) { - setLocationFlags(kFlagsEnter); - _cmdExec->run(z->_commands, z); - clearLocationFlags(kFlagsEnter); - _zoneTrap = z; + _vm->setLocationFlags(kFlagsEnter); + _vm->_cmdExec->run(z->_commands, z); + _vm->clearLocationFlags(kFlagsEnter); + _vm->_zoneTrap = z; } else - if (_zoneTrap) { - setLocationFlags(kFlagsExit); - _cmdExec->run(_zoneTrap->_commands, _zoneTrap); - clearLocationFlags(kFlagsExit); - _zoneTrap = nullZonePtr; + if (_vm->_zoneTrap) { + _vm->setLocationFlags(kFlagsExit); + _vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap); + _vm->clearLocationFlags(kFlagsExit); + _vm->_zoneTrap = nullZonePtr; } } -void Parallaction::finalizeWalk(Character &character) { +void PathWalker_NS::finalizeWalk() { _engineFlags &= ~kEngineWalking; Common::Point foot; - character.getFoot(foot); + _ch->getFoot(foot); checkDoor(foot); - delete character._walkPath; - character._walkPath = 0; + _ch->_walkPath.clear(); } -void Parallaction_ns::walk(Character &character) { +void PathWalker_NS::walk() { if ((_engineFlags & kEngineWalking) == 0) { return; } Common::Point curPos; - character.getFoot(curPos); + _ch->getFoot(curPos); // update target, if previous was reached - WalkNodeList::iterator it = character._walkPath->begin(); - if (it != character._walkPath->end()) { - if ((*it)->_x == curPos.x && (*it)->_y == curPos.y) { - debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y); - it = character._walkPath->erase(it); + 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 == character._walkPath->end()) { + if (it == _ch->_walkPath.end()) { debugC(1, kDebugWalk, "walk reached last node"); - finalizeWalk(character); + finalizeWalk(); targetPos = curPos; } else { - if (*it) { - // targetPos is saved to help setting character direction - targetPos.x = (*it)->_x; - targetPos.y = (*it)->_y; - } + // targetPos is saved to help setting character direction + targetPos = *it; Common::Point newPos(curPos); clipMove(newPos, targetPos); - character.setFoot(newPos); + _ch->setFoot(newPos); if (newPos == curPos) { debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle"); - finalizeWalk(character); + finalizeWalk(); targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected } } @@ -364,25 +352,283 @@ void Parallaction_ns::walk(Character &character) { // 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. - character.updateDirection(curPos, targetPos); + _ch->updateDirection(curPos, targetPos); } -WalkNode::WalkNode() : _x(0), _y(0) { + +PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) { } -WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) { + +bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) { + + Common::Point copy(from); + Common::Point p(copy); + + while (p != to) { + + 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--; + + if (p == copy && p != to) { + return false; + } + + copy = p; + } + + return true; } -WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) { +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; + } + + 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"); + + 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 + + return; } -void WalkNode::getPoint(Common::Point &p) const { - p.x = _x; - p.y = _y; + +void PathWalker_BR::walk() { + if ((_engineFlags & kEngineWalking) == 0) { + return; + } + +#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 + + GfxObj *obj = _ch->_ani->gfxobj; + + Common::Rect rect; + obj->getRect(_ch->_ani->_frame, rect); + + 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 (_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"); + } + } + + _ch->getFoot(_startFoot); + + _fieldC = 0; + _step++; + _step %= 8; + + int walkFrame = _step; + _dirFrame = 0; + Common::Point newpos(_startFoot), delta; + + 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; + } + + 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); + + 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; + } + + if (_fieldC || !_ch->_walkPath.empty()) { +// checkTrap(); + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n"); + return; + } + + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n"); + finalizeWalk(); + return; } -PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) { +PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) { + } 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/queen.h b/engines/queen/queen.h index 5ec18c1230..66931e037d 100644 --- a/engines/queen/queen.h +++ b/engines/queen/queen.h @@ -29,7 +29,7 @@ #include "engines/engine.h" namespace Common { - class InSaveFile; + class SeekableReadStream; } #if defined(_WIN32_WCE) && (_WIN32_WCE <= 300) @@ -112,7 +112,7 @@ public: void makeGameStateName(int slot, char *buf) const; int getGameStateSlot(const char *filename) const; void findGameStateDescriptions(char descriptions[100][32]); - Common::InSaveFile *readGameStateHeader(int slot, GameStateHeader *gsh); + Common::SeekableReadStream *readGameStateHeader(int slot, GameStateHeader *gsh); enum { SAVESTATE_CUR_VER = 1, diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp index 9cdd971857..6513a92018 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 diff --git a/engines/saga/displayinfo.h b/engines/saga/displayinfo.h index 7fee5fbee1..3775e904ff 100644 --- a/engines/saga/displayinfo.h +++ b/engines/saga/displayinfo.h @@ -348,9 +348,9 @@ static PanelButton IHNM_QuitPanelButtons[] = { }; static PanelButton IHNM_LoadPanelButtons[] = { - // TODO - {kPanelButtonLoad, 101,19, 60,16, kTextOK,'o',0, 0,0,0}, - {kPanelButtonLoadText, -1,5, 0,0, kTextLoadSuccessful,'-',0, 0,0,0}, + {kPanelButtonLoad, 26,80, 80,25, kTextOK,'o',0, 0,0,0}, + {kPanelButtonLoad, 156,80, 80,25, kTextCancel,'c',0, 0,0,0}, + {kPanelButtonLoadText, -1,30, 0,0, kTextLoadSavedGame,'-',0, 0,0,0}, }; static PanelButton IHNM_SavePanelButtons[] = { diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp index 7789949393..482b3a4c82 100644 --- a/engines/saga/font.cpp +++ b/engines/saga/font.cpp @@ -240,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'. @@ -259,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; } @@ -338,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 9d3831062c..4a4573ccef 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -94,7 +94,7 @@ static int IHNMTextStringIdsLUT[56] = { 8, // Give 10, // Options 11, // Test - 12, // + 12, // Demo 13, // Help 14, // Quit Game 16, // Fast @@ -905,10 +905,13 @@ void Interface::drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *p textFont = kKnownFontMedium; textShadowKnownColor = kKnownColorVerbTextShadow; } else { - if (panelButton->id < 39 || panelButton->id > 50) { + if ((panelButton->id < 39 || panelButton->id > 50) && panelButton->id != kTextLoadSavedGame) { // Read non-hardcoded strings from the LUT string table, loaded from the game // data files text = _vm->_script->_mainStrings.getString(IHNMTextStringIdsLUT[panelButton->id]); + } else if (panelButton->id == kTextLoadSavedGame) { + // a bit of a kludge, but it will do + text = _vm->getTextString(52); } else { // Hardcoded strings in IHNM are read from the ITE hardcoded strings text = _vm->getTextString(panelButton->id); @@ -1142,7 +1145,21 @@ void Interface::setLoad(PanelButton *panelButton) { _loadPanel.currentButton = NULL; switch (panelButton->id) { case kTextOK: - setMode(kPanelMain); + if (_vm->getGameType() == GType_ITE) { + setMode(kPanelMain); + } else { + if (_vm->getSaveFilesCount() > 0) { + if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) { + debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); + setMode(kPanelMain); + _vm->load(_vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber)); + } + } + } + break; + case kTextCancel: + // IHNM only + setMode(kPanelOption); break; } } @@ -1573,7 +1590,6 @@ void Interface::handleChapterSelectionClick(const Point& mousePoint) { } void Interface::setOption(PanelButton *panelButton) { - char * fileName; _optionPanel.currentButton = NULL; switch (panelButton->id) { case kTextContinuePlaying: @@ -1594,13 +1610,16 @@ void Interface::setOption(PanelButton *panelButton) { setMode(kPanelQuit); break; case kTextLoad: - if (_vm->getSaveFilesCount() > 0) { - if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) { - debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); - fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); - setMode(kPanelMain); - _vm->load(fileName); + if (_vm->getGameType() == GType_ITE) { + if (_vm->getSaveFilesCount() > 0) { + if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) { + debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); + setMode(kPanelMain); + _vm->load(_vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber)); + } } + } else { + setMode(kPanelLoad); } break; case kTextSave: diff --git a/engines/saga/itedata.cpp b/engines/saga/itedata.cpp index 43c3d21012..bbd5cbb615 100644 --- a/engines/saga/itedata.cpp +++ b/engines/saga/itedata.cpp @@ -339,7 +339,7 @@ FxTable ITE_SfxTable[ITE_SFXCOUNT] = { { 73, 64 } }; -const char *ITEinterfaceTextStrings[][52] = { +const char *ITEinterfaceTextStrings[][53] = { { // Note that the "Load Successful!" string is never used in ScummVM "Walk to", "Look At", "Pick Up", "Talk to", "Open", @@ -358,7 +358,8 @@ const char *ITEinterfaceTextStrings[][52] = { "There's no opening to close.", "I don't know how to do that.", "Show Dialog", - "What is Rif's reply?" + "What is Rif's reply?", + "Loading a saved game" }, // German { @@ -378,7 +379,8 @@ const char *ITEinterfaceTextStrings[][52] = { "Hier ist keine \231ffnung zum Schlie$en.", "Ich wei$ nicht, wie ich das machen soll.", "Text zeigen", - "Wie lautet die Antwort?" + "Wie lautet die Antwort?", + "Spielstand wird geladen" }, // Italian fan translation { @@ -398,7 +400,8 @@ const char *ITEinterfaceTextStrings[][52] = { "Nessuna apertura da chiudere.", "Non saprei come farlo.", "Dialoghi", - "Come risponderebbe Rif?" + "Come risponderebbe Rif?", + "Vuoi davvero caricare il gioco?" }, // Spanish IHNM { @@ -420,7 +423,8 @@ const char *ITEinterfaceTextStrings[][52] = { NULL, NULL, NULL, - NULL + NULL, + "Cardango una partida guardada" } }; diff --git a/engines/saga/itedata.h b/engines/saga/itedata.h index 00efd070c1..6d0f5a9d70 100644 --- a/engines/saga/itedata.h +++ b/engines/saga/itedata.h @@ -88,7 +88,7 @@ struct FxTable { extern ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT]; extern FxTable ITE_SfxTable[ITE_SFXCOUNT]; -extern const char *ITEinterfaceTextStrings[][52]; +extern const char *ITEinterfaceTextStrings[][53]; #define PUZZLE_PIECES 15 diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 123c11eb7d..0b6b3b1478 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -295,7 +295,8 @@ enum TextStringIds { kTextVoices, kTextText, kTextAudio, - kTextBoth + kTextBoth, + kTextLoadSavedGame }; struct GameResourceDescription { diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp index be4f2a423d..d9c7b446ba 100644 --- a/engines/saga/sprite.cpp +++ b/engines/saga/sprite.cpp @@ -74,9 +74,11 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) { Sprite::~Sprite(void) { debug(8, "Shutting down sprite subsystem..."); _mainSprites.freeMem(); - _inventorySprites.freeMem(); - _arrowSprites.freeMem(); - _saveReminderSprites.freeMem(); + if (_vm->getGameType() == GType_IHNM) { + _inventorySprites.freeMem(); + _arrowSprites.freeMem(); + _saveReminderSprites.freeMem(); + } free(_decodeBuf); } diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index 8f3175f098..609aca996d 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -49,14 +49,14 @@ void ScummEngine::loadCJKFont() { Common::File fp; _useCJKMode = false; _textSurfaceMultiplier = 1; - _newLineCharacter = 0xfe; + _newLineCharacter = 0; if (_game.version <= 5 && _game.platform == Common::kPlatformFMTowns && _language == Common::JA_JPN) { // FM-TOWNS v3 / v5 Kanji int numChar = 256 * 32; _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]; @@ -277,7 +277,7 @@ CharsetRenderer::CharsetRenderer(ScummEngine *vm) { _disableOffsX = false; _vm = vm; - _curId = 0; + _curId = -1; } CharsetRenderer::~CharsetRenderer() { @@ -289,7 +289,10 @@ CharsetRendererCommon::CharsetRendererCommon(ScummEngine *vm) _shadowColor = 0; } -void CharsetRendererCommon::setCurID(byte id) { +void CharsetRendererCommon::setCurID(int32 id) { + if (id == -1) + return; + assertRange(0, id, _vm->_numCharsets - 1, "charset"); _curId = id; @@ -308,7 +311,10 @@ void CharsetRendererCommon::setCurID(byte id) { _numChars = READ_LE_UINT16(_fontPtr + 2); } -void CharsetRendererV3::setCurID(byte id) { +void CharsetRendererV3::setCurID(int32 id) { + if (id == -1) + return; + assertRange(0, id, _vm->_numCharsets - 1, "charset"); _curId = id; @@ -668,7 +674,8 @@ void CharsetRenderer::translateColor() { void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) { static const SaveLoadEntry charsetRendererEntries[] = { - MKLINE(CharsetRenderer, _curId, sleByte, VER(73)), + MKLINE_OLD(CharsetRenderer, _curId, sleByte, VER(73), VER(73)), + MKLINE(CharsetRenderer, _curId, sleInt32, VER(74)), MKLINE(CharsetRenderer, _color, sleByte, VER(73)), MKEND() }; @@ -988,7 +995,10 @@ CharsetRendererNut::~CharsetRendererNut() { } } -void CharsetRendererNut::setCurID(byte id) { +void CharsetRendererNut::setCurID(int32 id) { + if (id == -1) + return; + int numFonts = ((_vm->_game.id == GID_CMI) && (_vm->_game.features & GF_DEMO)) ? 4 : 5; assert(id < numFonts); _curId = id; diff --git a/engines/scumm/charset.h b/engines/scumm/charset.h index b62dbc6006..dbe02fc8fc 100644 --- a/engines/scumm/charset.h +++ b/engines/scumm/charset.h @@ -67,7 +67,7 @@ public: protected: ScummEngine *_vm; - byte _curId; + int32 _curId; public: CharsetRenderer(ScummEngine *vm); @@ -80,7 +80,7 @@ public: void addLinebreaks(int a, byte *str, int pos, int maxwidth); void translateColor(); - virtual void setCurID(byte id) = 0; + virtual void setCurID(int32 id) = 0; int getCurID() { return _curId; } virtual int getFontHeight() = 0; @@ -113,7 +113,7 @@ protected: public: CharsetRendererCommon(ScummEngine *vm); - void setCurID(byte id); + void setCurID(int32 id); int getFontHeight(); }; @@ -142,7 +142,7 @@ protected: public: CharsetRendererNES(ScummEngine *vm) : CharsetRendererCommon(vm) {} - void setCurID(byte id) {} + void setCurID(int32 id) {} void printChar(int chr, bool ignoreCharsetMask); void drawChar(int chr, const Graphics::Surface &s, int x, int y); @@ -159,7 +159,7 @@ public: void printChar(int chr, bool ignoreCharsetMask); void drawChar(int chr, const Graphics::Surface &s, int x, int y); - void setCurID(byte id); + void setCurID(int32 id); void setColor(byte color); int getCharWidth(byte chr); }; @@ -168,7 +168,7 @@ class CharsetRendererV2 : public CharsetRendererV3 { public: CharsetRendererV2(ScummEngine *vm, Common::Language language); - void setCurID(byte id) {} + void setCurID(int32 id) {} int getCharWidth(byte chr) { return 8; } }; @@ -184,7 +184,7 @@ public: void printChar(int chr, bool ignoreCharsetMask); - void setCurID(byte id); + void setCurID(int32 id); int getFontHeight(); int getCharHeight(byte chr); 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/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/gfx.cpp b/engines/scumm/gfx.cpp index 6c8d24d25a..d09accafb0 100644 --- a/engines/scumm/gfx.cpp +++ b/engines/scumm/gfx.cpp @@ -669,7 +669,7 @@ void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, i x += 16; while (x + width >= _screenWidth) width -= 16; - if (width < 0) + if (width <= 0) return; } 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 6f68d56693..484e11cf50 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/resource.cpp b/engines/scumm/resource.cpp index 8f7617885a..9823a9483f 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 5894e837aa..426c7ee48b 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -479,6 +479,9 @@ Graphics::Surface *ScummEngine::loadThumbnailFromSlot(int slot) { Common::SeekableReadStream *in; SaveGameHeader hdr; + if (slot < 0) + return 0; + makeSavegameName(filename, slot, false); if (!(in = _saveFileMan->openForLoading(filename))) { return 0; @@ -507,6 +510,9 @@ bool ScummEngine::loadInfosFromSlot(int slot, InfoStuff *stuff) { Common::SeekableReadStream *in; SaveGameHeader hdr; + if (slot < 0) + return 0; + makeSavegameName(filename, slot, false); if (!(in = _saveFileMan->openForLoading(filename))) { return false; @@ -595,7 +601,7 @@ bool ScummEngine::loadInfos(Common::SeekableReadStream *file, InfoStuff *stuff) return true; } -void ScummEngine::saveInfos(Common::OutSaveFile* file) { +void ScummEngine::saveInfos(Common::WriteStream* file) { SaveInfoSection section; section.type = MKID_BE('INFO'); section.version = INFOSECTION_VERSION; diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index 0ddb4e5d2a..2d7ee64680 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -31,7 +31,7 @@ namespace Common { class SeekableReadStream; - class OutSaveFile; + class WriteStream; } namespace Scumm { @@ -50,7 +50,7 @@ namespace Scumm { * only saves/loads those which are valid for the version of the savegame * which is being loaded/saved currently. */ -#define CURRENT_VER 73 +#define CURRENT_VER 74 /** * An auxillary macro, used to specify savegame versions. We use this instead @@ -125,7 +125,7 @@ struct SaveLoadEntry { class Serializer { public: - Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, uint32 savegameVersion) + Serializer(Common::SeekableReadStream *in, Common::WriteStream *out, uint32 savegameVersion) : _loadStream(in), _saveStream(out), _savegameVersion(savegameVersion) { } @@ -151,7 +151,7 @@ public: protected: Common::SeekableReadStream *_loadStream; - Common::OutSaveFile *_saveStream; + Common::WriteStream *_saveStream; uint32 _savegameVersion; void saveArrayOf(void *b, int len, int datasize, byte filetype); 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/scumm.h b/engines/scumm/scumm.h index b1ab9f7386..b839d37b08 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -46,7 +46,7 @@ namespace GUI { using GUI::Dialog; namespace Common { class SeekableReadStream; - class OutSaveFile; + class WriteStream; } namespace Scumm { @@ -556,7 +556,7 @@ protected: public: int _numLocalScripts, _numImages, _numRooms, _numScripts, _numSounds; // Used by HE games int _numCostumes; // FIXME - should be protected, used by Actor::remapActorPalette - int _numCharsets; // FIXME - should be protected, used by CharsetRenderer + int32 _numCharsets; // FIXME - should be protected, used by CharsetRenderer BaseCostumeLoader *_costumeLoader; BaseCostumeRenderer *_costumeRenderer; @@ -633,8 +633,8 @@ public: protected: Graphics::Surface *loadThumbnail(Common::SeekableReadStream *file); bool loadInfos(Common::SeekableReadStream *file, InfoStuff *stuff); - void saveThumbnail(Common::OutSaveFile *file); - void saveInfos(Common::OutSaveFile* file); + void saveThumbnail(Common::WriteStream *file); + void saveInfos(Common::WriteStream* file); int32 _engineStartTime; int32 _pauseStartTime; diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp index f039e2ca23..700632e4b3 100644 --- a/engines/scumm/string.cpp +++ b/engines/scumm/string.cpp @@ -279,7 +279,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) { } c = *buffer++; - if (c == _newLineCharacter) { + if (_newLineCharacter != 0 && c == _newLineCharacter) { c = 13; break; } diff --git a/engines/sky/control.cpp b/engines/sky/control.cpp index a6ab5429dd..3edc087f57 100644 --- a/engines/sky/control.cpp +++ b/engines/sky/control.cpp @@ -986,7 +986,7 @@ void Control::handleKeyPress(Common::KeyState kbd, Common::String &textBuf) { if (kbd.keycode == Common::KEYCODE_BACKSPACE) { // backspace if (textBuf.size() > 0) textBuf.deleteLastChar(); - } else { + } else if (kbd.ascii) { // Cannot enter text wider than the save/load panel if (_enteredTextWidth >= PAN_LINE_WIDTH - 10) return; 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/logic.cpp b/engines/sky/logic.cpp index 6cd4ce505a..9f13bf9bee 100644 --- a/engines/sky/logic.cpp +++ b/engines/sky/logic.cpp @@ -1774,6 +1774,7 @@ bool Logic::fnChooser(uint32 a, uint32 b, uint32 c) { uint32 size = ((dataFileHeader *)data)->s_height * ((dataFileHeader *)data)->s_width; uint32 index = 0; uint32 width = ((dataFileHeader *)data)->s_width; + uint32 height = ((dataFileHeader *)data)->s_height; data += sizeof(dataFileHeader); @@ -1794,7 +1795,7 @@ bool Logic::fnChooser(uint32 a, uint32 b, uint32 c) { textCompact->xcood = TOP_LEFT_X; // set coordinates textCompact->ycood = ycood; - ycood += 12; + ycood += height; } if (p == _scriptVariables + TEXT1) diff --git a/engines/sky/sound.cpp b/engines/sky/sound.cpp index 928221a9a5..f15038c0b6 100644 --- a/engines/sky/sound.cpp +++ b/engines/sky/sound.cpp @@ -1025,6 +1025,7 @@ Sound::Sound(Audio::Mixer *mixer, Disk *pDisk, uint8 pVolume) { _mixer = mixer; _saveSounds[0] = _saveSounds[1] = 0xFFFF; _mainSfxVolume = pVolume; + _isPaused = false; } Sound::~Sound(void) { @@ -1254,14 +1255,20 @@ bool Sound::startSpeech(uint16 textNum) { void Sound::fnPauseFx(void) { - _mixer->pauseID(SOUND_CH0, true); - _mixer->pauseID(SOUND_CH1, true); + if (!_isPaused) { + _isPaused = true; + _mixer->pauseID(SOUND_CH0, true); + _mixer->pauseID(SOUND_CH1, true); + } } void Sound::fnUnPauseFx(void) { - _mixer->pauseID(SOUND_CH0, false); - _mixer->pauseID(SOUND_CH1, false); + if (_isPaused) { + _isPaused = false; + _mixer->pauseID(SOUND_CH0, false); + _mixer->pauseID(SOUND_CH1, false); + } } } // End of namespace Sky diff --git a/engines/sky/sound.h b/engines/sky/sound.h index 28e2e8c88a..0ad509700e 100644 --- a/engines/sky/sound.h +++ b/engines/sky/sound.h @@ -89,6 +89,8 @@ private: uint8 *_sampleRates, *_sfxInfo; uint8 _mainSfxVolume; + bool _isPaused; + static uint16 _speechConvertTable[8]; static SfxQueue _sfxQueue[MAX_QUEUED_FX]; }; 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/resman.cpp b/engines/sword2/resman.cpp index 04205e8d0e..880234aab0 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/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(¤tObj, 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(¤tObj); + } +} + +} // 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..7da4192456 --- /dev/null +++ b/engines/tinsel/detection.cpp @@ -0,0 +1,277 @@ +/* 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[] = { + + // The DW1 demo was based on an older revision of the Tinsel engine + // than the one used in the released game. We call it Tinsel v0 as + // opposed to v1 which was used in the full retail version of DW. + + { // 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_V0, + }, + + { + { // This version has *.gra files + "dw", + "Floppy", + AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_FLOPPY, + TINSEL_V1, + }, + + { // English CD. This version has *.gra files + { + "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_V1, + }, + + { // English CD with SCN files + { + "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_V1, + }, + +#if 0 + { // English Saturn CD. Not (yet?) supported + { + "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_V1, + }, +#endif + + { // 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_V1, + }, + + { 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..8639979b41 --- /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_V0 && + 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..44cc83af9b --- /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_V0 = 1 << 0, // Used in the DW1 demo only + TINSEL_V1 = 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/graphics/font.cpp b/graphics/font.cpp index b5817e613a..0b0405b4b4 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -585,8 +585,8 @@ NewFont *NewFont::loadFont(Common::SeekableReadStream &stream) { } bool NewFont::cacheFontData(const NewFont &font, const Common::String &filename) { - Common::File cacheFile; - if (!cacheFile.open(filename, Common::File::kFileWriteMode)) { + Common::DumpFile cacheFile; + if (!cacheFile.open(filename)) { warning("Couldn't open file '%s' for writing", filename.c_str()); return false; } diff --git a/gui/credits.h b/gui/credits.h index 41544a2248..ca2fda811d 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -7,6 +7,12 @@ static const char *credits[] = { "\\C\\c0""Max Horn", "\\C\\c0""Eugene Sandulenko", "\\C\\c0""", +"\\C\\c1""Retired Project Leaders", +"\\C\\c0""Vincent Hamm", +"\\C\\c2""ScummVM co-founder, Original Cruise/CinE author", +"\\C\\c0""Ludvig Strigeus", +"\\C\\c2""Original ScummVM and SimonVM author", +"\\C\\c0""", "\\C\\c1""Engine Teams", "\\C\\c1""SCUMM", "\\C\\c0""Torbj\366rn Andersson", @@ -98,10 +104,19 @@ static const char *credits[] = { "\\C\\c0""", "\\C\\c1""SAGA", "\\C\\c0""Torbj\366rn Andersson", +"\\C\\c0""Sven Hesse", "\\C\\c0""Filippos Karapetis", "\\C\\c0""Andrew Kurushin", "\\C\\c0""Eugene Sandulenko", "\\C\\c0""", +"\\C\\c1""Tinsel;", +"\\C\\c0""Torbj\366rn Andersson", +"\\C\\c0""Paul Gilbert", +"\\C\\c0""Sven Hesse", +"\\C\\c0""Max Horn", +"\\C\\c0""Filippos Karapetis", +"\\C\\c0""Joost Peters", +"\\C\\c0""", "\\C\\c1""Touch\351", "\\C\\c0""Gregory Montoir", "\\C\\c0""", @@ -190,8 +205,6 @@ static const char *credits[] = { "\\C\\c2""iMUSE, MIDI, all things musical", "\\C\\c0""R\374diger Hanke", "\\C\\c2""Port: MorphOS", -"\\C\\c0""Vincent Hamm", -"\\C\\c2""ScummVM co-founder, Original Cruise/CinE author", "\\C\\c0""Felix Jakschitsch", "\\C\\c2""Zak256 reverse engineering", "\\C\\c0""Mutwin Kraus", @@ -200,8 +213,6 @@ static const char *credits[] = { "\\C\\c2""Port: GP32", "\\C\\c0""Jeremy Newman", "\\C\\c2""Former webmaster", -"\\C\\c0""Ludvig Strigeus", -"\\C\\c2""Original ScummVM and SimonVM author", "\\C\\c0""Lionel Ulmer", "\\C\\c2""Port: X11", "\\C\\c0""Won Star", diff --git a/gui/launcher.cpp b/gui/launcher.cpp index cf95e09101..2953988ed3 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -452,7 +452,10 @@ void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat // Write back changes made to config object String newDomain(_domainWidget->getEditString()); if (newDomain != _domain) { - if (newDomain.empty() || ConfMan.hasGameDomain(newDomain)) { + if (newDomain.empty() + || newDomain.hasPrefix("_") + || newDomain == ConfigManager::kApplicationDomain + || ConfMan.hasGameDomain(newDomain)) { MessageDialog alert("This game ID is already taken. Please choose another one."); alert.runModal(); return; @@ -837,12 +840,7 @@ Common::String addGameToConf(const GameDescriptor &result) { // The auto detector or the user made a choice. // Pick a domain name which does not yet exist (after all, we // are *adding* a game to the config, not replacing). - String domain; - - if (result.contains("preferredtarget")) - domain = result["preferredtarget"]; - else - domain = result.gameid(); + String domain = result.preferredtarget(); assert(!domain.empty()); if (ConfMan.hasGameDomain(domain)) { diff --git a/gui/massadd.cpp b/gui/massadd.cpp index 687d367516..6842466ad9 100644 --- a/gui/massadd.cpp +++ b/gui/massadd.cpp @@ -23,7 +23,9 @@ */ #include "engines/metaengine.h" +#include "common/algorithm.h" #include "common/events.h" +#include "common/func.h" #include "common/config-manager.h" #include "gui/launcher.h" // For addGameToConf() @@ -113,10 +115,19 @@ MassAddDialog::MassAddDialog(const FilesystemNode &startDir) } } +struct GameDescLess { + bool operator()(const GameDescriptor &x, const GameDescriptor &y) const { + return x.preferredtarget().compareToIgnoreCase(y.preferredtarget()) < 0; + } +}; + void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { // FIXME: It's a really bad thing that we use two arbitrary constants if (cmd == kOkCmd) { + // Sort the detected games. This is not strictly necessary, but nice for + // people who want to edit their config file by hand after a mass add. + sort(_games.begin(), _games.end(), GameDescLess()); // Add all the detected games to the config for (GameList::const_iterator iter = _games.begin(); iter != _games.end(); ++iter) { printf(" Added gameid '%s', desc '%s'\n", @@ -118,7 +118,7 @@ iphone: $(OBJS) $(CXX) $(LDFLAGS) -o scummvm $(OBJS) \ $(OSX_STATIC_LIBS) \ -framework UIKit -framework CoreGraphics -framework CoreSurface \ - -framework LayerKit -framework GraphicsServices -framework CoreFoundation \ + -framework GraphicsServices -framework CoreFoundation -framework QuartzCore \ -framework Foundation -framework AudioToolbox -framework CoreAudio \ -lobjc -lz diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp index 7cf114dcc6..abc1e20076 100644 --- a/sound/midiparser_xmidi.cpp +++ b/sound/midiparser_xmidi.cpp @@ -133,6 +133,9 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) { } } } + } else if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) { + warning("Unsupported XMIDI controller %d (0x%2x)", + info.basic.param1, info.basic.param1); } break; diff --git a/sound/softsynth/mt32.cpp b/sound/softsynth/mt32.cpp index 053df544b1..360ef4539d 100644 --- a/sound/softsynth/mt32.cpp +++ b/sound/softsynth/mt32.cpp @@ -80,37 +80,41 @@ public: }; class MT32File : public MT32Emu::File { - Common::File file; + Common::File _in; + Common::DumpFile _out; public: bool open(const char *filename, OpenMode mode) { - Common::File::AccessMode accessMode = mode == OpenMode_read ? Common::File::kFileReadMode : Common::File::kFileWriteMode; - return file.open(filename, accessMode); + if (mode == OpenMode_read) + return _in.open(filename); + else + return _out.open(filename); } void close() { - return file.close(); + _in.close(); + _out.close(); } size_t read(void *in, size_t size) { - return file.read(in, size); + return _in.read(in, size); } bool readLine(char *in, size_t size) { - return file.readLine(in, size) != NULL; + return _in.readLine(in, size) != NULL; } bool readBit8u(MT32Emu::Bit8u *in) { - byte b = file.readByte(); - if (file.eof()) + byte b = _in.readByte(); + if (_in.eof()) return false; *in = b; return true; } size_t write(const void *in, size_t size) { - return file.write(in, size); + return _out.write(in, size); } bool writeBit8u(MT32Emu::Bit8u out) { - file.writeByte(out); - return !file.ioFailed(); + _out.writeByte(out); + return !_out.ioFailed(); } bool isEOF() { - return file.eof(); + return _in.isOpen() ? _in.eof() : _out.eof(); } }; diff --git a/test/common/bufferedreadstream.h b/test/common/bufferedreadstream.h new file mode 100644 index 0000000000..7733949d9a --- /dev/null +++ b/test/common/bufferedreadstream.h @@ -0,0 +1,27 @@ +#include <cxxtest/TestSuite.h> + +#include "common/stream.h" + +class BufferedReadStreamTestSuite : public CxxTest::TestSuite { + public: + void test_traverse(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + // Use a buffer size of 4 -- note that 10 % 4 != 0, + // so we test what happens if the cache can't be completly + // refilled. + Common::BufferedReadStream srs(&ms, 4); + + int i; + byte b; + for (i = 0; i < 10; ++i) { + TS_ASSERT( !srs.eos() ); + + b = srs.readByte(); + TS_ASSERT_EQUALS( i, b ); + } + + TS_ASSERT( srs.eos() ); + } +}; diff --git a/test/common/bufferedseekablereadstream.h b/test/common/bufferedseekablereadstream.h new file mode 100644 index 0000000000..63941904cd --- /dev/null +++ b/test/common/bufferedseekablereadstream.h @@ -0,0 +1,65 @@ +#include <cxxtest/TestSuite.h> + +#include "common/stream.h" + +class BufferedSeekableReadStreamTestSuite : public CxxTest::TestSuite { + public: + void test_traverse(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + Common::BufferedSeekableReadStream ssrs(&ms, 4); + + int i; + byte b; + for (i = 0; i < 10; ++i) { + TS_ASSERT( !ssrs.eos() ); + + TS_ASSERT_EQUALS( i, ssrs.pos() ); + + ssrs.read(&b, 1); + TS_ASSERT_EQUALS( i, b ); + } + + TS_ASSERT( ssrs.eos() ); + } + + void test_seek(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + Common::BufferedSeekableReadStream ssrs(&ms, 4); + byte b; + + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 ); + + ssrs.seek(1, SEEK_SET); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)1 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 1 ); + + ssrs.seek(5, SEEK_CUR); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 7 ); + + ssrs.seek(-3, SEEK_CUR); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)5 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 5 ); + + ssrs.seek(0, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)10 ); + TS_ASSERT( ssrs.eos() ); + + ssrs.seek(3, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 7 ); + + ssrs.seek(8, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)2 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 2 ); + } +}; diff --git a/test/common/func.h b/test/common/func.h new file mode 100644 index 0000000000..bf9b2ddff9 --- /dev/null +++ b/test/common/func.h @@ -0,0 +1,60 @@ +#include <cxxtest/TestSuite.h> + +#include "common/func.h" + +void myFunction1(int &dst, const int src) { dst = src; } +void myFunction2(const int src, int &dst) { dst = src; } + +class FuncTestSuite : public CxxTest::TestSuite +{ + public: + void test_bind1st() { + int dst = 0; + Common::bind1st(Common::ptr_fun(myFunction1), dst)(1); + TS_ASSERT_EQUALS(dst, 1); + } + + void test_bind2nd() { + int dst = 0; + Common::bind2nd(Common::ptr_fun(myFunction2), dst)(1); + TS_ASSERT_EQUALS(dst, 1); + } + + struct Foo { + void fooAdd(int &foo) { + ++foo; + } + + void fooSub(int &foo) const { + --foo; + } + }; + + void test_mem_fun_ref() { + Foo myFoos[4]; + int counter = 0; + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooAdd), counter)); + TS_ASSERT_EQUALS(counter, 4); + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooSub), counter)); + TS_ASSERT_EQUALS(counter, 0); + } + + void test_mem_fun() { + Foo *myFoos[4]; + for (int i = 0; i < 4; ++i) + myFoos[i] = new Foo; + + int counter = 0; + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooAdd), counter)); + TS_ASSERT_EQUALS(counter, 4); + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooSub), counter)); + TS_ASSERT_EQUALS(counter, 0); + + for (int i = 0; i < 4; ++i) + delete myFoos[i]; + } +}; diff --git a/test/common/ptr.h b/test/common/ptr.h index 11ed52d4b9..986330057c 100644 --- a/test/common/ptr.h +++ b/test/common/ptr.h @@ -61,6 +61,9 @@ class PtrTestSuite : public CxxTest::TestSuite TS_ASSERT(p1 != 0); TS_ASSERT(p2 == 0); + + p1.reset(); + TS_ASSERT(!p1); } struct A { diff --git a/test/common/seekablesubreadstream.h b/test/common/seekablesubreadstream.h index c4b21667c7..4e517093a5 100644 --- a/test/common/seekablesubreadstream.h +++ b/test/common/seekablesubreadstream.h @@ -2,22 +2,19 @@ #include "common/stream.h" -class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite -{ +class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite { public: - void test_traverse( void ) - { + void test_traverse(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); int start = 2, end = 8; - Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, start, end); + Common::SeekableSubReadStream ssrs(&ms, start, end); int i; byte b; - for (i = start; i < end; ++i) - { + for (i = start; i < end; ++i) { TS_ASSERT( !ssrs.eos() ); TS_ASSERT_EQUALS( uint32(i - start), ssrs.pos() ); @@ -29,12 +26,11 @@ class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite TS_ASSERT( ssrs.eos() ); } - void test_seek( void ) - { + void test_seek(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); - Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, 1, 9); + Common::SeekableSubReadStream ssrs(&ms, 1, 9); byte b; TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 ); diff --git a/test/common/str.h b/test/common/str.h index 76574ea70f..72d4df6f61 100644 --- a/test/common/str.h +++ b/test/common/str.h @@ -7,124 +7,154 @@ class StringTestSuite : public CxxTest::TestSuite public: void test_constructors(void) { Common::String str("test-string"); - TS_ASSERT( str == "test-string" ); + TS_ASSERT( str == "test-string"); str = Common::String(str.c_str()+5, 3); - TS_ASSERT( str == "str" ); + TS_ASSERT( str == "str"); str = "test-string"; - TS_ASSERT( str == "test-string" ); + TS_ASSERT( str == "test-string"); str = Common::String(str.c_str()+5, str.c_str()+8); - TS_ASSERT( str == "str" ); + TS_ASSERT( str == "str"); + } + + void test_trim(void) { + Common::String str(" This is a s tring with spaces "); + Common::String str2 = str; + str.trim(); + TS_ASSERT( str == "This is a s tring with spaces"); + TS_ASSERT( str2 == " This is a s tring with spaces "); } void test_empty_clear(void) { Common::String str("test"); - TS_ASSERT( !str.empty() ); + TS_ASSERT( !str.empty()); str.clear(); - TS_ASSERT( str.empty() ); + TS_ASSERT( str.empty()); } void test_lastChar(void) { Common::String str; - TS_ASSERT_EQUALS( str.lastChar(), '\0' ); + TS_ASSERT_EQUALS(str.lastChar(), '\0'); str = "test"; - TS_ASSERT_EQUALS( str.lastChar(), 't' ); + TS_ASSERT_EQUALS(str.lastChar(), 't'); Common::String str2("bar"); - TS_ASSERT_EQUALS( str2.lastChar(), 'r' ); + TS_ASSERT_EQUALS(str2.lastChar(), 'r'); } void test_concat1(void) { Common::String str("foo"); Common::String str2("bar"); str += str2; - TS_ASSERT_EQUALS( str, "foobar" ); - TS_ASSERT_EQUALS( str2, "bar" ); + TS_ASSERT_EQUALS(str, "foobar"); + TS_ASSERT_EQUALS(str2, "bar"); } void test_concat2(void) { Common::String str("foo"); str += "bar"; - TS_ASSERT_EQUALS( str, "foobar" ); + TS_ASSERT_EQUALS(str, "foobar"); } void test_concat3(void) { Common::String str("foo"); str += 'X'; - TS_ASSERT_EQUALS( str, "fooX" ); + TS_ASSERT_EQUALS(str, "fooX"); } void test_refCount(void) { + // using internal storage Common::String foo1("foo"); - Common::String foo2("foo"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += 'X'; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "foo" ); - TS_ASSERT_EQUALS( foo3, "foo""X" ); + TS_ASSERT_EQUALS(foo1, "foo"); + TS_ASSERT_EQUALS(foo2, "foo"); + TS_ASSERT_EQUALS(foo3, "foo""X"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "foo"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "foo""X"); } void test_refCount2(void) { + // using external storage Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); - Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += 'X'; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); - TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X" ); + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X"); } void test_refCount3(void) { Common::String foo1("0123456789abcdefghijk"); - Common::String foo2("0123456789abcdefghijk"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += "0123456789abcdefghijk"; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "0123456789abcdefghijk" ); - TS_ASSERT_EQUALS( foo3, "0123456789abcdefghijk""0123456789abcdefghijk" ); + TS_ASSERT_EQUALS(foo1, foo2); + TS_ASSERT_EQUALS(foo2, "0123456789abcdefghijk"); + TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "0123456789abcdefghijk"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk"); } void test_refCount4(void) { Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); - Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); - TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); + TS_ASSERT_EQUALS(foo1, foo2); + TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); } void test_hasPrefix(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.hasPrefix(""), true ); - TS_ASSERT_EQUALS( str.hasPrefix("this"), true ); - TS_ASSERT_EQUALS( str.hasPrefix("thit"), false ); - TS_ASSERT_EQUALS( str.hasPrefix("foo"), false ); + TS_ASSERT_EQUALS(str.hasPrefix(""), true); + TS_ASSERT_EQUALS(str.hasPrefix("this"), true); + TS_ASSERT_EQUALS(str.hasPrefix("thit"), false); + TS_ASSERT_EQUALS(str.hasPrefix("foo"), false); } void test_hasSuffix(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.hasSuffix(""), true ); - TS_ASSERT_EQUALS( str.hasSuffix("haha"), true ); - TS_ASSERT_EQUALS( str.hasSuffix("hahb"), false ); - TS_ASSERT_EQUALS( str.hasSuffix("hahah"), false ); + TS_ASSERT_EQUALS(str.hasSuffix(""), true); + TS_ASSERT_EQUALS(str.hasSuffix("haha"), true); + TS_ASSERT_EQUALS(str.hasSuffix("hahb"), false); + TS_ASSERT_EQUALS(str.hasSuffix("hahah"), false); } void test_contains(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.contains(""), true ); - TS_ASSERT_EQUALS( str.contains("haha"), true ); - TS_ASSERT_EQUALS( str.contains("hahb"), false ); - TS_ASSERT_EQUALS( str.contains("test"), true ); + TS_ASSERT_EQUALS(str.contains(""), true); + TS_ASSERT_EQUALS(str.contains("haha"), true); + TS_ASSERT_EQUALS(str.contains("hahb"), false); + TS_ASSERT_EQUALS(str.contains("test"), true); } void test_toLowercase(void) { Common::String str("Test it, NOW! 42"); + Common::String str2 = str; str.toLowercase(); - TS_ASSERT_EQUALS( str, "test it, now! 42" ); + TS_ASSERT_EQUALS(str, "test it, now! 42"); + TS_ASSERT_EQUALS(str2, "Test it, NOW! 42"); } void test_toUppercase(void) { Common::String str("Test it, NOW! 42"); + Common::String str2 = str; str.toUppercase(); - TS_ASSERT_EQUALS( str, "TEST IT, NOW! 42" ); + TS_ASSERT_EQUALS(str, "TEST IT, NOW! 42"); + TS_ASSERT_EQUALS(str2, "Test it, NOW! 42"); } }; diff --git a/test/common/subreadstream.h b/test/common/subreadstream.h index c48f57c3a8..4e14448c06 100644 --- a/test/common/subreadstream.h +++ b/test/common/subreadstream.h @@ -2,25 +2,22 @@ #include "common/stream.h" -class SubReadStreamTestSuite : public CxxTest::TestSuite -{ +class SubReadStreamTestSuite : public CxxTest::TestSuite { public: - void test_traverse( void ) - { + void test_traverse(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); int end = 5; - Common::SubReadStream srs = Common::SubReadStream(&ms, end); + Common::SubReadStream srs(&ms, end); int i; byte b; - for (i = 0; i < end; ++i) - { + for (i = 0; i < end; ++i) { TS_ASSERT( !srs.eos() ); - srs.read(&b, 1); + b = srs.readByte(); TS_ASSERT_EQUALS( i, b ); } diff --git a/tools/create_kyradat/create_kyradat.cpp b/tools/create_kyradat/create_kyradat.cpp index 173ba0f993..78de2b6bce 100644 --- a/tools/create_kyradat/create_kyradat.cpp +++ b/tools/create_kyradat/create_kyradat.cpp @@ -31,7 +31,7 @@ #include "md5.h" enum { - kKyraDatVersion = 28, + kKyraDatVersion = 31, kIndexSize = 12 }; @@ -53,6 +53,8 @@ enum { #include "malcolm.h" +#include "lol_demo.h" + const Game kyra1FanTranslations[] = { { kKyra1, IT_ITA, kTalkieVersion, "d0f1752098236083d81b9497bd2b6989", kyra1FreCD }, GAME_DUMMY_ENTRY @@ -251,6 +253,11 @@ const ExtractFilename extractFilenames[] = { { k3ItemMagicTable, k3TypeRaw16to8, "ITEMMAGIC.MAP" }, { k3ItemStringMap, kTypeRawData, "ITEMSTRINGS.MAP" }, + // LANDS OF LORE + + // Demo Sequence Player + { lSeqplayIntroTracks, k2TypeSoundList, "S_INTRO.TRA" }, + { -1, 0, 0 } }; @@ -288,7 +295,7 @@ bool getFilename(char *dstFilename, const Game *g, const int id) { void createFilename(char *dstFilename, const int gid, const int lang, const int special, const char *filename) { strcpy(dstFilename, filename); - static const char *gidExtensions[] = { "", ".K2", ".K3" }; + static const char *gidExtensions[] = { "", ".K2", ".K3", 0, ".LOL" }; strcat(dstFilename, gidExtensions[gid]); for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) { @@ -311,7 +318,7 @@ void createLangFilename(char *dstFilename, const int gid, const int lang, const } } - static const char *gidExtensions[] = { "", ".K2", ".K3" }; + static const char *gidExtensions[] = { "", ".K2", ".K3", 0, ".LOL" }; strcat(dstFilename, gidExtensions[gid]); for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) { @@ -723,11 +730,11 @@ bool extractHofSeqData(PAKFile &out, const Game *g, const byte *data, const uint controlOffs = 0; WRITE_BE_UINT16(output, controlOffs); - if (g->special != k2DemoVersion) + if (g->special != k2DemoVersion && g->special != k2DemoLol) ptr += 4; output += 2; - if (g->special != k2DemoVersion) { + if (g->special != k2DemoVersion && g->special != k2DemoLol) { for (int w = 0; w < 2; w++) { //startupCommand, finalCommand WRITE_BE_UINT16(output, READ_LE_UINT16(ptr)); ptr += 2; @@ -1063,9 +1070,9 @@ enum { uint32 getFeatures(const Game *g) { uint32 features = 0; - if (g->special == kTalkieVersion || g->special == k2CDFile1E || g->special == k2CDFile1F || g->special == k2CDFile1G || g->special == k2CDFile2E || g->special == k2CDFile2F || g->special == k2CDFile2G || g->game == kKyra3) + if (g->special == kTalkieVersion || g->special == k2CDFile1E || g->special == k2CDFile1F || g->special == k2CDFile1G || g->special == k2CDFile1I || g->special == k2CDFile2E || g->special == k2CDFile2F || g->special == k2CDFile2G || g->game == kKyra3) features |= GF_TALKIE; - else if (g->special == kDemoVersion || g->special == k2DemoVersion) + else if (g->special == kDemoVersion || g->special == k2DemoVersion || g->special == k2DemoLol) features |= GF_DEMO; else if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ || g->special == k2TownsFile1E || g->special == k2TownsFile1J || @@ -1344,6 +1351,8 @@ const Game *gameDescs[] = { kyra3Games, + lolDemos, + 0 }; diff --git a/tools/create_kyradat/create_kyradat.h b/tools/create_kyradat/create_kyradat.h index c8bcb7d583..8e985f9031 100644 --- a/tools/create_kyradat/create_kyradat.h +++ b/tools/create_kyradat/create_kyradat.h @@ -174,6 +174,8 @@ enum kExtractID { k3ItemMagicTable, k3ItemStringMap, + lSeqplayIntroTracks, + kMaxResIDs }; @@ -202,20 +204,22 @@ enum kSpecial { k2CDFile2E = 8, k2CDFile2F = 9, k2CDFile2G = 10, - - k2TownsFile1E = 11, - k2TownsFile1J = 12, - k2TownsFile2E = 13, - k2TownsFile2J = 14, - - k2FloppyFile1 = 15, - k2FloppyFile2 = 16, - - k2DemoVersion = 17, - - k2DemoVersionTlkE = 18, - k2DemoVersionTlkF = 19, - k2DemoVersionTlkG = 20 + // Italian fan translation + k2CDFile1I = 11, + + k2TownsFile1E = 12, + k2TownsFile1J = 13, + k2TownsFile2E = 14, + k2TownsFile2J = 15, + + k2FloppyFile1 = 16, + k2FloppyFile2 = 17, + + k2DemoVersion = 18, + k2DemoVersionTlkE = 19, + k2DemoVersionTlkF = 20, + k2DemoVersionTlkG = 21, + k2DemoLol = 22 }; struct SpecialExtension { @@ -225,8 +229,9 @@ struct SpecialExtension { enum kGame { kKyra1 = 0, - kKyra2, - kKyra3 + kKyra2 = 1, + kKyra3 = 2, + kLol = 4 }; struct Game { diff --git a/tools/create_kyradat/hof_cd.h b/tools/create_kyradat/hof_cd.h index 1393f74890..431adbb674 100644 --- a/tools/create_kyradat/hof_cd.h +++ b/tools/create_kyradat/hof_cd.h @@ -23,6 +23,12 @@ const ExtractEntry kyra2File1CDG[] = { { -1, 0, 0 } }; +const ExtractEntry kyra2File1CDI[] = { + { k2SeqplayStrings, 0x0002C566, 0x0002CE7C }, + { k2SeqplayTlkFiles, 0x0002A2AC, 0x0002A349 }, + { -1, 0, 0 } +}; + const ExtractEntry kyra2File2CDE[] = { { k2IngameSfxFiles, 0x0002CB30, 0x0002D221 }, { k2IngameSfxIndex, 0x000294F0, 0x00029848 }, @@ -47,5 +53,10 @@ const Game kyra2TalkieGames[] = { { kKyra2, EN_ANY, k2CDFile2E, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDE}, { kKyra2, FR_FRA, k2CDFile2F, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDF}, { kKyra2, DE_DEU, k2CDFile2G, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDG}, + + // Italian Fan Translation (using same offsets as English) + { kKyra2, IT_ITA, k2CDFile1I, "130795aa8f2333250c895dae9028b9bb", kyra2File1CDI}, + GAME_DUMMY_ENTRY }; + diff --git a/tools/create_kyradat/hof_demo.h b/tools/create_kyradat/hof_demo.h index f7b15ffa3d..2eaaa3c413 100644 --- a/tools/create_kyradat/hof_demo.h +++ b/tools/create_kyradat/hof_demo.h @@ -26,7 +26,8 @@ const Game kyra2Demos[] = { { kKyra2, EN_ANY, k2DemoVersion, "a620a37579dd44ab0403482285e3897f", kyra2Demo}, { kKyra2, EN_ANY, k2CDFile2E, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDE}, { kKyra2, FR_FRA, k2CDFile2F, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDF}, - { kKyra2, DE_DEU, k2CDFile2G, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDG}, - + { kKyra2, DE_DEU, k2CDFile2G, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDG}, GAME_DUMMY_ENTRY }; + + diff --git a/tools/create_kyradat/lol_demo.h b/tools/create_kyradat/lol_demo.h new file mode 100644 index 0000000000..ce114f4e73 --- /dev/null +++ b/tools/create_kyradat/lol_demo.h @@ -0,0 +1,15 @@ +const ExtractEntry lolDemo[] = { + { k2SeqplayPakFiles, 0x0001AC10, 0x0001AC1C }, + { k2SeqplayStrings, 0x0001B5EE, 0x0001B6F0 }, + { k2SeqplaySfxFiles, 0x0001B6F0, 0x0001B7B5 }, + { k2SeqplaySeqData, 0x0001B320, 0x0001B56C }, + { lSeqplayIntroTracks, 0x0001B7B5, 0x0001B7CF }, + { -1, 0, 0 } +}; + +const Game lolDemos[] = { + { kLol, EN_ANY, k2DemoLol, "30bb5af87d38adb47d3e6ce06b1cb042", lolDemo}, + GAME_DUMMY_ENTRY +}; + + diff --git a/tools/create_kyradat/misc.h b/tools/create_kyradat/misc.h index 1e1cd29cc9..f0de5283ad 100644 --- a/tools/create_kyradat/misc.h +++ b/tools/create_kyradat/misc.h @@ -363,6 +363,7 @@ const int kyra2CDFile1EngNeed[] = { k2SeqplayCreditsSpecial, k2SeqplayStrings, k2SeqplaySfxFiles, + k2SeqplayTlkFiles, k2SeqplaySeqData, k2SeqplayIntroTracks, k2SeqplayFinaleTracks, @@ -371,11 +372,19 @@ const int kyra2CDFile1EngNeed[] = { const int kyra2CDFile1FreNeed[] = { k2SeqplayStrings, + k2SeqplayTlkFiles, -1 }; const int kyra2CDFile1GerNeed[] = { k2SeqplayStrings, + k2SeqplayTlkFiles, + -1 +}; + +const int kyra2CDFile1ItaNeed[] = { + k2SeqplayStrings, + k2SeqplayTlkFiles, -1 }; @@ -472,6 +481,15 @@ const int kyra3Need[] = { -1 }; +const int lolDemoNeed[] = { + k2SeqplayPakFiles, + k2SeqplayStrings, + k2SeqplaySeqData, + k2SeqplaySfxFiles, + lSeqplayIntroTracks, + -1 +}; + const GameNeed gameNeedTable[] = { { kKyra1, -1, kyra1FloppyNeed }, { kKyra1, kTalkieVersion, kyra1CDNeed }, @@ -485,6 +503,7 @@ const GameNeed gameNeedTable[] = { { kKyra2, k2CDFile1E, kyra2CDFile1EngNeed }, { kKyra2, k2CDFile1F, kyra2CDFile1FreNeed }, { kKyra2, k2CDFile1G, kyra2CDFile1GerNeed }, + { kKyra2, k2CDFile1I, kyra2CDFile1ItaNeed }, // Italian fan translation { kKyra2, k2CDFile2E, kyra2CDFile2EngNeed }, { kKyra2, k2CDFile2F, kyra2CDFile2FreNeed }, { kKyra2, k2CDFile2G, kyra2CDFile2GerNeed }, @@ -496,6 +515,7 @@ const GameNeed gameNeedTable[] = { { kKyra2, k2DemoVersionTlkE, kyra2TlkDemoNeed}, { kKyra2, k2DemoVersionTlkF, kyra2TlkDemoNeed}, { kKyra2, k2DemoVersionTlkG, kyra2TlkDemoNeed}, + { kLol, k2DemoLol, lolDemoNeed}, { kKyra3, -1, kyra3Need }, @@ -512,14 +532,17 @@ const SpecialExtension specialTable[] = { { k2CDFile1E, "CD" }, { k2CDFile1F, "CD" }, { k2CDFile1G, "CD" }, + { k2CDFile1I, "CD" }, { k2CDFile2E, "CD" }, { k2CDFile2F, "CD" }, { k2CDFile2G, "CD" }, + { k2TownsFile1E, "TNS" }, { k2TownsFile1J, "TNS" }, { k2TownsFile2E, "TNS" }, { k2TownsFile2J, "TNS" }, { k2DemoVersion, "DEM" }, + { k2DemoLol, "DEM" }, { -1, 0 } }; diff --git a/tools/credits.pl b/tools/credits.pl index fa806db7de..d1a73ab9fb 100755 --- a/tools/credits.pl +++ b/tools/credits.pl @@ -463,6 +463,12 @@ begin_credits("Credits"); end_persons(); end_section(); + begin_section("Retired Project Leaders"); + begin_persons(); + add_person("Vincent Hamm", "yaz0r", "ScummVM co-founder, Original Cruise/CinE author"); + add_person("Ludvig Strigeus", "ludde", "Original ScummVM and SimonVM author"); + end_persons(); + end_section(); begin_section("Engine Teams"); begin_section("SCUMM"); @@ -566,11 +572,21 @@ begin_credits("Credits"); begin_section("SAGA"); add_person("Torbjörn Andersson", "eriktorbjorn", ""); + add_person("Sven Hesse", "DrMcCoy", ""); add_person("Filippos Karapetis", "[md5]", ""); add_person("Andrew Kurushin", "ajax16384", ""); add_person("Eugene Sandulenko", "sev", ""); end_section(); + begin_section("Tinsel;"); + add_person("Torbjörn Andersson", "eriktorbjorn", ""); + add_person("Paul Gilbert", "dreammaster", ""); + add_person("Sven Hesse", "DrMcCoy", ""); + add_person("Max Horn", "Fingolfin", ""); + add_person("Filippos Karapetis", "[md5]", ""); + add_person("Joost Peters", "joostp", ""); + end_section(); + begin_section("Touché"); add_person("Gregory Montoir", "cyx", ""); end_section(); @@ -669,12 +685,10 @@ begin_credits("Credits"); add_person("Ralph Brorsen", "painelf", "Help with GUI implementation"); add_person("Jamieson Christian", "jamieson630", "iMUSE, MIDI, all things musical"); add_person("Rüdiger Hanke", "", "Port: MorphOS"); - add_person("Vincent Hamm", "yaz0r", "ScummVM co-founder, Original Cruise/CinE author"); add_person("Felix Jakschitsch", "yot", "Zak256 reverse engineering"); add_person("Mutwin Kraus", "mutle", "Original MacOS porter"); add_person("Peter Moraliyski", "ph0x", "Port: GP32"); add_person("Jeremy Newman", "laxdragon", "Former webmaster"); - add_person("Ludvig Strigeus", "ludde", "Original ScummVM and SimonVM author"); add_person("Lionel Ulmer", "bbrox", "Port: X11"); add_person("Won Star", "wonst719", "Former GP32 porter"); end_persons(); diff --git a/tools/scumm-md5.txt b/tools/scumm-md5.txt index 721c43b568..4da978914d 100644 --- a/tools/scumm-md5.txt +++ b/tools/scumm-md5.txt @@ -58,14 +58,14 @@ maniac Maniac Mansion 7f45ddd6dbfbf8f80c0c0efea4c295bc 1972 en DOS V1 V1 - Fingolfin - d8d07efcb88f396bee0b402b10c3b1c9 262144 us NES NES - - - 3905799e081b80a61d4460b7b733c206 262144 gb NES NES - - + d8d07efcb88f396bee0b402b10c3b1c9 262144 gb NES NES - - + 3905799e081b80a61d4460b7b733c206 262144 us NES NES - - 81bbfa181184cb494e7a81dcfa94fbd9 262144 fr NES NES - - 257f8c14d8c584f7ddd601bcb00920c7 262144 de NES NES - - f163cf53f7850e43fb482471e5c52e1a 262144 es NES NES - - 22d07d6c386c9c25aca5dac2a0c0d94b 262144 se NES NES - - - 17f7296f63c78642724f057fd8e736a7 -1 us NES NES extracted - - 91d5db93187fab54d823f73bd6441cb6 -1 gb NES NES extracted - + 17f7296f63c78642724f057fd8e736a7 -1 gb NES NES extracted - + 91d5db93187fab54d823f73bd6441cb6 -1 us NES NES extracted - 1c7e7db2cfab1ad62746ab680a634204 -1 fr NES NES extracted - 3a5ec90d556d4920976c5578bfbfaf79 -1 de NES NES extracted - b7d37d6b786b5a22deea3b038eca96ca 2082 es NES NES extracted - |