/* ScummVM - Scumm Interpreter * Copyright (C) 2001 Ludvig Strigeus * Copyright (C) 2001/2002 The ScummVM project * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ #include "stdafx.h" #include "scumm.h" #include "sound/mixer.h" #include "sound/mididrv.h" #include "sound/imuse.h" #include "actor.h" #include "debug.h" #include "gameDetector.h" #include "gui.h" #include "newgui.h" #include "object.h" #include "resource.h" #include "string.h" #ifdef _WIN32_WCE extern void GraphicsOff(void); #endif // Use g_scumm from error() ONLY Scumm *g_scumm = 0; void autosave(Scumm * scumm) { scumm->_doAutosave = true; } void Scumm::initRandSeeds() { _randSeed1 = 0xA943DE33; _randSeed2 = 0x37A9ED29; } uint Scumm::getRandomNumber(uint max) { /* TODO: my own random number generator */ _randSeed1 = 0xDEADBF03 * (_randSeed1 + 1); _randSeed1 = (_randSeed1 >> 13) | (_randSeed1 << 19); return _randSeed1 % (max + 1); } uint Scumm::getRandomNumberRng(uint min, uint max) { return getRandomNumber(max - min) + min; } Scumm::Scumm (GameDetector *detector, OSystem *syst) : Engine(detector, syst) { OSystem::Property prop; // Use g_scumm from error() ONLY g_scumm = this; _debugMode = detector->_debugMode; _bootParam = detector->_bootParam; _exe_name = detector->_exe_name; _gameId = detector->_gameId; _gameText = detector->_gameText; _features = detector->_features; _soundCardType = detector->_soundCardType; _noSubtitles = detector->_noSubtitles; _cdrom = detector->_cdrom; _defaultTalkDelay = detector->_talkSpeed; _use_adlib = detector->_use_adlib; if (_gameId == GID_ZAK256) { // FmTowns is 320x240 _realWidth = 320; _realHeight = 240; } else { _realWidth = 320; _realHeight = 200; } _gui = new Gui(); _gui->init(this); _newgui = new NewGui(this); _bundle = new Bundle(this); _timer = new Timer(this); _sound = new Sound(this); _sound->_sound_volume_master = 0; _sound->_sound_volume_sfx = detector->_sfx_volume; _sound->_sound_volume_music = detector->_music_volume; /* Initialize backend */ syst->init_size(_realWidth, _realHeight); prop.cd_num = detector->_cdrom; syst->property(OSystem::PROP_OPEN_CD, &prop); /* Bind the mixer to the system => mixer will be invoked * automatically when samples need to be generated */ if (!_mixer->bind_to_system(syst)) { warning("Sound initialization failed"); if (detector->_use_adlib) { _use_adlib = false; detector->_use_adlib = false; detector->_midi_driver = MD_NULL; warning("Adlib music was selected, switching to midi null driver"); } } _mixer->set_volume(kDefaultSFXVolume); _mixer->set_music_volume(kDefaultMusicVolume); // Init iMuse if (detector->_use_adlib) { _imuse = IMuse::create_adlib(syst, _mixer); } else { _imuse = IMuse::create_midi(syst, detector->createMidi()); } if (detector->_gameTempo != 0) _imuse->property(IMuse::PROP_TEMPO_BASE, detector->_gameTempo); _imuse->set_music_volume(_sound->_sound_volume_music); // Load game from specified slot, if any if (detector->_save_slot != -1) { _saveLoadSlot = detector->_save_slot; _saveLoadFlag = 2; _saveLoadCompatible = false; } } Scumm::~Scumm () { delete [] _actors; delete _gui; delete _newgui; delete _bundle; delete _timer; delete _sound; } void Scumm::scummInit() { int i; Actor *a; tempMusic = 0; debug(9, "scummInit"); if (_features & GF_SMALL_HEADER) _resourceHeaderSize = 6; else _resourceHeaderSize = 8; if (!(_features & GF_SMALL_NAMES)) loadCharset(1); initScreens(0, 16, 320, 144); setShake(0); setupCursor(); _timer->init(); /* Allocate and initilise actors */ _actors = new Actor[MAX_ACTORS]; for (i = 1, a = getFirstActor(); ++a, i < NUM_ACTORS; i++) { a->number = i; a->initActorClass(this); a->initActor(1); } _vars[VAR_CHARINC] = 4; _numNestedScripts = 0; vm.cutSceneStackPointer = 0; memset(vm.cutScenePtr, 0, sizeof(vm.cutScenePtr)); memset(vm.cutSceneData, 0, sizeof(vm.cutSceneData)); for (i = 0; i < _maxVerbs; i++) { _verbs[i].verbid = 0; _verbs[i].right = 319; _verbs[i].oldleft = -1; _verbs[i].type = 0; _verbs[i].color = 2; _verbs[i].hicolor = 0; _verbs[i].charset_nr = 1; _verbs[i].curmode = 0; _verbs[i].saveid = 0; _verbs[i].center = 0; _verbs[i].key = 0; } if (!(_features & GF_AFTER_V7)) { camera._leftTrigger = 10; camera._rightTrigger = 30; camera._mode = 0; } camera._follows = 0; virtscr[0].xstart = 0; if (!(_features & GF_AFTER_V7)) { _vars[VAR_V5_DRAWFLAGS] = 11; _vars[VAR_59] = 3; _vars[VAR_CURRENT_LIGHTS] = LIGHTMODE_actor_base | LIGHTMODE_actor_color | LIGHTMODE_screen; } mouse.x = 104; mouse.y = 56; _ENCD_offs = 0; _EXCD_offs = 0; _currentScript = 0xFF; _sentenceNum = 0; _currentRoom = 0; _numObjectsInRoom = 0; _actorToPrintStrFor = 0; charset._bufPos = 0; _haveMsg = 0; _varwatch = -1; _screenStartStrip = 0; _vars[VAR_TALK_ACTOR] = 0; _talkDelay = 0; _keepText = false; _cursorState = 0; _userPut = 0; _newEffect = 129; _fullRedraw = true; clearDrawObjectQueue(); for (i = 0; i < 6; i++) { if (_features & GF_OLD256) { string[i].t_xpos = 0; string[i].t_ypos = 0; } else { string[i].t_xpos = 2; string[i].t_ypos = 5; } string[i].t_right = 319; string[i].t_color = 0xF; string[i].t_center = 0; string[i].t_charset = 0; } _numInMsgStack = 0; createResource(rtTemp, 6, 500); initScummVars(); if (!(_features & GF_AFTER_V6)) _vars[VAR_V5_TALK_STRING_Y] = -0x50; getGraphicsPerformance(); #ifdef COMPRESSED_SOUND_FILE _sound->_current_cache = 0; #endif _timer->installProcedure(&autosave, 5 * 60 * 1000); } void Scumm::initScummVars() { if (!(_features & GF_AFTER_V7)) { _vars[VAR_CURRENTDRIVE] = _currentDrive; _vars[VAR_FIXEDDISK] = checkFixedDisk(); _vars[VAR_SOUNDCARD] = _soundCardType; _vars[VAR_VIDEOMODE] = 0x13; _vars[VAR_HEAPSPACE] = 630; _vars[VAR_MOUSEPRESENT] = _mousePresent; _vars[VAR_SOUNDPARAM] = _soundParam; _vars[VAR_SOUNDPARAM2] = _soundParam2; _vars[VAR_SOUNDPARAM3] = _soundParam3; if (_features & GF_AFTER_V6) _vars[VAR_V6_EMSSPACE] = 10000; } } void Scumm::checkRange(int max, int min, int no, const char *str) { if (no < min || no > max) { error("Value %d is out of bounds (%d,%d) int script(%d) msg %s", no, min, max, vm.slot[_curExecScript].number, str); } } int Scumm::scummLoop(int delta) { static int counter = 0; #ifndef _WIN32_WCE if (_debugger) _debugger->on_frame(); #endif // Randomize the PRNG by calling it at regular intervals. This ensures // that it will be in a different state each time you run the program. getRandomNumber(2); _vars[VAR_TMR_1] += delta; _vars[VAR_TMR_2] += delta; _vars[VAR_TMR_3] += delta; _vars[VAR_TMR_4] += delta; if (delta > 15) delta = 15; decreaseScriptDelay(delta); _talkDelay -= delta; if (_talkDelay < 0) _talkDelay = 0; processKbd(); if (_features & GF_AFTER_V7) { _vars[VAR_CAMERA_POS_X] = camera._cur.x; _vars[VAR_CAMERA_POS_Y] = camera._cur.y; } else { _vars[VAR_CAMERA_POS_X] = camera._cur.x; } _vars[VAR_HAVE_MSG] = (_haveMsg == 0xFE) ? 0xFF : _haveMsg; _vars[VAR_VIRT_MOUSE_X] = _virtual_mouse_x; _vars[VAR_VIRT_MOUSE_Y] = _virtual_mouse_y; _vars[VAR_MOUSE_X] = mouse.x; _vars[VAR_MOUSE_Y] = mouse.y; _vars[VAR_DEBUGMODE] = _debugMode; if (_features & GF_AUDIOTRACKS) { if (delta) { if (++counter != 2) _vars[VAR_MI1_TIMER] += 5; else { counter = 0; _vars[VAR_MI1_TIMER] += 6; } } } else if (_features & GF_OLD256) { if(tempMusic == 3) { tempMusic = 0; _vars[VAR_MUSIC_FLAG]++; } else { tempMusic ++; } } if (_saveLoadFlag) { if (_saveLoadFlag == 1) { saveState(_saveLoadSlot, _saveLoadCompatible); // Ender: Disabled for small_header games, as // can overwrite game variables (eg, Zak256 cashcards) if (_saveLoadCompatible && !(_features & GF_SMALL_HEADER)) _vars[VAR_GAME_LOADED] = 201; } else { loadState(_saveLoadSlot, _saveLoadCompatible); // Ender: Disabled for small_header games, as // can overwrite game variables (eg, Zak256 cashcards) if (_saveLoadCompatible && !(_features & GF_SMALL_HEADER)) _vars[VAR_GAME_LOADED] = 203; } _saveLoadFlag = 0; } if (_doAutosave) { _saveLoadSlot = 0; sprintf(_saveLoadName, "Autosave %d", _saveLoadSlot); _saveLoadFlag = 1; _saveLoadCompatible = false; _doAutosave = false; } if (_completeScreenRedraw) { _completeScreenRedraw = false; gdi.clearUpperMask(); charset._hasMask = false; redrawVerbs(); _fullRedraw = true; } runAllScripts(); checkExecVerbs(); checkAndRunVar33(); if (_currentRoom == 0) { gdi._cursorActive = 0; CHARSET_1(); drawDirtyScreenParts(); _sound->processSoundQues(); camera._last = camera._cur; } else { walkActors(); moveCamera(); fixObjectFlags(); CHARSET_1(); if (camera._cur.x != camera._last.x || _BgNeedsRedraw || _fullRedraw || (_features & GF_AFTER_V7 && camera._cur.y != camera._last.y)) { redrawBGAreas(); } processDrawQue(); setActorRedrawFlags(); resetActorBgs(); if (!(_vars[VAR_CURRENT_LIGHTS] & LIGHTMODE_screen) && _vars[VAR_CURRENT_LIGHTS] & LIGHTMODE_flashlight) { drawFlashlight(); setActorRedrawFlags(); } processActors(); clear_fullRedraw(); cyclePalette(); palManipulate(); if (_doEffect) { _doEffect = false; fadeIn(_newEffect); clearClickedStatus(); } if (_cursorState > 0) { verbMouseOver(checkMouseOver(mouse.x, mouse.y)); } drawBlastObjects(); drawDirtyScreenParts(); removeBlastObjects(); if (!(_features & GF_AFTER_V6)) playActorSounds(); _sound->processSoundQues(); camera._last = camera._cur; } if (!(++_expire_counter)) { increaseResourceCounter(); } animateCursor(); /* show or hide mouse */ _system->show_mouse(_cursorState > 0); _vars[VAR_TIMER] = 0; return _vars[VAR_TIMER_NEXT]; } void Scumm::startScene(int room, Actor * a, int objectNr) { int i, where; Actor *at; CHECK_HEAP debug(1, "Loading room %d", room); clearMsgQueue(); fadeOut(_switchRoomEffect2); _newEffect = _switchRoomEffect; if (_currentScript != 0xFF) { if (vm.slot[_currentScript].where == WIO_ROOM || vm.slot[_currentScript].where == WIO_FLOBJECT) { if (vm.slot[_currentScript].cutsceneOverride != 0) error("Object %d stopped with active cutscene/override in exit", vm.slot[_currentScript].number); _currentScript = 0xFF; } else if (vm.slot[_currentScript].where == WIO_LOCAL) { if (vm.slot[_currentScript].cutsceneOverride != 0) error("Script %d stopped with active cutscene/override in exit", vm.slot[_currentScript].number); _currentScript = 0xFF; } } if (!(_features & GF_SMALL_HEADER)) // Disable for SH games. Overwrites _vars[VAR_NEW_ROOM] = room; // gamevars, eg Zak cashcards runExitScript(); killScriptsAndResources(); clearEnqueue(); stopCycle(0); for (i = 1, at = getFirstActor(); ++at, i < NUM_ACTORS; i++) { at->hideActor(); } if (!(_features & GF_AFTER_V7)) { for (i = 0; i < 0x100; i++) _shadowPalette[i] = i; } clearDrawObjectQueue(); _vars[VAR_ROOM] = room; _fullRedraw = true; increaseResourceCounter(); _currentRoom = room; _vars[VAR_ROOM] = room; if (room >= 0x80) _roomResource = _resourceMapper[room & 0x7F]; else _roomResource = room; _vars[VAR_ROOM_RESOURCE] = _roomResource; if (room != 0) ensureResourceLoaded(1, room); if (_currentRoom == 0) { _ENCD_offs = _EXCD_offs = 0; _numObjectsInRoom = 0; return; } initRoomSubBlocks(); if (_features & GF_SMALL_HEADER) loadRoomObjectsSmall(); else loadRoomObjects(); if (!(_features & GF_AFTER_V7)) { camera._mode = CM_NORMAL; camera._cur.x = camera._dest.x = 160; camera._cur.y = camera._dest.y = 100; } if (_features & GF_AFTER_V6) { _vars[VAR_V6_SCREEN_WIDTH] = _scrWidth; _vars[VAR_V6_SCREEN_HEIGHT] = _scrHeight; } if (_features & GF_AFTER_V7) { _vars[VAR_CAMERA_MIN_X] = 160; _vars[VAR_CAMERA_MAX_X] = _scrWidth - 160; _vars[VAR_CAMERA_MIN_Y] = 100; _vars[VAR_CAMERA_MAX_Y] = _scrHeight - 100; setCameraAt(160, 100); } else { _vars[VAR_CAMERA_MAX_X] = _scrWidth - 160; _vars[VAR_CAMERA_MIN_X] = 160; } if (_roomResource == 0) return; memset(gfxUsageBits, 0, sizeof(gfxUsageBits)); if (a) { where = whereIsObject(objectNr); if (where != WIO_ROOM && where != WIO_FLOBJECT) error("startScene: Object %d is not in room %d", objectNr, _currentRoom); int x, y, dir; getObjectXYPos(objectNr, x, y, dir); a->putActor(x, y, _currentRoom); a->setDirection(dir + 180); a->moving = 0; } showActors(); _egoPositioned = false; runEntryScript(); if (!(_features & GF_AFTER_V7)) { if (a && !_egoPositioned) { int x, y; getObjectXYPos(objectNr, x, y); a->putActor(x, y, _currentRoom); a->moving = 0; } } else { if (camera._follows) { Actor *a = derefActorSafe(camera._follows, "startScene: follows"); setCameraAt(a->x, a->y); } } _doEffect = true; CHECK_HEAP; } void Scumm::initRoomSubBlocks() { int i, offs; byte *ptr; byte *roomptr, *searchptr; RoomHeader *rmhd; _ENCD_offs = 0; _EXCD_offs = 0; _CLUT_offs = 0; _PALS_offs = 0; nukeResource(rtMatrix, 1); nukeResource(rtMatrix, 2); for (i = 1; i < _maxScaleTable; i++) nukeResource(rtScaleTable, i); roomptr = getResourceAddress(rtRoom, _roomResource); rmhd = (RoomHeader *)findResourceData(MKID('RMHD'), roomptr); if (_features & GF_AFTER_V7) { _scrWidth = READ_LE_UINT16(&(rmhd->v7.width)); _scrHeight = READ_LE_UINT16(&(rmhd->v7.height)); } else { _scrWidth = READ_LE_UINT16(&(rmhd->old.width)); _scrHeight = READ_LE_UINT16(&(rmhd->old.height)); } if (_features & GF_SMALL_HEADER) _IM00_offs = findResourceData(MKID('IM00'), roomptr) - roomptr; else _IM00_offs = findResource(MKID('IM00'), findResource(MKID('RMIM'), roomptr)) - roomptr; ptr = findResourceData(MKID('EXCD'), roomptr); if (ptr) { _EXCD_offs = ptr - roomptr; #ifdef DUMP_SCRIPTS dumpResource("exit-", _roomResource, ptr - 8); #endif } ptr = findResourceData(MKID('ENCD'), roomptr); if (ptr) { _ENCD_offs = ptr - roomptr; #ifdef DUMP_SCRIPTS dumpResource("entry-", _roomResource, ptr - 8); #endif } if (_features & GF_SMALL_HEADER) { ptr = findResourceData(MKID('BOXD'), roomptr); if (ptr) { byte numOfBoxes = *(ptr); int size; if (_features & GF_OLD256) size = numOfBoxes * (SIZEOF_BOX - 2) + 1; else size = numOfBoxes * SIZEOF_BOX + 1; createResource(rtMatrix, 2, size); memcpy(getResourceAddress(rtMatrix, 2), ptr, size); ptr += size; size = getResourceDataSize(ptr - size - 6) - size; if (size >= 0) { // do this :) createResource(rtMatrix, 1, size); memcpy(getResourceAddress(rtMatrix, 1), ptr, size); } } } else { ptr = findResourceData(MKID('BOXD'), roomptr); if (ptr) { int size = getResourceDataSize(ptr); createResource(rtMatrix, 2, size); roomptr = getResourceAddress(rtRoom, _roomResource); ptr = findResourceData(MKID('BOXD'), roomptr); memcpy(getResourceAddress(rtMatrix, 2), ptr, size); } ptr = findResourceData(MKID('BOXM'), roomptr); if (ptr) { int size = getResourceDataSize(ptr); createResource(rtMatrix, 1, size); roomptr = getResourceAddress(rtRoom, _roomResource); ptr = findResourceData(MKID('BOXM'), roomptr); memcpy(getResourceAddress(rtMatrix, 1), ptr, size); } } ptr = findResourceData(MKID('SCAL'), roomptr); if (ptr) { offs = ptr - roomptr; for (i = 1; i < _maxScaleTable; i++, offs += 8) { int a = READ_LE_UINT16(roomptr + offs); int b = READ_LE_UINT16(roomptr + offs + 2); int c = READ_LE_UINT16(roomptr + offs + 4); int d = READ_LE_UINT16(roomptr + offs + 6); if (a || b || c || d) { setScaleItem(i, b, a, d, c); roomptr = getResourceAddress(rtRoom, _roomResource); } } } memset(_localScriptList, 0, sizeof(_localScriptList)); searchptr = roomptr = getResourceAddress(rtRoom, _roomResource); if (_features & GF_SMALL_HEADER) { while ((ptr = findResourceSmall(MKID('LSCR'), searchptr)) != NULL) { int id = 0; ptr += _resourceHeaderSize; /* skip tag & size */ id = ptr[0]; #ifdef DUMP_SCRIPTS do { char buf[32]; sprintf(buf, "room-%d-", _roomResource); dumpResource(buf, id, ptr - 6); } while (0); #endif _localScriptList[id - _numGlobalScripts] = ptr + 1 - roomptr; searchptr = NULL; } } else { while ((ptr = findResource(MKID('LSCR'), searchptr)) != NULL) { int id = 0; ptr += _resourceHeaderSize; /* skip tag & size */ if (_features & GF_AFTER_V7) { id = READ_LE_UINT16(ptr); checkRange(2050, 2000, id, "Invalid local script %d"); _localScriptList[id - _numGlobalScripts] = ptr + 2 - roomptr; } else { id = ptr[0]; _localScriptList[id - _numGlobalScripts] = ptr + 1 - roomptr; } #ifdef DUMP_SCRIPTS do { char buf[32]; sprintf(buf, "room-%d-", _roomResource); dumpResource(buf, id, ptr - 8); } while (0); #endif searchptr = NULL; } } if (_features & GF_SMALL_HEADER) ptr = findResourceSmall(MKID('EPAL'), roomptr); else ptr = findResource(MKID('EPAL'), roomptr); if (ptr) _EPAL_offs = ptr - roomptr; if (_features & GF_SMALL_HEADER) ptr = findResourceSmall(MKID('CLUT'), roomptr); else ptr = findResourceData(MKID('CLUT'), roomptr); if (ptr) { _CLUT_offs = ptr - roomptr; setPaletteFromRes(); } if (_features & GF_AFTER_V6) { ptr = findResource(MKID('PALS'), roomptr); if (ptr) { _PALS_offs = ptr - roomptr; setPalette(0); } } if (_features & GF_SMALL_HEADER) ptr = findResourceData(MKID('CYCL'), roomptr); else ptr = findResourceData(MKID('CYCL'), roomptr); if (ptr) initCycl(findResourceData(MKID('CYCL'), roomptr)); ptr = findResourceData(MKID('TRNS'), roomptr); if (ptr) gdi._transparency = ptr[0]; else gdi._transparency = 255; initBGBuffers(_scrHeight); memset(_extraBoxFlags, 0, sizeof(_extraBoxFlags)); } void Scumm::setScaleItem(int slot, int a, int b, int c, int d) { byte *ptr; int cur, amounttoadd, i, tmp; ptr = createResource(rtScaleTable, slot, 200); if (a == c) return; cur = (b - d) * a; amounttoadd = d - b; for (i = 200; i > 0; i--) { tmp = cur / (c - a) + b; if (tmp < 1) tmp = 1; if (tmp > 255) tmp = 255; *ptr++ = tmp; cur += amounttoadd; } } void Scumm::dumpResource(char *tag, int idx, byte *ptr) { char buf[256]; FILE *out; uint32 size; if (_features & GF_SMALL_HEADER) size = READ_LE_UINT32(ptr); else size = READ_BE_UINT32_UNALIGNED(ptr + 4); #if defined(MACOS_CARBON) sprintf(buf, ":dumps:%s%d.dmp", tag, idx); #else sprintf(buf, "dumps/%s%d.dmp", tag, idx); #endif out = fopen(buf, "rb"); if (!out) { out = fopen(buf, "wb"); if (!out) return; fwrite(ptr, size, 1, out); } fclose(out); } void Scumm::clear_fullRedraw() { _fullRedraw = 0; } void Scumm::clearClickedStatus() { checkKeyHit(); _mouseButStat = 0; _leftBtnPressed = 0; _rightBtnPressed = 0; } int Scumm::checkKeyHit() { int a = _keyPressed; _keyPressed = 0; return a; } void Scumm::unkRoomFunc3(int unk1, int unk2, int rfact, int gfact, int bfact) { byte *pal = _currentPalette; byte *table = _shadowPalette; int i; warning("unkRoomFunc3(%d,%d,%d,%d,%d): not fully implemented", unk1, unk2, rfact, gfact, bfact); // TODO - correctly implement this function (see also patch #588501) // // Some "typical" examples of how this function is being invoked in real life: // // unkRoomFunc3(16, 255, 200, 200, 200) // // FOA: Sets up the colors for the boat and submarine shadows in the // diving scene. Are the shadows too light? Maybe unk1 is used to // darken the colors? // // unkRoomFunc3(0, 255, 700, 700, 700) // // FOA: Sets up the colors for the subway car headlight when it first // goes on. This seems to work ok. // // unkRoomFunc3(160, 191, 300, 300, 300) // unkRoomFunc3(160, 191, 289, 289, 289) // ... // unkRoomFunc3(160, 191, 14, 14, 14) // unkRoomFunc3(160, 191, 3, 3, 3) // // FOA: Sets up the colors for the subway car headlight for the later // half of the trip, where it fades out. This currently doesn't work // at all. The colors are too dark to be brightened. At first I thought // unk1 and unk2 were used to tell which color interval to manipulate, // but as far as I can tell the colors 160-191 aren't used at all to // draw the light, that can't be it. Apparently unk1 and/or unk2 are // used to brighten the colors. for (i = 0; i <= 255; i++) { int r = (int) (*pal++ * rfact) >> 8; int g = (int) (*pal++ * gfact) >> 8; int b = (int) (*pal++ * bfact) >> 8; *table++ = remapPaletteColor(r, g, b, (uint) -1); } } void Scumm::palManipulate(int start, int end, int color, int time, int e) { // TODO - correctly implement this function (see also bug #588501) // // The only place I know of where this function is being called is in the // FOA extro, to change the color to match the sinking sun. The following // three calls (with some pauses between them) are issued: // // palManipulate(16, 190, 32, 180, 1) // palManipulate(16, 190, 32, 1, 1) // palManipulate(16, 190, 32, 800, 1) // // The first two parameters seem to specify a palette range (as the colors // from 16 to 190 are the ones that make up water & sky). // // Maybe the change has to be done over a period of time, possibly specified // by the second last parameter - between call 1 and 2, about 17-18 seconds // seem to pass (well using get_msecs, I measured 17155 ms, 17613 ms, 17815 ms). // // No clue about the third and fifth parameter right now, they just always // are 32 and 1 - possibly finding another example of this function being // used would help a lot. Also, I can't currently compare it with the original, // doing that (and possibly, taking screenshots of it for analysis!) would // help a lot. static int sys_time = 0; int new_sys_time = _system->get_msecs(); warning("palManipulate(%d, %d, %d, %d, %d): not implemented", start, end, d, time, e); if (sys_time != 0) printf("Time since last call: %d\n", new_sys_time-sys_time); sys_time = new_sys_time; { int redScale = 0xFF; int greenScale = 0xFF - d; int blueScale = 0xFF - d; byte *cptr; byte *cur; int num; int color; cptr = _currentPalette + start * 3; cur = _currentPalette + start * 3; num = end - start + 1; do { color = *cptr++; if (redScale != 0xFF) color = color * redScale / 0xFF; if (color > 255) color = 255; *cur++ = color; color = *cptr++; if (greenScale != 0xFF) color = color * greenScale / 0xFF; if (color > 255) color = 255; *cur++ = color; color = *cptr++; if (blueScale != 0xFF) color = color * blueScale / 0xFF; if (color > 255) color = 255; *cur++ = color; } while (--num); setDirtyColors(start, end); } } void Scumm::pauseGame(bool user) { //_gui->pause(); _newgui->pauseDialog(); } void Scumm::setOptions() { _gui->options(); //_newgui->optionsDialog(); } void Scumm::shutDown(int i) { /* TODO: implement this */ warning("shutDown: not implemented"); } void Scumm::processKbd() { int saveloadkey; getKeyInput(0); if (_features & GF_OLD256) /* FIXME: Support ingame screen */ saveloadkey = 319; else saveloadkey = _vars[VAR_SAVELOADDIALOG_KEY]; _virtual_mouse_x = mouse.x + virtscr[0].xstart; if(_features & GF_AFTER_V7) _virtual_mouse_y = mouse.y + camera._cur.y-100; else _virtual_mouse_y = mouse.y; if (!(_features & GF_OLD256)) _virtual_mouse_y += virtscr[0].topline; else _virtual_mouse_y -= 16; if (_virtual_mouse_y < 0) _virtual_mouse_y = -1; if (_features & GF_OLD256) { if (_virtual_mouse_y >= virtscr[0].height + virtscr[0].topline) _virtual_mouse_y = -1; } else { if (_virtual_mouse_y >= virtscr[0].height) _virtual_mouse_y = -1; } if (!_lastKeyHit) return; if (_lastKeyHit == KEY_SET_OPTIONS) { setOptions(); return; } if (_lastKeyHit == _vars[VAR_RESTART_KEY]) { warning("Restart not implemented"); // pauseGame(true); return; } if (_lastKeyHit == _vars[VAR_PAUSE_KEY]) { pauseGame(true); /* pause */ return; } if (_lastKeyHit == _vars[VAR_CUTSCENEEXIT_KEY]) { if (_insaneState) { videoFinished = 1; } else exitCutscene(); } else if (_lastKeyHit == saveloadkey && _currentRoom != 0) { if (_features & GF_AFTER_V7) runScript(_vars[VAR_UNK_SCRIPT], 0, 0, 0); _gui->saveLoadDialog(); if (_features & GF_AFTER_V7) runScript(_vars[VAR_UNK_SCRIPT_2], 0, 0, 0); } else if (_lastKeyHit == _vars[VAR_TALKSTOP_KEY]) { _talkDelay = 0; if (_sound->_sfxMode == 2) stopTalk(); return; } else if (_lastKeyHit == '[') { // [, eg volume down _sound->_sound_volume_master-=5; if (_sound->_sound_volume_master < 0) _sound->_sound_volume_master = 0; _imuse->set_master_volume(_sound->_sound_volume_master); } else if (_lastKeyHit == ']') { // ], eg volume down _sound->_sound_volume_master+=5; if (_sound->_sound_volume_master > 128) _sound->_sound_volume_master = 128; _imuse->set_master_volume(_sound->_sound_volume_master); } else if (_lastKeyHit == '-') { // -, eg text speed down _defaultTalkDelay+=5; if (_defaultTalkDelay > 90) _defaultTalkDelay = 90; _vars[VAR_CHARINC] = _defaultTalkDelay / 20; } else if (_lastKeyHit == '+') { // +, eg text speed up _defaultTalkDelay-=5; if (_defaultTalkDelay < 5) _defaultTalkDelay = 5; _vars[VAR_CHARINC] = _defaultTalkDelay / 20; } else if (_lastKeyHit == 321) { // F7, display new GUI _newgui->saveloadDialog(); } _mouseButStat = _lastKeyHit; } int Scumm::getKeyInput(int a) { _mouseButStat = 0; _lastKeyHit = checkKeyHit(); if (a == 0) convertKeysToClicks(); if (mouse.x < 0) mouse.x = 0; if (mouse.x > _realWidth-1) mouse.x = _realWidth-1; if (mouse.y < 0) mouse.y = 0; if (mouse.y > _realHeight-1) mouse.y = _realHeight-1; if (_leftBtnPressed & msClicked && _rightBtnPressed & msClicked) { _mouseButStat = 0; _lastKeyHit = _vars[VAR_CUTSCENEEXIT_KEY]; } else if (_leftBtnPressed & msClicked) { _mouseButStat = MBS_LEFT_CLICK; } else if (_rightBtnPressed & msClicked) { _mouseButStat = MBS_RIGHT_CLICK; } if (_features & GF_AFTER_V7) { // _vars[VAR_LEFTBTN_DOWN] = (_leftBtnPressed&msClicked) != 0; _vars[VAR_LEFTBTN_HOLD] = (_leftBtnPressed & msDown) != 0; // _vars[VAR_RIGHTBTN_DOWN] = (_rightBtnPressed&msClicked) != 0; _vars[VAR_RIGHTBTN_HOLD] = (_rightBtnPressed & msDown) != 0; } _leftBtnPressed &= ~msClicked; _rightBtnPressed &= ~msClicked; return _lastKeyHit; } void Scumm::convertKeysToClicks() { if (_lastKeyHit && _cursorState > 0) { if (_lastKeyHit == 9) { _mouseButStat = MBS_RIGHT_CLICK; } else if (_lastKeyHit == 13) { _mouseButStat = MBS_LEFT_CLICK; } else return; _lastKeyHit = 0; } } Actor *Scumm::derefActor(int id) { return &_actors[id]; } Actor *Scumm::derefActorSafe(int id, const char *errmsg) { if (id < 1 || id >= NUM_ACTORS) { if (_debugMode) warning ("Invalid actor %d in %s (script %d, opcode 0x%x) - This is potentially a BIG problem.", id, errmsg, vm.slot[_curExecScript].number, _opcode); return NULL; } return derefActor(id); } void Scumm::makeCursorColorTransparent(int a) { int i, size; size = _cursorWidth * _cursorHeight; for (i = 0; i < size; i++) if (_grabbedCursor[i] == (byte)a) _grabbedCursor[i] = 0xFF; updateCursor(); } void Scumm::setStringVars(int slot) { StringTab *st = &string[slot]; st->xpos = st->t_xpos; st->ypos = st->t_ypos; st->center = st->t_center; st->overhead = st->t_overhead; st->no_talk_anim = st->t_no_talk_anim; st->right = st->t_right; st->color = st->t_color; st->charset = st->t_charset; } void Scumm::unkMiscOp9() { warning("stub unkMiscOp9()"); } void Scumm::startManiac() { warning("stub startManiac()"); } void Scumm::destroy() { freeResources(); free(_objectStateTable); free(_objectRoomTable); free(_objectOwnerTable); free(_inventory); free(_arrays); free(_verbs); free(_objs); free(_vars); free(_bitVars); free(_newNames); free(_classData); } const int new_dir_table[4] = { 270, 90, 180, 0, }; const int16 many_direction_tab[16] = {71, 109, 251, 289, -1, -1, -1, -1, 22, 72, 107, 157, 202, 252, 287, 337}; int newDirToOldDir(int dir) { if (dir >= 71 && dir <= 109) return 1; if (dir >= 109 && dir <= 251) return 2; if (dir >= 251 && dir <= 289) return 0; return 3; } int oldDirToNewDir(int dir) { return new_dir_table[dir]; } int numSimpleDirDirections(int dirType) { return dirType ? 8 : 4; } /* Convert an angle to a simple direction */ int toSimpleDir(int dirType, int dir) { int num = dirType ? 8 : 4; const int16 *dirtab = &many_direction_tab[dirType * 8]; for (int i = 1; i < num; i++, dirtab++) { if (dir >= dirtab[0] && dir <= dirtab[1]) return i; } return 0; } /* Convert a simple direction to an angle */ int fromSimpleDir(int dirType, int dir) { if (!dirType) return dir * 90; else return dir * 45; } int normalizeAngle(int angle) { int temp; temp = (angle + 360) % 360; return toSimpleDir(1, temp) * 45; } void NORETURN CDECL error(const char *s, ...) { char buf[1024]; #if defined( USE_WINDBG ) || defined ( _WIN32_WCE ) char buf2[1024]; #if defined( _WIN32_WCE ) TCHAR buf2w[2048]; #endif #endif va_list va; va_start(va, s); vsprintf(buf, s, va); va_end(va); if (g_scumm && g_scumm->_currentScript != 0xFF) { ScriptSlot *ss = &g_scumm->vm.slot[g_scumm->_currentScript]; fprintf(stderr, "Error(%d:%d:0x%X): %s!\n", g_scumm->_roomResource, ss->number, g_scumm->_scriptPointer - g_scumm->_scriptOrgPointer, buf); #if defined( USE_WINDBG ) || defined( _WIN32_WCE ) sprintf(buf2, "Error(%d:%d:0x%X): %s!\n", g_scumm->_roomResource, ss->number, g_scumm->_scriptPointer - g_scumm->_scriptOrgPointer, buf); #if defined ( _WIN32_WCE ) MultiByteToWideChar(CP_ACP, 0, buf2, strlen(buf2) + 1, buf2w, sizeof(buf2w)); GraphicsOff(); MessageBox(NULL, buf2w, TEXT("ScummVM error"), MB_OK); #else OutputDebugString(buf2); #endif #endif } else { fprintf(stderr, "Error: %s!\n", buf); #if defined( USE_WINDBG ) || defined( _WIN32_WCE ) sprintf(&buf[strlen(buf)], "\n"); #if defined ( _WIN32_WCE ) MultiByteToWideChar(CP_ACP, 0, buf, strlen(buf) + 1, buf2w, sizeof(buf2w)); GraphicsOff(); MessageBox(NULL, buf2w, TEXT("ScummVM error"), MB_OK); #else OutputDebugString(buf); #endif #endif } // Doesn't wait for any keypress!! Is it intended to? exit(1); } void CDECL warning(const char *s, ...) { char buf[1024]; va_list va; va_start(va, s); vsprintf(buf, s, va); va_end(va); fprintf(stderr, "WARNING: %s!\n", buf); #if defined( USE_WINDBG ) sprintf(&buf[strlen(buf)], "\n"); OutputDebugString(buf); #endif } uint16 _debugLevel = 1; void CDECL debug(int level, const char *s, ...) { char buf[1024]; va_list va; if (level > _debugLevel) return; va_start(va, s); vsprintf(buf, s, va); va_end(va); printf("%s\n", buf); #if defined( USE_WINDBG ) sprintf(&buf[strlen(buf)], "\n"); OutputDebugString(buf); #endif fflush(stdout); } void checkHeap() { #if defined(WIN32) if (_heapchk() != _HEAPOK) { error("Heap is invalid!"); } #endif } ScummDebugger g_debugger; void Scumm::waitForTimer(int msec_delay) { OSystem::Event event; uint32 start_time; if (_fastMode&2) msec_delay = 0; else if (_fastMode&1) msec_delay = 10; start_time = _system->get_msecs(); for(;;) { while (_system->poll_event(&event)) { // if newgui is running, copy event to EventList, and let the GUI handle it itself // we might consider this approach for ScummLoop as well, and clean up the current mess if (_newgui->isActive()) { if (event.event_code == OSystem::EVENT_MOUSEMOVE) { mouse.x = event.mouse.x; mouse.y = event.mouse.y; _system->set_mouse_pos(event.mouse.x, event.mouse.y); _system->update_screen(); } _newgui->handleEvent(event); continue; } switch(event.event_code) { case OSystem::EVENT_KEYDOWN: if (event.kbd.keycode >= '0' && event.kbd.keycode<='9' && (event.kbd.flags == OSystem::KBD_SHIFT || event.kbd.flags == OSystem::KBD_CTRL)) { _saveLoadSlot = event.kbd.keycode - '0'; sprintf(_saveLoadName, "Quicksave %d", _saveLoadSlot); _saveLoadFlag = (event.kbd.flags == OSystem::KBD_SHIFT) ? 1 : 2; _saveLoadCompatible = false; } else if (event.kbd.flags==OSystem::KBD_CTRL) { if (event.kbd.keycode=='f') _fastMode ^= 1; else if (event.kbd.keycode=='g') _fastMode ^= 2; else if ((event.kbd.keycode=='d') && (!_system->property(OSystem::PROP_GET_FULLSCREEN, 0))) g_debugger.attach(this); else if (event.kbd.keycode=='s') resourceStats(); } else _keyPressed = event.kbd.ascii; // Normal key press, pass on to the game. break; case OSystem::EVENT_MOUSEMOVE: mouse.x = event.mouse.x; mouse.y = event.mouse.y; _system->set_mouse_pos(event.mouse.x, event.mouse.y); _system->update_screen(); break; case OSystem::EVENT_LBUTTONDOWN: _leftBtnPressed |= msClicked|msDown; #ifdef _WIN32_WCE mouse.x = event.mouse.x; mouse.y = event.mouse.y; #endif break; case OSystem::EVENT_RBUTTONDOWN: _rightBtnPressed |= msClicked|msDown; #ifdef _WIN32_WCE mouse.x = event.mouse.x; mouse.y = event.mouse.y; #endif break; case OSystem::EVENT_LBUTTONUP: _leftBtnPressed &= ~msDown; break; case OSystem::EVENT_RBUTTONUP: _rightBtnPressed &= ~msDown; break; } } #ifdef COMPRESSED_SOUND_FILE if (_sound->updateMP3CD() == -1) #endif _system->update_cdrom(); /* Loop CD Audio if needed */ if (_system->get_msecs() >= start_time + msec_delay) break; _system->delay_msecs(10); } } void Scumm::updatePalette() { if (_palDirtyMax == -1) return; int first = _palDirtyMin; int num = _palDirtyMax - first + 1; int i; byte palette_colors[1024],*p = palette_colors; // Sam & Max film noir mode if (_gameId == GID_SAMNMAX && readVar(0x8000)) desaturatePalette(); for (i = _palDirtyMin; i <= _palDirtyMax; i++) { byte *data; if (_features & GF_SMALL_HEADER) data = _currentPalette + _shadowPalette[i] * 3; else data = _currentPalette + i * 3; *p++ = data[0]; *p++ = data[1]; *p++ = data[2]; *p++ = 0; } _system->set_palette(palette_colors, first, num); _palDirtyMax = -1; _palDirtyMin = 256; } void Scumm::mainRun() { int delta = 0; int last_time = _system->get_msecs(); int new_time; for(;;) { updatePalette(); _system->update_screen(); new_time = _system->get_msecs(); waitForTimer(delta * 15 + last_time - new_time); last_time = _system->get_msecs(); if (_gui->isActive()) { _gui->loop(); delta = 5; } else if (_newgui->isActive()) { _newgui->loop(); delta = 5; } else { delta = scummLoop(delta); if (delta < 1) // Ensure we don't get into a loop delta = 1; // by not decreasing sleepers. } } } void Scumm::launch() { charset._vm = this; gdi._vm = this; _fileHandle = NULL; _maxHeapThreshold = 450000; _minHeapThreshold = 400000; /* Create a primary virtual screen */ _videoBuffer = (byte *)calloc(328*200, 1); allocResTypeData(rtBuffer, MKID('NONE'), 10, "buffer", 0); initVirtScreen(0, 0, 0, 320, 200, false, false); if (_features & GF_AFTER_V7) setupScummVarsNew(); else setupScummVarsOld(); if ((_features & GF_AFTER_V7) || (_gameId == GID_SAMNMAX)) NUM_ACTORS = 30; else NUM_ACTORS = 13; if (_features & GF_AFTER_V7) OF_OWNER_ROOM = 0xFF; else OF_OWNER_ROOM = 0x0F; // if (_gameId==GID_MONKEY2 && _bootParam == 0) // _bootParam = 10001; if (_gameId == GID_INDY4 && _bootParam == 0) { _bootParam = -7873; } readIndexFile(); initRandSeeds(); if (_features & GF_NEW_OPCODES) setupOpcodes2(); else setupOpcodes(); scummInit(); if (!(_features & GF_AFTER_V7)) _vars[VAR_VERSION] = 21; _vars[VAR_DEBUGMODE] = _debugMode; if (_gameId == GID_MONKEY) _vars[74] = 1225; _sound->setupSound(); runScript(1, 0, 0, &_bootParam); // _scummTimer = 0; } void Scumm::go() { launch(); setupGUIColors(); mainRun(); } byte Scumm::getDefaultGUIColor(int color) { /* FIXME: strange IF line? */ if ((_features & GF_AFTER_V7) || (_features & GF_SMALL_HEADER)) return 0; if (_features & GF_AFTER_V6) { if (color == 8) color = 1; return readArray(110, 0, color); } else { return getStringAddress(21)[color]; } } void Scumm::setupGUIColors() { /* FIXME: strange IF line? */ if (_gameId && !(_features & GF_SMALL_HEADER) && !(_features & GF_AFTER_V7)) { _gui->_bgcolor = getDefaultGUIColor(0); _gui->_color = getDefaultGUIColor(1); _gui->_textcolor = getDefaultGUIColor(2); _gui->_textcolorhi = getDefaultGUIColor(6); _gui->_shadowcolor = getDefaultGUIColor(8); _newgui->_bgcolor = _gui->_bgcolor; _newgui->_color = _gui->_color; _newgui->_textcolor = _gui->_textcolor; _newgui->_textcolorhi = _gui->_textcolorhi; _newgui->_shadowcolor = _gui->_shadowcolor; } }