STARTREK: Begin implementing event system.

Textbox now responds to mouse input.
This commit is contained in:
Matthew Stewart 2018-03-11 19:22:24 -04:00 committed by Eugene Sandulenko
parent 295c55c510
commit fd26d0a790
6 changed files with 375 additions and 45 deletions

View File

@ -0,0 +1,94 @@
/* 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 "startrek/startrek.h"
namespace StarTrek {
void StarTrekEngine::initializeEventsAndMouse() {
_mouseMoveEventInQueue = false;
_tickEventInQueue = false;
// TODO: mouse
}
/**
* Returns false if there is no event waiting.
*/
bool StarTrekEngine::getNextEvent(TrekEvent *e) {
pollSystemEvents(); // TODO: put this somewhere else?
if (_eventQueue.empty())
return false;
*e = _eventQueue.front();
return true;
}
void StarTrekEngine::removeNextEvent() {
if (_eventQueue.empty())
return;
const TrekEvent &e = _eventQueue.front();
if (e.type == TREKEVENT_MOUSEMOVE)
_mouseMoveEventInQueue = false;
if (e.type == TREKEVENT_TICK)
_tickEventInQueue = false;
_eventQueue.pop_front();
}
bool StarTrekEngine::popNextEvent(TrekEvent *e) {
if (!getNextEvent(e))
return false;
removeNextEvent();
return true;
}
void StarTrekEngine::addEventToQueue(const TrekEvent &e) {
if (e.type == TREKEVENT_MOUSEMOVE && _mouseMoveEventInQueue) {
// Only allow one mouse move event at once
for (Common::List<TrekEvent>::iterator i = _eventQueue.begin(); i!=_eventQueue.end(); i++) {
if (i->type == TREKEVENT_MOUSEMOVE) {
*i = e;
return;
}
}
error("Couldn't find mouse move event in eventQueue");
}
if (e.type == TREKEVENT_TICK) {
// Only allow one tick event at once
if (_tickEventInQueue)
return;
_tickEventInQueue = true;
}
if (e.type == TREKEVENT_MOUSEMOVE)
_mouseMoveEventInQueue = true;
assert(_eventQueue.size() < 0x40);
_eventQueue.push_back(e);
}
}

View File

@ -47,23 +47,24 @@ const int MAX_SPRITES = 32;
const int MAX_MENUBUTTONS = 16; // This is arbitrary, the original game has no such limit
const int TEXTBOX_WIDTH = 26;
const int MAX_TEXTBOX_LINES = 12;
// Keeps track of data for a list of buttons making up a menu
struct Menu {
Sprite sprites[MAX_MENUBUTTONS];
uint16 retvals[MAX_MENUBUTTONS];
uint16 buttonVar2;
uint32 disabledButtons;
SharedPtr<FileStream> menuFile;
uint16 numButtons;
uint16 buttonVar1;
int16 selectedButton;
SharedPtr<Menu> nextMenu;
Menu() : nextMenu(SharedPtr<Menu>()) {}
};
class Graphics;
typedef String (Graphics::*TextGetterFunc)(int, int, String *);
typedef String (Graphics::*TextGetterFunc)(int, void *, String *);
class Graphics {
@ -115,11 +116,12 @@ private:
// text.cpp (TODO: separate class)
public:
int showText(TextGetterFunc textGetter, int var, int xoffset, int yoffset, int textColor, bool loopChoices, int maxTextLines, int arg10);
String tmpFunction(int choiceIndex, int var, String *headerTextOutput);
String readTextFromRdf(int choiceIndex, int rdfVar, String *headerTextOutput);
String readTextFromRdf(int choiceIndex, void *data, String *headerTextOutput);
String readTextFromBuffer(int choiceIndex, void *data, String *headerTextOutput);
private:
int handleTextboxEvents(uint32 arg0, bool arg4);
int handleTextboxEvents(uint32 ticksUntilClickingEnabled, bool arg4);
SharedPtr<TextBitmap> initTextSprite(int *xoffsetPtr, int *yoffsetPtr, byte textColor, int numTextLines, bool withHeader, Sprite *sprite);
void drawMainText(SharedPtr<TextBitmap> bitmap, int numTextLines, int numTextboxLines, const String &text, bool withHeader);
@ -134,20 +136,24 @@ private:
String skipTextAudioPrompt(const String &str);
String playTextAudio(const String &str);
int getMenuButtonAt(const Menu &menu, int x, int y);
void drawMenuButtonOutline(SharedPtr<Bitmap> bitmap, byte color);
void loadMenuButtons(String mnuFilename, int xpos, int ypos);
void setMenuButtonVar2Bits(uint32 bits);
void clearMenuButtonVar2Bits(uint32 bits);
uint16 _textboxVar1;
uint32 _textboxVar2;
uint32 _textboxVar3;
uint16 _textboxVar4;
uint16 _textboxVar5;
uint16 _textboxVar6;
uint16 _textboxVar7;
bool _textboxHasMultipleChoices;
SharedPtr<Menu> _activeMenu;
uint16 _textboxButtonVar4;
uint16 _word_4B422;
};
}

