/* 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 3 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, see . * */ #include "common/scummsys.h" #include "common/events.h" #include "common/system.h" #include "cine/main_loop.h" #include "cine/object.h" #include "cine/various.h" #include "cine/bg_list.h" #include "cine/sound.h" #include "backends/audiocd/audiocd.h" namespace Cine { struct MouseStatusStruct { int left; int right; }; MouseStatusStruct mouseData; uint16 mouseRight = 0; uint16 mouseLeft = 0; uint16 mouseUpdateStatus; uint16 dummyU16; static void processEvent(Common::Event &event) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: mouseLeft = 1; break; case Common::EVENT_RBUTTONDOWN: mouseRight = 1; break; case Common::EVENT_MBUTTONDOWN: mouseLeft = mouseRight = 1; break; case Common::EVENT_LBUTTONUP: mouseLeft = 0; break; case Common::EVENT_RBUTTONUP: mouseRight = 0; break; case Common::EVENT_MBUTTONUP: mouseLeft = mouseRight = 0; break; case Common::EVENT_MOUSEMOVE: break; case Common::EVENT_WHEELUP: g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_UP)); break; case Common::EVENT_WHEELDOWN: g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_DOWN)); break; case Common::EVENT_KEYDOWN: switch (event.kbd.keycode) { case Common::KEYCODE_RETURN: case Common::KEYCODE_KP_ENTER: case Common::KEYCODE_KP5: if (allowPlayerInput) { mouseLeft = 1; } break; case Common::KEYCODE_ESCAPE: if (allowPlayerInput) { mouseRight = 1; } break; case Common::KEYCODE_F1: if (allowPlayerInput) { playerCommand = 0; // EXAMINE makeCommandLine(); } break; case Common::KEYCODE_F2: if (allowPlayerInput) { playerCommand = 1; // TAKE makeCommandLine(); } break; case Common::KEYCODE_F3: if (allowPlayerInput && !inMenu) { playerCommand = 2; // INVENTORY makeCommandLine(); } break; case Common::KEYCODE_F4: if (allowPlayerInput && !inMenu) { playerCommand = 3; // USE makeCommandLine(); } break; case Common::KEYCODE_F5: if (allowPlayerInput) { playerCommand = 4; // ACTIVATE makeCommandLine(); } break; case Common::KEYCODE_F6: if (allowPlayerInput) { playerCommand = 5; // SPEAK makeCommandLine(); } break; case Common::KEYCODE_F9: if (allowPlayerInput && !inMenu) { makeActionMenu(); makeCommandLine(); } break; case Common::KEYCODE_F10: if (!inMenu) { g_cine->makeSystemMenu(); } break; case Common::KEYCODE_F11: renderer->showCollisionPage(true); break; case Common::KEYCODE_KP0: g_cine->setDefaultGameSpeed(); break; case Common::KEYCODE_MINUS: case Common::KEYCODE_KP_MINUS: g_cine->modifyGameSpeed(-1); // Slower break; case Common::KEYCODE_PLUS: case Common::KEYCODE_KP_PLUS: g_cine->modifyGameSpeed(+1); // Faster break; case Common::KEYCODE_KP4: moveUsingKeyboard(-1, 0); // Left break; case Common::KEYCODE_KP6: moveUsingKeyboard(+1, 0); // Right break; case Common::KEYCODE_KP8: moveUsingKeyboard(0, +1); // Up break; case Common::KEYCODE_KP2: moveUsingKeyboard(0, -1); // Down break; case Common::KEYCODE_KP9: moveUsingKeyboard(+1, +1); // Up & Right break; case Common::KEYCODE_KP7: moveUsingKeyboard(-1, +1); // Up & Left break; case Common::KEYCODE_KP1: moveUsingKeyboard(-1, -1); // Down & Left break; case Common::KEYCODE_KP3: moveUsingKeyboard(+1, -1); // Down & Right break; case Common::KEYCODE_LEFT: // fall through case Common::KEYCODE_RIGHT: // fall through case Common::KEYCODE_UP: // fall through case Common::KEYCODE_DOWN: // fall through default: g_cine->_keyInputList.push_back(event.kbd); break; } break; case Common::EVENT_KEYUP: switch (event.kbd.keycode) { case Common::KEYCODE_RETURN: case Common::KEYCODE_KP_ENTER: case Common::KEYCODE_KP5: if (allowPlayerInput) { mouseLeft = 0; } break; case Common::KEYCODE_ESCAPE: if (allowPlayerInput) { mouseRight = 0; } break; case Common::KEYCODE_F11: renderer->showCollisionPage(false); break; case Common::KEYCODE_KP4: // Left case Common::KEYCODE_KP6: // Right case Common::KEYCODE_KP8: // Up case Common::KEYCODE_KP2: // Down case Common::KEYCODE_KP9: // Up & Right case Common::KEYCODE_KP7: // Up & Left case Common::KEYCODE_KP1: // Down & Left case Common::KEYCODE_KP3: // Down & Right // Stop ego movement made with keyboard when releasing a known key moveUsingKeyboard(0, 0); break; default: break; } default: break; } } void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState, Common::Array rects) { Common::EventManager *eventMan = g_system->getEventManager(); Common::Point mousePos; uint keysPressed = g_cine->_keyInputList.size(); bool foundTarget = false; int eventsChecked = 0; uint16 maxMouseLeft = mouseLeft; uint16 maxMouseRight = mouseRight; uint32 waitStart = g_system->getMillis(); uint32 waitEnd = waitStart + g_cine->getTimerDelay(); uint32 frameEnd = waitStart + 20; bool frameEnded = false; bool waitEnded = false; bool checkWaitEnd = (eventTarget == UNTIL_WAIT_ENDED); bool updateScreen = false; bool updateAudio = false; do { Common::Event event = Common::Event(); int eventsCheckedBeforePolling = eventsChecked; while (!foundTarget && !frameEnded && (eventMan->pollEvent(event) || eventsChecked == 0)) { processEvent(event); eventsChecked++; maxMouseLeft = MAX(mouseLeft, maxMouseLeft); maxMouseRight = MAX(mouseRight, maxMouseRight); bool mouseButtonDown = (mouseLeft != 0 || mouseRight != 0); bool mouseButtonUp = !mouseButtonDown; switch (eventTarget) { case UNTIL_MOUSE_BUTTON_UP_DOWN_UP: // fall through case UNTIL_MOUSE_BUTTON_UP_DOWN: // fall through case UNTIL_MOUSE_BUTTON_UP: // fall through case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED: foundTarget = mouseButtonUp; break; case UNTIL_MOUSE_BUTTON_DOWN_UP: // fall through case UNTIL_MOUSE_BUTTON_DOWN: foundTarget = mouseButtonDown; break; case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS: foundTarget = mouseButtonDown; if (!g_cine->_keyInputList.empty()) { Common::KeyState key = g_cine->_keyInputList.back(); if (key.keycode == Common::KEYCODE_UP || key.keycode == Common::KEYCODE_DOWN) { foundTarget = true; } } mousePos = g_system->getEventManager()->getMousePos(); for (Common::Array::iterator it = rects.begin(); it != rects.end(); ++it) { if (it->contains(mousePos)) { foundTarget = true; break; } } break; case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT: foundTarget = mouseButtonDown || keysPressed < g_cine->_keyInputList.size(); break; default: break; } uint32 now = g_system->getMillis(); frameEnded = (now >= frameEnd); waitEnded = (now >= waitEnd); if (foundTarget) { switch (eventTarget) { case UNTIL_MOUSE_BUTTON_UP_DOWN_UP: eventTarget = UNTIL_MOUSE_BUTTON_DOWN_UP; foundTarget = false; break; case UNTIL_MOUSE_BUTTON_UP_DOWN: eventTarget = UNTIL_MOUSE_BUTTON_DOWN; foundTarget = false; break; case UNTIL_MOUSE_BUTTON_DOWN_UP: eventTarget = UNTIL_MOUSE_BUTTON_UP; foundTarget = false; break; case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED: eventTarget = UNTIL_WAIT_ENDED; checkWaitEnd = true; foundTarget = false; break; default: break; } } foundTarget |= (checkWaitEnd && waitEnded); } int eventsCheckedAfterPolling = eventsChecked; bool eventQueueEmpty = (eventsCheckedBeforePolling == eventsCheckedAfterPolling); if (eventQueueEmpty) { uint32 now = g_system->getMillis(); frameEnded = (now >= frameEnd); waitEnded = (now >= waitEnd); } if (eventTarget == UNTIL_WAIT_ENDED) { foundTarget = waitEnded; } if (eventTarget == EMPTY_EVENT_QUEUE) { foundTarget = eventQueueEmpty; } foundTarget |= (checkWaitEnd && waitEnded); updateScreen = updateAudio = (foundTarget || frameEnded); if (updateScreen) { if (callSource != EXECUTE_PLAYER_INPUT) { g_system->updateScreen(); } else { // Make the command line (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON") // responsive by updating it here. if (allowPlayerInput && playerCommand != -1 && !mouseLeft && !mouseRight) { // A player command is given, left and right mouse buttons are up mousePos = eventMan->getMousePos(); playerCommandMouseLeftRightUp(mousePos.x, mousePos.y); renderer->drawCommand(); } renderer->blit(); } } if (updateAudio) { g_system->getAudioCDManager()->update(); // For Future Wars CD version } if (frameEnded) { frameEnd += 20; } g_system->delayMillis(10); } while (!foundTarget && !g_cine->shouldQuit()); if (useMaxMouseButtonState) { mouseData.left = maxMouseLeft; mouseData.right = maxMouseRight; } else { mouseData.left = mouseLeft; mouseData.right = mouseRight; } } void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) { Common::Point mouse = g_system->getEventManager()->getMousePos(); *pX = mouse.x; *pY = mouse.y; *pButton = 0; if (mouseData.right) { (*pButton) |= 2; } if (mouseData.left) { (*pButton) |= 1; } } /** Removes elements from seqList that have their member variable var4 set to value -1. */ void purgeSeqList() { Common::List::iterator it = g_cine->_seqList.begin(); while (it != g_cine->_seqList.end()) { if (it->var4 == -1) { // Erase the element and jump to the next element it = g_cine->_seqList.erase(it); } else { // Let the element be and jump to the next element ++it; } } } void CineEngine::mainLoop(int bootScriptIdx) { byte di; if (_preLoad == false) { resetBgIncrustList(); setTextWindow(0, 0, 20, 200); errorVar = 0; addScriptToGlobalScripts(bootScriptIdx); menuVar = 0; // gfxRedrawPage(page0c, page0, page0c, page0, -1); // gfxWaitVBL(); // gfxRedrawMouseCursor(); inMenu = false; allowPlayerInput = 0; checkForPendingDataLoadSwitch = 0; reloadBgPalOnNextFlip = 0; forbidBgPalReload = 0; gfxFadeOutCompleted = 0; gfxFadeInRequested = 0; safeControlsLastAccessedMs = 0; lastSafeControlObjIdx = -1; isDrawCommandEnabled = 0; waitForPlayerClick = 0; menuCommandLen = 0; playerCommand = -1; g_cine->_commandBuffer = ""; g_cine->_globalVars[VAR_MOUSE_X_POS] = 0; g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0; if (g_cine->getGameType() == Cine::GType_OS) { g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = 0; g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = 0; g_cine->_globalVars[VAR_BYPASS_PROTECTION] = 0; // set to 1 to bypass the copy protection g_cine->_globalVars[VAR_LOW_MEMORY] = 0; // set to 1 to disable some animations, sounds etc. } renderer->setBlackPalette(true); // Sets _changePal = true strcpy(newPrcName, ""); strcpy(newRelName, ""); strcpy(newObjectName, ""); strcpy(newMsgName, ""); strcpy(currentCtName, ""); strcpy(currentPartName, ""); g_sound->stopMusic(); } do { // HACK: Force amount of oxygen left to maximum during Operation Stealth's first arcade sequence. // This makes it possible to pass the arcade sequence for now. // FIXME: Remove the hack and make the first arcade sequence normally playable. /* if (g_cine->getGameType() == Cine::GType_OS) { Common::String bgName(renderer->getBgName()); // Check if the background is one of the three backgrounds // that are only used during the first arcade sequence. if (bgName == "28.PI1" || bgName == "29.PI1" || bgName == "30.PI1") { static const uint oxygenObjNum = 202, maxOxygen = 264; // Force the amount of oxygen left to the maximum. g_cine->_objectTable[oxygenObjNum].x = maxOxygen; } }*/ // HACK: In Operation Stealth after the first arcade sequence jump player's position to avoid getting stuck. // After the first arcade sequence the player comes up stairs from // the water in Santa Paragua's downtown in front of the flower shop. // Previously he was completely stuck after getting up the stairs. // If the background is the one used in the flower shop scene ("21.PI1") // and the player is at the exact location after getting up the stairs // then we just nudge him a tiny bit away from the stairs and voila, he's free! // Maybe the real problem behind all this is collision data related as it looks // like there's some boundary right there near position (204, 110) which we can // jump over by moving the character to (204, 109). The script handling the // flower shop scene is AIRPORT.PRC's 13th script. // FIXME: Remove the hack and solve what's really causing the problem in the first place. if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS) { if (scumm_stricmp(renderer->getBgName(), "21.PI1") == 0 && g_cine->_objectTable[1].x == 204 && g_cine->_objectTable[1].y == 110) { g_cine->_objectTable[1].y--; // Move the player character upward on-screen by one pixel } } stopMusicAfterFadeOut(); di = executePlayerInput(); // Clear the zoneQuery table (Operation Stealth specific) if (g_cine->getGameType() == Cine::GType_OS) { Common::fill(g_cine->_zoneQuery.begin(), g_cine->_zoneQuery.end(), 0); } if (g_cine->getGameType() == Cine::GType_OS) { processSeqList(); } executeObjectScripts(); executeGlobalScripts(); purgeObjectScripts(); purgeGlobalScripts(); if (g_cine->getGameType() == Cine::GType_OS) { purgeSeqList(); } if (playerCommand == -1) { setMouseCursor(MOUSE_CURSOR_NORMAL); } else { setMouseCursor(MOUSE_CURSOR_CROSS); } if (gfxFadeInRequested) { gfxFadeOutCompleted = 0; } if (renderer->ready()) { renderer->drawFrame(true); } // NOTE: In the original Future Wars and Operation Stealth messages // were removed when running the drawOverlays function which is // currently called from the renderer's drawFrame function. removeMessages(); if (waitForPlayerClick) { _messageLen <<= 3; if (_messageLen < 800) _messageLen = 800; manageEvents(MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, UNTIL_MOUSE_BUTTON_UP_DOWN_UP); waitForPlayerClick = 0; } if (checkForPendingDataLoadSwitch) { checkForPendingDataLoad(); checkForPendingDataLoadSwitch = 0; } if (di) { if ("quit"[menuCommandLen] == (char)di) { ++menuCommandLen; if (menuCommandLen == 4) { quitGame(); } } else { menuCommandLen = 0; } } } while (!shouldQuit() && !_restartRequested); hideMouse(); g_sound->stopMusic(); //if (g_cine->getGameType() == Cine::GType_OS) { // freeUnkList(); //} closePart(); } } // End of namespace Cine