scummvm/engines/agi/preagi_troll.cpp
Martin Kiewitz 8a595e7771 AGI: graphics rewrite + cleanup
- graphics code fully rewritten
- Apple IIgs font support
- Amiga Topaz support
- Word parser rewritten
- menu code rewritten
- removed forced 2 second delay on all room changes
  replaced with heuristic to detect situations, where it's required
- lots of naming cleanup
- new console commands show_map, screenobj, vmvars and vmflags
- all sorts of hacks/workarounds removed
- added SCI wait mouse cursor
- added Apple IIgs mouse cursor
- added Atari ST mouse cursor
- added Amiga/Apple IIgs transition
- added Atari ST transition
- user can select another render mode and
  use Apple IIgs palette + transition for PC versions
- inventory screen rewritten
- SetSimple command now properly implemented
- PreAGI Mickey: Sierra logo now shown
- saved games: now saving controller key mapping
  also saving automatic save data (SetSimple command)
- fixed invalid memory access when saving games (31 bytes were saved
  using Common::String c_ptr()

Special Thanks to:
- fuzzie for helping out with the Apple IIgs font + valgrind
- eriktorbjorn for helping out with valgrind
- LordHoto for figuring out the code, that caused invalid memory
  access in the original code, when saving a game
- sev for help out with reversing the Amiga transition

currently missing:
- mouse support for menu
- mouse support for system dialogs
- predictive dialog support
2016-01-29 13:22:22 +01:00

782 lines
17 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 "agi/preagi.h"
#include "agi/preagi_troll.h"
#include "agi/graphics.h"
#include "common/events.h"
#include "common/textconsole.h"
#include "graphics/cursorman.h"
namespace Agi {
TrollEngine::TrollEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
}
TrollEngine::~TrollEngine() {
}
// User Interface
void TrollEngine::pressAnyKey(int col) {
drawStr(24, col, kColorDefault, IDS_TRO_PRESSANYKEY);
g_system->updateScreen();
getSelection(kSelAnyKey);
}
void TrollEngine::drawMenu(const char *szMenu, int iSel) {
clearTextArea();
drawStr(21, 0, kColorDefault, szMenu);
drawStr(22 + iSel, 0, kColorDefault, " *");
g_system->updateScreen();
}
bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) {
Common::Event event;
int y;
drawMenu(szMenu, *iSel);
while (!shouldQuit()) {
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_RTL:
case Common::EVENT_QUIT:
return 0;
case Common::EVENT_MOUSEMOVE:
y = event.mouse.y / 8;
if (y >= 22)
if (nSel > y - 22)
*iSel = y - 22;
drawMenu(szMenu, *iSel);
break;
case Common::EVENT_LBUTTONUP:
return true;
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_t:
case Common::KEYCODE_f:
inventory();
return false;
case Common::KEYCODE_DOWN:
case Common::KEYCODE_SPACE:
*iSel += 1;
if (*iSel == nSel)
*iSel = IDI_TRO_SEL_OPTION_1;
drawMenu(szMenu, *iSel);
break;
case Common::KEYCODE_UP:
*iSel -= 1;
if (*iSel == IDI_TRO_SEL_OPTION_1 - 1)
*iSel = nSel - 1;
drawMenu(szMenu, *iSel);
break;
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
return true;
case Common::KEYCODE_s:
if (event.kbd.hasFlags(Common::KBD_CTRL)) {
if (_soundOn) {
playTune(2, 1);
_soundOn = !_soundOn;
} else {
_soundOn = !_soundOn;
playTune(3, 1);
}
}
default:
break;
}
break;
default:
break;
}
}
_system->updateScreen();
_system->delayMillis(10);
}
return true;
}
// Graphics
void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
_picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
if (clr) {
clearScreen(0x0f, false);
_picture->clear();
}
_picture->setPictureData(_gameData + IDO_TRO_FRAMEPIC);
_picture->drawPicture();
_picture->setPictureData(_gameData + _pictureOffsets[iPic]);
int addFlag = 0;
if (troll)
addFlag = kPicFTrollMode;
if (f3IsCont) {
_picture->setPictureFlags(kPicFf3Cont | addFlag);
} else {
_picture->setPictureFlags(kPicFf3Stop | addFlag);
}
_picture->drawPicture();
_picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!!
g_system->updateScreen();
}
// Game Logic
void TrollEngine::inventory() {
char tmp[40];
int n;
clearScreen(0x07);
drawStr(1, 12, kColorDefault, IDS_TRO_TREASURE_0);
drawStr(2, 12, kColorDefault, IDS_TRO_TREASURE_1);
for (int i = 0; i < IDI_TRO_MAX_TREASURE - _treasuresLeft; i++) {
n = _inventory[i] - 1;
sprintf(tmp, " %2d ", i + 1);
drawStr(2 + i, 10, _items[n].bg << 4 | 0x0f, tmp);
drawStr(2 + i, 14, _items[n].bg << 4 | _items[n].fg, _items[n].name);
}
switch (_treasuresLeft) {
case 1:
sprintf(tmp, IDS_TRO_TREASURE_5, _treasuresLeft);
drawStr(20, 10, kColorDefault, tmp);
break;
case 0:
drawStr(20, 1, kColorDefault, IDS_TRO_TREASURE_6);
break;
case IDI_TRO_MAX_TREASURE:
drawStr(3, 17, kColorDefault, IDS_TRO_TREASURE_2);
break;
default:
sprintf(tmp, IDS_TRO_TREASURE_4, _treasuresLeft);
drawStr(20, 10, kColorDefault, tmp);
break;
}
pressAnyKey(6);
}
void TrollEngine::waitAnyKeyIntro() {
Common::Event event;
int iMsg = 0;
while (!shouldQuit()) {
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_RTL:
case Common::EVENT_QUIT:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_KEYDOWN:
return;
default:
break;
}
}
switch (iMsg) {
case 200:
iMsg = 0;
// fall through
case 0:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2);
g_system->updateScreen();
break;
case 100:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3);
g_system->updateScreen();
break;
}
iMsg++;
_system->updateScreen();
_system->delayMillis(10);
}
}
void TrollEngine::credits() {
clearScreen(0x07);
drawStr(1, 2, kColorDefault, IDS_TRO_CREDITS_0);
int color = 10;
char str[2];
str[1] = 0;
for (uint i = 0; i < strlen(IDS_TRO_CREDITS_1); i++) {
str[0] = IDS_TRO_CREDITS_1[i];
drawStr(7, 19 + i, color++, str);
if (color > 15)
color = 9;
}
drawStr(8, 19, kColorDefault, IDS_TRO_CREDITS_2);
drawStr(13, 11, 9, IDS_TRO_CREDITS_3);
drawStr(15, 8, 10, IDS_TRO_CREDITS_4);
drawStr(17, 7, 12, IDS_TRO_CREDITS_5);
drawStr(19, 2, 14, IDS_TRO_CREDITS_6);
g_system->updateScreen();
pressAnyKey();
}
void TrollEngine::tutorial() {
bool done = false;
int iSel = 0;
//char szTreasure[16] = {0};
while (!shouldQuit()) {
clearScreen(0xFF);
printStr(IDS_TRO_TUTORIAL_0);
getSelection(kSelSpace);
clearScreen(0x55);
setDefaultTextColor(0x0F);
done = false;
while (!done && !shouldQuit()) {
getMenuSel(IDS_TRO_TUTORIAL_1, &iSel, IDI_TRO_MAX_OPTION);
switch (iSel) {
case IDI_TRO_SEL_OPTION_1:
clearScreen(0x22, false);
g_system->updateScreen();
break;
case IDI_TRO_SEL_OPTION_2:
clearScreen(0x00, false);
g_system->updateScreen();
break;
case IDI_TRO_SEL_OPTION_3:
done = true;
break;
}
}
// do you need more practice ?
clearScreen(0x4F);
drawStr(7, 4, kColorDefault, IDS_TRO_TUTORIAL_5);
drawStr(9, 4, kColorDefault, IDS_TRO_TUTORIAL_6);
g_system->updateScreen();
if (!getSelection(kSelYesNo))
break;
}
// show info texts
clearScreen(0x5F);
drawStr(4, 1, kColorDefault, IDS_TRO_TUTORIAL_7);
drawStr(5, 1, kColorDefault, IDS_TRO_TUTORIAL_8);
g_system->updateScreen();
pressAnyKey();
clearScreen(0x2F);
drawStr(6, 1, kColorDefault, IDS_TRO_TUTORIAL_9);
g_system->updateScreen();
pressAnyKey();
clearScreen(0x19);
drawStr(7, 1, kColorDefault, IDS_TRO_TUTORIAL_10);
drawStr(8, 1, kColorDefault, IDS_TRO_TUTORIAL_11);
g_system->updateScreen();
pressAnyKey();
clearScreen(0x6E);
drawStr(9, 1, kColorDefault, IDS_TRO_TUTORIAL_12);
drawStr(10, 1, kColorDefault, IDS_TRO_TUTORIAL_13);
g_system->updateScreen();
pressAnyKey();
clearScreen(0x4C);
drawStr(11, 1, kColorDefault, IDS_TRO_TUTORIAL_14);
drawStr(12, 1, kColorDefault, IDS_TRO_TUTORIAL_15);
g_system->updateScreen();
pressAnyKey();
clearScreen(0x5D);
drawStr(13, 1, kColorDefault, IDS_TRO_TUTORIAL_16);
drawStr(14, 1, kColorDefault, IDS_TRO_TUTORIAL_17);
drawStr(15, 1, kColorDefault, IDS_TRO_TUTORIAL_18);
g_system->updateScreen();
pressAnyKey();
// show treasures
clearScreen(0x2A);
drawStr(2, 1, kColorDefault, IDS_TRO_TUTORIAL_19);
for (int i = 0; i < IDI_TRO_MAX_TREASURE; i++)
drawStr(19 - i, 11, kColorDefault, _items[i].name);
g_system->updateScreen();
pressAnyKey();
}
void TrollEngine::intro() {
// sierra on-line presents
clearScreen(0x2F);
drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0);
drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1);
g_system->updateScreen();
_system->delayMillis(3200);
CursorMan.showMouse(true);
// Draw logo
setDefaultTextColor(0x0f);
drawPic(45, false, true);
g_system->updateScreen();
// wait for keypress and alternate message
waitAnyKeyIntro();
// have you played this game before?
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_4);
drawStr(23, 6, kColorDefault, IDS_TRO_INTRO_5);
g_system->updateScreen();
if (!getSelection(kSelYesNo))
tutorial();
credits();
}
void TrollEngine::gameOver() {
// We do a check to see if the game should quit. Without this, the game show the picture, plays the
// music, and then quits. So if the game is quitting, we shouldn't run the "game over" part.
if (shouldQuit())
return;
char szMoves[40];
clearTextArea();
drawPic(42, true, true);
playTune(4, 25);
printUserMessage(16);
printUserMessage(33);
clearTextArea();
drawPic(46, true, true);
sprintf(szMoves, IDS_TRO_GAMEOVER_0, _moves);
drawStr(21, 1, kColorDefault, szMoves);
drawStr(22, 1, kColorDefault, IDS_TRO_GAMEOVER_1);
g_system->updateScreen();
pressAnyKey();
}
void TrollEngine::drawTroll() {
for (int i = 0; i < IDI_TRO_NUM_NONTROLL; i++)
if (_currentRoom == _nonTrollRooms[i]) {
_isTrollAway = true;
return;
}
drawPic(43, false, false, true);
}
int TrollEngine::drawRoom(char *menu) {
int n = 0;
bool contFlag = false;
if (_currentRoom == 1) {
_picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
clearScreen(0x00, false);
_picture->clear();
} else {
if (_currentRoom != 42) {
if (_roomPicDeltas[_currentRoom]) {
contFlag = true;
}
}
drawPic(_currentRoom, contFlag, true);
g_system->updateScreen();
if (_currentRoom == 42) {
drawPic(44, false, false); // don't clear
} else {
if (!_isTrollAway) {
drawTroll();
}
}
}
g_system->updateScreen();
char tmp[10];
strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39);
for (int i = 0; i < 3; i++) {
if (_roomDescs[_roomPicture - 1].options[i]) {
sprintf(tmp, "\n %d.", i);
strcat(menu, tmp);
strncat(menu, (char *)_gameData + _options[_roomDescs[_roomPicture - 1].options[i]- 1], 35);
n = i + 1;
}
}
return n;
}
void TrollEngine::playTune(int tune, int len) {
if (!_soundOn)
return;
int freq, duration;
int ptr = _tunes[tune - 1];
for (int i = 0; i < len; i++) {
freq = READ_LE_UINT16(_gameData + ptr);
ptr += 2;
duration = READ_LE_UINT16(_gameData + ptr);
ptr += 2;
playNote(freq, duration);
}
}
void TrollEngine::pickupTreasure(int treasureId) {
char tmp[40];
_inventory[IDI_TRO_MAX_TREASURE - _treasuresLeft] = treasureId;
if (_currentRoom != 24) {
clearTextArea();
drawPic(_currentRoom, false, true);
g_system->updateScreen();
}
printUserMessage(treasureId + 16);
clearTextArea();
_treasuresLeft--;
switch (_treasuresLeft) {
case 1:
drawStr(22, 1, kColorDefault, IDS_TRO_TREASURE_7);
break;
case 0:
drawStr(22, 1, kColorDefault, IDS_TRO_TREASURE_8);
drawStr(23, 4, kColorDefault, IDS_TRO_TREASURE_9);
_roomStates[6] = 1;
_locMessagesIdx[6] = IDO_TRO_ALLTREASURES;
break;
default:
sprintf(tmp, IDS_TRO_TREASURE_3, _treasuresLeft);
drawStr(22, 1, kColorDefault, tmp);
break;
}
pressAnyKey();
}
void TrollEngine::printUserMessage(int msgId) {
int i;
clearTextArea();
for (i = 0; i < _userMessages[msgId - 1].num; i++) {
drawStr(21 + i, 1, kColorDefault, _userMessages[msgId - 1].msg[i]);
}
if (msgId == 34) {
for (i = 0; i < 2; i++)
playTune(5, 11);
}
pressAnyKey();
}
void TrollEngine::gameLoop() {
bool done = false;
char menu[160+5];
int currentOption, numberOfOptions;
int roomParam;
int haveFlashlight;
_moves = 0;
_roomPicture = 1;
_treasuresLeft = IDI_TRO_MAX_TREASURE;
haveFlashlight = false;
_currentRoom = 0;
_isTrollAway = true;
_soundOn = true;
memset(_roomStates, 0, sizeof(_roomStates));
memset(_inventory, 0, sizeof(_inventory));
while (!done && !shouldQuit()) {
*menu = 0;
currentOption = 0;
numberOfOptions = drawRoom(menu);
if (getMenuSel(menu, &currentOption, numberOfOptions)) {
_moves++;
} else {
continue;
}
roomParam = _roomDescs[_roomPicture - 1].roomDescIndex[currentOption];
switch (_roomDescs[_roomPicture - 1].optionTypes[currentOption]) {
case OT_FLASHLIGHT:
if (!haveFlashlight) {
printUserMessage(13);
break;
}
// fall down
case OT_GO:
_currentRoom = roomParam;
_roomPicture = _roomPicStartIdx[_currentRoom];
_roomPicture += _roomStates[_currentRoom];
if (_currentRoom < 6 || _treasuresLeft == 0) {
_isTrollAway = true;
} else { // make odd 1:3
_isTrollAway = (rnd(3) != 2);
}
break;
case OT_GET:
if (!_isTrollAway) {
printUserMessage(34);
} else {
for (int i = 0; i < 4; i++) {
playTune(1, 3);
// delayMillis()
}
_roomStates[_currentRoom] = 1;
_roomPicDeltas[_currentRoom] = 0;
_roomPicture++;
if (_roomConnects[roomParam - 1] != 0xff) {
_roomStates[_roomConnects[roomParam - 1]] = 1;
}
if (roomParam == 1)
haveFlashlight = true;
_locMessagesIdx[_currentRoom] = IDO_TRO_LOCMESSAGES +
(roomParam + 42) * 39;
pickupTreasure(roomParam);
}
break;
case OT_DO:
if (roomParam != 16) {
printUserMessage(roomParam);
break;
}
done = true;
break;
default:
break;
}
}
}
void TrollEngine::fillOffsets() {
int i;
for (i = 0; i < IDI_TRO_PICNUM; i++)
_pictureOffsets[i] = READ_LE_UINT16(_gameData + IDO_TRO_PIC_START + i * 2);
for (i = 0; i < IDI_TRO_NUM_OPTIONS; i++)
_options[i] = READ_LE_UINT16(_gameData + IDO_TRO_OPTIONS + i * 2);
for (i = 0; i < IDI_TRO_NUM_NUMROOMS; i++) {
_roomPicStartIdx[i] = _gameData[IDO_TRO_PICSTARTIDX + i];
_roomPicDeltas[i] = _gameData[IDO_TRO_ROOMPICDELTAS + i];
_roomConnects[i] = _gameData[IDO_TRO_ROOMCONNECTS + i];
}
for (i = 0; i < IDI_TRO_NUM_LOCDESCS; i++)
_locMessagesIdx[i] = IDO_TRO_LOCMESSAGES + i * 39;
int start = READ_LE_UINT16(_gameData + IDO_TRO_ROOMDESCS);
int ptr;
int j;
for (i = 0; i < IDI_TRO_NUM_ROOMDESCS; i++, start += 2) {
ptr = READ_LE_UINT16(_gameData + start);
for (j = 0; j < 3; j++)
_roomDescs[i].options[j] = _gameData[ptr++];
for (j = 0; j < 3; j++) {
switch (_gameData[ptr++]) {
case 0:
_roomDescs[i].optionTypes[j] = OT_GO;
break;
case 1:
_roomDescs[i].optionTypes[j] = OT_GET;
break;
case 2:
_roomDescs[i].optionTypes[j] = OT_DO;
break;
case 3:
_roomDescs[i].optionTypes[j] = OT_FLASHLIGHT;
break;
default:
error("Bad data @ (%x) %d", ptr - 1, i);
}
}
for (j = 0; j < 3; j++)
_roomDescs[i].roomDescIndex[j] = _gameData[ptr++];
}
start = IDO_TRO_USERMESSAGES;
for (i = 0; i < IDI_TRO_NUM_USERMSGS; i++, start += 2) {
ptr = READ_LE_UINT16(_gameData + start);
_userMessages[i].num = _gameData[ptr++];
for (j = 0; j < _userMessages[i].num; j++, ptr += 39) {
memcpy(_userMessages[i].msg[j], _gameData + ptr, 39);
_userMessages[i].msg[j][39] = 0;
}
}
start = IDO_TRO_ITEMS;
for (i = 0; i < IDI_TRO_MAX_TREASURE; i++, start += 2) {
ptr = READ_LE_UINT16(_gameData + start);
_items[i].bg = _gameData[ptr++];
_items[i].fg = _gameData[ptr++];
memcpy(_items[i].name, _gameData + ptr, 15);
_items[i].name[15] = 0;
}
for (i = 0; i < IDO_TRO_NONTROLLROOMS; i++)
_nonTrollRooms[i] = _gameData[IDO_TRO_NONTROLLROOMS + i];
_tunes[0] = 0x3BFD;
_tunes[1] = 0x3C09;
_tunes[2] = 0x3C0D;
_tunes[3] = 0x3C11;
_tunes[4] = 0x3C79;
_tunes[5] = 0x3CA5;
}
// Init
void TrollEngine::init() {
_picture->setPictureVersion(AGIPIC_V15);
//SetScreenPar(320, 200, (char *)ibm_fontdata);
const int gaps[] = { 0x3A40, 0x4600, 0x4800, 0x5800, 0x5a00, 0x6a00,
0x6c00, 0x7400, 0x7600, 0x7c00, 0x7e00, 0x8e00,
0x9000, 0xa000, 0xa200, 0xb200, 0xb400, 0xc400,
0xc600, 0xd600, 0xd800, 0xe800, 0xea00, 0xfa00,
0xfc00, 0x10c00, 0x10e00, 0x11e00, 0x12000, 0x13000 };
Common::File infile;
if (!infile.open(IDA_TRO_BINNAME))
return;
_gameData = (byte *)malloc(0xD9C0);
bool flip = true;
byte *ptr = _gameData;
int diff;
for (int i = 0; i < ARRAYSIZE(gaps) - 1; i++) {
diff = gaps[i + 1] - gaps[i];
if (flip) {
infile.seek(gaps[i]);
infile.read(ptr, diff);
ptr += diff;
} else {
}
flip = !flip;
}
// One sector is off
infile.seek(0x18470);
infile.read(_gameData + 15632, 592);
infile.close();
fillOffsets();
}
Common::Error TrollEngine::go() {
init();
while (!shouldQuit()) {
intro();
gameLoop();
gameOver();
}
return Common::kNoError;
}
} // End of namespace Agi