View File

@ -4,6 +4,7 @@ MODULE_OBJS = \
bitmap.o \
common.o \
detection.o \
events.o \
filestream.o \
font.o \
lzss.o \

View File

@ -55,6 +55,9 @@ StarTrekEngine::~StarTrekEngine() {
}
Common::Error StarTrekEngine::run() {
_cdAudioEnabled = true;
_midiAudioEnabled = true;
_gfx = new Graphics(this);
_sound = new Sound(this);
@ -66,6 +69,8 @@ Common::Error StarTrekEngine::run() {
}
initGraphics(320, 200);
initializeEventsAndMouse();
// Hexdump data
#if 0
@ -151,21 +156,38 @@ Common::Error StarTrekEngine::run() {
_gfx->showText(&Graphics::readTextFromRdf, 0x2220, 150, 160, 0xb3, 0, 10, 0);
while (!shouldQuit()) {
pollEvents();
pollSystemEvents();
}
#endif
return Common::kNoError;
}
void StarTrekEngine::pollEvents() {
Room *StarTrekEngine::getRoom() {
return _room;
}
void StarTrekEngine::pollSystemEvents() {
Common::Event event;
TrekEvent trekEvent;
while (_eventMan->pollEvent(event)) {
trekEvent.mouse = event.mouse;
trekEvent.kbd = event.kbd;
switch (event.type) {
case Common::EVENT_QUIT:
_system->quit();
break;
case Common::EVENT_MOUSEMOVE:
trekEvent.type = TREKEVENT_MOUSEMOVE;
addEventToQueue(trekEvent);
break;
case Common::EVENT_LBUTTONDOWN:
trekEvent.type = TREKEVENT_LBUTTONDOWN;
addEventToQueue(trekEvent);
break;
default:
break;
}
@ -175,8 +197,14 @@ void StarTrekEngine::pollEvents() {
_system->delayMillis(1000/60);
}
Room *StarTrekEngine::getRoom() {
return _room;
void StarTrekEngine::playSound(int id) {
// TODO
}
void StarTrekEngine::updateClockTicks() {
// TODO (based on DOS interrupt 1A, AH=0; read system clock counter)
_clockTicks = 0;
}
SharedPtr<FileStream> StarTrekEngine::openFile(Common::String filename, int fileIndex) {

View File

@ -17,15 +17,13 @@
* 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: https://scummvm-startrek.googlecode.com/svn/trunk/startrek.h $
* $Id: startrek.h 14 2010-05-26 15:44:12Z clone2727 $
*
*/
#ifndef STARTREK_H
#define STARTREK_H
#include "common/events.h"
#include "common/list.h"
#include "common/ptr.h"
#include "common/rect.h"
#include "common/scummsys.h"
@ -59,6 +57,24 @@ enum StarTrekGameFeatures {
GF_DEMO = (1 << 0)
};
enum TrekEventType {
TREKEVENT_TICK = 0, // DOS clock changes (see updateClockTicks)
TREKEVENT_LBUTTONDOWN = 1,
TREKEVENT_MOUSEMOVE = 2,
TREKEVENT_LBUTTONUP = 3,
TREKEVENT_RBUTTONDOWN = 4,
TREKEVENT_RBUTTONUP = 5,
TREKEVENT_KEYDOWN = 6
};
struct TrekEvent {
TrekEventType type;
Common::KeyState kbd;
Common::Point mouse;
uint32 tick;
};
struct StarTrekGameDescription;
class Graphics;
class Sound;
@ -72,9 +88,35 @@ public:
virtual ~StarTrekEngine();
// Running the game
void pollEvents();
Room *getRoom();
void pollSystemEvents();
void playSound(int id); // TODO: rename, figure out what it is
// Events
public:
void initializeEventsAndMouse();
bool getNextEvent(TrekEvent *e);
void removeNextEvent();
bool popNextEvent(TrekEvent *e);
void addEventToQueue(const TrekEvent &e);
void clearEventBuffer();
uint32 getClockTicks();
void updateEvents();
void updateTimerEvent();
void updateMouseEvents();
void updateKeyboardEvents();
void updateClockTicks();
bool checkKeyPressed();
Common::EventManager *getEventMan() { return _eventMan; }
private:
Common::List<TrekEvent> _eventQueue;
bool _mouseMoveEventInQueue;
bool _tickEventInQueue;
public:
// Detection related functions
const StarTrekGameDescription *_gameDescription;
uint32 getFeatures() const;
@ -89,6 +131,14 @@ public:
// Movie related functions
void playMovie(Common::String filename);
void playMovieMac(Common::String filename);
uint32 _clockTicks;
bool _midiAudioEnabled;
bool _cdAudioEnabled;
bool _textboxVar4;
private:
Graphics *_gfx;

View File

@ -19,19 +19,20 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "common/events.h"
#include "common/stream.h"
#include "graphics/cursorman.h"
#include "startrek/graphics.h"
// List of events that can be returned by handleTextEvents.
// List of events that can be returned by handleTextboxEvents.
enum TextEvent {
TEXTEVENT_RCLICK_OFFBUTTON = 0,
TEXTEVENT_RCLICK_OFFBUTTON = -4,
TEXTEVENT_ENABLEINPUT, // Makes buttons selectable (occurs after a delay)
TEXTEVENT_RCLICK_ONBUTTON,
TEXTEVENT_LCLICK_OFFBUTTON,
TEXTEVENT_CONFIRM,
TEXTEVENT_CONFIRM = 0,
TEXTEVENT_SCROLLUP,
TEXTEVENT_SCROLLDOWN,
TEXTEVENT_PREVCHOICE,
@ -59,7 +60,7 @@ int Graphics::showText(TextGetterFunc textGetter, int var, int xoffset, int yoff
String speakerText;
while(true) {
String choiceText = (this->*textGetter)(numChoices, var, &speakerText);
String choiceText = (this->*textGetter)(numChoices, &var, &speakerText);
if (choiceText.empty())
break;
@ -73,8 +74,8 @@ int Graphics::showText(TextGetterFunc textGetter, int var, int xoffset, int yoff
numChoices++;
}
if (maxTextLines == 0 || maxTextLines > 12)
maxTextLines = 12;
if (maxTextLines == 0 || maxTextLines > MAX_TEXTBOX_LINES)
maxTextLines = MAX_TEXTBOX_LINES;
if (numTextboxLines > maxTextLines)
numTextboxLines = maxTextLines;
@ -87,12 +88,12 @@ int Graphics::showText(TextGetterFunc textGetter, int var, int xoffset, int yoff
int choiceIndex = 0;
int scrollOffset = 0;
if (tmpTextboxVar1 != 0 && tmpTextboxVar1 != 1 && numChoices == 1
&& _textboxVar5 != 0 && _textboxVar4 == 0)
&& _vm->_cdAudioEnabled && !_vm->_textboxVar4)
_textboxHasMultipleChoices = false;
else
_textboxHasMultipleChoices = true;
if (tmpTextboxVar1 >= 0 && tmpTextboxVar1 <= 2 && _textboxVar5 == 1 && _textboxVar4 == 0)
if (tmpTextboxVar1 >= 0 && tmpTextboxVar1 <= 2 && _vm->_cdAudioEnabled && !_vm->_textboxVar4)
_textboxVar6 = true;
else
_textboxVar6 = false;
@ -196,7 +197,12 @@ readjustScrollDown:
readjustScroll:
textboxSprite.bitmapChanged = true;
// sub_225d3(textBitmap, numTextLines-scrollOffset, numTextboxLines, lineFormattedText+scrollOffset*0x18);
drawMainText(
textBitmap,
numTextLines-scrollOffset,
numTextboxLines,
lineFormattedText.c_str() + scrollOffset*(TEXTBOX_WIDTH-2),
numChoicesWithNames != 0);
break;
case TEXTEVENT_PREVCHOICE:
@ -266,27 +272,97 @@ reloadText:
return choiceIndex;
}
int Graphics::handleTextboxEvents(uint32 arg0, bool arg4) {
// TODO
int Graphics::handleTextboxEvents(uint32 ticksUntilClickingEnabled, bool arg4) {
// TODO: finish
uint32 tickWhenClickingEnabled = _vm->_clockTicks + ticksUntilClickingEnabled;
while (true) {
_vm->pollEvents();
TrekEvent event;
while (_vm->popNextEvent(&event)) {
switch(event.type) {
case TREKEVENT_TICK: {
case TREKEVENT_MOUSEMOVE: // FIXME: actual game only uses TICK event here
Common::Point mousePos = getMousePos();
int buttonIndex = getMenuButtonAt(*_activeMenu, mousePos.x, mousePos.y);
if (buttonIndex != -1) {
if (_activeMenu->disabledButtons & (1<<buttonIndex))
buttonIndex = -1;
}
if (buttonIndex != _activeMenu->selectedButton) {
if (_activeMenu->selectedButton != -1) {
Sprite &spr = _activeMenu->sprites[_activeMenu->selectedButton];
drawMenuButtonOutline(spr.bitmap, 0x00);
spr.bitmapChanged = true;
}
if (buttonIndex != -1) {
Sprite &spr = _activeMenu->sprites[buttonIndex];
drawMenuButtonOutline(spr.bitmap, 0xda);
spr.bitmapChanged = true;
}
_activeMenu->selectedButton = buttonIndex;
}
// Not added: updating mouse position (scummvm handles that)
// sub_10492();
// sub_10A91();
drawAllSprites();
// sub_10BE7();
// sub_2A4B1();
if (_word_4B422 != 0) {
_word_4B422 = 0;
if (_textboxVar1 != 0) {
return TEXTEVENT_SPEECH_DONE;
}
}
// sub_1E88C();
_textboxVar3++;
if (ticksUntilClickingEnabled != 0 && _vm->_clockTicks >= tickWhenClickingEnabled)
return TEXTEVENT_ENABLEINPUT;
break;
}
case TREKEVENT_LBUTTONDOWN:
if (_activeMenu->selectedButton != -1) {
_vm->playSound(0x10);
return _activeMenu->retvals[_activeMenu->selectedButton];
}
else {
Common::Point mouse = getMousePos();
if (getMenuButtonAt(*_activeMenu, mouse.x, mouse.y) == -1) {
_vm->playSound(0x10);
return TEXTEVENT_LCLICK_OFFBUTTON;
}
}
break;
case TREKEVENT_RBUTTONDOWN:
// TODO
break;
case TREKEVENT_KEYDOWN:
// TODO
break;
default:
break;
}
}
}
}
String Graphics::tmpFunction(int choiceIndex, int var, String *headerTextOutput) {
if (headerTextOutput != nullptr)
*headerTextOutput = "Speaker ";
if (choiceIndex >= 1)
return NULL;
return "Text test";
}
String Graphics::readTextFromRdf(int choiceIndex, int rdfVar, String *headerTextOutput) {
/**
* Text getter for showText which reads from an rdf file.
*/
String Graphics::readTextFromRdf(int choiceIndex, void *data, String *headerTextOutput) {
Room *room = _vm->getRoom();
// FIXME: in original game "rdfVar" is a pointer to the variable holding the offset.
// Currently treating it as the offset itself.
int rdfVar = *(int*)data;
uint16 textOffset = room->readRdfWord(rdfVar + (choiceIndex+1)*2);
if (textOffset == 0)
@ -308,6 +384,20 @@ String Graphics::readTextFromRdf(int choiceIndex, int rdfVar, String *headerText
return (char*)&room->_rdfData[textOffset];
}
/**
* Text getter for showText which reads from a given buffer.
*/
String Graphics::readTextFromBuffer(int choiceIndex, void *data, String *headerTextOutput) {
char buf[TEXTBOX_WIDTH];
memcpy(buf, data, TEXTBOX_WIDTH-2);
buf[TEXTBOX_WIDTH-2] = '\0';
*headerTextOutput = String(buf);
char *text = (char*)data+TEXTBOX_WIDTH-2;
return String(text);
}
/**
* Creates a blank textbox in a TextBitmap, and initializes a sprite to use it.
*/
@ -407,7 +497,6 @@ void Graphics::drawMainText(SharedPtr<TextBitmap> bitmap, int numTextLines, int
lineIndex++;
}
debug("%d, %d\n", numTextLines, numTextboxLines);
// Fill all remaining blank lines
while (lineIndex != numTextboxLines) {
memset(dest, ' ', TEXTBOX_WIDTH-2);
@ -448,15 +537,15 @@ void Graphics::getTextboxHeader(String *headerTextOutput, String speakerText, in
String Graphics::readLineFormattedText(TextGetterFunc textGetter, int var, int choiceIndex, SharedPtr<TextBitmap> textBitmap, int numTextboxLines, int *numTextLines) {
String headerText;
String text = (this->*textGetter)(choiceIndex, var, &headerText);
String text = (this->*textGetter)(choiceIndex, &var, &headerText);
if (_textboxVar1 == 2 && _textboxVar5 == 1 && _textboxVar4 == 1) {
if (_textboxVar1 == 2 && _vm->_cdAudioEnabled && _vm->_textboxVar4) {
uint32 oldSize = text.size();
text = playTextAudio(text);
if (oldSize != text.size())
_textboxHasMultipleChoices = true;
}
else if ((_textboxVar1 == 0 || _textboxVar1 == 1) && _textboxVar5 == 1 && _textboxVar4 == 1) {
else if ((_textboxVar1 == 0 || _textboxVar1 == 1) && _vm->_cdAudioEnabled && _vm->_textboxVar4) {
text = playTextAudio(text);
}
else {
@ -598,6 +687,58 @@ String Graphics::playTextAudio(const String &str) {
return skipTextAudioPrompt(str);
}
/**
* Returns the index of the button at the given position, or -1 if none.
*/
int Graphics::getMenuButtonAt(const Menu &menu, int x, int y) {
for (int i=0; i<menu.numButtons; i++) {
const Sprite &spr = menu.sprites[i];
if (spr.drawMode != 2)
continue;
int left = spr.pos.x - spr.bitmap->xoffset;
int top = spr.pos.y - spr.bitmap->yoffset;
// Oddly, this doesn't account for x/yoffset...
int right = spr.pos.x + spr.bitmap->width - 1;
int bottom = spr.pos.y + spr.bitmap->height - 1;
if (x >= left && x <= right && y >= top && y <= bottom)
return i;
}
return -1;
}
/**
* Draws or removes the outline on menu buttons when the cursor hovers on them, or leaves
* them.
*/
void Graphics::drawMenuButtonOutline(SharedPtr<Bitmap> bitmap, byte color) {
int lineWidth = bitmap->width-2;
int offsetToBottom = (bitmap->height-3)*bitmap->width;
byte *dest = bitmap->pixels + bitmap->width + 1;
while (lineWidth--) {
*dest = color;
*(dest+offsetToBottom) = color;
dest++;
}
int lineHeight = bitmap->height - 2;
int offsetToRight = bitmap->width - 3;
dest = bitmap->pixels + bitmap->width + 1;
while (lineHeight--) {
*dest = color;
*(dest+offsetToRight) = color;
dest += bitmap->width;
}
}
void Graphics::loadMenuButtons(String mnuFilename, int xpos, int ypos) {
SharedPtr<Menu> oldMenu = _activeMenu;
_activeMenu = SharedPtr<Menu>(new Menu());
@ -629,6 +770,16 @@ void Graphics::loadMenuButtons(String mnuFilename, int xpos, int ypos) {
_activeMenu->sprites[i].field6 = 8;
}
if (_activeMenu->retvals[_activeMenu->numButtons-1] == 0) {
// Set default retvals for buttons
for (int i=0; i<_activeMenu->numButtons; i++)
_activeMenu->retvals[i] = i;
}
_activeMenu->selectedButton = -1;
_activeMenu->disabledButtons = 0;
_textboxButtonVar4 = 0;
}
// 0x0002: Disable scroll up