mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-08 10:51:11 +00:00
501 lines
13 KiB
C++
501 lines
13 KiB
C++
/* 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.
|
|
*
|
|
*/
|
|
|
|
#include "common/config-manager.h"
|
|
|
|
#include "agi/agi.h"
|
|
#include "agi/sprite.h"
|
|
#include "agi/graphics.h"
|
|
#include "agi/inv.h"
|
|
#include "agi/text.h"
|
|
#include "agi/keyboard.h"
|
|
#include "agi/menu.h"
|
|
#include "agi/systemui.h"
|
|
|
|
namespace Agi {
|
|
|
|
/**
|
|
* Set up new room.
|
|
* This function is called when ego enters a new room.
|
|
* @param n room number
|
|
*/
|
|
void AgiEngine::newRoom(int16 newRoomNr) {
|
|
ScreenObjEntry *screenObj;
|
|
ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
|
|
int i;
|
|
|
|
// Loading trigger
|
|
loadingTrigger_NewRoom(newRoomNr);
|
|
|
|
debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr);
|
|
_sound->stopSound();
|
|
|
|
i = 0;
|
|
for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
|
|
screenObj->objectNr = i++;
|
|
screenObj->flags &= ~(fAnimated | fDrawn);
|
|
screenObj->flags |= fUpdate;
|
|
screenObj->stepTime = 1;
|
|
screenObj->stepTimeCount = 1;
|
|
screenObj->cycleTime = 1;
|
|
screenObj->cycleTimeCount = 1;
|
|
screenObj->stepSize = 1;
|
|
}
|
|
agiUnloadResources();
|
|
|
|
_game.playerControl = true;
|
|
_game.block.active = false;
|
|
_game.horizon = 36;
|
|
setVar(VM_VAR_PREVIOUS_ROOM, getVar(VM_VAR_CURRENT_ROOM));
|
|
setVar(VM_VAR_CURRENT_ROOM, newRoomNr);
|
|
setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
|
|
setVar(VM_VAR_BORDER_CODE, 0);
|
|
setVar(VM_VAR_EGO_VIEW_RESOURCE, screenObjEgo->currentViewNr);
|
|
|
|
agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr);
|
|
|
|
// Reposition ego in the new room
|
|
switch (getVar(VM_VAR_BORDER_TOUCH_EGO)) {
|
|
case 1:
|
|
screenObjEgo->yPos = SCRIPT_HEIGHT - 1;
|
|
break;
|
|
case 2:
|
|
screenObjEgo->xPos = 0;
|
|
break;
|
|
case 3:
|
|
screenObjEgo->yPos = _game.horizon + 1;
|
|
break;
|
|
case 4:
|
|
screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize;
|
|
break;
|
|
}
|
|
|
|
uint16 agiVersion = getVersion();
|
|
|
|
if (agiVersion < 0x2000) {
|
|
warning("STUB: NewRoom(%d)", newRoomNr);
|
|
|
|
screenObjEgo->flags &= ~fDidntMove;
|
|
// animateObject(0);
|
|
agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr);
|
|
setView(screenObjEgo, screenObjEgo->currentViewNr);
|
|
|
|
} else {
|
|
if (agiVersion >= 0x3000) {
|
|
// this was only done in AGI3
|
|
if (screenObjEgo->motionType == kMotionEgo) {
|
|
screenObjEgo->motionType = kMotionNormal;
|
|
setVar(VM_VAR_EGO_DIRECTION, 0);
|
|
}
|
|
}
|
|
|
|
setVar(VM_VAR_BORDER_TOUCH_EGO, 0);
|
|
setFlag(VM_FLAG_NEW_ROOM_EXEC, true);
|
|
|
|
_game.exitAllLogics = true;
|
|
|
|
_game._vm->_text->statusDraw();
|
|
_game._vm->_text->promptRedraw();
|
|
}
|
|
}
|
|
|
|
void AgiEngine::resetControllers() {
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CONTROLLERS; i++) {
|
|
_game.controllerOccured[i] = false;
|
|
}
|
|
}
|
|
|
|
void AgiEngine::interpretCycle() {
|
|
ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
|
|
bool oldSound;
|
|
byte oldScore;
|
|
|
|
if (!_game.playerControl)
|
|
setVar(VM_VAR_EGO_DIRECTION, screenObjEgo->direction);
|
|
else
|
|
screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);
|
|
|
|
checkAllMotions();
|
|
|
|
oldScore = getVar(VM_VAR_SCORE);
|
|
oldSound = getFlag(VM_FLAG_SOUND_ON);
|
|
|
|
_game.exitAllLogics = false;
|
|
while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
|
|
setVar(VM_VAR_WORD_NOT_FOUND, 0);
|
|
setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
|
|
setVar(VM_VAR_BORDER_CODE, 0);
|
|
oldScore = getVar(VM_VAR_SCORE);
|
|
setFlag(VM_FLAG_ENTERED_CLI, false);
|
|
_game.exitAllLogics = false;
|
|
nonBlockingText_CycleDone();
|
|
resetControllers();
|
|
}
|
|
nonBlockingText_CycleDone();
|
|
resetControllers();
|
|
|
|
screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);
|
|
|
|
if (getVar(VM_VAR_SCORE) != oldScore || getFlag(VM_FLAG_SOUND_ON) != oldSound)
|
|
_game._vm->_text->statusDraw();
|
|
|
|
setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
|
|
setVar(VM_VAR_BORDER_CODE, 0);
|
|
setFlag(VM_FLAG_NEW_ROOM_EXEC, false);
|
|
setFlag(VM_FLAG_RESTART_GAME, false);
|
|
setFlag(VM_FLAG_RESTORE_JUST_RAN, false);
|
|
|
|
if (_game.gfxMode) {
|
|
updateScreenObjTable();
|
|
}
|
|
_gfx->updateScreen();
|
|
//_gfx->doUpdate();
|
|
}
|
|
|
|
void AgiEngine::newInputMode(InputMode mode) {
|
|
//if (mode == INPUTMODE_MENU && !getflag(VM_FLAG_MENUS_WORK) && !(getFeatures() & GF_MENUS))
|
|
// return;
|
|
|
|
_oldMode = _game.inputMode;
|
|
_game.inputMode = mode;
|
|
}
|
|
|
|
void AgiEngine::oldInputMode() {
|
|
_game.inputMode = _oldMode;
|
|
}
|
|
|
|
// If main_cycle returns false, don't process more events!
|
|
int AgiEngine::mainCycle(bool onlyCheckForEvents) {
|
|
uint16 key;
|
|
byte keyAscii;
|
|
ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
|
|
|
|
if (!onlyCheckForEvents) {
|
|
pollTimer();
|
|
}
|
|
|
|
key = doPollKeyboard();
|
|
|
|
// In AGI Mouse emulation mode we must update the mouse-related
|
|
// vars in every interpreter cycle.
|
|
//
|
|
// We run AGIMOUSE always as a side effect
|
|
//if (getFeatures() & GF_AGIMOUSE) {
|
|
setVar(VM_VAR_MOUSE_X, _mouse.pos.x / 2);
|
|
setVar(VM_VAR_MOUSE_Y, _mouse.pos.y);
|
|
//}
|
|
|
|
switch (_game.inputMode) {
|
|
case INPUTMODE_NORMAL:
|
|
case INPUTMODE_NONE:
|
|
// Click-to-walk mouse interface
|
|
if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) {
|
|
int toX = screenObjEgo->move_x;
|
|
int toY = screenObjEgo->move_y;
|
|
|
|
// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
|
|
// Amiga games use ego's sprite's bottom center for mouse walking target.
|
|
// Atari ST and Apple II GS seem to use the bottom left
|
|
if (getPlatform() == Common::kPlatformAmiga)
|
|
toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally
|
|
|
|
// Adjust ego's sprite's mouse walking target position (These parameters are
|
|
// controlled with the adj.ego.move.to.x.y-command). Note that these values rely
|
|
// on the horizontal centering of the ego's sprite at least on the Amiga platform.
|
|
toX += _game.adjMouseX;
|
|
toY += _game.adjMouseY;
|
|
|
|
screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize);
|
|
|
|
if (screenObjEgo->direction == 0)
|
|
inDestination(screenObjEgo);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
keyAscii = key & 0xFF;
|
|
if (keyAscii) {
|
|
setVar(VM_VAR_KEY, keyAscii);
|
|
}
|
|
|
|
handleMouseClicks(key);
|
|
|
|
if (!cycleInnerLoopIsActive()) {
|
|
// no inner loop active at the moment, regular processing
|
|
switch (_game.inputMode) {
|
|
case INPUTMODE_NORMAL:
|
|
if (key) {
|
|
if (!handleController(key)) {
|
|
if ((key) && (_text->promptIsEnabled())) {
|
|
_text->promptCharPress(key);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case INPUTMODE_NONE:
|
|
if (key) {
|
|
handleController(key);
|
|
if (key) {
|
|
_game.keypress = key;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
// inner loop active
|
|
// call specific workers
|
|
setVar(VM_VAR_KEY, 0); // clear keys, they must not be passed to the scripts
|
|
_game.keypress = 0;
|
|
|
|
switch (_game.cycleInnerLoopType) {
|
|
case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit()
|
|
case CYCLE_INNERLOOP_GETNUMBER:
|
|
if (key) {
|
|
_text->stringCharPress(key);
|
|
}
|
|
break;
|
|
|
|
case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show()
|
|
if (key) {
|
|
_inventory->charPress(key);
|
|
}
|
|
break;
|
|
|
|
case CYCLE_INNERLOOP_MENU_VIA_KEYBOARD:
|
|
if (key) {
|
|
_menu->charPress(key);
|
|
}
|
|
return false;
|
|
|
|
case CYCLE_INNERLOOP_MENU_VIA_MOUSE:
|
|
_menu->mouseEvent(key);
|
|
return false;
|
|
|
|
case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT:
|
|
if (key) {
|
|
_systemUI->savedGameSlot_CharPress(key);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_menu->delayedExecuteActive()) {
|
|
_menu->execute();
|
|
}
|
|
|
|
if (!onlyCheckForEvents) {
|
|
if (_game.msgBoxTicks > 0)
|
|
_game.msgBoxTicks--;
|
|
}
|
|
|
|
_gfx->updateScreen();
|
|
|
|
return true;
|
|
}
|
|
|
|
int AgiEngine::playGame() {
|
|
int ec = errOK;
|
|
|
|
debugC(2, kDebugLevelMain, "initializing...");
|
|
debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
|
|
|
|
_sound->stopSound();
|
|
|
|
// We need to do this accurately and reset the AGI priorityscreen to 4
|
|
// otherwise at least the fan game Nick's Quest will go into an endless
|
|
// loop, because the game draws views before it draws the first background picture.
|
|
// For further study see bug #3451122
|
|
_gfx->clear(0, 4);
|
|
|
|
_game.horizon = 36;
|
|
_game.playerControl = false;
|
|
|
|
setFlag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917
|
|
setFlag(VM_FLAG_NEW_ROOM_EXEC, true); // needed for MUMG and SQ2!
|
|
setFlag(VM_FLAG_SOUND_ON, true); // enable sound
|
|
setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed
|
|
|
|
_game.gfxMode = true;
|
|
_text->promptRow_Set(22);
|
|
|
|
// We run AGIMOUSE always as a side effect
|
|
//if (getFeatures() & GF_AGIMOUSE)
|
|
debug(1, "Using AGI Mouse 1.0 protocol");
|
|
|
|
if (getFeatures() & GF_AGIPAL)
|
|
debug(1, "Running AGIPAL game");
|
|
|
|
debug(0, "Running AGI script.\n");
|
|
|
|
setFlag(VM_FLAG_ENTERED_CLI, false);
|
|
setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
|
|
setVar(VM_VAR_WORD_NOT_FOUND, 0);
|
|
setVar(VM_VAR_KEY, 0);
|
|
|
|
debugC(2, kDebugLevelMain, "Entering main loop");
|
|
bool firstLoop = !getFlag(VM_FLAG_RESTART_GAME); // Do not restore on game restart
|
|
|
|
if (firstLoop) {
|
|
if (ConfMan.hasKey("save_slot")) {
|
|
// quick restore enabled
|
|
_game.automaticRestoreGame = true;
|
|
}
|
|
}
|
|
|
|
nonBlockingText_Forget();
|
|
|
|
do {
|
|
|
|
if (!mainCycle())
|
|
continue;
|
|
|
|
inGameTimerUpdate();
|
|
|
|
if (_passedPlayTimeCycles >= getVar(VM_VAR_TIME_DELAY)) {
|
|
_passedPlayTimeCycles = 0;
|
|
|
|
interpretCycle();
|
|
|
|
// Check if the user has asked to load a game from the command line
|
|
// or the launcher
|
|
if (_game.automaticRestoreGame) {
|
|
_game.automaticRestoreGame = false;
|
|
checkQuickLoad();
|
|
}
|
|
|
|
setFlag(VM_FLAG_ENTERED_CLI, false);
|
|
setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
|
|
setVar(VM_VAR_WORD_NOT_FOUND, 0);
|
|
setVar(VM_VAR_KEY, 0);
|
|
}
|
|
|
|
if (shouldPerformAutoSave(_lastSaveTime)) {
|
|
saveGame(getSavegameFilename(0), "Autosave");
|
|
}
|
|
|
|
} while (!(shouldQuit() || _restartGame));
|
|
|
|
_sound->stopSound();
|
|
|
|
return ec;
|
|
}
|
|
|
|
int AgiEngine::runGame() {
|
|
int ec = errOK;
|
|
|
|
// Execute the game
|
|
do {
|
|
debugC(2, kDebugLevelMain, "game loop");
|
|
debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
|
|
|
|
if (agiInit() != errOK)
|
|
break;
|
|
|
|
if (_restartGame) {
|
|
setFlag(VM_FLAG_RESTART_GAME, true);
|
|
setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed
|
|
|
|
// Reset in-game timer
|
|
inGameTimerReset();
|
|
|
|
_restartGame = false;
|
|
}
|
|
|
|
// Set computer type (v20 i.e. vComputer) and sound type
|
|
switch (getPlatform()) {
|
|
case Common::kPlatformAtariST:
|
|
setVar(VM_VAR_COMPUTER, kAgiComputerAtariST);
|
|
setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
|
|
break;
|
|
case Common::kPlatformAmiga:
|
|
if (getFeatures() & GF_OLDAMIGAV20)
|
|
setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld);
|
|
else
|
|
setVar(VM_VAR_COMPUTER, kAgiComputerAmiga);
|
|
setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
|
|
break;
|
|
case Common::kPlatformApple2GS:
|
|
setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS);
|
|
if (getFeatures() & GF_2GSOLDSOUND)
|
|
setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld);
|
|
else
|
|
setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
|
|
break;
|
|
case Common::kPlatformDOS:
|
|
default:
|
|
setVar(VM_VAR_COMPUTER, kAgiComputerPC);
|
|
setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
|
|
break;
|
|
}
|
|
|
|
// Set monitor type (v26 i.e. vMonitor)
|
|
switch (_renderMode) {
|
|
case Common::kRenderCGA:
|
|
setVar(VM_VAR_MONITOR, kAgiMonitorCga);
|
|
break;
|
|
case Common::kRenderHercA:
|
|
case Common::kRenderHercG:
|
|
setVar(VM_VAR_MONITOR, kAgiMonitorHercules);
|
|
break;
|
|
// Don't know if Amiga AGI games use a different value than kAgiMonitorEga
|
|
// for vMonitor so I just use kAgiMonitorEga for them (As was done before too).
|
|
case Common::kRenderAmiga:
|
|
case Common::kRenderApple2GS:
|
|
case Common::kRenderAtariST:
|
|
case Common::kRenderEGA:
|
|
case Common::kRenderVGA:
|
|
default:
|
|
setVar(VM_VAR_MONITOR, kAgiMonitorEga);
|
|
break;
|
|
}
|
|
|
|
setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value
|
|
setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38);
|
|
_game.inputMode = INPUTMODE_NONE;
|
|
_text->promptDisable();
|
|
|
|
_game.state = STATE_RUNNING;
|
|
ec = playGame();
|
|
_game.state = STATE_LOADED;
|
|
agiDeinit();
|
|
} while (_restartGame);
|
|
|
|
delete _menu;
|
|
_menu = NULL;
|
|
|
|
releaseImageStack();
|
|
|
|
return ec;
|
|
}
|
|
|
|
} // End of namespace Agi
|